Commit Graph
8525 Commits
Author SHA1 Message Date
Tianyi Cui fb6ccdff04 docs(subagent): scope report acceptance to parent resolution, not delivery
The README claimed "acceptance is governed by registry presence" as a
universal statement, but `sendReport` translates a registered parent's
send rejection into the same PARENT_UNAVAILABLE code — registry presence
governs parent *resolution*, while acceptance additionally depends on the
parent's log still admitting appends. Soften both languages to the
precise contract and re-record the pair.
2026-08-02 14:05:37 +08:00
Tianyi Cui 295e56b61e fix(web): keep removal-time availability invalidation across an in-flight pull
The `host/session-removed` invalidation flipped the owned catalog and
addressed children to `parentAvailable:false`, but a `subagent.list` pull
already in flight was requested before the removal and its ok-response
carries the pre-removal `parentAvailable:true` — the response then
overwrote both the catalog and every addressed child, resurrecting the
writable-editor-against-a-dead-continuation-owner bug the invalidation
closes, with no refresh scheduled to converge afterwards.

Mark the owner stale when a pull is in flight at removal time, so one
trailing refresh runs after the in-flight response settles and the
post-removal host truth lands. Adds a regression test: removal mid-pull,
stale ok response, trailing pull, final state stays unavailable on the
catalog and the addressed child.
2026-08-02 14:05:37 +08:00
Tianyi Cui 5c98cbd8f6 test(web): run the subagent-conversation e2e against the locale-aware copy
The ui-subagent catalog and read-only composer copy moved from hardcoded
Chinese to the locale-aware `subagent` namespace, so an en-US headless
browser now renders English. The e2e's selectors and goldens still
asserted the old hardcoded Chinese strings, leaving the scenario unable
to find the catalog trigger.

Convert the selectors to the default (en-US) render and re-record the
catalog goldens (ui, tree, nested) in English. The locale-aware parts of
the remaining goldens were already English (recorded under the en-US
default), so sidebar and fork are untouched.
2026-08-02 14:05:37 +08:00
Tianyi Cui daf9554804 refactor(subagent): scope the setup transaction to the creation callback
The setup validation and commit moved into the callback, so the outer
definite-assignment slot and its type import are no longer needed; declare
the transaction as a callback-local const.
2026-08-02 14:05:37 +08:00
Tianyi Cui e55d3e96d9 docs(subagent): re-record bilingual pairs after the stack end-result doc edits
Two pairs needed their confirmed-consistent state refreshed: the
intent-named note's supersession clause (zh link normalized to the shared
`.md` target, since the pairing contract requires identical link targets)
and the report README's acceptance-semantics rewrite (both sides edited).
Re-record both pairs so the translation-pairing gate passes.
2026-08-02 14:05:36 +08:00
Tianyi Cui 8bba72639a chore(docs): refresh the persistence catalog after the descriptor doc edit
The maxTokens contract sentences added lines above the `subagent/descriptor`
declaration, shifting its source anchor from line 32 to 36; regenerate the
catalog so the source link stays accurate.
2026-08-02 14:05:36 +08:00
Tianyi Cui df01ed926a fix(subagent): type the schema-resolved reportDelivery shape
Config() applies the schemastery default at runtime, but its return type
keeps the input's optional field, so assert the resolved shape at the
seam — keeping the dead fallback branch gone.
2026-08-02 14:05:36 +08:00
Tianyi Cui 902b46b86b feat(web): localize the subagent catalog and read-only composer copy
The catalog action (diagnostics, relative times, loading/error/retry,
mode and activity labels, branch toggles, descendant counts, tree aria)
and the read-only composer were hardcoded to Simplified Chinese, so an
English-locale session rendered mixed-language UI. Register a `subagent`
locale namespace (zh source of truth + en dictionary), declare it on both
slot registrations, thread the locale `t` seat through the components, and
mount the locale service in the plugin specs.

The UI spec's zh assertions now run against the real dictionary through a
`t` stub that interpolates `{name}` params exactly like the locale
service.
2026-08-02 14:05:36 +08:00
Tianyi Cui 3114947324 docs(subagent): correct report acceptance semantics for closing parents
The tool README claimed a "missing, disposed, or closing parent" fails
the call — but acceptance is governed by the parent's registry presence:
`resolveReportParent` only rejects when the durable parent id is absent
from the registry, so a host-owned parent already in disposal but still
registered still accepts (the pinned host-disposing-parent behavior).
The claim misled callers into treating disposal state as a delivery
signal.

