* test(#2939): prove shouldFlattenDispatch ignores the depth budget Failing-first regression for #2939. shouldFlattenDispatch checks only background+backgroundDispatch, never nested/subagentToolkit/maxDepth, so a maxDepth:1 descriptor (no room for a bg orchestrator plus a leaf) is told it may background. Row 1 (codex-like, maxDepth:1) asserts true (flatten) and fails today; rows 2/3 guard the unchanged depth-sufficient cases. * fix(#2939): honor the declared depth budget in shouldFlattenDispatch shouldFlattenDispatch checked only background+backgroundDispatch, never nested/subagentToolkit/maxDepth, so a maxDepth:1 descriptor (no room for a backgrounded orchestrator plus a delegated leaf) was told it may background — producing a depth-2 tree (Codex MultiAgent V2) the declared contract cannot support. canBackground now ALSO requires nested:true + subagentToolkit:"full" + a depth budget > 1 (or unbounded -1), reusing the exact predicate shape from bin/install.js _normalizeDispatchCallSpan and matching degradationFor's treatment of maxDepth===1 as flat. Non-finite/missing maxDepth fails closed to flatten. Correct the two existing pins that asserted the buggy output (bare {bg,bgDispatch} now fail-closes on missing depth; the codex-like maxDepth:1 pin flips to flatten) and add a maxDepth:2 negative-space row. * fix(#2939): propagate depth-aware flatten to all pinned descriptors + tests The isolated adversarial review found the depth-aware predicate reclassifies codex/kimi/kimi-code (previously background-eligible under the two-field rule) to flatten — the correct behavior, since each lacks what a backgrounded nesting orchestrator needs: - codex: maxDepth:1 (no room for a depth-2 leaf) - kimi: nested:false (cannot host a nesting orchestrator) - kimi-code: subagentToolkit:'built-in-only' (cannot delegate to full subagents) Only cursor (maxDepth:2) remains background-eligible. Update the three test files that pinned the old contract (host-integration-descriptors EXPECTED_FLATTEN, kimi-upgrades UPGRADE 2, trae-imperative-reference), and align the unbounded convention to maxDepth < 0 (matching degradationFor/negotiateHostCapabilities) with an accurate docstring noting the deliberate nested-check addition over _normalizeDispatchCallSpan. * fix(#2939): update dispatch-should-flatten CLI query pins for codex The depth-aware rule (a0ad0f680) reclassifies codex (maxDepth:1) to flatten, but command-routing-hub.test.cjs exercises the contract through the CLI query route (runGsdTools query dispatch-should-flatten), not a direct shouldFlattenDispatch call — so neither the reviewer's caller-search nor a grep for the symbol found it; only the full gsd-test matrix did. Update the codex query assertions to shouldFlatten=true (maxDepth:1 insufficient), preserving cursor (maxDepth:2 → false) and the backgroundDispatch:true descriptor field. * chore(#2939): add changeset fragment pr:0 placeholder backfilled with the real PR number once the PR exists. * fix(#2939): rephrase changeset for product-name-purity + opencode flatten pin Two failures from the full gsd-test matrix on the prior sha: 1. product-name-purity: changeset fragments must not include parenthetical product descriptions (they render verbatim into CHANGELOG.md). 'Codex (and kimi/kimi-code)' tripped it — rephrase to lead with the behavior, naming runtimes inline without the parenthetical. lint:ci changeset-lint does not catch this; only the test does. 2. opencode-imperative-reference: the #2087-retraction pin flipped only the two background booleans and asserted shouldFlatten:false. Under #2939 that is no longer sufficient (opencode lacks nested + full toolkit + depth budget), so the retracted axes now correctly flatten — update the pin to true with rationale. * chore(#2939): backfill changeset PR number 3063 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -569,13 +569,34 @@ function negotiateHostCapabilities(
|
||||
* may be backgrounded.
|
||||
*
|
||||
* A host may background only if it can reliably background a nesting-capable
|
||||
* orchestrator — i.e. both `background` AND `backgroundDispatch` are
|
||||
* explicitly `true`. Any other value (false, missing, 'undocumented') fails
|
||||
* closed to inline (the always-safe path).
|
||||
* orchestrator that still has room to delegate to a leaf — i.e. ALL of:
|
||||
* - `background === true` AND `backgroundDispatch === true` (it can background at all), AND
|
||||
* - `nested === true` AND `subagentToolkit === 'full'` (it can host a nesting orchestrator), AND
|
||||
* - a depth budget greater than 1, or unbounded (`maxDepth < 0`): a budget of exactly 1
|
||||
* is consumed by the backgrounded orchestrator itself (depth 1) and leaves no room for the
|
||||
* delegated leaf (depth 2) its own contract would require.
|
||||
*
|
||||
* Any other value (false, missing, 'undocumented', or an insufficient depth budget) fails closed
|
||||
* to inline (the always-safe path). This closes #2939, where a `maxDepth:1` descriptor
|
||||
* (the live Codex capability) was told it may background a nesting orchestrator that then
|
||||
* produced a depth-2 tree its declared contract cannot support.
|
||||
*
|
||||
* The depth-budget test mirrors the convention already used elsewhere in the codebase:
|
||||
* `degradationFor` (same file) treats `nested && depth >= 2` as full-depth, `maxDepth === 1`
|
||||
* as flat, and any `maxDepth < 0` as unbounded; `negotiateHostCapabilities` (same file) also
|
||||
* treats `maxDepth < 0` as unbounded; `bin/install.js`'s `_normalizeDispatchCallSpan` uses
|
||||
* `subagentToolkit === 'full' && (maxDepth === -1 || maxDepth > 1)`. This function adopts the
|
||||
* broader `maxDepth < 0` = unbounded convention from `degradationFor`/`negotiateHostCapabilities`
|
||||
* (every shipped descriptor uses `-1` for unbounded, so the two conventions agree on live input).
|
||||
* It is STRICTER than `_normalizeDispatchCallSpan` in one respect: it also requires `nested === true`,
|
||||
* because a host that cannot nest cannot host a backgrounded orchestrator that delegates —
|
||||
* `_normalizeDispatchCallSpan` runs per-call after the dispatch decision and does not need that gate.
|
||||
*
|
||||
* This graduates the #853 prose rule (originally `RUNTIME === 'codex'`, then
|
||||
* extended to cursor) to a typed, documentation-sourced decision; codex AND
|
||||
* cursor are both background-eligible in the registry. See
|
||||
* extended to cursor) to a typed, documentation-sourced decision; of the shipped
|
||||
* background-capable hosts only cursor (`maxDepth:2`) remains background-eligible under the
|
||||
* depth-aware rule — codex (`maxDepth:1`), kimi (`nested:false`), and kimi-code
|
||||
* (`built-in-only` toolkit) now correctly flatten. See
|
||||
* docs/reference/host-integration-capability-matrix.md.
|
||||
*
|
||||
* Null-safety: if dispatch is null, undefined, or not an object, returns true
|
||||
@@ -585,8 +606,18 @@ type UnvalidatedDispatch = (Partial<DispatchCapability> & { background?: unknown
|
||||
|
||||
function shouldFlattenDispatch(dispatch: UnvalidatedDispatch): boolean {
|
||||
if (!dispatch || typeof dispatch !== 'object') return true;
|
||||
// Can background at all: both background flags must be explicitly true.
|
||||
const canBackground = dispatch.background === true && dispatch.backgroundDispatch === true;
|
||||
return !canBackground;
|
||||
if (!canBackground) return true;
|
||||
// #2939: can background a NESTING orchestrator with room to delegate. A depth budget of 1
|
||||
// is consumed by the backgrounded orchestrator itself; it needs > 1 (or unbounded, maxDepth < 0)
|
||||
// to host a delegated leaf at depth 2. Non-finite/missing maxDepth fails closed (no budget →
|
||||
// flatten), mirroring degradationFor's treatment of non-finite depth as 0.
|
||||
const canNest = dispatch.nested === true && dispatch.subagentToolkit === 'full';
|
||||
if (!canNest) return true;
|
||||
const depth = typeof dispatch.maxDepth === 'number' && Number.isFinite(dispatch.maxDepth) ? dispatch.maxDepth : 0;
|
||||
const depthSufficient = depth < 0 || depth > 1;
|
||||
return !depthSufficient;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user