This commit is contained in:
@@ -128,7 +128,7 @@ Module owning bounded, never-throw git repository introspection — the single s
|
||||
Module owning runtime identity normalization at runtime-selection seams. Canonicalizes alias signals from env/config (`GSD_RUNTIME`, `.planning/config.json:runtime`) to supported runtime IDs so output emitters and query runtime gates stay consistent across naming variants (for example `codex-app`/`codex-cli` -> `codex`). Sources: `gsd-core/bin/lib/runtime-name-policy.cjs`, alias manifest `gsd-core/bin/shared/runtime-aliases.manifest.json`.
|
||||
|
||||
### Host-Integration Interface
|
||||
Pure, additive, no-I/O Module owning the versioned, negotiated contract over the six host-integration interface points (command, dispatch, model, hooks, state, artifact) — ADR-1239 Phase A. Extends the ADR-1016 runtime descriptor with nine closed-vocabulary axes carried under `capability.json` `runtime.hostIntegration`: `embeddingMode` (`imperative|declarative`), `commandSurface` (`slash-file|slash-programmatic|slash-toml|palette|prose-only`), `dispatch` (`{namedDispatch,nested,maxDepth,background,backgroundDispatch,subagentToolkit}`), `modelMode` (`active|passive`), `hookBus` (`host|engine|none`), `stateIO` (`filesystem|sandboxed-storage|session-log-append`), `transport` (`mcp|native-extension`), `runtime` (`node|bun|sandboxed-web|python|go|rust|electron|other`), `effortSurface` (`argv|none` — how reasoning effort reaches the host; ADR-1239 amendment #2481, the first axis whose consumer is an invocation-time argument rather than an install-time artifact). Interface: `negotiateHostCapabilities(host, engine?) → { protocolVersion, effective, points, warnings }` enforcing the trust-boundary invariant `effective ⊆ host-declared ∩ engine-known` (never augment with an undeclared or unknown/future-`protocolVersion` value — fail-closed via the most-restrictive-known `SAFE_DEFAULTS`); `degradationFor(point, axes) → { level, fallback }` (a pure Full/Degraded/Absent ladder table, never throws); `profileOf(axes) → 'programmatic-cli'|'declarative-cli'|'ide'|null`; plus `PROTOCOL_VERSION` (integer, starts at 1 — distinct from the package `version`/`engines.gsd` semver), `HOST_INTEGRATION_AXES` (the frozen closed vocabulary, single source of truth), `PROFILE_BASELINES`, and `shouldFlattenDispatch(dispatch) → boolean` (ADR-1239 Phase B / #1708 — graduates the #853 rule: returns `true` = run the orchestrator inline UNLESS the host is documented to background a nesting-capable orchestrator (`background === true && backgroundDispatch === true`); fail-closed to inline; exposed to the plan/execute workflows via the `gsd_run query dispatch-should-flatten --raw` CLI, which replaced the former scattered `RUNTIME === 'codex'` prose check). The runtime-descriptor validator (`gsd-core/bin/lib/capability-validator.cjs` `validateRuntimeBody`) mirrors the closed vocabulary inline (exported as `_HOST_INTEGRATION_VOCAB`) and is kept in lock-step by the parity guard `tests/host-integration-validator-parity.test.cjs`. Orthogonal axes (resolved explicitly per ADR-1239 Phase A): `commandStyle` (GSD emission style, retained) vs `commandSurface` (host surface type); `hookEvents` dialect vs `hookBus` ownership (a host with `hooksSurface:none` may still be `hookBus:host` — e.g. opencode); `runtimeCompat` (feature→host) vs these negotiated runtime→engine axes. Phase A defined the interface; Phase B (#1679) wires it incrementally — `destSubpath` write-confinement (#1704) and the typed documentation-sourced #853 dispatch-flatten (#1708, the first consumer of a negotiated `dispatch` axis); adapters/MCP/host-bindings remain Phases C–E. Source of truth: `gsd-core/bin/lib/host-integration.cjs` (generated from `src/host-integration.cts`). See ADR-1239 and ADR-1016.
|
||||
Pure, additive, no-I/O Module owning the versioned, negotiated contract over the six host-integration interface points (command, dispatch, model, hooks, state, artifact) — ADR-1239 Phase A. Extends the ADR-1016 runtime descriptor with nine closed-vocabulary axes carried under `capability.json` `runtime.hostIntegration`: `embeddingMode` (`imperative|declarative`), `commandSurface` (`slash-file|slash-programmatic|slash-toml|palette|prose-only`), `dispatch` (`{namedDispatch,nested,maxDepth,background,backgroundDispatch,subagentToolkit,isolation}`), `modelMode` (`active|passive`), `hookBus` (`host|engine|none`), `stateIO` (`filesystem|sandboxed-storage|session-log-append`), `transport` (`mcp|native-extension`), `runtime` (`node|bun|sandboxed-web|python|go|rust|electron|other`), `effortSurface` (`argv|none` — how reasoning effort reaches the host; ADR-1239 amendment #2481, the first axis whose consumer is an invocation-time argument rather than an install-time artifact). `dispatch.isolation` (`harness-worktree|orchestrator-worktree|none` — how a host isolates concurrent same-wave executors; ADR-1239 Codex-binding amendment #2584; declared and negotiated but not yet consumed by any scheduler — Phase 1 of #2584). Interface: `negotiateHostCapabilities(host, engine?) → { protocolVersion, effective, points, warnings }` enforcing the trust-boundary invariant `effective ⊆ host-declared ∩ engine-known` (never augment with an undeclared or unknown/future-`protocolVersion` value — fail-closed via the most-restrictive-known `SAFE_DEFAULTS`); `degradationFor(point, axes) → { level, fallback }` (a pure Full/Degraded/Absent ladder table, never throws); `profileOf(axes) → 'programmatic-cli'|'declarative-cli'|'ide'|null`; plus `PROTOCOL_VERSION` (integer, starts at 1 — distinct from the package `version`/`engines.gsd` semver), `HOST_INTEGRATION_AXES` (the frozen closed vocabulary, single source of truth), `PROFILE_BASELINES`, and `shouldFlattenDispatch(dispatch) → boolean` (ADR-1239 Phase B / #1708 — graduates the #853 rule: returns `true` = run the orchestrator inline UNLESS the host is documented to background a nesting-capable orchestrator (`background === true && backgroundDispatch === true`); fail-closed to inline; exposed to the plan/execute workflows via the `gsd_run query dispatch-should-flatten --raw` CLI, which replaced the former scattered `RUNTIME === 'codex'` prose check). The runtime-descriptor validator (`gsd-core/bin/lib/capability-validator.cjs` `validateRuntimeBody`) mirrors the closed vocabulary inline (exported as `_HOST_INTEGRATION_VOCAB`) and is kept in lock-step by the parity guard `tests/host-integration-validator-parity.test.cjs`. Orthogonal axes (resolved explicitly per ADR-1239 Phase A): `commandStyle` (GSD emission style, retained) vs `commandSurface` (host surface type); `hookEvents` dialect vs `hookBus` ownership (a host with `hooksSurface:none` may still be `hookBus:host` — e.g. opencode); `runtimeCompat` (feature→host) vs these negotiated runtime→engine axes. Phase A defined the interface; Phase B (#1679) wires it incrementally — `destSubpath` write-confinement (#1704) and the typed documentation-sourced #853 dispatch-flatten (#1708, the first consumer of a negotiated `dispatch` axis); adapters/MCP/host-bindings remain Phases C–E. Source of truth: `gsd-core/bin/lib/host-integration.cjs` (generated from `src/host-integration.cts`). See ADR-1239 and ADR-1016.
|
||||
|
||||
### Statusline
|
||||
Host-integration hook (`hooks/gsd-statusline.js`) that renders the session status line: model name, context-window meter, workspace directory, and the GSD-state segment (`formatGsdState()` projecting `.planning/` STATE.md). Opt-in segments are gated by `.planning/config.json` keys (`statusline.show_last_command`, `statusline.context_position`, plus the approved `statusline.show_context_tokens` and `statusline.state_format`), each registered across `gsd-core/bin/shared/config-schema.manifest.json` + `src/config.cts` + the `loadConfig` whitelist + `docs/CONFIGURATION.md`. The compact GSD-state format consumes the canonical status vocabulary from `normalizeStateStatus()` (STATE.md Document Module) rather than a parallel keyword list. **Data-source boundary (ADR-2164):** the statusline sources only local, read-only data — it refines the stdin payload Claude Code already sends and may add a new *local* source (e.g. `git`), but does not read credentials or call external/network APIs for data; account/usage/platform-level state is out of scope.
|
||||
|
||||
@@ -82,7 +82,8 @@
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "harness-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "read-only",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -96,7 +96,8 @@
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -75,7 +75,8 @@
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -91,7 +91,8 @@
|
||||
"maxDepth": 2,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "harness-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "read-only",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -82,7 +82,8 @@
|
||||
"maxDepth": -1,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
"coder",
|
||||
"explore",
|
||||
"plan"
|
||||
]
|
||||
],
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -68,7 +68,8 @@
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
"maxDepth": 0,
|
||||
"background": false,
|
||||
"backgroundDispatch": false,
|
||||
"subagentToolkit": "undocumented"
|
||||
"subagentToolkit": "undocumented",
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -80,7 +80,8 @@
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -74,7 +74,8 @@
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "engine",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "engine",
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
"maxDepth": "undocumented",
|
||||
"background": "undocumented",
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
"maxDepth": "undocumented",
|
||||
"background": false,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -66,6 +66,7 @@ consumed verbatim by `gen:capability-registry` and validated by `capability-vali
|
||||
| dispatch.background | true | https://code.claude.com/docs/en/sub-agents | "Subagents can run in the foreground, blocking the main conversation and passing permission prompts to you, or in the bac" |
|
||||
| dispatch.subagentToolkit | full | https://code.claude.com/docs/en/sub-agents | "If all tools remain selected, the subagent inherits all tools available to the main conversation." |
|
||||
| dispatch.backgroundDispatch | false | https://code.claude.com/docs/en/sub-agents | "Background subagents are limited to a depth of five and cannot spawn further, " |
|
||||
| dispatch.isolation | harness-worktree | https://code.claude.com/docs/en/sub-agents ; Claude Code Agent tool (`Agent(isolation="worktree")`) | The Claude Code Agent tool accepts an `isolation="worktree"` harness primitive — the host's own harness creates + binds a git worktree per executor; GSD passes the flag and calls no git itself (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://code.claude.com/docs/en/sub-agents
|
||||
@@ -144,6 +145,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/tool/task.ts (v1.15.0, commit 22de34c4d) + src/effect/runtime-flags.ts (v1.17, commit 81f6e0668) | "New in v1.15.0: experimental background subagents — the Task tool gains a `background` parameter (`Schema.optional(Schema.Boolean)`) that launches subagents asynchronously with completion notifications. v1.17: `BACKGROUND_SUBAGENTS_ENABLED = true` (\"feat: enable background subagents by default\") — default-on, concurrent execution in all modes. (#2087, superseding the stale sst/opencode#5887 snapshot)" |
|
||||
| dispatch.subagentToolkit | full | https://opencode.ai/docs/agents | "The 'general' subagent \"Has full tool access (except todo), so it can make file changes when needed.\"" |
|
||||
| dispatch.backgroundDispatch | true | https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/effect/runtime-flags.ts (v1.17, commit 81f6e0668) + src/server/routes/instance/httpapi/handlers/experimental.ts | "v1.17 `BACKGROUND_SUBAGENTS_ENABLED = true` enables background subagent execution by default in all modes; the experimental capabilities endpoint exposes `{ backgroundSubagents: true }`. Background-spawned subagents run concurrently without blocking the main interaction flow. (#2087)" |
|
||||
| dispatch.isolation | orchestrator-worktree | https://opencode.ai/docs/cli ; opencode.ai/docs/plugins ; opencode issues #14195/#29638/#5887 | "`opencode run --dir <path>` sets an explicit working root at the process level" — native subagent dispatch is synchronous-only, so GSD creates + manages the worktree and process-spawns the executor into it via `--dir` (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://opencode.ai/docs/plugins
|
||||
@@ -177,6 +179,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://cursor.com/docs/subagents | "Background, which returns immediately while the subagent works independently, best for long-running tasks or parallel wo" |
|
||||
| dispatch.subagentToolkit | full | https://cursor.com/docs/subagents | "Subagents can utilize MCP tools, inheriting all tools available to their parent agent, including those from configured s" |
|
||||
| dispatch.backgroundDispatch | true | https://cursor.com/docs/subagents (FAQ: Can subagents launch other subagents?) and https://cursor.com/docs/sdk/typescript (Subagents > Nested subagents) | FAQ: "As of Cursor 2.5, subagents have the capability to launch child subagents, enabling the creation of a hierarchical structure for coordinated tasks. This nested launching functionality requires T |
|
||||
| dispatch.isolation | harness-worktree | https://cursor.com/docs/cli/reference/parameters ; cursor.com/docs/cli/using ; cursor.com/docs/cli/changelog | "`-w, --worktree [name]` — cursor-agent creates/binds a git worktree per agent (`~/.cursor/worktrees/…`); native parallel-agent dispatch" (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://cursor.com/docs/subagents
|
||||
@@ -212,6 +215,7 @@ Sources consulted:
|
||||
| dispatch.background | true | /cline/cline (Context7) — https://github.com/cline/cline/blob/main/docs/features/subagents.mdx | "Commands executed by subagents run in the background and are strictly limited to read-only operations" |
|
||||
| dispatch.subagentToolkit | read-only | /cline/cline (Context7) — https://github.com/cline/cline/blob/main/docs/features/subagents.mdx | "Subagents are equipped with tools for read-only operations, including reading file contents (read_file), listing directo" |
|
||||
| dispatch.backgroundDispatch | false | https://docs.cline.bot/features/subagents (mirrored at https://github.com/cline/cline/blob/main/docs/features/subagents.mdx) | "They cannot edit files, use the browser, or spawn nested subagents" — and from the GitHub source: "subagents are restricted from editing files, using the browser, accessing MCP servers, or creating n |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://github.com/cline/cline/blob/main/docs/sdk/plugins.mdx
|
||||
@@ -248,6 +252,7 @@ Sources consulted:
|
||||
| dispatch.background | true | https://github.com/NousResearch/hermes-agent/releases/tag/v2026.6.19 | "delegate_task(background=true) dispatches a subagent that runs in the background and returns a handle immediately" |
|
||||
| dispatch.subagentToolkit | read-only | https://hermes-agent.nousresearch.com/docs/guides/delegation-patterns | "Nested delegation is opt-in; by default, leaf subagents cannot call delegate_task, clarify, memory, send_message, or exe" |
|
||||
| dispatch.backgroundDispatch | false | https://github.com/nousresearch/hermes-agent/blob/main/website/docs/user-guide/features/delegation.md (via Context7 query of /nousresearch/hermes-agent) | "Nested delegation is an opt-in feature, requiring role=\"orchestrator\" for children and an increased max_spawn_depth from its default of 1. It can also be globally disabled with orchestrator_enabled |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://hermes-agent.nousresearch.com/docs/user-guide/features/delegation
|
||||
@@ -284,6 +289,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/ | "Antigravity CLI orchestrates multiple agents for complex tasks in the background" |
|
||||
| dispatch.subagentToolkit | full | https://antigravity.google/docs/cli/features | "Capabilities: Subagents have full access to tools such as code search, file editing, terminal commands, and web searches to complete their assigned tasks." (#2096 EoS migration — the page is JS-rendered/blank on a static fetch; confirmed via headless-browser render) |
|
||||
| dispatch.backgroundDispatch | undocumented | no authoritative doc — Multiple sources consulted: antigravity.google/docs/cli-subagents (returned blank/JS-rendered), antigravity.google/docs/agent (blank), github.com/google-antigravity/antigravity-cli README, Context7 /google-antigravity/antigravity-cli | All documentation consulted describes a two-level orchestrator→subagent architecture. Background subagents run asynchronously while the main agent continues accepting prompts. The DataCamp tutorial st |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://github.com/alphaperseii3000/google-antigravity-docs/blob/master/google-antigravity-docs.md
|
||||
@@ -321,6 +327,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://docs.augmentcode.com/cli/subagents | "Subagents run in parallel with other subagents... will show a summary of their current progress in the main thread." |
|
||||
| dispatch.subagentToolkit | full | https://docs.augmentcode.com/cli/subagents | "If neither [tools nor disabled_tools] is specified, the subagent has access to all tools (default behavior)." |
|
||||
| dispatch.backgroundDispatch | undocumented | no authoritative doc — https://docs.augmentcode.com/cosmos/automations | The Augment Code (Cosmos) docs describe workers as 'sub-agents launched mid-session by a manager Expert using the worker-launch command. Each worker is its own session with its own messages and permis |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://docs.augmentcode.com/cli/plugins
|
||||
@@ -383,6 +390,7 @@ upgrade coverage is in `tests/augment-upgrades.test.cjs`.
|
||||
| dispatch.background | true | https://qwenlm.github.io/qwen-code-docs/en/users/features/sub-agents/ | "Runs in background, parent continues immediately... Forks run parallel to the parent; the main conversation continues im" |
|
||||
| dispatch.subagentToolkit | full | https://qwenlm.github.io/qwen-code-docs/en/users/features/sub-agents/ | "When omitted, the subagent inherits all available tools from the parent session." |
|
||||
| dispatch.backgroundDispatch | false | https://qwenlm.github.io/qwen-code-docs/en/users/features/sub-agents/ (official Qwen Code documentation, 'Subagents' user guide page) and https://qwenlm.github.io/qwen-code-docs/en/design/fork-subagent/fork-subagent-design (Qwen Code fork-subagent design document, section '4. Recursive Fork Prevention') | The official user-facing Qwen Code docs state verbatim: "Fork children cannot create further forks. If a fork attempts spawning another fork, it receives an error instructing direct task execution ins |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://qwenlm.github.io/qwen-code-docs/en/developers/channel-plugins
|
||||
@@ -418,6 +426,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://www.codebuddy.ai/docs/cli/sub-agents | "Launch a background agent using the run_in_background: true parameter ... Tasks return immediately with an ID" |
|
||||
| dispatch.subagentToolkit | full | https://www.codebuddy.ai/docs/cli/sub-agents | "By default, sub-agents inherit all tools when the tools field is omitted ... Sub-agents can access MCP tools from config" |
|
||||
| dispatch.backgroundDispatch | false | https://www.codebuddy.ai/docs/cli/sub-agents | "This prevents infinite nesting of agents (sub-agents cannot spawn other sub-agents)" — the restriction is stated as universal in the Sub-Agents documentation page. The daemon/background docs (https:/ |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://www.codebuddy.ai/docs/cli/plugins
|
||||
@@ -449,6 +458,7 @@ Sources consulted:
|
||||
| dispatch.background | true | https://docs.github.com/en/copilot/how-tos/copilot-cli/speed-up-task-completion | "Allow Copilot to use subagents and work autonomously to implement the plan without any further input." |
|
||||
| dispatch.subagentToolkit | full | https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli | "By default, custom agents have access to all tools. If you restrict an agent's access, a tools specification is added" |
|
||||
| dispatch.backgroundDispatch | false | https://code.visualstudio.com/docs/copilot/agents/subagents | "By default, subagents cannot spawn further subagents. This prevents infinite recursion when agents accidentally call themselves in a loop." The setting `chat.subagents.allowInvocationsFromSubagents` |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://github.com/github/copilot-cli/blob/main/README.md (via Context7 /github/copilot-cli)
|
||||
@@ -483,6 +493,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://kilo.ai/docs/code-with-ai/agents/orchestrator-mode | "Agents are also capable of launching multiple subagent sessions concurrently to facilitate parallel processing." |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://kilo.ai/docs/customize/custom-subagents | — |
|
||||
| dispatch.backgroundDispatch | false | https://kilo.ai/docs/automate/tools/new-task | "Importantly, subagents cannot spawn further subagents; only primary agents can use the `new_task` tool." |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://kilo.ai/docs/automate/extending/plugins
|
||||
@@ -519,6 +530,7 @@ Documentation gaps:
|
||||
| dispatch.background | undocumented | no authoritative doc — searched: https://docs.devin.ai/desktop/acp.md, https://docs.devin.ai/cli/subagents.md | — |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://docs.devin.ai/cli/subagents.md | — |
|
||||
| dispatch.backgroundDispatch | undocumented | no authoritative doc — https://docs.devin.ai/desktop/cascade/cascade and https://docs.devin.ai/desktop/devin-local (official Windsurf/Devin docs, via docs.windsurf.com redirects) | The Windsurf/Cascade docs describe a background planning agent only in these terms: "In the background, a specialized planning agent continuously refines the long-term plan while your selected model f |
|
||||
| dispatch.isolation | none | shipped descriptor (`dispatch.backgroundDispatch: undocumented`) | no documented background/concurrent-dispatch primitive — isolation is moot; same-wave plans run inline (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://docs.devin.ai/desktop/cascade/workflows
|
||||
@@ -559,6 +571,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://news.aibase.com/news/22829 | "SOLO 'supports multi-tasking, allowing you to work on multiple development tasks simultaneously'; 'run multiple agents i" |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://docs.trae.ai/ide/agent | — |
|
||||
| dispatch.backgroundDispatch | undocumented | no authoritative doc — https://docs.trae.ai/ide/agent; https://github.com/bytedance/trae-agent/blob/main/docs/roadmap.md | Trae's official documentation (docs.trae.ai) and the trae-agent GitHub roadmap do not document background/async agent dispatch or whether a background-spawned agent can itself spawn further sub-agents |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://docs.trae.ai/ide/model-context-protocol
|
||||
@@ -599,6 +612,7 @@ Documentation gaps:
|
||||
| dispatch.background | true | https://moonshotai.github.io/kimi-cli/en/customization/agents.html | "Subagents support foreground and background modes. The `run_in_background` parameter allows tasks to execute asynchronou" |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://moonshotai.github.io/kimi-cli/en/customization/agents.html | — |
|
||||
| dispatch.backgroundDispatch | true (#2095 Upgrade 2; was `false`) | https://moonshotai.github.io/kimi-cli/en/customization/agents.html | "Subagents support foreground and background modes. The `run_in_background` parameter allows tasks to execute asynchronously" (same evidence as dispatch.background above — the root agent's `Agent` tool call itself takes the `run_in_background` param) |
|
||||
| dispatch.isolation | orchestrator-worktree | https://github.com/moonshotai/kimi-cli/blob/main/docs/en/faq.md ; /docs/en/customization/agents.md | "`--work-dir` flag sets an explicit working directory"; concurrent "explore" subagents documented — GSD creates + manages the worktree and points the executor at it via `--work-dir` (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://moonshotai.github.io/kimi-cli/en/customization/hooks.html
|
||||
@@ -635,6 +649,7 @@ Documentation gaps:
|
||||
| dispatch.background | false | https://zcode.z.ai/en/docs/subagents | "**Foreground execution.** Subagents run in the foreground ... Background execution is not enabled yet." |
|
||||
| dispatch.subagentToolkit | full | https://zcode.z.ai/en/docs/subagents | "**general-purpose** is the default built-in subagent ... It has access to all tools"; custom subagents default to "All permissions by default" (inherits every tool). |
|
||||
| dispatch.backgroundDispatch | false | https://zcode.z.ai/en/docs/subagents | "Background execution is not enabled yet" — background dispatch is therefore impossible. |
|
||||
| dispatch.isolation | none | https://zcode.z.ai/en/docs/subagents (shipped descriptor: `dispatch.backgroundDispatch: false`) | "Background execution is not enabled yet" — no concurrent fan-out primitive, so same-wave plans run inline/sequentially (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://zcode.z.ai/en/docs/skill
|
||||
@@ -675,6 +690,7 @@ EoS migration status (#2101, ADR-1239): ZCode's install is fully dogfooded throu
|
||||
| dispatch.background | false | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | No documented background/async subagent-execution primitive. |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | pi has no named-dispatch primitive (see `dispatch.namedDispatch`), so there is no subagent tool-surface to classify as `full`/`read-only`. |
|
||||
| dispatch.backgroundDispatch | false | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | Same gap as `dispatch.background` — no background-dispatch primitive is documented, so a background-dispatched agent spawning further named sub-agents is not possible. |
|
||||
| dispatch.isolation | none | shipped descriptor (`dispatch.background: false`, `dispatch.backgroundDispatch: false`) | pi has no named-dispatch/background-dispatch primitive documented — cannot fan out concurrently, so isolation is moot (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://pi.dev
|
||||
@@ -721,6 +737,7 @@ EoS migration status (#2102 Stage 2, ADR-1239): Stage 1's "in-process `gsd-core`
|
||||
| dispatch.background | true | https://code.visualstudio.com/api/extension-guides/ai/tools | Language Model Tools can be invoked as part of an asynchronous agent turn (the primary agent does not block synchronously on a single extension call). |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc found at authoring time | VS Code's subagent documentation does not state whether a subagent's tool surface is restricted to read-only tools or the full set an extension registers; recorded `undocumented` (fails closed to `read-only` in negotiation) rather than guessed. |
|
||||
| dispatch.backgroundDispatch | undocumented | no authoritative doc found at authoring time | Whether a background-dispatched subagent can itself spawn further NAMED subagents (the #853 discriminator) is not stated in the sources reviewed; recorded `undocumented` (fails closed to `false`) rather than guessed. |
|
||||
| dispatch.isolation | undocumented | not researched / no concurrent fan-out documented for this axis | no authoritative source consulted for concurrent-executor isolation on this host — fails closed to `none` (sequential) in negotiation (#2584) |
|
||||
|
||||
Sources consulted:
|
||||
- https://code.visualstudio.com/api/references/vscode-api
|
||||
|
||||
@@ -176,7 +176,8 @@ const capabilities = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -374,7 +375,8 @@ const capabilities = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -505,7 +507,8 @@ const capabilities = {
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "harness-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -676,7 +679,8 @@ const capabilities = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "read-only",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -854,7 +858,8 @@ const capabilities = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -935,7 +940,8 @@ const capabilities = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -1031,7 +1037,8 @@ const capabilities = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -1141,7 +1148,8 @@ const capabilities = {
|
||||
"maxDepth": 2,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "harness-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -1492,7 +1500,8 @@ const capabilities = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "read-only",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -1639,7 +1648,8 @@ const capabilities = {
|
||||
"maxDepth": -1,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -1733,7 +1743,8 @@ const capabilities = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -1818,7 +1829,8 @@ const capabilities = {
|
||||
"coder",
|
||||
"explore",
|
||||
"plan"
|
||||
]
|
||||
],
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -2140,7 +2152,8 @@ const capabilities = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -2264,7 +2277,8 @@ const capabilities = {
|
||||
"maxDepth": 0,
|
||||
"background": false,
|
||||
"backgroundDispatch": false,
|
||||
"subagentToolkit": "undocumented"
|
||||
"subagentToolkit": "undocumented",
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -2442,7 +2456,8 @@ const capabilities = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -2792,7 +2807,8 @@ const capabilities = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "engine",
|
||||
@@ -2943,7 +2959,8 @@ const capabilities = {
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "engine",
|
||||
@@ -3023,7 +3040,8 @@ const capabilities = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": "undocumented",
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -3132,7 +3150,8 @@ const capabilities = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": false,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4159,7 +4178,8 @@ const runtimes = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4274,7 +4294,8 @@ const runtimes = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4359,7 +4380,8 @@ const runtimes = {
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "harness-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4442,7 +4464,8 @@ const runtimes = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "read-only",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -4559,7 +4582,8 @@ const runtimes = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4640,7 +4664,8 @@ const runtimes = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4736,7 +4761,8 @@ const runtimes = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4846,7 +4872,8 @@ const runtimes = {
|
||||
"maxDepth": 2,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "harness-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -4954,7 +4981,8 @@ const runtimes = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "read-only",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -5049,7 +5077,8 @@ const runtimes = {
|
||||
"maxDepth": -1,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -5143,7 +5172,8 @@ const runtimes = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -5228,7 +5258,8 @@ const runtimes = {
|
||||
"coder",
|
||||
"explore",
|
||||
"plan"
|
||||
]
|
||||
],
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -5326,7 +5357,8 @@ const runtimes = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": true
|
||||
"backgroundDispatch": true,
|
||||
"isolation": "orchestrator-worktree"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -5396,7 +5428,8 @@ const runtimes = {
|
||||
"maxDepth": 0,
|
||||
"background": false,
|
||||
"backgroundDispatch": false,
|
||||
"subagentToolkit": "undocumented"
|
||||
"subagentToolkit": "undocumented",
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
@@ -5497,7 +5530,8 @@ const runtimes = {
|
||||
"maxDepth": 1,
|
||||
"background": true,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -5597,7 +5631,8 @@ const runtimes = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "engine",
|
||||
@@ -5653,7 +5688,8 @@ const runtimes = {
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "engine",
|
||||
@@ -5733,7 +5769,8 @@ const runtimes = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": "undocumented",
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
"backgroundDispatch": "undocumented",
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
@@ -5842,7 +5879,8 @@ const runtimes = {
|
||||
"maxDepth": "undocumented",
|
||||
"background": false,
|
||||
"subagentToolkit": "full",
|
||||
"backgroundDispatch": false
|
||||
"backgroundDispatch": false,
|
||||
"isolation": "none"
|
||||
},
|
||||
"modelMode": "passive",
|
||||
"hookBus": "host",
|
||||
|
||||
@@ -739,6 +739,9 @@ const VALID_HOST_RUNTIMES = new Set(['node', 'bun', 'sandboxed-web', 'python
|
||||
const VALID_SUBAGENT_TOOLKITS = new Set(['full', 'read-only', 'built-in-only']);
|
||||
// ADR-1239 amendment (#2481): how reasoning effort reaches the host.
|
||||
const VALID_EFFORT_SURFACES = new Set(['argv', 'none']);
|
||||
// ADR-1239 Codex-binding amendment (#2584): how a host isolates concurrent
|
||||
// same-wave executors — a dispatch sub-field, not a top-level axis.
|
||||
const VALID_DISPATCH_ISOLATION = new Set(['harness-worktree', 'orchestrator-worktree', 'none']);
|
||||
|
||||
// GATE A: installSurface → allowed hooksSurface values (DEFECT.GENERATIVE-FIX: parity invariant)
|
||||
// Derived from the actual pairings in the 16 real runtime descriptors.
|
||||
@@ -1291,6 +1294,25 @@ function validateRuntimeBody(cap) {
|
||||
'runtime.hostIntegration.dispatch.backgroundDispatch must be a boolean or "undocumented" (got: ' + JSON.stringify(d.backgroundDispatch) + ')',
|
||||
);
|
||||
}
|
||||
|
||||
// isolation — ADR-1239 Codex-binding amendment (#2584).
|
||||
// OPTIONAL, like effortSurface: added after descriptors already existed,
|
||||
// so requiring it would invalidate every descriptor authored before it —
|
||||
// including third-party ones, breaking the "purely additive" property
|
||||
// ADR-1239 promises for external descriptors. An omitted isolation is
|
||||
// legitimate: negotiation degrades it to 'none' (the safe floor) and
|
||||
// warns, exactly as for any other undeclared dispatch sub-field. Only a
|
||||
// PRESENT value is checked against the closed vocabulary.
|
||||
if (d.isolation === undefined) {
|
||||
// absent — nothing to validate; negotiateHostCapabilities fails it closed.
|
||||
} else if (d.isolation === '__proto__' || d.isolation === 'constructor' || d.isolation === 'prototype') {
|
||||
errors.push('runtime.hostIntegration.dispatch.isolation "' + d.isolation + '" is a reserved name');
|
||||
} else if (d.isolation !== 'undocumented' && !VALID_DISPATCH_ISOLATION.has(d.isolation)) {
|
||||
errors.push(
|
||||
'runtime.hostIntegration.dispatch.isolation must be one of: ' + [...VALID_DISPATCH_ISOLATION].join(', ') +
|
||||
' (or "undocumented") (got: ' + JSON.stringify(d.isolation) + ')',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2321,6 +2343,7 @@ module.exports = {
|
||||
VALID_TRANSPORTS,
|
||||
VALID_HOST_RUNTIMES,
|
||||
VALID_SUBAGENT_TOOLKITS,
|
||||
VALID_DISPATCH_ISOLATION,
|
||||
_HOST_INTEGRATION_VOCAB: {
|
||||
embeddingMode: [...VALID_EMBEDDING_MODES],
|
||||
commandSurface: [...VALID_COMMAND_SURFACES],
|
||||
@@ -2331,6 +2354,7 @@ module.exports = {
|
||||
runtime: [...VALID_HOST_RUNTIMES],
|
||||
subagentToolkit: [...VALID_SUBAGENT_TOOLKITS],
|
||||
effortSurface: [...VALID_EFFORT_SURFACES],
|
||||
isolation: [...VALID_DISPATCH_ISOLATION],
|
||||
},
|
||||
INSTALL_SURFACE_TO_ALLOWED_HOOKS_SURFACES,
|
||||
GEMINI_AGENT_EVENTS,
|
||||
|
||||
@@ -54,6 +54,22 @@ const HOST_INTEGRATION_AXES = Object.freeze({
|
||||
// runtime in #1928/#1996, and neither its successor Antigravity CLI nor ZCode
|
||||
// documents a reasoning setting. Adding a member with no host would be a guess.
|
||||
effortSurface: Object.freeze(['argv', 'none'] as const),
|
||||
// ADR-1239 Codex-binding amendment (#2584): a `dispatch` sub-field — not a new
|
||||
// axis — declaring how a host isolates concurrent same-wave executors.
|
||||
// `harness-worktree` — the host's own harness creates + binds a git worktree
|
||||
// per executor; GSD passes the host's own isolation flag and calls no git
|
||||
// itself (host-driven fan-out).
|
||||
// `orchestrator-worktree` — GSD itself process-spawns each executor with an
|
||||
// explicit working directory into a worktree GSD created, validated, and
|
||||
// merges (GSD-driven fan-out; concurrency is OS-level, not the host's).
|
||||
// `none` — no isolation primitive; same-wave plans run inline/sequentially
|
||||
// (the #853 flatten rule).
|
||||
// `undocumented` is NOT a member here; it is the corpus-wide sentinel above.
|
||||
// Mechanism-specific ("worktree"), not abstract — same "name only what a
|
||||
// host actually has" rule that kept effortSurface from guessing a
|
||||
// config-file member above. A future non-worktree isolation mechanism adds a
|
||||
// `*-container` member then, evidence-backed.
|
||||
isolation: Object.freeze(['harness-worktree', 'orchestrator-worktree', 'none'] as const),
|
||||
});
|
||||
|
||||
const INTERFACE_POINTS = Object.freeze(['command', 'dispatch', 'model', 'hooks', 'state', 'artifact'] as const);
|
||||
@@ -71,6 +87,7 @@ type Transport = 'mcp' | 'native-extension';
|
||||
type HostRuntime = 'node' | 'bun' | 'sandboxed-web' | 'python' | 'go' | 'rust' | 'electron' | 'other';
|
||||
type SubagentToolkit = 'full' | 'read-only';
|
||||
type EffortSurface = 'argv' | 'none';
|
||||
type DispatchIsolation = 'harness-worktree' | 'orchestrator-worktree' | 'none';
|
||||
type DegradationLevel = 'full' | 'degraded' | 'absent';
|
||||
type InterfacePoint = 'command' | 'dispatch' | 'model' | 'hooks' | 'state' | 'artifact';
|
||||
|
||||
@@ -81,6 +98,7 @@ interface DispatchCapability {
|
||||
background: boolean;
|
||||
subagentToolkit: SubagentToolkit;
|
||||
backgroundDispatch: boolean;
|
||||
isolation: DispatchIsolation;
|
||||
}
|
||||
|
||||
interface HostIntegrationAxes {
|
||||
@@ -109,7 +127,7 @@ interface DegradationResult {
|
||||
const SAFE_DEFAULTS: HostIntegrationAxes = {
|
||||
embeddingMode: 'declarative',
|
||||
commandSurface: 'prose-only',
|
||||
dispatch: { namedDispatch: false, nested: false, maxDepth: 0, background: false, subagentToolkit: 'read-only', backgroundDispatch: false },
|
||||
dispatch: { namedDispatch: false, nested: false, maxDepth: 0, background: false, subagentToolkit: 'read-only', backgroundDispatch: false, isolation: 'none' },
|
||||
modelMode: 'passive',
|
||||
hookBus: 'none',
|
||||
stateIO: 'session-log-append',
|
||||
@@ -123,7 +141,7 @@ const PROFILE_BASELINES: Readonly<Record<'programmatic-cli' | 'declarative-cli'
|
||||
'programmatic-cli': Object.freeze({
|
||||
embeddingMode: 'imperative',
|
||||
commandSurface: 'slash-file',
|
||||
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
|
||||
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' }),
|
||||
modelMode: 'passive',
|
||||
hookBus: 'host',
|
||||
stateIO: 'filesystem',
|
||||
@@ -134,7 +152,7 @@ const PROFILE_BASELINES: Readonly<Record<'programmatic-cli' | 'declarative-cli'
|
||||
'declarative-cli': Object.freeze({
|
||||
embeddingMode: 'declarative',
|
||||
commandSurface: 'slash-file',
|
||||
dispatch: Object.freeze({ namedDispatch: true, nested: false, maxDepth: 1, background: false, subagentToolkit: 'full', backgroundDispatch: false }),
|
||||
dispatch: Object.freeze({ namedDispatch: true, nested: false, maxDepth: 1, background: false, subagentToolkit: 'full', backgroundDispatch: false, isolation: 'none' }),
|
||||
modelMode: 'passive',
|
||||
hookBus: 'host',
|
||||
stateIO: 'filesystem',
|
||||
@@ -145,7 +163,7 @@ const PROFILE_BASELINES: Readonly<Record<'programmatic-cli' | 'declarative-cli'
|
||||
'ide': Object.freeze({
|
||||
embeddingMode: 'imperative',
|
||||
commandSurface: 'palette',
|
||||
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: 5, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
|
||||
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: 5, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' }),
|
||||
modelMode: 'active',
|
||||
hookBus: 'engine',
|
||||
stateIO: 'sandboxed-storage',
|
||||
@@ -278,7 +296,7 @@ const DEFAULT_ENGINE: EngineCapabilities = {
|
||||
axes: {
|
||||
embeddingMode: 'imperative',
|
||||
commandSurface: 'slash-file',
|
||||
dispatch: { namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true },
|
||||
dispatch: { namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' },
|
||||
modelMode: 'active',
|
||||
hookBus: 'host',
|
||||
stateIO: 'filesystem',
|
||||
@@ -404,6 +422,7 @@ function negotiateHostCapabilities(
|
||||
let effectiveBackgroundDispatch: boolean;
|
||||
let effectiveSubagentToolkit: SubagentToolkit;
|
||||
let effectiveMaxDepth: number;
|
||||
let effectiveIsolation: DispatchIsolation;
|
||||
|
||||
if (hostDispatch === null) {
|
||||
// Host didn't declare dispatch at all — fail-closed to most-restrictive values
|
||||
@@ -414,6 +433,7 @@ function negotiateHostCapabilities(
|
||||
effectiveBackgroundDispatch = false;
|
||||
effectiveSubagentToolkit = 'read-only';
|
||||
effectiveMaxDepth = 0;
|
||||
effectiveIsolation = 'none';
|
||||
} else {
|
||||
// N1: observability warnings for 'undocumented' sentinel on dispatch fields
|
||||
if (hostDispatch.namedDispatch === 'undocumented') {
|
||||
@@ -431,6 +451,9 @@ function negotiateHostCapabilities(
|
||||
if (hostDispatch.backgroundDispatch === 'undocumented') {
|
||||
warnings.push(`dispatch.backgroundDispatch is undocumented — degraded closed`);
|
||||
}
|
||||
if (hostDispatch.isolation === 'undocumented') {
|
||||
warnings.push(`dispatch.isolation is undocumented — degraded closed (none)`);
|
||||
}
|
||||
|
||||
effectiveNamedDispatch = (hostDispatch.namedDispatch === true) && engineDispatch.namedDispatch;
|
||||
effectiveNested = (hostDispatch.nested === true) && engineDispatch.nested;
|
||||
@@ -443,6 +466,15 @@ function negotiateHostCapabilities(
|
||||
const engineToolkit = engineDispatch.subagentToolkit === 'read-only' ? 'read-only' : 'full';
|
||||
effectiveSubagentToolkit = (hostToolkit === 'read-only' || engineToolkit === 'read-only') ? 'read-only' : 'full';
|
||||
|
||||
// isolation: effective = the host's declared value only if it is a known
|
||||
// valid vocabulary member; otherwise 'none'. NOT host && engine gated —
|
||||
// GSD owns the vocabulary, so "engine-known" == "in the valid set" (this
|
||||
// still satisfies effective ⊆ host-declared ∩ engine-known).
|
||||
const hostIso = hostDispatch.isolation;
|
||||
effectiveIsolation = (typeof hostIso === 'string' && (HOST_INTEGRATION_AXES.isolation as readonly string[]).includes(hostIso))
|
||||
? hostIso as DispatchIsolation
|
||||
: 'none';
|
||||
|
||||
// maxDepth: missing/non-number/non-finite → 0 + warning
|
||||
let hostMaxDepth: number;
|
||||
if (typeof hostDispatch.maxDepth !== 'number' || !Number.isFinite(hostDispatch.maxDepth)) {
|
||||
@@ -474,6 +506,7 @@ function negotiateHostCapabilities(
|
||||
background: effectiveBackground,
|
||||
subagentToolkit: effectiveSubagentToolkit,
|
||||
backgroundDispatch: effectiveBackgroundDispatch,
|
||||
isolation: effectiveIsolation,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const fc = require('fast-check');
|
||||
|
||||
const hi = require('../gsd-core/bin/lib/host-integration.cjs');
|
||||
const {
|
||||
@@ -27,6 +30,27 @@ const {
|
||||
EXTENSION_EVENT_SURFACES,
|
||||
} = hi;
|
||||
|
||||
const {
|
||||
_HOST_INTEGRATION_VOCAB,
|
||||
validateCapability,
|
||||
} = require('../gsd-core/bin/lib/capability-validator.cjs');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
|
||||
/**
|
||||
* A real shipped runtime descriptor with its `dispatch.isolation` value
|
||||
* stripped, so validator behavioral tests exercise the actual dispatch shape
|
||||
* shipped for a host rather than a hand-modeled fixture (fixture-provenance,
|
||||
* #2371 — mirrors `shippedDescriptorWithout` in tests/effort-surface-axis.test.cjs).
|
||||
*/
|
||||
function shippedClaudeCapabilityWithoutIsolation() {
|
||||
const cap = JSON.parse(
|
||||
fs.readFileSync(path.join(REPO_ROOT, 'capabilities', 'claude', 'capability.json'), 'utf8'),
|
||||
);
|
||||
delete cap.runtime.hostIntegration.dispatch.isolation;
|
||||
return cap;
|
||||
}
|
||||
|
||||
describe('hookEventSurfaceFor (MANAGED-hook dialect consumer — claude/gemini only)', () => {
|
||||
test('returns the full Claude managed-hook surface for "claude"', () => {
|
||||
const s = hookEventSurfaceFor('claude');
|
||||
@@ -165,6 +189,17 @@ describe('CONTRACT-PIN', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('isolation values (sorted) — #2584 ADR-1239 Codex-binding amendment', () => {
|
||||
assert.deepStrictEqual(
|
||||
[...HOST_INTEGRATION_AXES.isolation].sort(),
|
||||
['harness-worktree', 'none', 'orchestrator-worktree'],
|
||||
);
|
||||
});
|
||||
|
||||
test('isolation: "undocumented" is NOT a vocabulary member — it is the corpus sentinel', () => {
|
||||
assert.ok(!HOST_INTEGRATION_AXES.isolation.includes('undocumented'));
|
||||
});
|
||||
|
||||
test('INTERFACE_POINTS frozen and contains expected values', () => {
|
||||
assert.ok(Object.isFrozen(INTERFACE_POINTS), 'INTERFACE_POINTS must be frozen');
|
||||
const expected = ['command', 'dispatch', 'model', 'hooks', 'state', 'artifact'].sort();
|
||||
@@ -1098,3 +1133,187 @@ describe('Fix 2: negotiate — host omits dispatch → subagentToolkit read-only
|
||||
'Host missing dispatch must produce subagentToolkit "read-only"; got "' + result.effective.dispatch.subagentToolkit + '"');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #2584 — ADR-1239 Codex-binding amendment: dispatch.isolation sub-field
|
||||
// (Phase 1 — declared and negotiated, but NOT consumed by any scheduler yet).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('#2584 dispatch.isolation — negotiation', () => {
|
||||
const BASE_DISPATCH = {
|
||||
namedDispatch: true, nested: false, maxDepth: 1, background: false,
|
||||
subagentToolkit: 'full', backgroundDispatch: false,
|
||||
};
|
||||
|
||||
for (const value of HOST_INTEGRATION_AXES.isolation) {
|
||||
test(`host declares isolation:"${value}" → effective.dispatch.isolation === "${value}"`, () => {
|
||||
const result = negotiateHostCapabilities({
|
||||
dispatch: { ...BASE_DISPATCH, isolation: value },
|
||||
});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, value);
|
||||
});
|
||||
}
|
||||
|
||||
test('isolation:"undocumented" → effective "none" + a warning naming dispatch.isolation', () => {
|
||||
const result = negotiateHostCapabilities({
|
||||
dispatch: { ...BASE_DISPATCH, isolation: 'undocumented' },
|
||||
});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'none');
|
||||
const warnText = result.warnings.join(' ');
|
||||
assert.ok(warnText.includes('dispatch.isolation') && warnText.includes('undocumented'),
|
||||
`Expected a warning naming dispatch.isolation as undocumented; got: ${warnText}`);
|
||||
});
|
||||
|
||||
test('isolation: unknown/garbage value (not the sentinel) → effective "none", no throw', () => {
|
||||
const result = negotiateHostCapabilities({
|
||||
dispatch: { ...BASE_DISPATCH, isolation: 'quantum-worktree' },
|
||||
});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'none');
|
||||
});
|
||||
|
||||
test('isolation: non-string value (number/object/array/null) → effective "none", no throw', () => {
|
||||
for (const bogus of [42, {}, [], null, true]) {
|
||||
const result = negotiateHostCapabilities({
|
||||
dispatch: { ...BASE_DISPATCH, isolation: bogus },
|
||||
});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'none',
|
||||
`isolation=${JSON.stringify(bogus)} must degrade to "none"`);
|
||||
}
|
||||
});
|
||||
|
||||
test('host declares dispatch but omits isolation entirely → effective "none"', () => {
|
||||
const result = negotiateHostCapabilities({ dispatch: { ...BASE_DISPATCH } });
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'none');
|
||||
});
|
||||
|
||||
test('host omits dispatch entirely → effective.dispatch.isolation === "none"', () => {
|
||||
const result = negotiateHostCapabilities({});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'none');
|
||||
});
|
||||
|
||||
test('negotiateHostCapabilities({}) → SAFE_DEFAULTS floor carries isolation "none"', () => {
|
||||
// FAIL_CLOSED_FLOOR.dispatch.isolation (src/host-integration.cts SAFE_DEFAULTS)
|
||||
const result = negotiateHostCapabilities({});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'none');
|
||||
});
|
||||
|
||||
test('isolation is NOT gated by namedDispatch:false — unlike nested/background/backgroundDispatch, it is not capped', () => {
|
||||
// orchestrator-worktree fan-out is OS-level (process-spawn), independent of
|
||||
// the host's native named-subagent dispatch (ADR-1239 §2584: "does not use
|
||||
// the host's native subagent tool"). A host may plausibly declare
|
||||
// namedDispatch:false yet still have isolation info; either way it must not
|
||||
// silently flip to a DIFFERENT valid value or throw.
|
||||
const result = negotiateHostCapabilities({
|
||||
dispatch: { ...BASE_DISPATCH, namedDispatch: false, isolation: 'orchestrator-worktree' },
|
||||
});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'orchestrator-worktree');
|
||||
});
|
||||
|
||||
// ─── Boundary: exact valid-set membership ──────────────────────────────────
|
||||
|
||||
describe('boundary — a value one character off a valid member fails closed to "none"', () => {
|
||||
const NEAR_MISSES = [
|
||||
'harness-worktre', // missing trailing 'e' (limit-1)
|
||||
'harness-worktreee', // extra trailing 'e' (limit+1)
|
||||
'Harness-Worktree', // case mismatch
|
||||
'orchestrator-worktre', // missing trailing 'e'
|
||||
'orchestrator-worktrees', // extra trailing 's'
|
||||
'non', // missing trailing 'e' of "none"
|
||||
'nonee', // extra trailing 'e'
|
||||
' none', // leading space
|
||||
'none ', // trailing space
|
||||
];
|
||||
for (const nearMiss of NEAR_MISSES) {
|
||||
test(`isolation:${JSON.stringify(nearMiss)} → "none"`, () => {
|
||||
const result = negotiateHostCapabilities({
|
||||
dispatch: { ...BASE_DISPATCH, isolation: nearMiss },
|
||||
});
|
||||
assert.strictEqual(result.effective.dispatch.isolation, 'none');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Property: valid-set-passthrough-else-none contract ─────────────────────
|
||||
|
||||
test('property: effective.dispatch.isolation equals the declared value iff it is a known vocabulary member, else "none"', () => {
|
||||
const declaredArb = fc.oneof(
|
||||
fc.constantFrom(...HOST_INTEGRATION_AXES.isolation, 'undocumented'),
|
||||
fc.string(),
|
||||
);
|
||||
fc.assert(
|
||||
fc.property(declaredArb, (declared) => {
|
||||
const result = negotiateHostCapabilities({
|
||||
dispatch: { ...BASE_DISPATCH, isolation: declared },
|
||||
});
|
||||
const eff = result.effective.dispatch.isolation;
|
||||
assert.ok(HOST_INTEGRATION_AXES.isolation.includes(eff),
|
||||
`effective.dispatch.isolation '${eff}' must always be a known vocabulary member`);
|
||||
if (HOST_INTEGRATION_AXES.isolation.includes(declared)) {
|
||||
assert.strictEqual(eff, declared, `a valid declared value ('${declared}') must pass through unchanged`);
|
||||
} else {
|
||||
assert.strictEqual(eff, 'none', `an invalid/sentinel declared value ('${declared}') must degrade to "none"`);
|
||||
}
|
||||
}),
|
||||
{ numRuns: 200, seed: 2584 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#2584 dispatch.isolation — validator', () => {
|
||||
test('_HOST_INTEGRATION_VOCAB.isolation matches HOST_INTEGRATION_AXES.isolation (parity guard)', () => {
|
||||
assert.deepEqual(
|
||||
[..._HOST_INTEGRATION_VOCAB.isolation].sort(),
|
||||
[...HOST_INTEGRATION_AXES.isolation].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
test('a descriptor that omits dispatch.isolation entirely still validates clean (added after existing descriptors)', () => {
|
||||
const cap = shippedClaudeCapabilityWithoutIsolation();
|
||||
const errors = validateCapability(cap, 'claude');
|
||||
assert.deepEqual(errors, [], `omitted isolation must validate clean, got: ${JSON.stringify(errors)}`);
|
||||
});
|
||||
|
||||
for (const value of ['harness-worktree', 'orchestrator-worktree', 'none', 'undocumented']) {
|
||||
test(`dispatch.isolation:"${value}" → ZERO validator errors`, () => {
|
||||
const cap = shippedClaudeCapabilityWithoutIsolation();
|
||||
cap.runtime.hostIntegration.dispatch.isolation = value;
|
||||
const errors = validateCapability(cap, 'claude');
|
||||
const isoErrors = errors.filter((e) => e.includes('dispatch.isolation'));
|
||||
assert.strictEqual(isoErrors.length, 0,
|
||||
`"${value}" must produce no validator errors; got: ${JSON.stringify(isoErrors)}`);
|
||||
});
|
||||
}
|
||||
|
||||
test('a present invalid dispatch.isolation value is rejected', () => {
|
||||
const cap = shippedClaudeCapabilityWithoutIsolation();
|
||||
cap.runtime.hostIntegration.dispatch.isolation = 'quantum-worktree';
|
||||
const errors = validateCapability(cap, 'claude');
|
||||
assert.ok(
|
||||
errors.some((e) => e.includes('dispatch.isolation')),
|
||||
`an invalid dispatch.isolation must produce a validator error; got: ${JSON.stringify(errors)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('a reserved-name dispatch.isolation value ("__proto__") is rejected', () => {
|
||||
const cap = shippedClaudeCapabilityWithoutIsolation();
|
||||
cap.runtime.hostIntegration.dispatch.isolation = '__proto__';
|
||||
const errors = validateCapability(cap, 'claude');
|
||||
assert.ok(
|
||||
errors.some((e) => e.includes('dispatch.isolation') && e.includes('reserved name')),
|
||||
`"__proto__" must produce a reserved-name validator error; got: ${JSON.stringify(errors)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('every shipped runtime descriptor with an isolation value passes validateCapability', () => {
|
||||
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
|
||||
for (const [id, cap] of Object.entries(registry.runtimes)) {
|
||||
const iso = cap && cap.runtime && cap.runtime.hostIntegration && cap.runtime.hostIntegration.dispatch
|
||||
&& cap.runtime.hostIntegration.dispatch.isolation;
|
||||
if (iso === undefined) continue;
|
||||
const errors = validateCapability(cap, id);
|
||||
const isoErrors = errors.filter((e) => e.includes('dispatch.isolation'));
|
||||
assert.strictEqual(isoErrors.length, 0,
|
||||
`${id}: shipped dispatch.isolation:"${iso}" must validate clean; got: ${JSON.stringify(isoErrors)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user