Commit Graph

5688 Commits

Author SHA1 Message Date
Tianyi Cui b54381f3e7 fix(agent): commit mutable setup at publication
Agent setup may await while a mutable contribution registry changes. The previous subagent path validated and committed its provisioning batch inside the setup callback. A revocation queued after that callback returned therefore treated the installation as resident and released it, even though AgentLoop had not published the child yet. AgentLoop could then admit and announce a child whose required capability had already disappeared.

Introduce AgentSetupCommit as the optional synchronous result of create and resume setup. AgentLoop now awaits setup, invokes that commit with no intervening asynchronous boundary, and only then enters the Session and Agent registries. A commit failure follows the existing private-transaction rollback, so neither identity is published and the caller can reuse the id.

Keep continuable-subagent installations provisional until this publication commit. Contribution removal still releases every installation immediately, but now marks an unpublished batch invalid so its commit rejects with ACTIVATION_SETUP_REVOKED. Once the commit succeeds, later removal remains ordinary live revocation.

Cover create and resume ordering, resume commit rejection and identity reuse, and an assembled microtask revocation that leaves only the parent Agent and Session. Update the public JSDoc, architecture flow, package contracts, current Agent Notes, Chinese counterparts, pairing records, and generated Cordis API to describe the new boundary.

Validated with the four focused Agent/subagent test files (91 tests), the isolated assembled regression, targeted TypeScript project builds, generated Cordis API freshness, export JSDoc verification, scoped translation pairing, Markdown wrapping, and Mermaid parsing.
2026-08-02 20:09:05 +08:00
Huanqi Cao efcee43c7d docs(tools): document the abort message and the background call card; pin both with tests 2026-08-02 20:07:43 +08:00
Huanqi Cao 5e437d3996 docs(pwsh): scope the encoding claim, document the PATH probe and preamble limitation, and fix catalog requires lists 2026-08-02 19:37:45 +08:00
Huanqi Cao 12c6f43bf1 fix(tool-bash): report foreground aborts as the TOOL_ABORTED HarnessError 2026-08-02 19:37:29 +08:00
Huanqi Cao d8ee37d87a fix(tool-pwsh): present background calls on the generic card like the bash tool 2026-08-02 19:37:25 +08:00
Huanqi Cao c376802f44 fix(acp-snapshot): skip pwshOnly scenarios without pwsh and mount task tools in the pwsh composition 2026-08-02 19:37:09 +08:00
kingwl 4329b93989 Merge remote-tracking branch 'origin/master' into codex/fork-real-turn-tail
# Conflicts:
#	packages/client/runtime/README.i18n.yaml
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/chat/ChatView.tsx
#	packages/client/ui-conversation/src/client/chat/MessageIconActions.tsx
2026-08-02 18:14:32 +08:00
imccyu 7d08e43720 fix(web): address steering review feedback 2026-08-02 17:53:07 +08:00
imccyu d59ab5596e docs(host): align queue mirror contract 2026-08-02 17:53:07 +08:00
imccyu a31b3eb7b8 test(host): cover queue mirror event expiry 2026-08-02 17:53:07 +08:00
imccyu 3a9f78c55e feat(web): restore actions after steering consumption 2026-08-02 17:53:07 +08:00
imccyu dffe955ed2 feat(web): surface and configure composer steering 2026-08-02 17:53:07 +08:00
kingwl 5b17d3bab5 fix(web): match queue steer icon to design 2026-08-02 17:53:07 +08:00
kingwl 955a12cca4 feat(web): steer queued messages into active turns 2026-08-02 17:53:07 +08:00
Huanqi Cao 447c3fd17f fix(sdk): include bash-env in the generated bash feature composition 2026-08-02 17:49:58 +08:00
Huanqi Cao 7201a417c2 fix(subagent): mount bash-env in the spawn and headless e2e harnesses 2026-08-02 17:25:47 +08:00
Huanqi Cao a79d7c896c fix(tool-bash): mount bash-env in the sandboxed and HMR-safety harnesses; normalize the pwsh-tool-turn fixture line endings for Linux replay 2026-08-02 16:53:29 +08:00
Huanqi Cao 480c12077f fix(tool-bash): mount bash-env in the base test harness 2026-08-02 16:43:30 +08:00
kingwl 6e3f77da60 Merge remote-tracking branch 'origin/master' into codex/fork-real-turn-tail
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
2026-08-02 16:25:38 +08:00
kingwl 76547dfe0c fix(web): restrict message forks to completed turn tails 2026-08-02 16:25:02 +08:00
Huanqi Cao 1361e9718e Merge remote-tracking branch 'origin/feat/ripgrep-packaged-binary' into feat/pwsh-tool
# Conflicts:
#	apps/cli/package.json
#	packages/core/tools/tests/gen-tool-catalog.spec.ts
2026-08-02 16:22:24 +08:00
Huanqi Cao 782894ff8c Merge remote-tracking branch 'origin/master' into feat/ripgrep-packaged-binary 2026-08-02 16:18:46 +08:00
kingwl cf70a52dcc Merge origin/master into codex/todo-goal-queue-layout 2026-08-02 14:34:13 +08:00
Huanqi Cao 4e62fea095 docs: record the pwsh/bash parity decision and refresh catalogs and bilingual pairs 2026-08-02 14:18:55 +08:00
Huanqi Cao 96cf8a2fbc chore(compositions): mount bash-env and the pwsh tool in shipped and demo compositions 2026-08-02 14:18:42 +08:00
Huanqi Cao 33810ae774 feat(tool-pwsh): mirror dsh-tool-bash call-for-call minus the sandbox surface 2026-08-02 14:18:29 +08:00
Huanqi Cao af9af8ca05 fix(pwsh-local): pin UTF-8 I/O so the Windows PowerShell 5.1 fallback cannot garble output 2026-08-02 14:18:15 +08:00
Huanqi Cao 87db82e821 refactor(tool-bash): consume ctx.bashEnv from the shared bash-env package 2026-08-02 14:18:01 +08:00
Huanqi Cao d73888478a feat(bash-env): extract the shared DSH_* environment registry into its own package 2026-08-02 14:17:46 +08:00
Tianyi Cui dbe053fe08 refactor(host): share the fenced-live-agent resolution between agentFor paths
The live fast-path fence and the raced-collision catch duplicated the same
subagent-ownership classification, tripping the duplication gate. Extract
`fencedLiveAgent` so both paths resolve one live identity through the
fence identically.
2026-08-02 14:05:37 +08:00
Tianyi Cui d004c694f1 test(web): narrow the stale-pull assertion to the root catalog's calls 2026-08-02 14:05:37 +08:00
Tianyi Cui c8b2e70988 build(web): declare the locale dependency for ui-subagent
The client plugin now consumes `ctx.locale` (dictionary registration plus
the slot `t` seat), but the package graph did not know it: no
`dshClient.inject` entry, no peer/devDependency, no tsconfig project
reference. Mirror the ui-conversation convention so the dependency graph,
HMR/preflight metadata, and standalone packaging all recognize the
`@deepseek-ai/dsh-client-locale` seam.
2026-08-02 14:05:37 +08:00
Tianyi Cui d7a70f6efa fix(host): hand a raced plain-agent winner back from agentFor
The raced-collision catch mirrored only the subagent-owned half of
ensureSession's `.catch`: a concurrent plain-agent publish winning the
identity still fell through to `internal`, where ensureSession returns
the winner. Mirror in full — classify a subagent-owned winner as
`agent-busy`, return a clean plain-agent winner directly.
2026-08-02 14:05:37 +08:00
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 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 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 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