Reuse the existing run input for command images and ordinary queue/steer delivery.
Align Unicode command separators, bound goal observation, preserve plan input encoding,
and handle explicitly expected Windows cancellation disconnects in runtime tests.
* refactor(llm-deepseek): share the /models listing machinery
Move the account route's `/models` request and decoding, its
credential-scoped listing cache, and listed-catalog resolution with the
version gate into the shared DeepSeek Messages library, so a second
route can follow the same listing. Names drop the account prefix, the
cache takes each access's endpoint settings with the call, and it gains
`invalidate()` for a forced reload. `isBuiltInCatalog()` recognizes the
catalog an uncustomized `models` setting resolves to.
The account route keeps its behavior; only its imports and the
override diagnostic wording ("model listing") change.
* feat(llm-deepseek-api-key): follow the official /models listing
While `models` equals the built-in catalog and the Messages origin is
https://api.deepseek.com, `deepseek-official` serves the official
`GET /models` listing, authenticated with the API key in `x-api-key`.
Loading, refresh, fallback, merging, and the client-version gate match
the account route: the first access waits up to `modelsTimeoutMs`, a
listing older than `modelsMaxAgeMs` refreshes in the background on the
next access, a failure keeps the last good listing or the built-in
catalog, and listed models this build cannot serve are refused before
network I/O.
A customized `models` list replaces the catalog and requests nothing.
Switching between a customized catalog and the listing discards the
cached listing, so Restore defaults reloads it on the next access.
Suites that boot the route on the official endpoint now answer the
listing themselves instead of reaching the network.
* docs: describe the official route model listing
Document when `deepseek-official` follows the official `/models`
listing, its timing fields, customization precedence, and the forced
reload after Restore defaults in the API-key README, the shared
transport README, and the provider guide. Record the decision in a new
Agent Note, keep the account note's statement of where the shared
listing code lives current, and regenerate the configuration catalog.
* test(llm-deepseek-api-key): pin the once-per-listing hidden-entry diagnostic
* docs: regenerate module and event graphs for the shared listing
* fix(llm-deepseek-api-key): report credential failures on the model request
On the official endpoint, `connect` waited for the listing view, whose
credential lookup rethrew a malformed key or a credential-store failure
before the agent loop recorded the prompt. A failed lookup now resolves
the connection from the current listing view, so `resolveAuth` reports
the failure on the model request and the turn keeps the prompt and the
failed attempt. A caller abort still rejects.
* docs(llm-deepseek-api-key): name which hidden listing entries are logged
* test: keep unit suites off the official model listing
Suites that boot the DeepSeek routes with a dummy or inherited key now
request `GET https://api.deepseek.com/models` on the official endpoint.
A unit-lane setup file rejects that request at once, so such a suite
serves the built-in catalog instead of reaching production or waiting
out the listing deadline on a network that never answers; suites that
exercise the listing answer `fetch` themselves. Other requests pass
through unchanged.
* feat(llm-deepseek): read tool_update from the model listing
Both DeepSeek routes now take a listed model's Messages tool update mode
from `api_capabilities.anthropic_messages.tool_update`, alongside the
system prompt mode. `in-history` and `addition-only` replace the
bundled mode; any other value reads as `leading-only` and removes it.
An absent or `null` mode keeps the bundled one, so a listing that does
not declare the field leaves `deepseek-flash` on `addition-only`.
`system_prompt_update: null` likewise keeps the bundled mode now; it
previously read as `leading-only`, which a serializer writing empty
values as null would have turned into a cache-unfriendly downgrade.
* fix(llm-deepseek): sync the built-in context window with the official listing
The official `/models` listing declares a 1,048,576-token context window
for `deepseek-flash` and `deepseek-v4-pro`; the built-in catalog said
1,000,000, a decimal-versus-binary mismatch. Both built-in entries now
carry the listed value, so offline starts and listing failures size the
context the same way the listing does. The Models page shows the exact
count for these rows, since 1,048,576 has no decimal K/M spelling.
`defaultContextWindow`, the fallback for model ids the catalog does not
describe, stays 1,000,000.
* test(llm-deepseek-api-key): reject the stubbed listing request with an Error
* test(cli): keep the built SDK smoke off the official model listing
The SDK profile's initialize now resolves the deepseek-official catalog,
which requests GET https://api.deepseek.com/models on the official
endpoint. With the smoke's fixture key that request answers 401 and the
route warns on stderr, so the test's empty-stderr assertion failed in the
e2e and snapshots lanes. Point the run at a loopback mock and assert it
received no model call.
* test(llm-deepseek): compose the malformed-key turn without the native executor
release/dsh-0.2.1 predates @deepseek-ai/dsh-tool-executor-native; its tool
runtime executes tools itself. Compose the official-route turn the way
account-routing.spec.ts does on this branch.
* fix(llm-deepseek-api-key): gate nothing when the credential lookup fails
The listing cache switches scope only after it reads a credential, so a
failed lookup left `connect` serving the previous credential's listing.
After a key whose listing refuses a model was replaced by a malformed one,
`admit` refused that model with CLIENT_UPGRADE_REQUIRED before the turn
recorded the prompt, instead of `resolveAuth` reporting the credential
error on the request. Serve the built-in catalog without listing gates
when the lookup fails.
* test(llm-deepseek-api-key): pin the once-per-listing diagnostic across request preparation
The hidden-entry diagnostic test repeated the same discovery assertion to
prove a second pass reports nothing new, which read as a duplicate. Assert
the single warning after the request preparations instead; they resolve
the same listing, so dropping the per-listing deduplication still fails
the test.
* docs(llm-deepseek-api-key): note that a list matching an earlier built-in catalog counts as customized
Customization is judged against the running version's built-in catalog,
so a saved `models` list equal to an earlier version's catalog stops
following the listing once the built-in catalog changes, until Restore
defaults. Record the consequence in the Agent Note and the README's known
limitations.
Add @deepseek-ai/dsh-agent-model. It provides ctx.agentModel
(resolveForNextStep, select, applyConfiguredRoute) and owns the
modelSelection projection, which the Agent registry no longer registers.
dsh-agent-loop now injects agentModel; the base and SDK-minimal bundles
and the Python SDK runtime include it.
The provider keeps the projection registered until consumers that are
still unloading finish their cleanup. The model-switch notice becomes an
opt-in per-Agent plugin at @deepseek-ai/dsh-agent-model/notice, and
agent/request now also rejects reasoning-effort changes after step
preparation.
Mount the provider in every composition that loads dsh-agent-loop, and
update consumers, the switch-notice snapshot driver, docs, and the
upgrade guide.
#5938 turned @deepseek-ai/dsh-session-persistence-jsonl into a function
plugin without a default export, while the SDK resume spec from #5988
still default-imported it. Typecheck failed with TS1192 and the spec's
six cases mounted an undefined plugin, which kept the coverage, snapshot
and Windows build checks red for every pull request.
Integrate the observed master checkpoint 7ab215e23e5d977aae7eb5d4fb91782d39969dbb without rewriting the earlier published checkpoints. Master adds current-directory events, directory-aware consumers, and retained Git worktrees; its old subagent startup paths overlapped the unified activation implementation.
Resolve directory selection within managed startup after acquiring parent ownership. Preserve the original Session origin while setting each new local child directory, retain persisted directory state on cold resume, and pass a required absolute cwd to external providers. Keep unified local/external ownership, full completion notices, parked-input host completion, SDK wait/error/shutdown behavior, and ACP cancellation semantics. Retain removal of the old in-process driver and continuation managers.
Combine SDK wait with the new directory requests, preserve the new master directory/worktree fixtures, regenerate derived catalogs from the combined sources, and refresh only the four stale prompt/schema sidecars. Preserve every protected historical schema, archived note, catalog-v2 acknowledgement, and recorded predecessor input.
Validation: full build; 59 CI static gates; 855 subagent tests with 100% coverage on changed core/external sources; affected consumer suites including 119 repaired-fixture tests; SDK 11 and headless 16 recorded scenarios; two local headless expected cases; six built nested SDK/ACP cases; lint, strict cast inventory, workspace constraints, and whitespace checks. Native Windows worker diagnostics remain additive and require the next CI run to classify the original crash.
The cwd regression used a fake Context cast through unknown. Updating its service lookup for descendant shutdown changed the exact legacy assertion fingerprint, which the no-new-unknown-casts CI gate correctly rejected.
Exercise initialize, prompt, and wait with the real Context, Agent registry, Session store, and a scripted LLM adapter. Spy on the real service methods to keep the cwd, reasoning effort, and token-limit assertions. Remove both obsolete unknown assertions and prune only their recorded allowances; do not add or broaden an exception.
Validation: the cwd regression passes; all 54 SDK server tests pass with 100% server coverage. The full check:ci:static aggregate passes 59/59, including the strict unknown-cast inventory. No production behavior changes.
SDK shutdown disposed its root Agent handles without stopping managed
Activations below those exact roots. The manager owns descendant handles
in a separate context, so an interrupted child with parked input remained
registered after shutdown resolved. Its settlement watcher could then
outlive the SDK root and race later projection teardown.
Close scoped descendant admission and await child-first drain before
releasing root handles or the server adapter. Keep draining limited to the
server's exact root Agents so unrelated parent trees remain live. Collect
drain failures without skipping the existing root/adapter teardown, and
preserve the final single-error or aggregate-error result.
Add real AgentLoop/SubagentRuntime regressions for parked children at
one and two levels, child-first disposal, preservation of an unrelated
parked tree, and descendant/root teardown failure aggregation. Correct an
existing Context.get test stub to return its LLM only for the LLM service.
Document the lifecycle order in the server JSDoc and bilingual README.
Validation: 52 SDK server tests with 100% server.ts statements, branches,
functions, and lines; four final shutdown regressions; host TypeScript
build; full lint; 21 quick documentation checks; staged pairing/diff checks.
Doc-sync passed 41/42 checks; the pre-existing config catalog source link
for subagent-acp/index.ts is stale at base and left to integration refresh.
Interrupting a local child can preserve unclaimed steering while leaving the Agent idle. Activation residency intentionally continues in that state, but completion barriers joined its result forever. The real SDK server reproduced the hang for both direct children and a nested parked leaf.
Join progressing work and cleanup while retaining parked inboxes and their activation results. Observe local activity and maintenance, external execution, unpublished ownership holds, and materialization; rescan after changes. Recheck resumed Agents after concurrent idle observations. Inbox insertion and ownership publication wake pending observations, so late non-waking injection cannot strand a result wait. Existing descendants remain responsible for their own work; no second state store or polling timer is introduced.
Document that persistent SDK/ACP hosts keep these children available for later wake-up, while headless shutdown discards unclaimed input. Simplify the final settlement recheck to retry changed readiness uniformly. Update generated API and event references from their sources.
Validation: real SDK negative controls fail at both depths on the prior manager with clean teardown; 545 tests pass across 27 files. Manager, local activation, and headless runner each reach 100% statements/branches/functions/lines. Six deterministic races cover revival, preparation publication/rollback, flush-time injection, and late children. doc-sync 42/42, targeted build, lint, and whitespace checks pass.
The completion coverage audit left two uncovered paths: replaying an ACP
abort that occurred before listener registration, and ignoring an Agent
error from outside the SDK server's owned root sessions.
Remove the redundant ACP abort replay. The private cancelPrompt method
is the sole owner of admissionController.abort(), sets cancelRequested
first, and never resets it. Settlement checks that sticky cancellation
state after its initial idle observation and before awaiting the local
cancelled promise. A pre-listener abort therefore never needs to resolve
that promise; the listener still handles cancellation during child waits.
Add a real local subagent failure regression for SDK ownership. The child
emits agent/error through the normal AgentLoop path and returns an error
result, while the SDK root's wait and a subsequent prompt both complete.
The test also rejects observer warnings, so dropping the ownership guard
fails on the swallowed TypeError rather than merely executing the branch.
No fake Agent identities or private-state mutation are used.
Validation:
- Focused descendant test passed; removing the ownership guard as a
temporary negative control failed on the unexpected observer error.
- pnpm exec vitest run packages/acp/acp/tests packages/sdk/server/tests packages/subagent/subagent-dsh-sdk/tests --coverage --coverage.include=packages/acp/acp/src/session.ts --coverage.include=packages/sdk/server/src/server.ts --coverage.reportsDirectory=/tmp/pr4394-completion-coverage-00243b2f89e-consumer
(251 tests passed; measured statements, branches, functions and lines 100%)
- Focused lint and git diff --check passed.
The early-cancellation audit also reproduced a separate initial-idle wait
liveness issue. It is intentionally left for its own authorized follow-up
commit rather than being combined with this coverage change.
A summary can fail while appending turn/start or turn/end and still leave
the root idle with a stable Session sequence. The SDK wait operation then
returned {}, letting the external provider treat the preceding completed
terminal as the result of the entire task. A listener installed only for
the wait would also miss failures between prompt admission and the wait
request.
Retain one latest live failure on each existing SDK Session record, scoped
to the exact owned Agent. Record the Session's next log offset when the
failure occurs. A turn/end from the exact owned Session clears it only
when that terminal was committed at or after that offset. This preserves
failures raised during terminal publication, keeps ordinary represented
turn errors in the durable outcome, and permits later successful activity
to recover. At stable root/descendant completion, session/wait rejects any
remaining failure. Raw session.status and HarnessSession.run behavior are
unchanged; no generic failure history or new notification is introduced.
Add seven owner-local cases for later start/end failures before and during
wait, subsequent successful prompts, represented errors, and publication
ordering including an undefined thrown value. Six failure cases returned
success before the fix. Strengthen listener teardown coverage to assert
all actual subscriptions are disposed after a disposer fails.
Extend the existing recorded subagent-continuable SDK scenario to exercise
session/wait. It still asserts that run() returns DONE at parent turn 1,
then requires child completion and parent turn 2/idle plus its final
summary to be delivered before the wait response. Bypassing the wait made
that ordering assertion fail. No recorded Session or golden output was
invented or refreshed. Update the protocol/server bilingual READMEs and
regenerate the event-consumer matrix for the new error listener.
Validation:
- pnpm exec vitest run packages/sdk/server/tests/wait.spec.ts before the fix:
6 failed, 1 passed (the represented-error case)
- pnpm exec vitest run packages/sdk/server/tests/server.spec.ts packages/sdk/server/tests/wait.spec.ts (40 passed)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts (8 passed)
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts (8 passed)
- pnpm run test:snapshot snapshots/sdk/sdk.snapshot.ts -t 'replays subagent-continuable through dsh --profile sdk' (1 passed, 24 unselected)
- pnpm run doc-sync: Host compile/bundle, doc-typecheck and 40 other gates
passed; the generated event graph was stale, then regenerated and passed
pnpm run verify-doc-graphs (all 7 graph docs current)
- pnpm run lint:contracts-ready and focused lint of all changed SDK code
- named translation pairing checks and git diff --check
Independent SDK rereview and 7/7 focused tests found no remaining issue.
The parent task owns unchanged Python advanced/recovery validation on the
combined branch; this commit changes no Python or high-level SDK results.
The unified delegation tool returns as soon as an activation accepts its
prompt. SDK and ACP child runtimes could therefore reach root idle while
an admitted fork was still running. Both external providers interpreted
that idle edge as successful task completion and disposed the runtime,
returning a waiting message while cancelling the nested work.
Add the opt-in SDK session/wait request for existing SDK-owned sessions.
It waits for root idle, managed descendants, and any resulting root turns,
then requires stable idle state and Session sequence across the final
check. Preserve raw session.status notifications and HarnessSession.run's
receipt-to-next-idle API; only the task-owning SDK provider uses the new
completion operation. Subscribe before prompt admission, validate and
fold queued root events through the FIFO wait response, and retain
accepted partial output on cancellation or transport failure.
Keep ACP prompt ownership through descendant completion and later root
summary turns, including their stop reasons and output-delivery failures.
Race child waiting against the existing prompt cancellation controller;
cancellation releases the prompt without permanently closing descendant
admission or repinning the original model selection onto later turns.
Session close retains child-first descendant teardown.
Add real Loader/profile regressions with a gated nested fork for SDK and
ACP completion, cancellation, and final-summary failure. Add focused
coverage for unknown/malformed wait requests, SDK root summary ordering,
ACP cancellation followed by another prompt, and later-summary image
output draining/failure. Update the five owning bilingual README pairs.
Validation:
- pnpm run build
- pnpm run typecheck
- pnpm exec vitest run packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts packages/sdk/server/tests/server.spec.ts packages/acp/acp/tests/turns.spec.ts (123 tests at initial fix)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts packages/sdk/client/tests/sdk-client.spec.ts (80 tests)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts packages/sdk/server/tests/server.spec.ts (70 tests)
- pnpm exec vitest run packages/acp/acp/tests (144 tests)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 tests)
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 tests)
- pnpm run test:docs (21 gates)
- pnpm run doc-sync (42 gates)
- pnpm run lint:contracts-ready
- git diff --check
The unchanged SDK next-idle API remains covered by its existing client
tests; no raw notification or persisted Session event format changes.