Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7da94c9223 | ||
|
|
0e99dfbaec | ||
|
|
d132060697 | ||
|
|
4f956effff | ||
|
|
dadcf69327 | ||
|
|
ff027ef6ac | ||
|
|
80728e7d75 | ||
|
|
a183975b8a |
@@ -16,6 +16,7 @@
|
|||||||
".roo/**",
|
".roo/**",
|
||||||
".windsurf/**",
|
".windsurf/**",
|
||||||
"tools/oxlint/anti-slop/**",
|
"tools/oxlint/anti-slop/**",
|
||||||
|
"src/anti-overengineering.md",
|
||||||
"node_modules/**",
|
"node_modules/**",
|
||||||
"dist/**",
|
"dist/**",
|
||||||
"coverage/**",
|
"coverage/**",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ stanp-opencode is an OpenCode V2 coordinator with specialist workers for fast, g
|
|||||||
|
|
||||||
## Install the latest release
|
## Install the latest release
|
||||||
|
|
||||||
Requires OpenCode 2.0.4, Bun 1.4.2+, Bash, curl, jq, and `sha256sum` on Linux or WSL.
|
Requires OpenCode 2.0.15, Bun 1.4.2+, Bash, curl, jq, and `sha256sum` on Linux or WSL.
|
||||||
|
|
||||||
Run the installer without sudo:
|
Run the installer without sudo:
|
||||||
|
|
||||||
@@ -40,10 +40,15 @@ Both installers use `~/.config/opencode`, respect `XDG_CONFIG_HOME`, and accept
|
|||||||
|
|
||||||
See [GPT configuration](examples/gpt.opencode.jsonc) and [custom model assignments](examples/custom.opencode.jsonc).
|
See [GPT configuration](examples/gpt.opencode.jsonc) and [custom model assignments](examples/custom.opencode.jsonc).
|
||||||
|
|
||||||
For an installed plugin, use `./plugins/stanp-opencode` as the plugin package path. Set `lunaPriority` to `false` to avoid requesting priority pricing.
|
For an installed plugin, use `./plugins/stanp-opencode` as the plugin package path. In GPT mode with the `openai` provider, set top-level `lunaPriority` to `false` to avoid requesting priority pricing. Other providers do not accept this option. Custom top-level and per-agent `provider` and `lunaPriority` fields are invalid.
|
||||||
Custom configurations require the six worker assignments. The legacy optional `coordinate`
|
Custom configurations require the four `fast-*` and `good-*` assignments; `deep-lo` and `deep-hi` are optional. Assignments use `provider/model` model names and require both `model` and `effort` when supplied. If either deep assignment is omitted, both deep workers are disabled and orchestration uses `good-lo`.
|
||||||
assignment is accepted for compatibility but deprecated and ignored; orchestration inherits
|
The legacy optional `coordinate` assignment is accepted for compatibility but ignored. Orchestration
|
||||||
`deep-lo`, or `good-lo` when the configured `deep-lo` model is unavailable.
|
inherits `deep-lo`, or `good-lo` when the configured `deep-lo` model is unavailable.
|
||||||
|
|
||||||
|
The plugin includes the anti-overengineering skill for keeping changes minimal without
|
||||||
|
weakening correctness. `coordinate` can use it to audit worker review findings, reject
|
||||||
|
redundant defensive additions, and ask before delegating substantial defensive complexity.
|
||||||
|
Ordinary in-scope fixes do not require additional approval.
|
||||||
|
|
||||||
## Multi-work
|
## Multi-work
|
||||||
|
|
||||||
|
|||||||
@@ -9,22 +9,22 @@
|
|||||||
"zod": "4.6.5",
|
"zod": "4.6.5",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opencode/client": "2.0.4",
|
"@opencode/client": "2.0.15",
|
||||||
"@opencode/plugin": "2.0.4",
|
"@opencode/plugin": "2.0.15",
|
||||||
"@opencode/schema": "2.0.4",
|
"@opencode/schema": "2.0.15",
|
||||||
"@opentui/core": "0.5.11",
|
"@opentui/core": "0.5.12",
|
||||||
"@opentui/solid": "0.5.11",
|
"@opentui/solid": "0.5.12",
|
||||||
"@oxlint/plugins": "1.83.0",
|
"@oxlint/plugins": "1.85.0",
|
||||||
"@types/bun": "1.4.2",
|
"@types/bun": "1.4.2",
|
||||||
"oxfmt": "0.68.0",
|
"oxfmt": "0.70.0",
|
||||||
"oxlint": "1.83.0",
|
"oxlint": "1.85.0",
|
||||||
"solid-js": "1.9.15",
|
"solid-js": "1.9.15",
|
||||||
"typescript": "7.0.2",
|
"typescript": "7.0.2",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opencode/plugin": "2.0.4",
|
"@opencode/plugin": "2.0.15",
|
||||||
"@opentui/core": ">=0.5.11",
|
"@opentui/core": ">=0.5.12",
|
||||||
"@opentui/solid": ">=0.5.11",
|
"@opentui/solid": ">=0.5.12",
|
||||||
"solid-js": ">=1.9.0",
|
"solid-js": ">=1.9.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -202,17 +202,17 @@
|
|||||||
|
|
||||||
"@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="],
|
"@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="],
|
||||||
|
|
||||||
"@opencode/ai": ["@opencode/ai@2.0.4", "", { "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", "@opencode/schema": "2.0.4", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-rc.112", "google-auth-library": "10.5.0" } }, "sha512-T/2wTuhy/I1Wxdy/GdhtMqugpl1OWKgVp+8A45mtS1jDrTSeHq2x5YhkComkJZ6BwdG/OvU6afTGDPA7/vd4RA=="],
|
"@opencode/ai": ["@opencode/ai@2.0.15", "", { "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", "@opencode/schema": "2.0.15", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-rc.112", "google-auth-library": "10.5.0" } }, "sha512-dY3HG6bp+UMJOi+m1nfzvQaTPpCSmMNDswAOLQq/MJghGrD54WrcEC4IhAQ1hJY7b/hhQYOzPAMeJsvF4aRYYQ=="],
|
||||||
|
|
||||||
"@opencode/client": ["@opencode/client@2.0.4", "", { "dependencies": { "@opencode/protocol": "2.0.4", "@opencode/schema": "2.0.4" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-GBFWT2os+gEtGXpHRvDUNkf0H9x9njDUVbUvvc4drH2CuAihVcHqdi9Wm4UiwkBrSLjrKPs4U1NJBh2Z7/x4tQ=="],
|
"@opencode/client": ["@opencode/client@2.0.15", "", { "dependencies": { "@opencode/protocol": "2.0.15", "@opencode/schema": "2.0.15" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-Lu3Pb6Z/lJ797VryQrsdqEgVBXjtYZWOxLNZKyBUDgXhO/GpzOpXi1H1Uo4g+qWrH8LwUISDBdxHHOWJy5HcqQ=="],
|
||||||
|
|
||||||
"@opencode/plugin": ["@opencode/plugin@2.0.4", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode/ai": "2.0.4", "@opencode/client": "2.0.4", "@opencode/protocol": "2.0.4", "@opencode/schema": "2.0.4", "@opencode/util": "2.0.4", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { "@opencode/theme": "2.0.4", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-NsC1STARfXxe+ZoXjXzpxvtS9l4h9Aen8ZccaCd1d9+1eyYdp+q8DU3Rxh7Ch0jiwiQMybS2Z+KgAfZJBnTBbA=="],
|
"@opencode/plugin": ["@opencode/plugin@2.0.15", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode/ai": "2.0.15", "@opencode/client": "2.0.15", "@opencode/protocol": "2.0.15", "@opencode/schema": "2.0.15", "@opencode/util": "2.0.15", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { "@opencode/theme": "2.0.15", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-TsSXd2g/433De7ViY+5d/tlLtkZCSmYKo8ScnhtcmPVoM/U5ptQDFXEXx5psTlAfkKcISJh+5vMSOe4u3uCqUQ=="],
|
||||||
|
|
||||||
"@opencode/protocol": ["@opencode/protocol@2.0.4", "", { "dependencies": { "@opencode/schema": "2.0.4", "effect": "4.0.0-rc.112" } }, "sha512-uWe2dmMouprSkfBu29XLR6GCWckkhm0YOo9ve07kTRqJCSoJzgQDF/8pouWG0cDCJ6oWj3bRQajXriBQyWSpUw=="],
|
"@opencode/protocol": ["@opencode/protocol@2.0.15", "", { "dependencies": { "@opencode/schema": "2.0.15", "effect": "4.0.0-rc.112" } }, "sha512-fFi3VYdgNQzT4TswfKGul7DzseOyhA4b/hE/elUe3xxm5iki4tP/pjT8CU3rRWX8sK/75ICr36NAUhSayFtJuw=="],
|
||||||
|
|
||||||
"@opencode/schema": ["@opencode/schema@2.0.4", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-UyGcCsRb2hTH98rnBqfJogFve8kJsIbsEreRxqhkBIEGrtDTqcUd5NnqB7qlJp2khesMvpOH0GmCHlgz44C+uQ=="],
|
"@opencode/schema": ["@opencode/schema@2.0.15", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-D0JDwF4uTNmFA2MyiSnZ5DlT/t2PF1EEwVbRPulB/Ppcss/z5Fcn6VybHrapki2bKDVQu1Mnn8zBW3G6KP5I6g=="],
|
||||||
|
|
||||||
"@opencode/util": ["@opencode/util@2.0.4", "", { "dependencies": { "@effect/opentelemetry": "4.0.0-rc.112", "@effect/platform-node": "4.0.0-rc.112", "@effect/platform-node-shared": "4.0.0-rc.112", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "cross-spawn": "7.0.6", "effect": "4.0.0-rc.112", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "pacote": "21.5.1" } }, "sha512-jo6zhWqJ4WaU+fUjM8/Ea4i06gxTQFEFgfq2XG9J8gSDUhfsj4l9wJ3cLtSuKFQGeUeWqJJwRgDhCW+rUbis6Q=="],
|
"@opencode/util": ["@opencode/util@2.0.15", "", { "dependencies": { "@effect/opentelemetry": "4.0.0-rc.112", "@effect/platform-node": "4.0.0-rc.112", "@effect/platform-node-shared": "4.0.0-rc.112", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "cross-spawn": "7.0.6", "effect": "4.0.0-rc.112", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "pacote": "21.5.1" } }, "sha512-s0Z+2rxb+2Tif+ZV1EvTiGQ0tRiemIZq3WOOjBw2XPBSBt2Sfv9rWXJhTzSpkOZucfTyymuIVXslXPGFF7aaQA=="],
|
||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
@@ -240,103 +240,103 @@
|
|||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.5.11", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.11", "@opentui/core-darwin-x64": "0.5.11", "@opentui/core-linux-arm64": "0.5.11", "@opentui/core-linux-arm64-musl": "0.5.11", "@opentui/core-linux-x64": "0.5.11", "@opentui/core-linux-x64-musl": "0.5.11", "@opentui/core-win32-arm64": "0.5.11", "@opentui/core-win32-x64": "0.5.11" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-pImMfjCNx7JUp9Df1LRZBDLisWqgzOLXZKXO+hh3jA9ujBFPQnZOqbG+/N5uVAAX8IZGaqhYSQRwSZIFBmcfbQ=="],
|
"@opentui/core": ["@opentui/core@0.5.12", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.12", "@opentui/core-darwin-x64": "0.5.12", "@opentui/core-linux-arm64": "0.5.12", "@opentui/core-linux-arm64-musl": "0.5.12", "@opentui/core-linux-x64": "0.5.12", "@opentui/core-linux-x64-musl": "0.5.12", "@opentui/core-win32-arm64": "0.5.12", "@opentui/core-win32-x64": "0.5.12" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-ZXBE5gmvdovmV8zJQrOQf6E44v1tJRDEgrM2MYhEglzgXZ+smIUp95O8zeRYGsuIzQIiMPMgQqKtTJuzvAb7BQ=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DRXY5ioq+n1ZNAMAcaFaBunr0cmi2gqucjbTW7lgFp8t9uN3fNZnLTDOqkCTQtT2XhrU4GXqySaPSQ4qFG/EAQ=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YdVnP0tAyerBNl0mIcmQEOotPeZzW1VnSXKBl5cyZ5e6nDd2Y+ui/8eRPpn1oqcamf1NCnzS4ohMgejOvna8Zg=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-yP/8GliJDiJNm8YYJKvgWuy6xyCEd8d4GwBVOIzCFOI7ZIbGP8GOTvmwIjRW4Paw20pTttWMWyRoQiCvOXvH2g=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-uRrQJdHmLUSj3PV23QPi3WSimYTTxcXnVouxF6U4xMXlOv4N3SxnHfVwMRQkPqbGOfvVWHeLE6FdK4C+ubU0sQ=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-zBIsRFHlLUYFNhapRSNt9dz4mC8gZ4Wxcfy3A+2AwqsgCipcr2FkIuAXYqN08q+IvqFX7DfqgIFGWDNedHTPUg=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-XeKhuIaEtgipvuPHbl4qPOBj+Ut+2zObmsxMVM1jDcjz/FatG9PGeGQPx1G1SnvH2AgpT4K+eCu7DUF0+yIqoQ=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-x+xeR2LYibvIi/qQetRjJR008sFRve60QuDcO8ItxUwzFeKTDzl5CEiZpBXfm5I4FhRNuyuj0TSPIFadMvrjFQ=="],
|
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-VZ2sNMw1d/r1SLPjUbOP9LKscKz1CQjID8adTL6gG8Lrrq+mYcIUxutyB+P/eG0J/7oRZLPR6OMt7dUOap6RTg=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.11", "", { "os": "linux", "cpu": "x64" }, "sha512-pSOXqOADrv+zINOgR3FDFA9zVRaim3zl8/yhtO+X9rEJ6f34z3gDund0Gf88hNJSMpZK5xWtipEVm28RY5VF8w=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.12", "", { "os": "linux", "cpu": "x64" }, "sha512-eZiCjEzwbb6qClPPfk32Nha9xmr9obt69Xj0+9SKsXxWLBKkjQEGOMRoh/R9ObaQF4aq8If1xV3VEY0sD9W9vg=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.11", "", { "os": "linux", "cpu": "x64" }, "sha512-MyqOnSs8pTYG2xmFr1xt6xZIuHu2Xu4pkle9my9JdE+WClmusHf0YN9Eas6jQLAq5XUi34r22wMeK0juk93zyw=="],
|
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.12", "", { "os": "linux", "cpu": "x64" }, "sha512-WWW0hVBoSYZ3D6AgZ4u2Y5/u/IyIq2pDb+4yI3WgJ70Wyt6ofHy+6kRGRgbXFn1p+rPInAHjCXD2v6C7iEKSrA=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-MGGRXIDJ//HyaqC5ndSr7/CUl+ICdYEAMcjbA00UWsthh4ZO/rYhPkyqOaTn5ck6+G4ca3/+M76J+jImMYTjNg=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-aLbm6870Ybls6CYL4zMOCImTBPLZHZMUXJFGqMI44lIWxitkAtT6zg5lYA4oRqFRzzryDclxr29+hDgT3p3Blw=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.11", "", { "os": "win32", "cpu": "x64" }, "sha512-sMEGX9rhiPd1gBa190jzj5uIdzKCMImxHmr22hJLxIRWqejxWvqGnRBkW68wT8NfMViDiMtZWavgWL7GgmYCAQ=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.12", "", { "os": "win32", "cpu": "x64" }, "sha512-KTwtwpfd2zF9opVh3SyRJYDd1o3Xv4XL8OZb8Zi+CqWUel6Y2IDCiVivCv8fGJt3J7wOIXXtuZI9ZUkLyKJCiQ=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.5.11", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.11", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-u8RJ4UMwzi+r9M1sopZdnT+7XFSHmLzVmhPhc6N2LfBrA9Db4lZ9sQA+ywgdSDkzdANpAOwGLNUQlY/F1OOCLA=="],
|
"@opentui/solid": ["@opentui/solid@0.5.12", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.12", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-hAiVlVMtT7AkHGblKwcW1YAuXtxkSy1XSf/RRc4j3IlG3mTNX0bhJdnGOo3Xw14EqeZMp41Mcp5WzHAzMm/DzA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.68.0", "", { "os": "android", "cpu": "arm" }, "sha512-dhfYPbzv/h9JgHjNkl2R6sOjUfxDyLGOZVb3g8/ScaTNwwJcYgmHh8kcYFDUhinuy1QAoANCWUvw1jlk+z6gAg=="],
|
"@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.70.0", "", { "os": "android", "cpu": "arm" }, "sha512-Xd7YO4/T2axEj6FTLcj4Why3mTBqFMg+x24xtorT4Lb2+1g82090GH0a/4U1m0pABGYiix2bq1pqkYrmV3f0Sw=="],
|
||||||
|
|
||||||
"@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.68.0", "", { "os": "android", "cpu": "arm64" }, "sha512-v3Njdi6qY0O/5eGfg01ww2w6gTn2mUvZ72Bnx1/UN53A9wruh3Nk6otc3WkgJLkXD4Qgz1SOcVQieH1oD03V9Q=="],
|
"@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.70.0", "", { "os": "android", "cpu": "arm64" }, "sha512-x9rlMYyKXdgKdYyUJzGsK1ZV8P4di/J32ipzcS6Jet6p9r9UAh28neXIMtdlSaJJycdi61Z4YkcLKLpk8ueFjg=="],
|
||||||
|
|
||||||
"@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.68.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ei4MCMzHFREmZwPJ7KuWUB4kBuHdsgDrnXGJVcEAopU7fj7S42I8BChdFILWdHvhFqR08FLJtOfbZIr2CDw0cA=="],
|
"@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.70.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IUTUPvrBVYy7POh4stXzRdz4IVC/1QSaviCWoyenSlOhGu0X9j5K07vCTM9biLjAA2Zs31l0Rj5vvRpj9n95wA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.68.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-UrKgzZxYhwB9DSvTX+vdgl9M32wLUNKJcAKIoiyx/Kzn/zveqi7W6kYVcRroFMhS3Kwz0KhTk3WBeSuQn4YCTg=="],
|
"@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.70.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-vw745q870oTd6J517O24asoX4/E+eK0nxYIFoedSLqgJ+nI5En7+ZS82iZSHZ69zevQrnOXiyHP01dA+t8xD8w=="],
|
||||||
|
|
||||||
"@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.68.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6jrEKgpJbilM1QaRv7hEtKXr4p4AK4jvvyOtajwyhu0kOz3e0O7OLnSTk6tBotRqCcUC4ehZRJ1Zx+Y99wieLw=="],
|
"@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.70.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-NO14EgSM9dFkcg+MfGPxvsKqXYs9LKaxPrOKXpv1R0rLokGGFDcCq6dBMq18dE4wlpFOovX0UZY2uh1P30O7QA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.68.0", "", { "os": "linux", "cpu": "arm" }, "sha512-YOIVnKOBaLeGullskS179N12hjSAdFYnzLjOaKiLhAKNWgnShq9w4xRdtmUm6BlnP65l2/EA9Aw/KlftNxDM7Q=="],
|
"@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-139OEhHarj9CYoJ/i9gXlPv4KLBGtLj2toseOWYFf09QwlhklaZk+wW3aOvlqoeZtuayvkoSNVWra7WJ21s3VQ=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.68.0", "", { "os": "linux", "cpu": "arm" }, "sha512-xW5XoEHVNqydPBv2KXvk9lmEzyAlOQHVEazKoXUuAacqekjya+OdiaFjjEBl0oJD02raG8g3TRl9OVCh9PDIHA=="],
|
"@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-GEh2PY3IWTE0M24eNhTduountANSbWyDmMnzFSQE/nGg/bjPugbUgiGuFu+xdqcQSd/HKSwH80/F2yVVD48yhA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.68.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-QCvYwVVQieu6oyJglAgV9vH/YMDxZyR4cwVSYtoq9oOXd5N+D3TDUBjNwxFrLn5AdcJZOcvn/7IB17vJGp+2Og=="],
|
"@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-En5i+UJmZSPxuSf47F2Hl5YOzKB0bicQLnGQkeTCMQ35cWLtbrSwACJKfiLqRZrk05DwSnsJkhBRaM3OURtIaA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.68.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4TVz5iFQ8ndrHnhX50UXiz9BIWAtUSOHJ6Nus4qWFfJBXq/Ed/krXbF/ehJu42BXM5tIvBs99jNIM22s9agY5A=="],
|
"@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WWOoV5W9Im3flVwOVrWn/2DUlOF8v5vcCip+kcNuaMpulRCh6nzzt1Su2vcL2F908YJIXNV3HvegbBHuyLwKHg=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.68.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-qLe3ao0RP84bnPxBvRI+GnlK/jybo538NWu0Xrm+zYeTmJtpzqLhnnd5BH21NafqKnplbGZjtN1cnrOlWF73lw=="],
|
"@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.70.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-YUouneIqW+5n7aE8xx/zeZ6/utr/KH7oykcGoFyd8Uz8uh591T1oKlnoWA3BsRq/ZR42oY1w4MUYvS/0e/MQOA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.68.0", "", { "os": "linux", "cpu": "none" }, "sha512-Yvyl7a6gbb0vM6r925KW2dO+/CmXySO5TVbcX7o/uZJ+d108HFOG0TxIyHApmn5USuj5mhDPxzhiVwOlGP7uSA=="],
|
"@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-iEnMf21S5aGVa4hViDGY8sAQ/AHyCu2JPyrQF8P06wtHhSkD1YJBeT4m/KiGewgf7+a5XCYSCRIPcRQa1xwEoQ=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.68.0", "", { "os": "linux", "cpu": "none" }, "sha512-mJlFuFVCxzrYM5sFStN433D/s/mb6Wq4aCQAM02vs/OudHywnaSAd2rb1vlYUJtqdYIciJtiasuxvfbYkv5fLg=="],
|
"@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-91Sdniaj20fQzyMeCxMDzTP4c9s4RB8dGQ308xHhDR0n6U7+1Xq7N9klE7mfXq8iV3lRmIGSXi5X23Hn/0XX/g=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.68.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-RlfSg++qs1hbKltRR6lYvV9EoI3MdlfSQD9w1hdHVYjHqjIn1tkH4FWOpMSmjKGN20zr+nI+W9o4ARogCDudGQ=="],
|
"@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.70.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-uUV30M6E+2TKKGMaKiwfeL4RZrviHXlUxsrYJ/jFBb+1EZy+pnFT+hF73eeWdzh5NqPOAnw0iiMAIqjqiLZPFg=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.68.0", "", { "os": "linux", "cpu": "x64" }, "sha512-nyzRB9U+dlYUKu3pMo3afHzZBUv/oTHZMG36ZfJViNVfOIzp70Q4GS8FFRGgYJ/p0zcyDCgpBvYISOdJOMh+jQ=="],
|
"@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ivMcX6kNDPhqtbOaBt/ItFlLlTlXNHLgRuNmxP6Na6UuYXRT10llpJcAPbGeRgjjb3Qzv4jwPp3fB0hui50WNQ=="],
|
||||||
|
|
||||||
"@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.68.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iCx3sbZRIvGrL1RafphEiUKBaW1lc0/tAjKOIB/Wjw2+STRBEdu5+fH1Gc1faEWEmc2k5Ks4iUUV54Zd5C9a1A=="],
|
"@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-w+S+fERxYmlZSyZlJK/U292FjyBoH8cCEj21/tYJX6atX5kNSn+HDkhlFQKT2zcMwUW0uAtUL/bOrlwJZwqRdA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.68.0", "", { "os": "none", "cpu": "arm64" }, "sha512-x2X5AZez7OgyLLFpwIgItoXBUqudDM7yiaTsxv8R8vKQ6e81l0jVw0NFeUCXzcl1sAJq8h+tC8N4mY8EiMeL4w=="],
|
"@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.70.0", "", { "os": "none", "cpu": "arm64" }, "sha512-Zlom1Xkx257R8bk4ZI4zJsrGno2opknz1+5v5baka3nn4FPyvNSdh8JUL4CdN1S1OWRMvJ9UJQ+RfIqGGCUEfA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.68.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-AHVPjXkenLPQUh6kB8zSC8pX2ct9r4T1Edk9r/RNJyov6wPsS5uAfYtipwG4chn6+3bPFG5rI/3DxEeH8vib1w=="],
|
"@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.70.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-FQgPW5R17vzt7cgrJ8eG/dqX00o2xHsqFeLfw4xzA9FRHpN/DjFo9YDonvIIXGxiEuS9F/jZPnGO+KHNKuCo4Q=="],
|
||||||
|
|
||||||
"@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.68.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-n09SjEk5VH7z8Hl4WVP7hho+cCwGViENkQFiM45vbW85dJd7kEhWaHRUobaPzEmrWyu6uumd4EuNfNyDKLtzDA=="],
|
"@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.70.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-ZfZublNhZ+XBndMiXhkiLlPE+XyGRDa0CweeTL6t1fZypfCh1LTg7e5CvnOeTBunq15MskOcRempumSPGAaCQA=="],
|
||||||
|
|
||||||
"@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.68.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gPe+dJLXaPuWPWqlpklDAJp0k+K9KhQPYiQLHfb+i2rmFuUGfJ/5Qlj6tr1mO6of5g0DiLjG/XCFHIaPhotqqA=="],
|
"@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.70.0", "", { "os": "win32", "cpu": "x64" }, "sha512-HlIZEn+WzLQL0DszNzldiRl/DPRCX5R0Vkt6qeUPR1YHwy52hZZo4x6HoTOVmKRP2wUiwPGtKsihNY/f8KRaBg=="],
|
||||||
|
|
||||||
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.83.0", "", { "os": "android", "cpu": "arm" }, "sha512-0yGY24EwsLk5YDe6F+VkmZyRHSwJDALa3nIrPpq7FXmp2lV2d0TzvBCGeZk+wgiULRGr5blhyr4QMp5KCXJUqA=="],
|
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.85.0", "", { "os": "android", "cpu": "arm" }, "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg=="],
|
||||||
|
|
||||||
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.83.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hHfJ0vc17A4iUjH5p9BsTUPYbYRNxGpvD2lbu1aBRk54bzNIx9o5TtYF39QPZcV95DagZd+4DEAw2RH3G2ZsMg=="],
|
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.85.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA=="],
|
||||||
|
|
||||||
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.83.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hsOjYjszLb/3zym/TkzUMPAoQlTJcuzSyEPOAyA+skXJIX9M0o+4JfOtqopX/Vf4hSLrJ98j0nvFo23gzk8auQ=="],
|
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.85.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA=="],
|
||||||
|
|
||||||
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.83.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-mjh5oH2EA+wl5yRJYT9K9G61O2zFlpuv+yf2JwZOi0+dq2FnTUtm1h8i+5Ik0fXPWIu/k84I1psZR9aQsLAnyA=="],
|
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.85.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw=="],
|
||||||
|
|
||||||
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.83.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fNHr64/YaO8YssuoDVC8+F4Uk5enR86q5uxfHkQrjAPs1dbAILOrD2uaud+J7MO8Fx774g44ERLD0IGIvZE48w=="],
|
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.85.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Qpwy3zzAwMj+8/lyYItHmkSMwbkprFNWTK7jPYDOxSyxEhaSLOWYUTCMkjF334J8/WD0nznCCsoBbIH6hpsuIw=="],
|
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.83.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s+BirYLFq7JL2k9sP0XI3ZXJ9dYvJ8sX3jLCLoag7tt+zrSHpZxP0jqznfL+Gdgwu7ay0dYgGYJXrQvq3iWloA=="],
|
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7lihXt3vKr+GIyapNbHrnFHm/biiW30le6Zv/DExbAFPF6YwCQXVFlONPFehxs0CpGO4CBfYPM9rdDT+XMoIlg=="],
|
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.83.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-q63JalLYVkZiZvls1z3PPUnpmQluOMXp0khqQMznCeAPLGydfNY8JhvuA4WlK57JfrvikU8wB5lPVveqpIXvew=="],
|
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.83.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-krQmDF+dRbxvdqVPV88ZuOoPPu8X5BuqDA8Hd+qcS4YMRQCb+nexA57DazgGsc/rGdKBe3QmV0mnv0bdpW/p5g=="],
|
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.85.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-MmOl8Y6txEAXZU1RG8Rr264jQ6D7VPmqFsU/45x/FeWsGe32hklTqGrLE6UxHzp5Rjt0wP+20tY8YXKgSFB3mw=="],
|
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.83.0", "", { "os": "linux", "cpu": "none" }, "sha512-u1rMymh0W3JZkq370kzQsYPULGWqhE09pZRqnZvUSoYaI9pVO5yVX+iYIslmWuEgwuzH9YAaOsScJiobWCHoOw=="],
|
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.83.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-y0zK3HNwGysu7rqtE+BQG/d0bx5gh/KwlOtghN8oWeK1KcWzeaLqtZrbm8owqdma1lFyrce/hTO5ismuNu+INQ=="],
|
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.85.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rS5gM0NgD7ngmuJmbIehsidtrOwKkLFwCQbKEeb9KuyQrrWNq5Zkn0uV6AYdXOMJ0grrWEiLwBuvMxt8w5vsNw=="],
|
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg=="],
|
||||||
|
|
||||||
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.83.0", "", { "os": "linux", "cpu": "x64" }, "sha512-W2IH4EtpcPaWcvNGCA95YoDg4vxqE/ZiPCi3arrxEEpsK7+JQN9WYwrlYFx9pcdP6KPXqRqkv3zdQPHcx7b6YQ=="],
|
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA=="],
|
||||||
|
|
||||||
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.83.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6LyKkUyoajssTPLlZmDbZIbu4IZ5B4bGuRUnBgCGpEvHP3FQMaYITncHA/unPUo7q+Z+pIu2HhdkQ+8d1SG7iA=="],
|
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.85.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA=="],
|
||||||
|
|
||||||
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.83.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Uz/fObEtF0jmNJQJ8CGRBKfefYstS0/wjD3s6IGzP8nUwsJykHQJBiN3npHwKiGRGn/vvBEgNr4B3cCzmmatvg=="],
|
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.85.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA=="],
|
||||||
|
|
||||||
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.83.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-u7XcvPW6Bk58tY5iWs2ESb0vJjoE/kuSpHxopbwp/p3ZtWVQXZ6wor5w3ssVTHOqd/v8b+QdhSFWQ4grEUNWpA=="],
|
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.85.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A=="],
|
||||||
|
|
||||||
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.83.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LZRubd7ph13QmAg4fFecTYVZkiYbROR2Htaxh/ufWRkDhPOm2wrwaEYR89e0YpPFD3dqBrPoxS7myBw5hmYA7Q=="],
|
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.85.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA=="],
|
||||||
|
|
||||||
"@oxlint/plugins": ["@oxlint/plugins@1.83.0", "", {}, "sha512-qD64wr6SS1DL7YbJQ8WWjyUfFfrJsvAy9Vo0sG/PYUQy30w0uemN2h6j4xuvj4noyETDpzcGC20sv+FH7GrN5A=="],
|
"@oxlint/plugins": ["@oxlint/plugins@1.85.0", "", {}, "sha512-qqHqML3ME3cxGgFvWJG8wwMnJc9ol5SMdtqkq0k8r7l0USmC+HRXYjzDacME4Dqkx4yqrUsBhj0JYvji8fHxJQ=="],
|
||||||
|
|
||||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||||
|
|
||||||
@@ -706,9 +706,9 @@
|
|||||||
|
|
||||||
"npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="],
|
"npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="],
|
||||||
|
|
||||||
"oxfmt": ["oxfmt@0.68.0", "", { "dependencies": { "tinypool": "2.1.2" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.68.0", "@oxfmt/binding-android-arm64": "0.68.0", "@oxfmt/binding-darwin-arm64": "0.68.0", "@oxfmt/binding-darwin-x64": "0.68.0", "@oxfmt/binding-freebsd-x64": "0.68.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.68.0", "@oxfmt/binding-linux-arm-musleabihf": "0.68.0", "@oxfmt/binding-linux-arm64-gnu": "0.68.0", "@oxfmt/binding-linux-arm64-musl": "0.68.0", "@oxfmt/binding-linux-ppc64-gnu": "0.68.0", "@oxfmt/binding-linux-riscv64-gnu": "0.68.0", "@oxfmt/binding-linux-riscv64-musl": "0.68.0", "@oxfmt/binding-linux-s390x-gnu": "0.68.0", "@oxfmt/binding-linux-x64-gnu": "0.68.0", "@oxfmt/binding-linux-x64-musl": "0.68.0", "@oxfmt/binding-openharmony-arm64": "0.68.0", "@oxfmt/binding-win32-arm64-msvc": "0.68.0", "@oxfmt/binding-win32-ia32-msvc": "0.68.0", "@oxfmt/binding-win32-x64-msvc": "0.68.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-Z0XMofcXCGUXbcpBHnWyUiX93BGiw1B+lcHNbQDWEtOhX06ewoFfu4zXkyiLhRrNnMq0twqXRHUcJetf+GsiQQ=="],
|
"oxfmt": ["oxfmt@0.70.0", "", { "dependencies": { "tinypool": "2.1.2" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.70.0", "@oxfmt/binding-android-arm64": "0.70.0", "@oxfmt/binding-darwin-arm64": "0.70.0", "@oxfmt/binding-darwin-x64": "0.70.0", "@oxfmt/binding-freebsd-x64": "0.70.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.70.0", "@oxfmt/binding-linux-arm-musleabihf": "0.70.0", "@oxfmt/binding-linux-arm64-gnu": "0.70.0", "@oxfmt/binding-linux-arm64-musl": "0.70.0", "@oxfmt/binding-linux-ppc64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-gnu": "0.70.0", "@oxfmt/binding-linux-riscv64-musl": "0.70.0", "@oxfmt/binding-linux-s390x-gnu": "0.70.0", "@oxfmt/binding-linux-x64-gnu": "0.70.0", "@oxfmt/binding-linux-x64-musl": "0.70.0", "@oxfmt/binding-openharmony-arm64": "0.70.0", "@oxfmt/binding-win32-arm64-msvc": "0.70.0", "@oxfmt/binding-win32-ia32-msvc": "0.70.0", "@oxfmt/binding-win32-x64-msvc": "0.70.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-IsHxZ4y0wQLLMhnrJblBJgZsLDzfULrJnAw5j/QqsTlMa/m3AqsbToi+W71uhBGaqlqq/PbbjvHc09TJwdv3Tw=="],
|
||||||
|
|
||||||
"oxlint": ["oxlint@1.83.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.83.0", "@oxlint/binding-android-arm64": "1.83.0", "@oxlint/binding-darwin-arm64": "1.83.0", "@oxlint/binding-darwin-x64": "1.83.0", "@oxlint/binding-freebsd-x64": "1.83.0", "@oxlint/binding-linux-arm-gnueabihf": "1.83.0", "@oxlint/binding-linux-arm-musleabihf": "1.83.0", "@oxlint/binding-linux-arm64-gnu": "1.83.0", "@oxlint/binding-linux-arm64-musl": "1.83.0", "@oxlint/binding-linux-ppc64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-gnu": "1.83.0", "@oxlint/binding-linux-riscv64-musl": "1.83.0", "@oxlint/binding-linux-s390x-gnu": "1.83.0", "@oxlint/binding-linux-x64-gnu": "1.83.0", "@oxlint/binding-linux-x64-musl": "1.83.0", "@oxlint/binding-openharmony-arm64": "1.83.0", "@oxlint/binding-win32-arm64-msvc": "1.83.0", "@oxlint/binding-win32-ia32-msvc": "1.83.0", "@oxlint/binding-win32-x64-msvc": "1.83.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-cyDzSzaw3uzP0TeCeq3lLRPPoaUxkbB4ZOXj+kn+5r+BX9V+4bNVGk9lxer+WrgcpebH4JxLlJ3KQjveVztOLQ=="],
|
"oxlint": ["oxlint@1.85.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.85.0", "@oxlint/binding-android-arm64": "1.85.0", "@oxlint/binding-darwin-arm64": "1.85.0", "@oxlint/binding-darwin-x64": "1.85.0", "@oxlint/binding-freebsd-x64": "1.85.0", "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", "@oxlint/binding-linux-arm-musleabihf": "1.85.0", "@oxlint/binding-linux-arm64-gnu": "1.85.0", "@oxlint/binding-linux-arm64-musl": "1.85.0", "@oxlint/binding-linux-ppc64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-musl": "1.85.0", "@oxlint/binding-linux-s390x-gnu": "1.85.0", "@oxlint/binding-linux-x64-gnu": "1.85.0", "@oxlint/binding-linux-x64-musl": "1.85.0", "@oxlint/binding-openharmony-arm64": "1.85.0", "@oxlint/binding-win32-arm64-msvc": "1.85.0", "@oxlint/binding-win32-ia32-msvc": "1.85.0", "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg=="],
|
||||||
|
|
||||||
"p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
"p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||||
|
|
||||||
|
|||||||
+8
-7
@@ -3,7 +3,7 @@
|
|||||||
`multi-work` is a **primary-only** agent for coordinating independent work in
|
`multi-work` is a **primary-only** agent for coordinating independent work in
|
||||||
the current Herdr root checkout or new Herdr-managed Git worktrees. Like
|
the current Herdr root checkout or new Herdr-managed Git worktrees. Like
|
||||||
`coordinate`, it uses the configured `deep-lo` model and effort, falling back
|
`coordinate`, it uses the configured `deep-lo` model and effort, falling back
|
||||||
to `good-lo` when `deep-lo` is unavailable. No extra custom model assignment is
|
to `good-lo` when `deep-lo` is unavailable or either custom deep assignment is omitted. No extra custom model assignment is
|
||||||
required.
|
required.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -35,8 +35,9 @@ creation. Creating/opening the root workspace remains a user action.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Use OpenCode **2.0.4**, the version this integration targets. At both the root and
|
Use OpenCode **2.0.15**, the version this integration targets. The package
|
||||||
destination locations, configure:
|
retains Effect `4.0.0-rc.112` because OpenCode 2.0.15's client requires that
|
||||||
|
exact peer version. At both the root and destination locations, configure:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
@@ -136,7 +137,7 @@ The plugin composes the original **Effect executor** in place, preserving the
|
|||||||
foreground results, background job handling, and completion notifications. It does
|
foreground results, background job handling, and completion notifications. It does
|
||||||
not create substitute top-level sessions or a separate job scheduler.
|
not create substitute top-level sessions or a separate job scheduler.
|
||||||
|
|
||||||
OpenCode 2.0.4 awaits a child-ID progress callback after child creation and before
|
OpenCode 2.0.15 awaits a child-ID progress callback after child creation and before
|
||||||
prompting. The wrapper uses that boundary to create the worktree, move the explicit
|
prompting. The wrapper uses that boundary to create the worktree, move the explicit
|
||||||
child ID, and confirm its actual session location before allowing the task prompt.
|
child ID, and confirm its actual session location before allowing the task prompt.
|
||||||
A move-request acknowledgement alone is insufficient. Confirmation is bounded to
|
A move-request acknowledgement alone is insufficient. Confirmation is bounded to
|
||||||
@@ -166,15 +167,15 @@ run the exported plugin through the real Promise adapter, apply configuration af
|
|||||||
both plugin transforms, replay twice, and test separate location registries sharing
|
both plugin transforms, replay twice, and test separate location registries sharing
|
||||||
disk-backed bindings across plugin scope restarts. Their executor is simulated.
|
disk-backed bindings across plugin scope restarts. Their executor is simulated.
|
||||||
|
|
||||||
An opt-in integration check runs the **real OpenCode 2.0.4 server and native
|
An opt-in integration check runs the **real OpenCode 2.0.15 server and native
|
||||||
subagent executor**:
|
subagent executor**:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
OPENCODE_204=/absolute/path/to/2.0.4/opencode bun scripts/test-multi-work-host.ts
|
OPENCODE_2015=/absolute/path/to/2.0.15/opencode bun scripts/test-multi-work-host.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
It requires the exact-version binary (for Linux x64, published as
|
It requires the exact-version binary (for Linux x64, published as
|
||||||
`@opencode/cli-linux-x64@2.0.4`), uses fresh HOME/XDG/config/database paths under
|
`@opencode/cli-linux-x64@2.0.15`), uses fresh HOME/XDG/config/database paths under
|
||||||
`/tmp/opencode`, and starts only loopback services. Herdr is a fake CLI performing
|
`/tmp/opencode`, and starts only loopback services. Herdr is a fake CLI performing
|
||||||
real Git worktree operations in the fixture repository; the model is a deterministic
|
real Git worktree operations in the fixture repository; the model is a deterministic
|
||||||
HTTP endpoint. Fixture resources and logs are retained at the printed path.
|
HTTP endpoint. Fixture resources and logs are retained at the printed path.
|
||||||
|
|||||||
@@ -3,22 +3,21 @@
|
|||||||
// Required for multi-work -> coordinate -> workers. Apply in worktrees too.
|
// Required for multi-work -> coordinate -> workers. Apply in worktrees too.
|
||||||
// Multi-work and coordinate use deep-lo, falling back to good-lo when unavailable.
|
// Multi-work and coordinate use deep-lo, falling back to good-lo when unavailable.
|
||||||
"experimental": { "subagent_depth": 2 },
|
"experimental": { "subagent_depth": 2 },
|
||||||
// All six worker assignments are required. Model IDs are local to the provider.
|
// Fast and good assignments are required; both deep assignments are optional.
|
||||||
// An unavailable deep-lo omits deep workers and uses good-lo for orchestration.
|
// Omitting either deep assignment disables both deep workers and uses good-lo.
|
||||||
|
// Models use provider/model names; each assignment needs model and effort.
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"package": "stanp-opencode",
|
"package": "stanp-opencode",
|
||||||
"options": {
|
"options": {
|
||||||
"mode": "custom",
|
"mode": "custom",
|
||||||
"provider": "openai",
|
|
||||||
"lunaPriority": false,
|
|
||||||
"agents": {
|
"agents": {
|
||||||
"fast-lo": { "model": "gpt-5.6-luna", "effort": "low" },
|
"fast-lo": { "model": "openai/gpt-6-luna", "effort": "low" },
|
||||||
"fast-hi": { "model": "gpt-5.6-luna", "effort": "high" },
|
"fast-hi": { "model": "openai/gpt-6-luna", "effort": "high" },
|
||||||
"good-lo": { "model": "gpt-5.6-sol", "effort": "low" },
|
"good-lo": { "model": "openai/gpt-6-sol", "effort": "low" },
|
||||||
"good-hi": { "model": "gpt-5.6-sol", "effort": "high" },
|
"good-hi": { "model": "openai/gpt-6-sol", "effort": "high" },
|
||||||
"deep-lo": { "model": "gpt-6-astra", "effort": "low" },
|
"deep-lo": { "model": "openai/gpt-6-astra", "effort": "low" },
|
||||||
"deep-hi": { "model": "gpt-6-astra", "effort": "high" },
|
"deep-hi": { "model": "openai/gpt-6-astra", "effort": "high" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
// Requires OpenCode 2.0.4. Native worktree delegation is version-sensitive.
|
// Requires OpenCode 2.0.15. Native worktree delegation is version-sensitive.
|
||||||
// Required for multi-work -> coordinate -> workers. Apply in worktrees too.
|
// Required for multi-work -> coordinate -> workers. Apply in worktrees too.
|
||||||
"experimental": { "subagent_depth": 2 },
|
"experimental": { "subagent_depth": 2 },
|
||||||
// Availability uses the native catalog, including integrations and earlier plugins.
|
// Availability uses the native catalog, including integrations and earlier plugins.
|
||||||
|
|||||||
+11
-11
@@ -38,22 +38,22 @@
|
|||||||
"build": "bun scripts/build.ts && tsc --project tsconfig.build.json --emitDeclarationOnly"
|
"build": "bun scripts/build.ts && tsc --project tsconfig.build.json --emitDeclarationOnly"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opencode/client": "2.0.4",
|
"@opencode/client": "2.0.15",
|
||||||
"@opencode/plugin": "2.0.4",
|
"@opencode/plugin": "2.0.15",
|
||||||
"@opencode/schema": "2.0.4",
|
"@opencode/schema": "2.0.15",
|
||||||
"@opentui/core": "0.5.11",
|
"@opentui/core": "0.5.12",
|
||||||
"@opentui/solid": "0.5.11",
|
"@opentui/solid": "0.5.12",
|
||||||
"@oxlint/plugins": "1.83.0",
|
"@oxlint/plugins": "1.85.0",
|
||||||
"@types/bun": "1.4.2",
|
"@types/bun": "1.4.2",
|
||||||
"oxfmt": "0.68.0",
|
"oxfmt": "0.70.0",
|
||||||
"oxlint": "1.83.0",
|
"oxlint": "1.85.0",
|
||||||
"solid-js": "1.9.15",
|
"solid-js": "1.9.15",
|
||||||
"typescript": "7.0.2"
|
"typescript": "7.0.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opencode/plugin": "2.0.4",
|
"@opencode/plugin": "2.0.15",
|
||||||
"@opentui/core": ">=0.5.11",
|
"@opentui/core": ">=0.5.12",
|
||||||
"@opentui/solid": ">=0.5.11",
|
"@opentui/solid": ">=0.5.12",
|
||||||
"solid-js": ">=1.9.0"
|
"solid-js": ">=1.9.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Opt-in real-host check: OPENCODE_204=/absolute/path/to/2.0.4/opencode bun scripts/test-multi-work-host.ts
|
// Opt-in real-host check: OPENCODE_2015=/absolute/path/to/2.0.15/opencode bun scripts/test-multi-work-host.ts
|
||||||
// Only fake Herdr/model servers and fresh temporary repositories/databases are used.
|
// Only fake Herdr/model servers and fresh temporary repositories/databases are used.
|
||||||
import { mkdir, mkdtemp, writeFile, chmod, readFile } from "node:fs/promises";
|
import { mkdir, mkdtemp, writeFile, chmod, readFile } from "node:fs/promises";
|
||||||
import { join, resolve } from "node:path";
|
import { join, resolve } from "node:path";
|
||||||
@@ -7,8 +7,8 @@ import { OpenCode } from "@opencode/client";
|
|||||||
import { command } from "../src/herdr.js";
|
import { command } from "../src/herdr.js";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const binary = process.env.OPENCODE_204;
|
const binary = process.env.OPENCODE_2015;
|
||||||
if (!binary) throw new Error("Set OPENCODE_204 to an isolated OpenCode 2.0.4 binary");
|
if (!binary) throw new Error("Set OPENCODE_2015 to an isolated OpenCode 2.0.15 binary");
|
||||||
const temp = await mkdtemp("/tmp/opencode/native-multi-work-");
|
const temp = await mkdtemp("/tmp/opencode/native-multi-work-");
|
||||||
console.log(`Fixture retained at ${temp}`);
|
console.log(`Fixture retained at ${temp}`);
|
||||||
const root = join(temp, "root"),
|
const root = join(temp, "root"),
|
||||||
@@ -18,8 +18,8 @@ const root = join(temp, "root"),
|
|||||||
const herdrCreation = join(temp, "herdr-creation.txt");
|
const herdrCreation = join(temp, "herdr-creation.txt");
|
||||||
for (const path of [root, bin, config, join(temp, "plugin")]) await mkdir(path);
|
for (const path of [root, bin, config, join(temp, "plugin")]) await mkdir(path);
|
||||||
const signal = AbortSignal.timeout(120_000);
|
const signal = AbortSignal.timeout(120_000);
|
||||||
if ((await command([binary, "--version"], temp, signal)) !== "opencode v2.0.4")
|
if ((await command([binary, "--version"], temp, signal)) !== "opencode v2.0.15")
|
||||||
throw new Error("Exact 2.0.4 binary required");
|
throw new Error("Exact 2.0.15 binary required");
|
||||||
await command(["git", "init", "-b", "main"], root, signal);
|
await command(["git", "init", "-b", "main"], root, signal);
|
||||||
await command(
|
await command(
|
||||||
[
|
[
|
||||||
@@ -145,7 +145,7 @@ async function ready() {
|
|||||||
)?.[1];
|
)?.[1];
|
||||||
if (password)
|
if (password)
|
||||||
headers.authorization = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`;
|
headers.authorization = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`;
|
||||||
await client.server.status();
|
await client.server.info();
|
||||||
return;
|
return;
|
||||||
} catch {
|
} catch {
|
||||||
await Bun.sleep(100);
|
await Bun.sleep(100);
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
name: anti-overengineering
|
||||||
|
description: Prevent unnecessary complexity while planning, implementing, fixing, refactoring, or reviewing code. Use whenever work may add abstractions, dependencies, configuration, generalized frameworks, speculative flexibility, broad rewrites, compatibility for unshipped code, or unrelated cleanup. Also use when the user asks for simple, minimal, surgical, KISS, YAGNI, or the least code.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Anti-overengineering
|
||||||
|
|
||||||
|
Build the smallest correct solution at the right layer. Minimize concepts, moving parts, and maintenance burden—not investigation, correctness, or raw line count.
|
||||||
|
|
||||||
|
## Core invariant
|
||||||
|
|
||||||
|
First understand, then reduce.
|
||||||
|
|
||||||
|
A tiny patch at the wrong layer is not minimal. A short implementation that omits required behavior is not simple. A reusable design with no current reuse is inventory.
|
||||||
|
|
||||||
|
## Correctness floor
|
||||||
|
|
||||||
|
Never simplify by weakening or skipping:
|
||||||
|
|
||||||
|
- explicit user requirements or observable behavior;
|
||||||
|
- root-cause analysis and affected-caller discovery;
|
||||||
|
- trust-boundary validation, authentication, authorization, privacy, or security;
|
||||||
|
- data integrity, error handling that prevents corruption or silent failure, or required concurrency guarantees;
|
||||||
|
- accessibility, compatibility promises, migrations, or public contracts;
|
||||||
|
- measured performance requirements or operationally necessary observability;
|
||||||
|
- the narrowest meaningful verification of the changed behavior.
|
||||||
|
|
||||||
|
Preserve an existing abstraction until you understand why it exists. Fewer lines are useful only when the result is easier to understand and equally correct.
|
||||||
|
|
||||||
|
## Before editing: establish the scope contract
|
||||||
|
|
||||||
|
Capture these facts internally before writing code. State them to the user only when they affect the chosen path.
|
||||||
|
|
||||||
|
1. **Required outcome:** one sentence describing the current behavior the request needs.
|
||||||
|
2. **Current path:** the relevant implementation, callers, local conventions, and root cause for a bug.
|
||||||
|
3. **Invariants:** behavior and boundaries that must remain unchanged.
|
||||||
|
4. **In scope:** the smallest set of responsibilities that must change—not a guessed file count.
|
||||||
|
5. **Out of scope:** cleanup, modernization, and future capabilities not needed for the outcome.
|
||||||
|
6. **Acceptance signal:** the specific runtime scenario, diagnostic, or test that proves the outcome.
|
||||||
|
|
||||||
|
Every changed line must trace to the required outcome, a necessary invariant, or its verification. If it cannot, leave it untouched and mention it separately only when useful.
|
||||||
|
|
||||||
|
## Use the least-power ladder
|
||||||
|
|
||||||
|
Walk the ladder in order. Stop at the first option that completely satisfies the current requirement.
|
||||||
|
|
||||||
|
1. **No new code:** existing behavior, deletion, configuration, documentation, or a workflow change already solves it.
|
||||||
|
2. **Reuse local behavior:** use the repository's existing helper, type, service path, error semantics, and test pattern.
|
||||||
|
3. **Use a standard primitive:** prefer the language standard library, framework primitive, database/query feature, browser API, or platform capability.
|
||||||
|
4. **Use an installed dependency:** do not add a second mechanism the repository already has.
|
||||||
|
5. **Write direct concrete code:** implement the current case in the owning module with the fewest new concepts.
|
||||||
|
6. **Introduce an abstraction:** only after it passes the evidence gate below.
|
||||||
|
|
||||||
|
A shorter expression does not outrank readability. A new dependency must remove more owned complexity than it adds and must solve a current need unavailable from earlier rungs.
|
||||||
|
|
||||||
|
## Make complexity prove its value
|
||||||
|
|
||||||
|
Interfaces, factories, strategies, adapters, registries, plugin systems, generic frameworks, feature flags, configuration knobs, wrappers, and new layers are presumed unnecessary until current evidence justifies them.
|
||||||
|
|
||||||
|
An abstraction is earned when at least one condition is true:
|
||||||
|
|
||||||
|
- multiple current consumers or implementations share the same semantics and the abstraction reduces total concepts now;
|
||||||
|
- a real framework, process, trust, persistence, or public-API boundary requires it;
|
||||||
|
- one authoritative implementation prevents a present consistency, security, or data-integrity risk;
|
||||||
|
- measured performance or test isolation requires the seam and a direct alternative is inadequate.
|
||||||
|
|
||||||
|
Even then, it must have a clear owner, a narrow contract, and less branching than the concrete code it replaces. "We may need it later" is not evidence.
|
||||||
|
|
||||||
|
Apply these existence tests:
|
||||||
|
|
||||||
|
- Every parameter has a current caller that supplies meaningful variation.
|
||||||
|
- Every option has a current use case.
|
||||||
|
- Every branch handles a reachable state.
|
||||||
|
- Every wrapper adds domain semantics, policy, or isolation—not only forwarding.
|
||||||
|
- Every stored or passed value cannot be cheaply derived from values already in scope.
|
||||||
|
- Every compatibility path protects code that has actually shipped or an explicit external contract.
|
||||||
|
|
||||||
|
Prefer two clear copies over a guessed shared abstraction. Extract after the pattern and reason to change are stable—often at the third real occurrence. Textually similar code that changes for different reasons is not duplication. If a shared helper accumulates flags and conditional paths for callers, inline it and let the real boundaries reappear.
|
||||||
|
|
||||||
|
## Implement one present-tense path
|
||||||
|
|
||||||
|
- Fix bugs at the cause, not with repeated caller fallbacks or symptom suppression.
|
||||||
|
- Match established repository structure and style; never introduce a second convention.
|
||||||
|
- Keep a concrete implementation concrete until variation exists.
|
||||||
|
- Do not add unused hooks, optionality, extension points, scaffolding, placeholders, aliases, or `TODO` paths for imagined follow-up work.
|
||||||
|
- Do not preserve compatibility for earlier, unshipped iterations. Update all callers and remove the obsolete path.
|
||||||
|
- Do not mix unrelated cleanup, renames, reformatting, import churn, comment rewrites, type tightening, logging, or dependency updates into the change.
|
||||||
|
- Separate optional structural cleanup from behavioral work. Perform a structural change in the same patch only when it is required for the correct fix.
|
||||||
|
- Prefer obvious control flow and data structures over clever compression. Comments explain non-obvious reasons and constraints, not the conversation that produced the code.
|
||||||
|
- Write for a maintainer who did not see the prompt, plan, or patch history.
|
||||||
|
|
||||||
|
When the user explicitly requests a broad refactor or new extensibility, that is current scope—not speculation. Still implement only the requested variants and contracts.
|
||||||
|
|
||||||
|
## Reject common rationalizations
|
||||||
|
|
||||||
|
| Rationalization | Required response |
|
||||||
|
| --- | --- |
|
||||||
|
| "We'll need this later." | Add it when the need and constraints exist. |
|
||||||
|
| "This makes it reusable." | Name the second current use; otherwise keep it concrete. |
|
||||||
|
| "This should be configurable." | Name current variation; otherwise keep the value local. |
|
||||||
|
| "A clean architecture needs another layer." | Name the boundary or policy it owns; otherwise follow the existing direct path. |
|
||||||
|
| "A wrapper keeps things consistent." | Put real policy in the wrapper or call the underlying API directly. |
|
||||||
|
| "Defensive code is safer." | Prove the state is reachable; retain checks at real trust and integrity boundaries. |
|
||||||
|
| "A local fallback is a smaller bug fix." | Trace the shared path and fix the root cause once. |
|
||||||
|
| "We should clean this up while here." | Leave it untouched unless correctness requires it. |
|
||||||
|
| "Keeping the old path is safer." | Keep it only for shipped behavior or an explicit compatibility contract. |
|
||||||
|
| "More tests are always safer." | Add only tests that defend a changed observable contract or plausible regression. |
|
||||||
|
|
||||||
|
## Final subtraction pass
|
||||||
|
|
||||||
|
After the behavior works, inspect only the changed surface and ask:
|
||||||
|
|
||||||
|
1. Does each changed line trace to the scope contract?
|
||||||
|
2. Did the change add a file, layer, dependency, option, parameter, state field, or public symbol that direct code can avoid?
|
||||||
|
3. Is there now more than one path for the same behavior?
|
||||||
|
4. Can any one-caller helper, forwarding wrapper, dead compatibility path, speculative branch, or explanatory comment disappear?
|
||||||
|
5. Did a refactor leak beyond what the behavioral change requires?
|
||||||
|
6. Does the result make sense without the conversation?
|
||||||
|
7. Is the narrow acceptance signal proven?
|
||||||
|
|
||||||
|
Delete generated scaffolding and obsolete paths. Do not simplify untouched code as a drive-by. Do not remove an existing construct whose purpose remains unknown.
|
||||||
|
|
||||||
|
## Escalation
|
||||||
|
|
||||||
|
Proceed with the smaller correct path by default. Ask one precise question only when simplification would materially alter explicit behavior, a public API, persisted data, a security boundary, compatibility, or another difficult-to-reverse decision.
|
||||||
|
|
||||||
|
Treat an unexpectedly broad change as a diagnostic, not an automatic failure: explain why each responsibility must move, then look again for a narrower correct boundary. Do not use arbitrary line or file limits; optimize reviewer cognitive load and reversibility.
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
|
||||||
|
Keep implementation reports compact:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Minimal path: [the direct solution and owning layer]
|
||||||
|
Avoided: [notable abstraction, dependency, configuration, or drive-by work omitted]
|
||||||
|
Verified: [specific behavior exercised]
|
||||||
|
```
|
||||||
|
|
||||||
|
Omit `Avoided` when nothing notable was rejected. For reviews, rank only actionable reductions by impact and include the safety caveat that preserves behavior. If no meaningful complexity can be removed, say so rather than inventing findings.
|
||||||
+43
-16
@@ -9,40 +9,67 @@ export const workerNames = [
|
|||||||
"deep-hi",
|
"deep-hi",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const assignment = z.strictObject({
|
const gptAssignment = z.strictObject({
|
||||||
model: z.string().trim().min(1),
|
model: z.string().trim().min(1),
|
||||||
effort: z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]),
|
effort: z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const customAssignment = gptAssignment.extend({
|
||||||
|
model: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((model) => {
|
||||||
|
const slash = model.indexOf("/");
|
||||||
|
return slash > 0 && slash < model.length - 1;
|
||||||
|
}, "model must use the provider/model convention"),
|
||||||
|
});
|
||||||
|
|
||||||
const common = {
|
const common = {
|
||||||
provider: z.string().trim().min(1).default("openai"),
|
|
||||||
lunaPriority: z.boolean().default(true),
|
|
||||||
playwright: z.boolean().default(false),
|
playwright: z.boolean().default(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const settings = z.discriminatedUnion("mode", [
|
export const settings = z.discriminatedUnion("mode", [
|
||||||
z.strictObject({ ...common, mode: z.literal("gpt") }),
|
z
|
||||||
|
.strictObject({
|
||||||
|
...common,
|
||||||
|
mode: z.literal("gpt"),
|
||||||
|
provider: z.string().trim().min(1).default("openai"),
|
||||||
|
lunaPriority: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
.transform((value, context) => {
|
||||||
|
if (value.provider !== "openai" && value.lunaPriority !== undefined) {
|
||||||
|
context.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
path: ["lunaPriority"],
|
||||||
|
message: "lunaPriority is only supported by the openai provider",
|
||||||
|
});
|
||||||
|
return z.NEVER;
|
||||||
|
}
|
||||||
|
return value.provider === "openai"
|
||||||
|
? { ...value, lunaPriority: value.lunaPriority ?? true }
|
||||||
|
: value;
|
||||||
|
}),
|
||||||
z.strictObject({
|
z.strictObject({
|
||||||
...common,
|
...common,
|
||||||
mode: z.literal("custom"),
|
mode: z.literal("custom"),
|
||||||
agents: z.strictObject({
|
agents: z.strictObject({
|
||||||
"fast-lo": assignment,
|
"fast-lo": customAssignment,
|
||||||
"fast-hi": assignment,
|
"fast-hi": customAssignment,
|
||||||
"good-lo": assignment,
|
"good-lo": customAssignment,
|
||||||
"good-hi": assignment,
|
"good-hi": customAssignment,
|
||||||
"deep-lo": assignment,
|
"deep-lo": customAssignment.optional(),
|
||||||
"deep-hi": assignment,
|
"deep-hi": customAssignment.optional(),
|
||||||
// Deprecated compatibility field. Coordinate is derived from deep-lo/good-lo.
|
// Deprecated compatibility field. Coordinate is derived from deep-lo/good-lo.
|
||||||
coordinate: assignment.optional(),
|
coordinate: customAssignment.optional(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const gptAgents = {
|
export const gptAgents = {
|
||||||
"fast-lo": { model: "gpt-5.6-luna", effort: "low" },
|
"fast-lo": { model: "gpt-6-luna", effort: "low" },
|
||||||
"fast-hi": { model: "gpt-5.6-luna", effort: "high" },
|
"fast-hi": { model: "gpt-6-luna", effort: "high" },
|
||||||
"good-lo": { model: "gpt-5.6-sol", effort: "low" },
|
"good-lo": { model: "gpt-6-sol", effort: "low" },
|
||||||
"good-hi": { model: "gpt-5.6-sol", effort: "high" },
|
"good-hi": { model: "gpt-6-sol", effort: "high" },
|
||||||
"deep-lo": { model: "gpt-6-astra", effort: "low" },
|
"deep-lo": { model: "gpt-6-astra", effort: "low" },
|
||||||
"deep-hi": { model: "gpt-6-astra", effort: "high" },
|
"deep-hi": { model: "gpt-6-astra", effort: "high" },
|
||||||
} satisfies Record<(typeof workerNames)[number], z.infer<typeof assignment>>;
|
} satisfies Record<(typeof workerNames)[number], z.infer<typeof gptAssignment>>;
|
||||||
|
|||||||
+63
-11
@@ -3,7 +3,7 @@ import type { RpcRegistration } from "@opencode/plugin/promise/rpc";
|
|||||||
import type { ModelInfo } from "@opencode/client";
|
import type { ModelInfo } from "@opencode/client";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { gptAgents, settings, workerNames } from "./config.js";
|
import { gptAgents, settings, workerNames } from "./config.js";
|
||||||
import { discover } from "./provider.js";
|
import { discover, modelKey } from "./provider.js";
|
||||||
import { descriptions, routingGuide } from "./routing.js";
|
import { descriptions, routingGuide } from "./routing.js";
|
||||||
import { QuotaTracker } from "./quota.js";
|
import { QuotaTracker } from "./quota.js";
|
||||||
import { QuotaRpc } from "./quota-rpc.js";
|
import { QuotaRpc } from "./quota-rpc.js";
|
||||||
@@ -12,6 +12,10 @@ import { Plugin as EffectPlugin } from "@opencode/plugin/effect";
|
|||||||
import { fromPromise } from "@opencode/plugin/promise/adapter";
|
import { fromPromise } from "@opencode/plugin/promise/adapter";
|
||||||
import { Effect } from "effect";
|
import { Effect } from "effect";
|
||||||
import { bindingKey, bindingSchema, installMultiWork } from "./multi-work.js";
|
import { bindingKey, bindingSchema, installMultiWork } from "./multi-work.js";
|
||||||
|
import { Skill } from "@opencode/schema/skill";
|
||||||
|
import { AbsolutePath } from "@opencode/schema/schema";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import antiOverengineering from "./anti-overengineering.md" with { type: "text" };
|
||||||
|
|
||||||
// Permission actions use wildcards, not filesystem globs.
|
// Permission actions use wildcards, not filesystem globs.
|
||||||
function matches(value: string, pattern: string) {
|
function matches(value: string, pattern: string) {
|
||||||
@@ -32,6 +36,7 @@ const reading = [
|
|||||||
"webfetch",
|
"webfetch",
|
||||||
"websearch",
|
"websearch",
|
||||||
"question",
|
"question",
|
||||||
|
"skill",
|
||||||
"external_directory",
|
"external_directory",
|
||||||
"doom_loop",
|
"doom_loop",
|
||||||
];
|
];
|
||||||
@@ -50,6 +55,10 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
id: "stanp-opencode",
|
id: "stanp-opencode",
|
||||||
async setup(context) {
|
async setup(context) {
|
||||||
const selected = settings.parse({ mode: "gpt", ...context.options });
|
const selected = settings.parse({ mode: "gpt", ...context.options });
|
||||||
|
const lunaPriority =
|
||||||
|
selected.mode === "gpt" &&
|
||||||
|
selected.provider === "openai" &&
|
||||||
|
selected.lunaPriority === true;
|
||||||
let models = new Map<string, { model: ModelInfo; serviceTier: string | undefined }>();
|
let models = new Map<string, { model: ModelInfo; serviceTier: string | undefined }>();
|
||||||
let inherited: Agent.Info["permissions"] = Agent.Info.default(
|
let inherited: Agent.Info["permissions"] = Agent.Info.default(
|
||||||
Agent.ID.make("build"),
|
Agent.ID.make("build"),
|
||||||
@@ -83,7 +92,7 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (event.type === "model.updated") {
|
if (event.type === "model.updated") {
|
||||||
models = await discover(context, selected.provider);
|
models = await discover(context, lunaPriority);
|
||||||
await context.agent.reload();
|
await context.agent.reload();
|
||||||
} else if (event.type === "agent.updated") {
|
} else if (event.type === "agent.updated") {
|
||||||
// The configured build transform may run after ours. Read the final
|
// The configured build transform may run after ours. Read the final
|
||||||
@@ -118,19 +127,42 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
if (!controller.signal.aborted)
|
if (!controller.signal.aborted)
|
||||||
console.error("stanp-opencode model update failed", error);
|
console.error("stanp-opencode model update failed", error);
|
||||||
});
|
});
|
||||||
models = await discover(context, selected.provider);
|
models = await discover(context, lunaPriority);
|
||||||
const { data: initialAgents } = await context.agent.list();
|
const { data: initialAgents } = await context.agent.list();
|
||||||
inherited =
|
inherited =
|
||||||
initialAgents.find((agent) => agent.id === "build")?.permissions ?? inherited;
|
initialAgents.find((agent) => agent.id === "build")?.permissions ?? inherited;
|
||||||
permissionsChanged(inherited);
|
permissionsChanged(inherited);
|
||||||
permissionKey = JSON.stringify(inherited);
|
permissionKey = JSON.stringify(inherited);
|
||||||
const assignments = selected.mode === "gpt" ? gptAgents : selected.agents;
|
const assignments =
|
||||||
|
selected.mode === "gpt"
|
||||||
|
? Object.fromEntries(
|
||||||
|
workerNames.map((name) => [
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
...gptAgents[name],
|
||||||
|
provider: selected.provider,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
: selected.agents;
|
||||||
let workers: (typeof workerNames)[number][] = [];
|
let workers: (typeof workerNames)[number][] = [];
|
||||||
const active = new Set<string>();
|
const active = new Set<string>();
|
||||||
const playwrightCommand = selected.playwright
|
const playwrightCommand = selected.playwright
|
||||||
? await playwrightMcpCommand(context.location.directory)
|
? await playwrightMcpCommand(context.location.directory)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
await context.skill.transform((editor) => {
|
||||||
|
if (editor.get("anti-overengineering")) return;
|
||||||
|
editor.add({
|
||||||
|
id: Skill.ID.make("anti-overengineering"),
|
||||||
|
name: Skill.Name.make("anti-overengineering"),
|
||||||
|
description:
|
||||||
|
"Build the smallest correct solution without sacrificing correctness.",
|
||||||
|
path: AbsolutePath.make(fileURLToPath(import.meta.url)),
|
||||||
|
content: antiOverengineering,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await context.mcp.transform((editor) => {
|
await context.mcp.transform((editor) => {
|
||||||
if (playwrightCommand && !editor.get("playwright"))
|
if (playwrightCommand && !editor.get("playwright"))
|
||||||
editor.set("playwright", {
|
editor.set("playwright", {
|
||||||
@@ -163,11 +195,29 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
});
|
});
|
||||||
|
|
||||||
await context.agent.transform((editor) => {
|
await context.agent.transform((editor) => {
|
||||||
const deep = models.has(assignments["deep-lo"].model);
|
const coordinates = (assignment: { model: string }) => {
|
||||||
|
if (selected.mode === "gpt")
|
||||||
|
return { provider: selected.provider, model: assignment.model };
|
||||||
|
const slash = assignment.model.indexOf("/");
|
||||||
|
return {
|
||||||
|
provider: assignment.model.slice(0, slash),
|
||||||
|
model: assignment.model.slice(slash + 1),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const availableFor = (assignment: { model: string }) => {
|
||||||
|
const { provider, model } = coordinates(assignment);
|
||||||
|
return models.get(modelKey(provider, model));
|
||||||
|
};
|
||||||
|
const deepLo = assignments["deep-lo"];
|
||||||
|
const deep =
|
||||||
|
deepLo !== undefined &&
|
||||||
|
assignments["deep-hi"] !== undefined &&
|
||||||
|
availableFor(deepLo) !== undefined;
|
||||||
const candidates = workerNames.filter((name) => deep || !name.startsWith("deep-"));
|
const candidates = workerNames.filter((name) => deep || !name.startsWith("deep-"));
|
||||||
const coordinate = assignments[deep ? "deep-lo" : "good-lo"];
|
const coordinate = deep ? deepLo : assignments["good-lo"];
|
||||||
workers = [coordinate, ...candidates.map((name) => assignments[name])].every(
|
workers = [coordinate, ...candidates.map((name) => assignments[name])].every(
|
||||||
({ model }) => models.has(model),
|
(assignment) =>
|
||||||
|
assignment !== undefined && availableFor(assignment) !== undefined,
|
||||||
)
|
)
|
||||||
? candidates
|
? candidates
|
||||||
: [];
|
: [];
|
||||||
@@ -175,8 +225,8 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
for (const name of workers) active.add(name);
|
for (const name of workers) active.add(name);
|
||||||
if (workers.length === 0) return;
|
if (workers.length === 0) return;
|
||||||
for (const name of workers) {
|
for (const name of workers) {
|
||||||
const assignment = assignments[name];
|
const assignment = assignments[name]!;
|
||||||
const available = models.get(assignment.model)!;
|
const available = availableFor(assignment)!;
|
||||||
editor.update(name, (agent) => {
|
editor.update(name, (agent) => {
|
||||||
const hasVariant = available.model.variants.some(
|
const hasVariant = available.model.variants.some(
|
||||||
({ id }) => id === assignment.effort,
|
({ id }) => id === assignment.effort,
|
||||||
@@ -196,7 +246,7 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
};
|
};
|
||||||
if (selected.lunaPriority && available.serviceTier)
|
if (lunaPriority && available.serviceTier)
|
||||||
agent.request.settings.serviceTier = available.serviceTier;
|
agent.request.settings.serviceTier = available.serviceTier;
|
||||||
agent.permissions = [
|
agent.permissions = [
|
||||||
...structuredClone(inherited),
|
...structuredClone(inherited),
|
||||||
@@ -205,7 +255,7 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
editor.update("coordinate", (agent) => {
|
editor.update("coordinate", (agent) => {
|
||||||
const available = models.get(coordinate.model)!;
|
const available = availableFor(coordinate)!;
|
||||||
const hasVariant = available.model.variants.some(
|
const hasVariant = available.model.variants.some(
|
||||||
({ id }) => id === coordinate.effort,
|
({ id }) => id === coordinate.effort,
|
||||||
);
|
);
|
||||||
@@ -224,6 +274,8 @@ const makeBasePlugin = (permissionsChanged: (permissions: Agent.Info["permission
|
|||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
};
|
};
|
||||||
|
if (lunaPriority && available.serviceTier)
|
||||||
|
agent.request.settings.serviceTier = available.serviceTier;
|
||||||
agent.system = routingGuide(deep);
|
agent.system = routingGuide(deep);
|
||||||
agent.permissions = [
|
agent.permissions = [
|
||||||
{ action: "*", resource: "*", effect: "deny" },
|
{ action: "*", resource: "*", effect: "deny" },
|
||||||
|
|||||||
+1
-1
@@ -317,7 +317,7 @@ export const installMultiWork = (
|
|||||||
...call,
|
...call,
|
||||||
progress: (metadata) =>
|
progress: (metadata) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
// Native 2.0.4 awaits this update after creation and before prompt admission.
|
// Native 2.0.15 awaits this update after creation and before prompt admission.
|
||||||
yield* call.progress(metadata);
|
yield* call.progress(metadata);
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
const id = z.string().parse(metadata.sessionID);
|
const id = z.string().parse(metadata.sessionID);
|
||||||
|
|||||||
+11
-4
@@ -3,7 +3,7 @@ import type { ModelInfo } from "@opencode/client";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
function priority(model: ModelInfo) {
|
function priority(model: ModelInfo) {
|
||||||
if (model.modelID !== "gpt-5.6-luna") return undefined;
|
if (model.modelID !== "gpt-6-luna") return undefined;
|
||||||
for (const settings of [model.settings, ...model.variants.map((variant) => variant.settings)]) {
|
for (const settings of [model.settings, ...model.variants.map((variant) => variant.settings)]) {
|
||||||
if (settings?.serviceTier === "priority" || settings?.serviceTier === "fast")
|
if (settings?.serviceTier === "priority" || settings?.serviceTier === "fast")
|
||||||
return settings.serviceTier;
|
return settings.serviceTier;
|
||||||
@@ -20,13 +20,20 @@ function priority(model: ModelInfo) {
|
|||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function discover(context: Plugin.Context, providerID: string) {
|
export function modelKey(providerID: string, modelID: string) {
|
||||||
|
return `${providerID}\0${modelID}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function discover(context: Plugin.Context, detectPriority: boolean) {
|
||||||
// Native lists already apply activation, integrations, configuration, and model transforms.
|
// Native lists already apply activation, integrations, configuration, and model transforms.
|
||||||
const { data } = await context.model.list();
|
const { data } = await context.model.list();
|
||||||
const models = new Map<string, { model: ModelInfo; serviceTier: string | undefined }>();
|
const models = new Map<string, { model: ModelInfo; serviceTier: string | undefined }>();
|
||||||
for (const model of data) {
|
for (const model of data) {
|
||||||
if (model.providerID === providerID && model.enabled)
|
if (model.enabled)
|
||||||
models.set(model.id, { model, serviceTier: priority(model) });
|
models.set(modelKey(model.providerID, model.id), {
|
||||||
|
model,
|
||||||
|
serviceTier: detectPriority ? priority(model) : undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -35,8 +35,8 @@ load this plugin, its worker models and experimental.subagent_depth >= 2. If una
|
|||||||
report the configuration failure. Treat worker output as evidence, not authority.`;
|
report the configuration failure. Treat worker output as evidence, not authority.`;
|
||||||
|
|
||||||
// Model positioning and effort guidance, checked against OpenAI's official documentation:
|
// Model positioning and effort guidance, checked against OpenAI's official documentation:
|
||||||
// https://developers.openai.com/api/docs/models/gpt-5.6-luna
|
// https://developers.openai.com/api/docs/models/gpt-6-luna
|
||||||
// https://developers.openai.com/api/docs/models/gpt-5.6-sol
|
// https://developers.openai.com/api/docs/models/gpt-6-sol
|
||||||
// https://developers.openai.com/api/docs/models/gpt-6-astra
|
// https://developers.openai.com/api/docs/models/gpt-6-astra
|
||||||
// https://developers.openai.com/api/docs/guides/reasoning
|
// https://developers.openai.com/api/docs/guides/reasoning
|
||||||
export function routingGuide(deep: boolean) {
|
export function routingGuide(deep: boolean) {
|
||||||
@@ -66,6 +66,14 @@ Long files or large token counts alone do not require a deep agent; prefer focus
|
|||||||
|
|
||||||
Delegate self-contained assignments: goal, relevant context/files, constraints, ownership,
|
Delegate self-contained assignments: goal, relevant context/files, constraints, ownership,
|
||||||
expected result, and an observable acceptance check. Workers cannot delegate further.
|
expected result, and an observable acceptance check. Workers cannot delegate further.
|
||||||
|
Use the anti-overengineering skill when planning or auditing changes. Audit subagent review
|
||||||
|
findings for defensive additions that clutter incremental development: reject overkill or
|
||||||
|
frivolously redundant findings with concise evidence. For a legitimate defensive finding
|
||||||
|
that would introduce substantial new logic or complexity, explain the reachable scenario,
|
||||||
|
the complexity and a simpler correct alternative, then ask the user whether to implement
|
||||||
|
the proposed addition BEFORE delegating it. Ordinary in-scope bugfixes need no extra approval.
|
||||||
|
Never waive correctness, security or data integrity for simplicity; if required protection
|
||||||
|
needs substantial complexity, explain the blocker and ask the user how to proceed.
|
||||||
Parallelize genuinely independent tasks with disjoint write ownership; serialize dependencies
|
Parallelize genuinely independent tasks with disjoint write ownership; serialize dependencies
|
||||||
and edits to shared files. Do not pay for redundant investigation or repeat completed work.
|
and edits to shared files. Do not pay for redundant investigation or repeat completed work.
|
||||||
Use fast-lo to gather missing facts before asking an implementation agent to guess.
|
Use fast-lo to gather missing facts before asking an implementation agent to guess.
|
||||||
|
|||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
declare module "*.md" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
+23
-3
@@ -11,6 +11,7 @@ import { AbsolutePath } from "@opencode/schema/schema";
|
|||||||
import type { Context } from "@opencode/plugin/effect/plugin";
|
import type { Context } from "@opencode/plugin/effect/plugin";
|
||||||
import type { AgentEditor } from "@opencode/plugin/effect/agent";
|
import type { AgentEditor } from "@opencode/plugin/effect/agent";
|
||||||
import type { ToolEditor } from "@opencode/plugin/effect/tool";
|
import type { ToolEditor } from "@opencode/plugin/effect/tool";
|
||||||
|
import type { SkillEditor } from "@opencode/plugin/effect/skill";
|
||||||
import type { SessionHooks } from "@opencode/plugin/effect/session";
|
import type { SessionHooks } from "@opencode/plugin/effect/session";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import plugin from "../src/index.js";
|
import plugin from "../src/index.js";
|
||||||
@@ -218,7 +219,7 @@ function host(fixture: Awaited<ReturnType<typeof scenario>>, directory: string)
|
|||||||
const store = (key: string) =>
|
const store = (key: string) =>
|
||||||
join(fixture.directory, "store", `${encodeURIComponent(key)}.json`);
|
join(fixture.directory, "store", `${encodeURIComponent(key)}.json`);
|
||||||
const providerID = Provider.ID.make("fixture");
|
const providerID = Provider.ID.make("fixture");
|
||||||
const models = ["gpt-5.6-luna", "gpt-5.6-sol", "gpt-6-astra"].map((id) => ({
|
const models = ["gpt-6-luna", "gpt-6-sol", "gpt-6-astra"].map((id) => ({
|
||||||
...Model.Info.default(providerID, Model.ID.make(id)),
|
...Model.Info.default(providerID, Model.ID.make(id)),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
}));
|
}));
|
||||||
@@ -227,7 +228,7 @@ function host(fixture: Awaited<ReturnType<typeof scenario>>, directory: string)
|
|||||||
const context = {
|
const context = {
|
||||||
options: { provider: "fixture" },
|
options: { provider: "fixture" },
|
||||||
location,
|
location,
|
||||||
app: { version: "2.0.4-fixture" },
|
app: { version: "2.0.15-fixture" },
|
||||||
command: {},
|
command: {},
|
||||||
experimental: { terminal: {} },
|
experimental: { terminal: {} },
|
||||||
generate: {},
|
generate: {},
|
||||||
@@ -235,7 +236,26 @@ function host(fixture: Awaited<ReturnType<typeof scenario>>, directory: string)
|
|||||||
permission: {},
|
permission: {},
|
||||||
plugin: {},
|
plugin: {},
|
||||||
reference: {},
|
reference: {},
|
||||||
skill: {},
|
skill: {
|
||||||
|
transform: (callback: (editor: SkillEditor) => void) =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
const skills = new Map<string, NonNullable<ReturnType<SkillEditor["get"]>>>();
|
||||||
|
callback({
|
||||||
|
list: () => [...skills.values()],
|
||||||
|
get: (id) => skills.get(id),
|
||||||
|
add: (skill) => {
|
||||||
|
skills.set(skill.id, skill);
|
||||||
|
},
|
||||||
|
update(id, update) {
|
||||||
|
const skill = skills.get(id);
|
||||||
|
if (skill) update(skill);
|
||||||
|
},
|
||||||
|
remove: (id) => {
|
||||||
|
skills.delete(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}).pipe(Effect.andThen(registration)),
|
||||||
|
},
|
||||||
vcs: { branch: {} },
|
vcs: { branch: {} },
|
||||||
websearch: {},
|
websearch: {},
|
||||||
worktree: {},
|
worktree: {},
|
||||||
|
|||||||
Vendored
+1
-1
@@ -37,7 +37,7 @@ export default Plugin.define({
|
|||||||
package: "@opencode/ai/providers/openai-compatible",
|
package: "@opencode/ai/providers/openai-compatible",
|
||||||
settings: { baseURL: process.env.FIXTURE_MODEL_URL! },
|
settings: { baseURL: process.env.FIXTURE_MODEL_URL! },
|
||||||
},
|
},
|
||||||
models: ["gpt-5.6-luna", "gpt-5.6-sol", "gpt-6-astra"].map((id) =>
|
models: ["gpt-6-luna", "gpt-6-sol", "gpt-6-astra"].map((id) =>
|
||||||
Model.Info.default(providerID, Model.ID.make(id)),
|
Model.Info.default(providerID, Model.ID.make(id)),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
|||||||
+264
-33
@@ -8,8 +8,10 @@ import type { DeepMutable } from "@opencode/plugin/promise/types";
|
|||||||
import type { Info, ToolContext, ToolEditor } from "@opencode/plugin/promise/tool";
|
import type { Info, ToolContext, ToolEditor } from "@opencode/plugin/promise/tool";
|
||||||
import type { JsonValue, ModelInfo, SessionGetInput } from "@opencode/client";
|
import type { JsonValue, ModelInfo, SessionGetInput } from "@opencode/client";
|
||||||
import type { SessionContext } from "@opencode/plugin/promise/session";
|
import type { SessionContext } from "@opencode/plugin/promise/session";
|
||||||
|
import type { SkillEditor } from "@opencode/plugin/promise/skill";
|
||||||
|
import type { Skill } from "@opencode/schema/skill";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { gptAgents } from "../src/config.js";
|
import { gptAgents, settings, workerNames } from "../src/config.js";
|
||||||
import { basePlugin as plugin } from "../src/index.js";
|
import { basePlugin as plugin } from "../src/index.js";
|
||||||
import { playwrightMcpCommand } from "../src/playwright.js";
|
import { playwrightMcpCommand } from "../src/playwright.js";
|
||||||
import { bindingKey } from "../src/multi-work.js";
|
import { bindingKey } from "../src/multi-work.js";
|
||||||
@@ -27,7 +29,31 @@ function reasoningModel(id: string): ModelInfo {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const allModels = ["gpt-5.6-luna", "gpt-5.6-sol", "gpt-6-astra"].map(reasoningModel);
|
function providerModel(provider: string, id: string): ModelInfo {
|
||||||
|
return {
|
||||||
|
...reasoningModel(id),
|
||||||
|
providerID: Provider.ID.make(provider),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const allModels = ["gpt-6-luna", "gpt-6-sol", "gpt-6-astra"].map(reasoningModel);
|
||||||
|
type CustomAssignment = {
|
||||||
|
model: string;
|
||||||
|
effort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
||||||
|
};
|
||||||
|
const customAssignments = (provider = "test") => {
|
||||||
|
const assignment = (name: (typeof workerNames)[number]): CustomAssignment => ({
|
||||||
|
...gptAgents[name],
|
||||||
|
model: `${provider}/${gptAgents[name].model}`,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
"fast-lo": assignment("fast-lo"),
|
||||||
|
"fast-hi": assignment("fast-hi"),
|
||||||
|
"good-lo": assignment("good-lo"),
|
||||||
|
"good-hi": assignment("good-hi"),
|
||||||
|
"deep-lo": assignment("deep-lo"),
|
||||||
|
"deep-hi": assignment("deep-hi"),
|
||||||
|
};
|
||||||
|
};
|
||||||
const cleanups: Plugin.Cleanup[] = [];
|
const cleanups: Plugin.Cleanup[] = [];
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
for (const cleanup of cleanups.splice(0)) await cleanup();
|
for (const cleanup of cleanups.splice(0)) await cleanup();
|
||||||
@@ -83,6 +109,7 @@ async function host(
|
|||||||
});
|
});
|
||||||
const storage = new Map<string, JsonValue>();
|
const storage = new Map<string, JsonValue>();
|
||||||
const tools = new Map<string, Info>();
|
const tools = new Map<string, Info>();
|
||||||
|
const skills = new Map<string, Skill.Info>();
|
||||||
const mcpTools = new Set(configuredMcpTools);
|
const mcpTools = new Set(configuredMcpTools);
|
||||||
const mcpServers = new Map(Object.entries(structuredClone(configuredMcp)));
|
const mcpServers = new Map(Object.entries(structuredClone(configuredMcp)));
|
||||||
let mcpTransform!: (editor: {
|
let mcpTransform!: (editor: {
|
||||||
@@ -180,6 +207,25 @@ async function host(
|
|||||||
return registration;
|
return registration;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
skill: {
|
||||||
|
async transform(callback: (editor: SkillEditor) => void) {
|
||||||
|
callback({
|
||||||
|
list: () => [...skills.values()],
|
||||||
|
get: (id) => skills.get(id),
|
||||||
|
add: (skill) => {
|
||||||
|
skills.set(skill.id, skill);
|
||||||
|
},
|
||||||
|
update(id, update) {
|
||||||
|
const skill = skills.get(id);
|
||||||
|
if (skill) update(skill);
|
||||||
|
},
|
||||||
|
remove: (id) => {
|
||||||
|
skills.delete(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return registration;
|
||||||
|
},
|
||||||
|
},
|
||||||
agent: {
|
agent: {
|
||||||
async list() {
|
async list() {
|
||||||
return { data: [...agents.values()] };
|
return { data: [...agents.values()] };
|
||||||
@@ -248,6 +294,7 @@ async function host(
|
|||||||
if (cleanup) cleanups.push(cleanup);
|
if (cleanup) cleanups.push(cleanup);
|
||||||
return {
|
return {
|
||||||
agents,
|
agents,
|
||||||
|
skills,
|
||||||
build,
|
build,
|
||||||
mcpServers,
|
mcpServers,
|
||||||
defaultAgent,
|
defaultAgent,
|
||||||
@@ -345,22 +392,27 @@ test("orchestration inherits deep-lo and falls back to good-lo with available wo
|
|||||||
expect(String(full.agents.get("coordinate")?.model?.variant)).toBe("low");
|
expect(String(full.agents.get("coordinate")?.model?.variant)).toBe("low");
|
||||||
const reduced = await host(allModels.slice(0, 2));
|
const reduced = await host(allModels.slice(0, 2));
|
||||||
expect(reduced.agents.has("deep-hi")).toBe(false);
|
expect(reduced.agents.has("deep-hi")).toBe(false);
|
||||||
expect(String(reduced.agents.get("coordinate")?.model?.id)).toBe("gpt-5.6-sol");
|
expect(String(reduced.agents.get("coordinate")?.model?.id)).toBe("gpt-6-sol");
|
||||||
expect(String(reduced.agents.get("coordinate")?.model?.variant)).toBe("low");
|
expect(String(reduced.agents.get("coordinate")?.model?.variant)).toBe("low");
|
||||||
expect(reduced.agents.get("coordinate")?.system).toContain("Deep agents are unavailable");
|
expect(reduced.agents.get("coordinate")?.system).toContain("Deep agents are unavailable");
|
||||||
expect((await host([])).agents.has("coordinate")).toBe(false);
|
expect((await host([])).agents.has("coordinate")).toBe(false);
|
||||||
expect((await host(allModels.slice(1))).agents.has("coordinate")).toBe(false);
|
expect((await host(allModels.slice(1))).agents.has("coordinate")).toBe(false);
|
||||||
|
|
||||||
const customAgents = {
|
const customAgents = {
|
||||||
...gptAgents,
|
...customAssignments(),
|
||||||
"good-lo": { model: "custom-good", effort: "high" as const },
|
"good-lo": {
|
||||||
"deep-lo": { model: "custom-deep", effort: "medium" as const },
|
model: "test/custom-good",
|
||||||
|
effort: "high" as const,
|
||||||
|
},
|
||||||
|
"deep-lo": {
|
||||||
|
model: "test/custom-deep",
|
||||||
|
effort: "medium" as const,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const custom = await host(
|
const custom = await host(
|
||||||
[...allModels, reasoningModel("custom-good"), reasoningModel("custom-deep")],
|
[...allModels, reasoningModel("custom-good"), reasoningModel("custom-deep")],
|
||||||
{
|
{
|
||||||
mode: "custom",
|
mode: "custom",
|
||||||
provider: "test",
|
|
||||||
agents: customAgents,
|
agents: customAgents,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -370,7 +422,6 @@ test("orchestration inherits deep-lo and falls back to good-lo with available wo
|
|||||||
});
|
});
|
||||||
const customFallback = await host([...allModels, reasoningModel("custom-good")], {
|
const customFallback = await host([...allModels, reasoningModel("custom-good")], {
|
||||||
mode: "custom",
|
mode: "custom",
|
||||||
provider: "test",
|
|
||||||
agents: customAgents,
|
agents: customAgents,
|
||||||
});
|
});
|
||||||
expect(customFallback.agents.get("coordinate")?.model).toMatchObject({
|
expect(customFallback.agents.get("coordinate")?.model).toMatchObject({
|
||||||
@@ -382,13 +433,167 @@ test("orchestration inherits deep-lo and falls back to good-lo with available wo
|
|||||||
(
|
(
|
||||||
await host(allModels, {
|
await host(allModels, {
|
||||||
mode: "custom",
|
mode: "custom",
|
||||||
provider: "test",
|
|
||||||
agents: customAgents,
|
agents: customAgents,
|
||||||
})
|
})
|
||||||
).agents.has("coordinate"),
|
).agents.has("coordinate"),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("custom deep assignments are optional and a partial deep tier falls back to good-lo", async () => {
|
||||||
|
for (const omitted of [["deep-lo", "deep-hi"], ["deep-lo"], ["deep-hi"]] as const) {
|
||||||
|
const agents: Partial<ReturnType<typeof customAssignments>> = customAssignments();
|
||||||
|
for (const name of omitted) delete agents[name];
|
||||||
|
const result = await host(allModels, { mode: "custom", agents });
|
||||||
|
expect(result.agents.get("coordinate")?.model).toMatchObject({
|
||||||
|
id: "gpt-6-sol",
|
||||||
|
variant: "low",
|
||||||
|
});
|
||||||
|
expect(result.agents.get("coordinate")?.system).toContain("Deep agents are unavailable");
|
||||||
|
for (const name of ["fast-lo", "fast-hi", "good-lo", "good-hi"])
|
||||||
|
expect(result.agents.has(name)).toBe(true);
|
||||||
|
expect(result.agents.has("deep-lo")).toBe(false);
|
||||||
|
expect(result.agents.has("deep-hi")).toBe(false);
|
||||||
|
expect((await result.tools("coordinate")).subagent?.input.properties).toMatchObject({
|
||||||
|
agent: { type: "string", enum: ["fast-lo", "fast-hi", "good-lo", "good-hi"] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("registers the embedded anti-overengineering skill and makes it available to coordinate", async () => {
|
||||||
|
const result = await host();
|
||||||
|
const skill = result.skills.get("anti-overengineering")!;
|
||||||
|
expect(String(skill.name)).toBe("anti-overengineering");
|
||||||
|
expect(skill.content).toContain("## Correctness floor");
|
||||||
|
expect(skill.content).toContain("## Final subtraction pass");
|
||||||
|
expect(await Bun.file(skill.path).exists()).toBe(true);
|
||||||
|
const coordinate = result.agents.get("coordinate")!;
|
||||||
|
expect(coordinate.permissions).toContainEqual({
|
||||||
|
action: "skill",
|
||||||
|
resource: "*",
|
||||||
|
effect: "allow",
|
||||||
|
});
|
||||||
|
expect(coordinate.permissions.some((rule) => rule.action === "shell")).toBe(false);
|
||||||
|
for (const models of [allModels, allModels.slice(0, 2)]) {
|
||||||
|
const prompt = (await host(models)).agents.get("coordinate")?.system;
|
||||||
|
expect(prompt).toContain("Audit subagent review");
|
||||||
|
expect(prompt).toContain("reject overkill or");
|
||||||
|
expect(prompt).toContain("BEFORE delegating it");
|
||||||
|
expect(prompt).toContain("Ordinary in-scope bugfixes need no extra approval");
|
||||||
|
expect(prompt).toContain("Never waive correctness, security or data integrity");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("custom assignments resolve duplicate model IDs by provider and refresh provider-aware fallback", async () => {
|
||||||
|
const agents = customAssignments("alpha");
|
||||||
|
agents["fast-lo"] = { ...agents["fast-lo"], model: "beta/shared" };
|
||||||
|
agents["fast-hi"] = { ...agents["fast-hi"], model: "alpha/shared" };
|
||||||
|
agents["deep-lo"] = { ...agents["deep-lo"], model: "beta/deep" };
|
||||||
|
agents["deep-hi"] = { ...agents["deep-hi"], model: "beta/deep" };
|
||||||
|
const base = [
|
||||||
|
providerModel("alpha", "shared"),
|
||||||
|
providerModel("beta", "shared"),
|
||||||
|
providerModel("alpha", "gpt-6-sol"),
|
||||||
|
providerModel("alpha", "gpt-6-astra"),
|
||||||
|
];
|
||||||
|
const result = await host(base, { mode: "custom", agents });
|
||||||
|
expect(String(result.agents.get("fast-lo")?.model?.providerID)).toBe("beta");
|
||||||
|
expect(String(result.agents.get("fast-hi")?.model?.providerID)).toBe("alpha");
|
||||||
|
expect(String(result.agents.get("coordinate")?.model?.id)).toBe("gpt-6-sol");
|
||||||
|
expect(result.agents.has("deep-lo")).toBe(false);
|
||||||
|
|
||||||
|
await result.refresh([...base, providerModel("beta", "deep")]);
|
||||||
|
expect(result.agents.get("coordinate")?.model).toMatchObject({
|
||||||
|
providerID: "beta",
|
||||||
|
id: "deep",
|
||||||
|
});
|
||||||
|
expect(result.agents.has("deep-lo")).toBe(true);
|
||||||
|
|
||||||
|
const nested = customAssignments("alpha");
|
||||||
|
nested["fast-lo"] = { ...nested["fast-lo"], model: "alpha/group/shared" };
|
||||||
|
const nestedResult = await host(
|
||||||
|
[...base, providerModel("alpha", "gpt-6-luna"), providerModel("alpha", "group/shared")],
|
||||||
|
{ mode: "custom", agents: nested },
|
||||||
|
);
|
||||||
|
expect(String(nestedResult.agents.get("fast-lo")?.model?.id)).toBe("group/shared");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("custom configuration requires qualified models and rejects obsolete fields", () => {
|
||||||
|
expect(() => settings.parse({ mode: "custom", agents: customAssignments() })).not.toThrow();
|
||||||
|
for (const omitted of [["deep-lo", "deep-hi"], ["deep-lo"], ["deep-hi"]] as const) {
|
||||||
|
const agents: Partial<ReturnType<typeof customAssignments>> = customAssignments();
|
||||||
|
for (const name of omitted) delete agents[name];
|
||||||
|
expect(() => settings.parse({ mode: "custom", agents })).not.toThrow();
|
||||||
|
}
|
||||||
|
for (const name of ["fast-lo", "fast-hi", "good-lo", "good-hi"] as const) {
|
||||||
|
const agents: Partial<ReturnType<typeof customAssignments>> = customAssignments();
|
||||||
|
delete agents[name];
|
||||||
|
expect(() => settings.parse({ mode: "custom", agents })).toThrow();
|
||||||
|
}
|
||||||
|
for (const name of ["deep-lo", "deep-hi"] as const)
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({
|
||||||
|
mode: "custom",
|
||||||
|
agents: { ...customAssignments(), [name]: { model: "test/model" } },
|
||||||
|
}),
|
||||||
|
).toThrow();
|
||||||
|
for (const model of ["bare", "/model", "provider/"])
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({
|
||||||
|
mode: "custom",
|
||||||
|
agents: {
|
||||||
|
...customAssignments(),
|
||||||
|
"fast-lo": { model, effort: "low" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow();
|
||||||
|
const nested = customAssignments();
|
||||||
|
nested["fast-lo"] = { model: "provider/group/model", effort: "low" };
|
||||||
|
expect(() => settings.parse({ mode: "custom", agents: nested })).not.toThrow();
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({ mode: "custom", provider: "test", agents: customAssignments() }),
|
||||||
|
).toThrow();
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({ mode: "custom", lunaPriority: false, agents: customAssignments() }),
|
||||||
|
).toThrow();
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({
|
||||||
|
mode: "custom",
|
||||||
|
agents: {
|
||||||
|
...customAssignments(),
|
||||||
|
"fast-lo": { ...customAssignments()["fast-lo"], provider: "test" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow();
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({
|
||||||
|
mode: "custom",
|
||||||
|
agents: {
|
||||||
|
...customAssignments(),
|
||||||
|
"fast-lo": { ...customAssignments()["fast-lo"], lunaPriority: true },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow();
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({
|
||||||
|
mode: "custom",
|
||||||
|
agents: { ...customAssignments(), coordinate: customAssignments()["deep-lo"] },
|
||||||
|
}),
|
||||||
|
).not.toThrow();
|
||||||
|
for (const obsolete of [{ provider: "test" }, { lunaPriority: true }])
|
||||||
|
expect(() =>
|
||||||
|
settings.parse({
|
||||||
|
mode: "custom",
|
||||||
|
agents: {
|
||||||
|
...customAssignments(),
|
||||||
|
coordinate: {
|
||||||
|
...customAssignments()["deep-lo"],
|
||||||
|
...obsolete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
test("worker inheritance and coordinator isolation preserve build and default", async () => {
|
test("worker inheritance and coordinator isolation preserve build and default", async () => {
|
||||||
const result = await host();
|
const result = await host();
|
||||||
expect(result.defaultAgent).toBe("build");
|
expect(result.defaultAgent).toBe("build");
|
||||||
@@ -689,33 +894,57 @@ test("coordinate prompt admission requires owned ready placement; local and move
|
|||||||
|
|
||||||
test("reasoning settings and Luna priority use native advertised or official support", async () => {
|
test("reasoning settings and Luna priority use native advertised or official support", async () => {
|
||||||
const luna = {
|
const luna = {
|
||||||
...model("gpt-5.6-luna"),
|
...providerModel("openai", "gpt-6-luna"),
|
||||||
package: "@opencode/ai/providers/openai",
|
package: "@opencode/ai/providers/openai",
|
||||||
settings: { baseURL: "https://api.openai.com/v1" },
|
settings: { baseURL: "https://api.openai.com/v1" },
|
||||||
};
|
};
|
||||||
const ordinary = await host();
|
const openaiModels = [
|
||||||
|
luna,
|
||||||
|
providerModel("openai", "gpt-6-sol"),
|
||||||
|
providerModel("openai", "gpt-6-astra"),
|
||||||
|
];
|
||||||
|
const ordinary = await host([providerModel("openai", "gpt-6-luna"), ...openaiModels.slice(1)], {
|
||||||
|
provider: "openai",
|
||||||
|
});
|
||||||
expect(ordinary.agents.get("fast-lo")?.request.settings).toEqual({});
|
expect(ordinary.agents.get("fast-lo")?.request.settings).toEqual({});
|
||||||
expect(String(ordinary.agents.get("fast-lo")?.model?.variant)).toBe("low");
|
expect(String(ordinary.agents.get("fast-lo")?.model?.variant)).toBe("low");
|
||||||
const official = await host([luna, ...allModels.slice(1)]);
|
const official = await host(openaiModels, { provider: "openai" });
|
||||||
expect(official.agents.get("fast-hi")?.request.settings).toEqual({
|
expect(official.agents.get("fast-hi")?.request.settings).toEqual({ serviceTier: "priority" });
|
||||||
reasoningEffort: "high",
|
const proxy = await host(
|
||||||
serviceTier: "priority",
|
[{ ...luna, settings: { baseURL: "https://proxy.example/v1" } }, ...openaiModels.slice(1)],
|
||||||
});
|
{ provider: "openai" },
|
||||||
const proxy = await host([
|
);
|
||||||
{ ...luna, settings: { baseURL: "https://proxy.example/v1" } },
|
expect(proxy.agents.get("fast-lo")?.request.settings).toEqual({});
|
||||||
...allModels.slice(1),
|
const advertised = await host(
|
||||||
]);
|
[
|
||||||
expect(proxy.agents.get("fast-lo")?.request.settings).toEqual({ reasoningEffort: "low" });
|
|
||||||
const advertised = await host([
|
|
||||||
{ ...luna, package: "custom", settings: { serviceTier: "fast" } },
|
{ ...luna, package: "custom", settings: { serviceTier: "fast" } },
|
||||||
...allModels.slice(1),
|
...openaiModels.slice(1),
|
||||||
]);
|
],
|
||||||
|
{ provider: "openai" },
|
||||||
|
);
|
||||||
expect(advertised.agents.get("fast-lo")?.request.settings.serviceTier).toBe("fast");
|
expect(advertised.agents.get("fast-lo")?.request.settings.serviceTier).toBe("fast");
|
||||||
const disabled = await host([luna, ...allModels.slice(1)], {
|
const sdkAlias = await host(
|
||||||
provider: "test",
|
[{ ...luna, package: "@ai-sdk/openai" }, ...openaiModels.slice(1)],
|
||||||
|
{ provider: "openai" },
|
||||||
|
);
|
||||||
|
expect(sdkAlias.agents.get("fast-lo")?.request.settings.serviceTier).toBe("priority");
|
||||||
|
const disabled = await host(openaiModels, {
|
||||||
|
provider: "openai",
|
||||||
lunaPriority: false,
|
lunaPriority: false,
|
||||||
});
|
});
|
||||||
expect(disabled.agents.get("fast-lo")?.request.settings).toEqual({ reasoningEffort: "low" });
|
expect(disabled.agents.get("fast-lo")?.request.settings).toEqual({});
|
||||||
|
|
||||||
|
const custom = customAssignments("openai");
|
||||||
|
const customResult = await host(openaiModels, {
|
||||||
|
mode: "custom",
|
||||||
|
agents: custom,
|
||||||
|
});
|
||||||
|
expect(customResult.agents.get("fast-lo")?.request.settings.serviceTier).toBeUndefined();
|
||||||
|
expect(customResult.agents.get("fast-hi")?.request.settings.serviceTier).toBeUndefined();
|
||||||
|
|
||||||
|
const other = await host(allModels, { provider: "test" });
|
||||||
|
expect(other.agents.get("fast-lo")?.request.settings.serviceTier).toBeUndefined();
|
||||||
|
expect(() => settings.parse({ mode: "gpt", provider: "test", lunaPriority: true })).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("coordinator uses inherited effort and ignores the legacy custom assignment", async () => {
|
test("coordinator uses inherited effort and ignores the legacy custom assignment", async () => {
|
||||||
@@ -730,15 +959,17 @@ test("coordinator uses inherited effort and ignores the legacy custom assignment
|
|||||||
|
|
||||||
const fallback = await host(allModels.slice(0, 2));
|
const fallback = await host(allModels.slice(0, 2));
|
||||||
expect(fallback.agents.get("coordinate")?.model).toMatchObject({
|
expect(fallback.agents.get("coordinate")?.model).toMatchObject({
|
||||||
id: "gpt-5.6-sol",
|
id: "gpt-6-sol",
|
||||||
variant: "low",
|
variant: "low",
|
||||||
});
|
});
|
||||||
expect(fallback.agents.get("coordinate")?.request.settings.reasoningEffort).toBeUndefined();
|
expect(fallback.agents.get("coordinate")?.request.settings.reasoningEffort).toBeUndefined();
|
||||||
|
|
||||||
const custom = await host([...allModels, model("local")], {
|
const custom = await host([...allModels, model("local")], {
|
||||||
mode: "custom",
|
mode: "custom",
|
||||||
provider: "test",
|
agents: {
|
||||||
agents: { ...gptAgents, coordinate: { model: "local", effort: "xhigh" } },
|
...customAssignments(),
|
||||||
|
coordinate: { model: "test/local", effort: "xhigh" },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(custom.agents.get("coordinate")?.model).toMatchObject({
|
expect(custom.agents.get("coordinate")?.model).toMatchObject({
|
||||||
id: "gpt-6-astra",
|
id: "gpt-6-astra",
|
||||||
@@ -748,8 +979,8 @@ test("coordinator uses inherited effort and ignores the legacy custom assignment
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("the current host model API filters discovery and reloads changed availability", async () => {
|
test("the current host model API filters discovery and reloads changed availability", async () => {
|
||||||
const disabled = { ...reasoningModel("gpt-5.6-luna"), enabled: false };
|
const disabled = { ...reasoningModel("gpt-6-luna"), enabled: false };
|
||||||
const other = { ...reasoningModel("gpt-5.6-sol"), providerID: Provider.ID.make("other") };
|
const other = { ...reasoningModel("gpt-6-sol"), providerID: Provider.ID.make("other") };
|
||||||
expect((await host([disabled, other, ...allModels.slice(1)])).agents.has("coordinate")).toBe(
|
expect((await host([disabled, other, ...allModels.slice(1)])).agents.has("coordinate")).toBe(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -760,7 +991,7 @@ test("the current host model API filters discovery and reloads changed availabil
|
|||||||
expect(result.agents.get("coordinate")?.request.settings.reasoningEffort).toBeUndefined();
|
expect(result.agents.get("coordinate")?.request.settings.reasoningEffort).toBeUndefined();
|
||||||
await result.refresh(allModels.slice(0, 2));
|
await result.refresh(allModels.slice(0, 2));
|
||||||
expect(result.agents.has("deep-lo")).toBe(false);
|
expect(result.agents.has("deep-lo")).toBe(false);
|
||||||
expect(String(result.agents.get("coordinate")?.model?.id)).toBe("gpt-5.6-sol");
|
expect(String(result.agents.get("coordinate")?.model?.id)).toBe("gpt-6-sol");
|
||||||
expect(String(result.agents.get("coordinate")?.model?.variant)).toBe("low");
|
expect(String(result.agents.get("coordinate")?.model?.variant)).toBe("low");
|
||||||
expect(result.agents.get("coordinate")?.request.settings.reasoningEffort).toBeUndefined();
|
expect(result.agents.get("coordinate")?.request.settings.reasoningEffort).toBeUndefined();
|
||||||
await result.refresh([]);
|
await result.refresh([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user