Restate the contract in both languages: absence from the registry is the
only `PARENT_UNAVAILABLE` case, and a failed tool call does not prove
non-delivery — a later `tools/post-execute` veto can fail a call whose
report was already accepted, so the durable child transcript remains the
recovery source.

Adds a regression test pinning acceptance into a host-disposing but
still-registered parent, and rejection after disposal settles.
2026-08-02 14:05:36 +08:00
Tianyi Cui 5da2ac5835 docs(subagent): state that per-activation knobs are not restored on cold resume
The descriptor deliberately snapshots a curated composition field set
rather than the merge-extensible `AgentOptions`, and it already names the
per-activation exclusions (`outputSchema`). `maxTokens` is the same class
of property — it budgets one activation, and on cold resume there is no
parent to inherit a limit from, so the resumed activation runs under the
deployment defaults. Spell that out in the module contract so the
fallback is a documented decision instead of a silent surprise for
deployments that set explicit child token limits.
2026-08-02 14:05:36 +08:00
Tianyi Cui 98ccbade7e fix(subagent): drop the dead reportDelivery destructure default
`apply()` resolved the deployment config through schemastery's `Config()`,
which always fills the schema default (`quiet`, pinned by the config test),
so the `= 'quiet'` destructure fallback was dead at runtime on every path —
and as a defaulted parameter it formed a branch no test could ever
exercise against the per-file coverage gate. Remove the fallback and let
the schema be the single home of the default.
2026-08-02 14:05:36 +08:00
Tianyi Cui 879a623095 fix(subagent): cover the scope-disposal effect registration with setup rollback
The `childCtx.effect()` that routes scope disposal into `releaseChild` was
registered after the install loop's try/catch, so a hypothetical throw
from the registration itself (effect() rejects only on an inactive fiber,
which a live unpublished scope cannot be) would leak the just-installed
batch — neither the setup-rollback catch nor `releaseChild` would release
it. Move the registration inside the try so the existing rollback path
covers it; no observable behavior change.
2026-08-02 14:05:36 +08:00
Tianyi Cui 42ee4e22de fix(subagent): validate setup transactions before agent publication
`materialize` ran `setupTransaction.assertIntact()` only after
`ctx.agents.create()/resume()` resolved — but the factory publishes
`session/created` (and the persistence backend writes the descriptor seed)
inside that call, and `rollbackUnpublished()` only disposes the live
handle; the persistence seam has no delete. A setup contribution revoked
during construction therefore left a durable ghost: `startContinuable()`
rejected with `ACTIVATION_SETUP_REVOKED` and returned no child id, yet
`list_agents` surfaced a persisted `continuable` child whose log carries a
valid descriptor — so a later `send_message` could cold-resume a child the
deployment had explicitly refused to establish.

Move the validation into the creation callback, before the factory can
publish: `assertIntact()` then rejects the create/resume call itself, so
no session is ever persisted for a rejected child. Commit the batch in the
same callback so a later contribution removal releases the installation
instead of invalidating a child already being established (live
revocation, matching the resident semantics).

Pins the rollback regression test to assert that no `session/created` is
ever announced for the rejected child (the parent is created before the
listener registers), in addition to the existing registry assertion.
2026-08-02 14:05:36 +08:00
Tianyi Cui 2a3a8ff66d docs(subagent): mark the superseded flush-required clause in the intent-named note
The 2026-07-27 intent-named operations note still declared that a
continuable provider requires `flush()` to resolve `true` at its final
result boundary and maps `false`/rejection to `DURABILITY_FAILED`. The
activation-based record (2026-07-28-continuable-subagent-conversations)
superseded that contract: the manager awaits the final flush as a
best-effort barrier and deliberately ignores the boolean, because listener
participation cannot identify a persistence backend.

Active notes are the current source of truth — sync both sides of the
bilingual pair by marking the old clause superseded with a link to the
record that replaced it.
2026-08-02 14:05:36 +08:00
Tianyi Cui cb835c7ea9 fix(acp): keep per-session teardown failure reasons in the aggregate log
The connection-close teardown path threw a bare `AggregateError` whose
message counts the failed sessions, and its only production consumer logs
through `String(error)` — which renders the message alone. Compared with
the previous `Promise.all` behavior, every actual disposal failure reason
disappeared from operational logs.

Join the per-session reasons into the aggregate message, matching the
subagent seam's own aggregate disposal messages, and pin the reason in
the dispose spec's warning assertion.
2026-08-02 14:05:36 +08:00
Tianyi Cui e81267945a docs(host): refresh the stale agentFor resume-on-miss comment
The commands entry's inline comment described the old routing shape
("clients only send a sessionId for a published session") without the
ownership fence that agentFor now applies on every path — the fence's
contract home is the api/commands.ts module JSDoc, so trim the duplicate
and point at the routing shape only, keeping one home per fact.
2026-08-02 14:05:36 +08:00
Tianyi Cui c68c3dbb43 fix(host): check subagent ownership before cwd conflict in ensureSession
Explicit-id adoption of a cold session-backed subagent under a *different*
cwd answered `session-conflict` because the cwd check ran before the
persistence inspection classified the identity. The api/commands.ts
contract states explicit-id `session.create` adoption rejects
session-backed subagents with `agent-busy` — ownership is an identity
property, so it must win regardless of the requested workspace.

Reorder the stored-session branch to inspect and classify ownership
first, then enforce the cwd match, making the response match the
documented contract.
2026-08-02 14:05:36 +08:00
Tianyi Cui 468fd29e51 fix(host): classify a raced cold-resume ID collision as agent-busy
When a generic `agentFor` cold resume loses the identity to a parent's
concurrent `enter()` — the collision rejection arrives from
`ctx.agents.resume` publication after the pre-resume re-check — the error
fell through to the `internal` mapping. Clients retrying then see a
transient-looking internal failure instead of the stable ownership error
that `ensureSession`'s `.catch` already produces for the exact same
published-winner case.

Mirror that re-classification in `agentFor`'s resume error path: after the
typed errors, re-check the registry and attached store and answer
`agent-busy` when the raced winner is subagent-owned. Adds a regression
test whose resume mock publishes the subagent winner before throwing the
ID-collision error.
2026-08-02 14:05:36 +08:00
Tianyi Cui 56e252bed3 fix(host): fence the agentFor live fast path on the agent's own session
`agentFor` fenced subagent ownership through the attached session store
(`ctx.sessions.get`) and only then returned a live registered agent. A
registered agent whose session is ever absent from the attached store —
an invariant nothing in this package guarantees — would therefore be
handed out through generic Host routing unfenced, bypassing subagent
delivery entirely.

Fence `live.session` directly whenever a live agent exists, and keep the
attached-store check only for the not-live durable classification.
`ensureSession`'s race `.catch` already fences `live.session`; this makes
the fast path the same check instead of an asymmetric weaker one.
2026-08-02 14:05:36 +08:00
Tianyi Cui 4b2fa3317e perf(host): scan the own-suffix for a subagent descriptor without copying
`hasSubagentDescriptor` sliced the whole own-suffix events array on every
Agent-bound RPC — including each `session.prompt` and `sessions.models`
call on long transcripts — and `ensureSession` rescans the same suffix
after creation. Replace the slice-then-some with an indexed loop from the
seed boundary, so the classification is a plain O(suffix) read with no
allocation.
2026-08-02 14:05:36 +08:00
Tianyi Cui b2187cabf6 fix(cli): keep the core-web overlay at its documented two-tool surface
The opt-in `core-web.cordis.yml` profile promises "exactly persistent
`bash` plus `str_replace_editor`" (its header comment and `apps/cli/
README.md`), but the base registration of `tool-subagent-list-agents`
(added with the durable child catalog) was not disabled by the overlay,
so the profile actually exposed `bash`, `str_replace_editor`, and
`list_agents`. The assembled snapshot was updated to accept the third
tool, which ratified the contract break instead of fixing it.

Disable `tool-subagent-list-agents` in the overlay and restore the
snapshot's expected tool registry to the documented two tools.
2026-08-02 14:05:36 +08:00
Tianyi Cui 8431dbead3 fix(web): invalidate catalog availability when the owning parent is removed
A removed session can no longer be the delivery owner of its continuable
children, but the `host/session-removed` handler only reconciled the
removed row's own activity. `parentAvailable` was updated exclusively from
`refreshSubagents` success, and removal schedules no catalog refresh — so
after the parent's Activation detaches, an addressed child kept a writable
editor against a dead continuation owner until an unrelated refresh (or
forever, for a closed menu).

Flip `parentAvailable` to false on the owned catalog and push
`handleSubagentParentAvailable(false)` to every addressed child Session at
removal time, matching the refresh path's notification. New Session
instances already read `parentAvailable` from the catalog, so they inherit
the invalidated state.

Adds a regression test: removing the catalog's owning parent flips the
snapshot's `parentAvailable` and notifies the addressed child instance.
2026-08-02 14:05:36 +08:00
Tianyi Cui b270b3ef9f fix(web): run a trailing catalog refresh for coalesced membership changes
`refreshSubagents` single-flights per catalog owner: a request arriving
while a pull is in flight returns the in-flight promise and is silently
coalesced into it. The in-flight response was requested before the
triggering change, so it can never contain that change — a debounced
membership refresh (50ms after `host/session-added`) firing during a slow
pull therefore lost the new child, and the catalog stayed stale until an
unrelated trigger (reselection, menu reopen, reconnect).

Mark the owner stale on coalescing and re-arm one trailing pull in the
settlement `finally`, so every membership change observed during a pull is
carried by a follow-up refresh exactly once. Bounded: the trailing pull
only runs when a refresh request was actually coalesced, and a new
coalescing during the trailing pull re-marks the same set.

Adds a fake-timer regression test: a `host/session-added` debounce firing
mid-pull yields exactly two `subagent.list` calls and the catalog
eventually contains the new child.
2026-08-02 14:05:36 +08:00
imccyu ab320ba991 Merge pull request #1114 from deepseek-harness/codex/gui-subagents-navigation
fix(web): preserve nested subagent navigation
2026-08-02 13:03:32 +08:00
imccyu 23680e838b fix(web): synchronize subagent navigation state 2026-08-02 12:51:11 +08:00
imccyu b94d2f9c1d fix(web): stabilize nested subagent navigation 2026-08-02 12:51:11 +08:00
imccyu 53ae9abea2 style(web): polish subagent and bash chrome 2026-08-02 12:51:11 +08:00
imccyu ca1d838afc style(web): refine subagent navigation chrome 2026-08-02 12:51:11 +08:00
imccyu 42d34473af fix(web): allow follow-ups to running subagents 2026-08-02 12:51:11 +08:00
imccyu 130410bb98 fix(web): preserve subagent navigation and fork grouping 2026-08-02 12:51:11 +08:00
imccyu 8c9cd4c15d feat: optimize subagent list children query 2026-08-02 12:51:11 +08:00
imccyu 5d56019d22 feat: revert crumbs navigation for subagents 2026-08-02 12:51:11 +08:00
Dudu-0223 431fb4b035 feat(subagent): add explicit child reports 2026-08-02 12:51:10 +08:00
imccyu a1b3bebb61 fix(subagent): scope drains and soften final flush 2026-08-02 12:51:10 +08:00
imccyu d7153768f5 test(web): cover post-fork subagent continuation 2026-08-02 12:51:10 +08:00
Dudu-0223 9a7be21b7f fix(host): preserve subagent continuation ownership 2026-08-02 12:51:10 +08:00
imccyu 1aedb23ca6 test(web): align subagent access mode golden 2026-08-02 12:51:09 +08:00
imccyu 6a02570e8f test(web): close rebased subagent coverage gaps 2026-08-02 12:51:09 +08:00
imccyu 5ef5feb01a fix(web): reconcile rebased subagent contracts 2026-08-02 12:51:09 +08:00
imccyu d950150cdc fix(persistence): preserve subagent session origin 2026-08-02 12:51:09 +08:00
imccyu 9d77a50c23 test(cli): include list_agents in shipped catalog 2026-08-02 12:51:09 +08:00
imccyu b8be3583e0 test(cordis): refresh session origin API snapshot 2026-08-02 12:51:09 +08:00
imccyu 5113b831e8 test(client): align card fixtures with subagent session state 2026-08-02 12:51:09 +08:00
Dudu-0223 8a518e353b feat(web): rewrite subagent conversations for FIFO activation 2026-08-02 12:51:09 +08:00
Dudu-0223 f0ab04273d fix(web): deduplicate subagent navigation 2026-08-02 12:51:09 +08:00
Dudu-0223 16ffd63115 feat(web): add nested subagent conversations 2026-08-02 12:51:09 +08:00
Dudu-0223 a27492507d feat(web): add subagent conversation transport 2026-08-02 12:51:09 +08:00
imccyu 9adfc576da chore(knip): include subagent settlement fixture 2026-08-02 12:51:09 +08:00
Dudu-0223 a977ef30ee fix(subagent): preserve published run failures 2026-08-02 12:51:09 +08:00
Dudu-0223 8b0a7a5d8d fix(subagent): close stacked catalog integration gaps 2026-08-02 12:51:09 +08:00