A reasoning route always declares at least one level, so the adapter's
"no floor" path is defensive; the resolver rejects an empty list before any
consumer sees it. Mark that branch with the repo's v8-ignore convention and
state why.
`purpose` is a label for replay and transport metadata, so no adapter should
branch on one caller's value. Exact-model metadata now reports
`reasoning.floorEffort`, the least effort a route accepts, and the title
service selects it for its request. `llm-pi-ai` and `llm-deepseek` dispatch
the effort they receive and no longer recognize `session-title`; the
`minimizeReasoning` field is gone. A route with no registered adapter, such
as the recorded replay lanes serve, reports no floor, so the title request
keeps the adapter default instead of failing before dispatch.
Replaces the `minimizeReasoning` field from the previous commit.
`purpose` is a provider-neutral label for replay and transport metadata, so
an adapter must not branch on one caller's value. `GenerateOptions` gains
`minimizeReasoning`, which any auxiliary caller sets when reasoning would
consume the output budget it reserved for a short visible result; both
shipped adapters dispatch such a request at the least reasoning the model
supports, and `session-title-llm` sets it instead of relying on the
adapter recognizing `'session-title'`. The pasted type in the subsystem
page and the generated Cordis API catalog follow.
Prompted by review: llm-pi-ai must not know about session-title.
A request body also carries the session log, whose recorded title event
embeds the framed prompt, so a body-wide marker search matches the main
request too; both mocks now read the request's own messages. The headless
scenarios also assert the dispatched cap, which is the base-bundle ceiling
on the 256000-token route and the route override's 1024 in the pi-ai
compatibility scenario. The extraction cases cover asterisks that are not
emphasis.
The lowest-level dispatch keeps reasoning off the title's output ceiling,
not a small cap's visible text, and it deliberately skips the
requested-effort check. The README pair records that the dispatched cap
is the smaller of the configured ceiling and the latest folded
request/header, including the tighter-main-cap and override consequences,
and the accepted-title comment states the deliberate whole-line emphasis
rule.
Headless stream-json and session expected outputs record the title
request's maxTokens, and every one of them still recorded the previous
base-bundle cap.
The README pair states that an accepted result is the first non-empty
line of the model text, with one wrapping emphasis pair removed, and that
the dispatched cap is the smaller of maxOutputTokens and the cap the
routed adapter declares. The pair is re-recorded with both sides.
The headless and Web mock providers recognize a title request by its
max_tokens, which still assumed the previous 256-token base-bundle cap.
The recorded expected outputs carry that cap too and follow with it.
The prompt asks for a short title, so the cap only stops runaway output:
reasoning a model cannot switch off also counts as output, and 256 tokens
truncated titles on the HFAI gateway. The measured worst title response
was 153 output tokens, so 4096 is about 25 times the observed maximum.
Claude wraps some titles in asterisk emphasis and writes the title, a
blank line, then an explanation when the messages say little. Joining the
text blocks made that explanation part of the saved title. Take the first
non-empty line, drop one emphasis pair around it, and ask the model for a
short best-effort title instead of an explanation.
The dispatched cap is now the smaller of the configured cap and the cap
the session's current main request recorded, so a route the deployment
capped below the shipped ceiling is never asked for more than that cap.
A session-title request dispatches off exactly as a selected off does,
which on protocols that omit the reasoning field leaves the gateway
default in effect. The README now says so instead of implying reasoning
is always switched off. requestReasoningLevel follows the
resolveReasoningLevel it calls and names when it returns no level, and
the Loader composition helper takes provider config and further entries
as separate parameters.
Headless stream-json and session expected outputs record the title
request's maxTokens, and the headless and Web mock providers recognize a
title request by its max_tokens. Both still assumed the previous 64-token
base-bundle cap.
The base bundle capped title generation at 64 output tokens, sized for a
route that disables thinking. Models whose reasoning cannot be switched
off spend output on it even at their lowest level: on the HFAI gateway,
GPT-6 Sol and GPT-6 Astra used up to 87 and 121 output tokens for
five-word titles at reasoning low, so Sol still finished with max-tokens
on most first prompts. Raise the shipped cap to 256; routes that disable
thinking stop after the title and are unaffected.
Session-title calls carry a 64-token output cap and rely on the adapter to
keep reasoning out of it. llm-deepseek maps purpose 'session-title' to
thinking disabled, but llm-pi-ai ignored the purpose, so pi-ai routes sent
title calls at the route's default effort. On HFAI's adaptive-thinking
Claude models (profile reasoning: max) the thinking consumed the cap, the
call finished with max-tokens, and the session kept its fallback title.
The pi-ai adapter now dispatches a session-title request at the model's
lowest supported level: off when the model offers it, otherwise the
lowest declared level. GenerateOptions.purpose documents that obligation
for every adapter.
A successful verifier message was treated as terminal even though the
Worker Thread was still alive. The parent immediately terminated the
worker and detached cancellation, so an error, nonzero exit, or caller
cancellation after that message could be ignored and verification could
resolve successfully.
Keep the first successful response pending until an observed natural exit
with code 0. A missing response or nonzero exit rejects after the worker
has already exited. Error and abort callbacks retain the existing
terminate-and-join path, including its first-error and AggregateError
behavior. The scheduler stays unchanged: its permit remains held until
the verification promise settles, now after successful worker quiescence.
The worker already closes its parent port in finally, so success needs
neither a forced stop nor another protocol message.
Replace success-termination expectations with deterministic message/exit
barriers and cover post-response errors, nonzero exit, cancellation, and
permit handoff. Remove the obsolete hand-written termination thenable and
its retired unknown-assertion baseline entry. Add real source workers for
plain and Zstandard input, and require the built plain-Node migration smoke
to complete with zero termination calls. Document the settlement condition.
Native Windows CI recorded process access violation 0xC0000005 while a
migration fixture was active. Removing unnecessary successful-worker
termination is consistent with that evidence, but this commit does not
claim that the native faulting module or mechanism has been established.
The deterministic regressions prove the lifecycle defect independently;
the next native CI run must assess whether the observed crash recurs.
Validation:
- Original source failed all new message-to-exit regression scenarios;
the updated focused lifecycle file passes 19 tests with 100% coverage.
- Node 24 owning package: 19 files, 741 passed, 1 skipped; every included
package source file reaches 100% statements/branches/functions/lines.
- Real source workers passed for none/zstd; built plain-Node smoke passed
with zero terminate calls and no timeout or process signal.
- Exact Node 22.19 source-mode none/zstd workers naturally exited 0 with
zero terminate calls, exercising the async Module.register fallback.
- Host TypeScript build, focused lint, no-new-unknown-assertion gate,
README translation pairing, and all 42 doc-sync gates passed.
Related to #4387 and #4394.
The master cwd integration updated the shipped headless profile, but three
current snapshot scenarios merged without textual conflicts and retained
expectations from before the directory service was composed. Full CI
replay found the stale product-subagent-both persona and missing current
working-directory context in the no-message-tool and structured-parent
parent logs; those two scenarios also owned stale child/header sidecars.
Refresh only those three scenarios through the real built headless profile.
Record the required runtime cwd section in their four parent/child V4
logs, remove static cwd text from three prompt sidecars, and include the
new cwd start parameters and working_directory tool in three schema
sidecars. Preserve every model response and event count. No production
code, scenario input, workspace oracle, or historical V0-V3 fixture changes.
Validation: selective refresh passed 3/3. Full DSH_EXAMPLE_MODE=lib pnpm
run test:snapshot passed all 8 files: 197 passed and 2 PowerShell scenarios
skipped. The first full local run selected macOS CPython 3.9.6 and failed
the two Python PTC scenarios; the final full run prepended the existing
CPython 3.12.13 bin directory to PATH, with no runtime or fixture workaround.
SHA256 guards confirmed all 363 retained historical inputs unchanged;
JSON comparison confirmed the four changed logs differ only by cwd context.
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.
Repeated native Windows coverage runs stopped a Vitest fork while a JSONL
migration suite was unfinished. Vitest's generic unexpected-exit error
discarded the process exit code and signal, preventing classification of
the failure despite identifying the missing test file from the inventory.
Reuse the public ForksPoolWorker diagnostic implementation and protocol
fixtures from independently reviewed PR #4388, pinned at
77e215ff76711ec75ed8aa08aa5b414780e539ae. The runtime helper is unchanged.
It reports PID, project, worker identity, outstanding file request, code,
and signal before stock Vitest reports the original failure. It clears
ownership only on the typed testfileFinished response, suppresses expected
stop, and leaves cancellation and failure handling with the original pool.
Wire that existing pool into generated coverage partition configs. Keep
the original worker counts, partition scheduling, test timeouts, coverage
thresholds, and failure verdicts. No environment, raw argv, or test object
is serialized. This commit adds diagnostic evidence; it does not claim to
repair the still-unidentified Windows crash. PR #4388 remains the separate
reviewable owner; no duplicate diagnostic framework or PR is introduced.
Adapt the copied fixtures to the current no-new-unknown-assertions rule:
declare the private preload exit function, validate consumed logger fields
with the public logger type, and assign matcher/rejection values to typed
unknown locals without assertions. Add a concise bilingual testing note.
Validation:
- Diagnostic suite: 10 passed, including a real configured Vitest fork
exiting 23, stock-pool negative control, successful silent shutdown,
unexpected exit 0, early exit, independent forks, and signal metadata.
- Coverage coordinator suite: 51 passed.
- Targeted lint and focused strict TypeScript check passed.
- pnpm exec tsc -b tsconfig.host.json passed.
- verify-no-unknown-casts passed; 1430 existing assertions unchanged.
- doc-sync passed 40 gates; the two initially failing gates were repaired
and rerun successfully: doc-typecheck and verify-doc-budgets.
- Translation pairing and git diff --check passed.
Refs #4387, #4388, #4394.
Co-authored-by: Chinesezjc <[email protected]>
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.
The gated final-flush fixture awaited the real flush but dropped its boolean return value. The regression ran correctly under Vitest, but the full Host build rejected the mock against Sessions.flush Promise<boolean>.
Return the real flush result after the gate. This retains the observation order and matches the service signature without a cast or altered production behavior.
Validation: targeted terminal-capture-failure regression passes; the complete build is rerun after this typing correction.
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.
Removing the duplicate ACP directory helpers moved its Config declaration by one line. The generated English and Chinese catalogs still referenced the old line, so the exact-source freshness gate rejected the otherwise unchanged configuration.
Regenerate only the affected source links. No configuration fields, defaults, examples, or translated prose change.
Validation: pnpm run gen-config-catalog produced exactly these two link updates; the combined doc-sync run verifies freshness.
A child completion can wake the parent after its first completed turn. If
that summary cannot append turn/start or turn/end, AgentLoop reports an
agent/error and contains the rejected driver. Whole-agent idleness then
resolves, but the headless runner previously reused the older completed
terminal and exited successfully, sometimes printing the older answer.
Retain failures from the exact owned Agent with the current Session log
offset. Only a later terminal committed by the same Session clears that
failure. After work and persistence drain, fail the run before emitting a
final answer when no such terminal exists. Dispose both observers with
the run. Ordinary durable error outcomes and later successful recovery
continue to use the log's final terminal.
Add real AgentLoop regressions for missing summary start/end commits in
plain and JSON output, ordinary hook failures with and without recovery,
recovery after persistence failures, and unrelated Agent errors. Update
the bilingual exit contract and generated event consumer matrix.
Validation: 106 headless package tests; 100% runner statements, branches,
functions, and lines; 9 focused terminal-failure tests after the typed
append wrapper correction; package TypeScript build; full lint including
host build; 21 quick documentation checks; 42 doc-sync checks; diff check.
Local activation results were derived only from committed Session events.
When the first turn/end append failed, no ending existed and the result
defaulted to completed. When the first turn/start append failed, the initial
message remained unclaimed and kept the idle activation resident forever.
Both paths emitted agent/error but had no durable terminal to project.
Retain the log offset of the latest exact-Agent live failure on the local
activation. Clear it only when the same Session commits a turn/end at or
after that offset, so a later recovery can supply its own outcome and a
reentrant older terminal notification cannot erase a newer failure. The
child scope owns both observers and removes them with its Agent handle.
Capture error while that live failure remains. Let the failed local
activation settle despite its own unclaimed inbox, while preserving the
existing requirement to wait for owned descendants. Wake its settlement
observer when a live failure changes readiness. No public API, raw Agent
idle semantics, or descendant teardown policy changes.
Add regressions through real local activation creation for first start/end
append failures, recovery by later durable endings, exact-Agent isolation,
and an error emitted reentrantly during an older terminal notification.
Update the bilingual package contract and generated event consumer graph.
Validation:
- Both first-append regressions reproduced before the fix: turn/start
remained pending; turn/end returned completed instead of error.
- Six focused behavior cases passed after the fix.
- 423 subagent package tests passed; activation.ts and manager.ts reached
100% statements, branches, functions, and lines under scoped coverage.
- pnpm exec tsc -b packages/subagent/subagent/tsconfig.json passed.
- Focused source/test oxlint and git diff --check passed.
- doc-sync passed 41 gates; its sole stale event-graph failure was corrected
with gen-doc-graphs, then verify-doc-graphs passed for all seven graphs.
- README translation pairing regenerated and passed the doc-sync check.
Caller delivery changes result ownership, but does not make local workflow children private or remove their continuable catalog entries. The workflow README stated durable discovery without making the message and cold-resume consequences explicit.
Document that resident workflow children accept continuation and that cold continuation requires persistence and Session query. An ephemeral child therefore returns NOT_RESUMABLE after disposal. This describes current supported behavior and does not add fallback storage or a new child mode.
Validation: checked against startActivation, sendMessage, and cold-resume admission; English and Chinese pairing updated. Full doc-sync follows the combined review fixes.
ACP retained a private copy of the external-provider directory helpers after the other providers adopted the shared implementation. The copies differed on absolute-path validation and imposed separate filesystem branches and maintenance.
Use resolveChildCwd and validateConfiguredCwd from the existing subagent package. Preserve configuration-time relative-path resolution, parent workspace inheritance, missing-directory failures, and ACP diagnostic normalization. This deletes the duplicate directory implementation without introducing an abstraction or dependency.
Validation: 85 passing tests across the ACP provider and shared out-of-process helper suites, including real child process cwd, configured overrides, and invalid-directory rejection.
Parent completion notices now always carry the child closing answer, even when the child can send progress messages. Two assembled headless expectations still asserted the removed omission and made the snapshots-and-artifacts CI lane fail.
Require CHILD_RESULT in the settlement assertion and refresh only the owning parent and stream output expectations. Preserve released replay inputs, event ordering, sandbox denial evidence, and the recorded model responses.
Validation: DSH_EXAMPLE_MODE=lib pnpm run test:expected apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts (18 passed).
The subsystem result paragraph still said that a parent log records the
final cleanup-failure status and that the result had already been returned.
The manager instead captures the execution result, performs cleanup, sends
that preserved result in the parent notice, and then resolves the public
result. Cleanup failure separately rejects dispose and marks the live
subagent/end event as error.
Correct the bilingual paragraph to name those actual recipients and timing.
Do not imply that the successful execution notice becomes a failed notice,
or change the runtime's independent execution/cleanup outcomes.
Validation: the updated bilingual pair passes. The statement is checked
against finishDisposal/notifySettlement and the existing cleanup-failure
regressions; final documentation gates run before publication.
The initial-cancellation regression supplied a synchronous content array to
defineContentToolFixture, whose executor contract requires a Promise of
content blocks. Vitest executes the fixture, but the strict repository
Host build correctly rejects its signature with TS2739.
Return the same blocks through Promise.resolve. Keep the deterministic
stream/output gates and assertions unchanged; do not widen the shared
fixture API merely to accept this one caller.
Validation: the focused initial-cancellation regression passes. The full
Host build is rerun before publication to validate the fixture signature.
Cancelling during the prompt's initial whenIdle() wait could retain its
slot indefinitely. If a child delivered a result while the aborted root
was unwinding, Agent.whenIdle() followed the replacement driver and waited
for the newly autonomous summary. The earlier child-wait recheck could
not help because settlement had not yet reached that child wait.
This is an inherited latent issue: the pre-PR ACP bridge also awaited the
same replacement-following whenIdle() after cancellation. Existing
continuable spawn children could expose it; the unified asynchronous fork
default makes the ordering possible for fork delegations as well.
Give the existing InflightPrompt one cancellation-completion promise,
replacing the local cancellation wake-up and its listener. The private
admission AbortSignal is the cancellation flag instead of duplicated
boolean state. The exact root's emitted status identifies the interrupted
activity's retirement and synchronously adopts that output queue. Later
root work and later output cannot replace the first captured completion.
Record whether cancellation began at public idle. Maintenance has that
public status too, so its next running transition establishes release of
work behind maintenance; the ordinary whenIdle promise provides a
fallback when no transition occurs or plugin disposal detaches observers.
Both observations settle the same promise. Settlement races that promise
against initial idle and child waits, then waits only for the interrupted
activity/output on the cancellation path. No future work is re-cancelled,
child admission remains open, and session close keeps its separate full
quiescence and descendant teardown.
Add two deterministic real-Agent regressions. One gates aborted model
cleanup, previously queued image delivery, and the later autonomous
summary independently. The other gates cancelled maintenance cleanup and
the later summary. Both failed before this fix; both now wait for old work
and output, return cancelled while later work remains gated, and permit a
subsequent ACP prompt to execute another real local delegation. Existing
plugin-disposal tests also verify the idle fallback after observer cutoff.
Update the bilingual ACP semantics and generated event-consumer matrix.
Validation:
- Both focused regressions reproduced pending cancellation before the fix.
- pnpm exec vitest run packages/acp/acp/tests --coverage --coverage.include=packages/acp/acp/src/session.ts --coverage.include=packages/acp/acp/src/index.ts --coverage.reportsDirectory=/tmp/pr4394-acp-owned-cancel-89ec33e5788-consumer-v2
(150 passed; both changed source files have 100% statements, branches,
functions, and lines)
- pnpm exec tsc -b packages/acp/acp/tsconfig.json
- pnpm exec tsdown --config-loader native --env.DSH_BUILD_FACE host --filter '@deepseek-ai/dsh-acp'
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts -t '^external tasks.*acp'
(3 passed, 3 unselected)
- Focused lint, named translation pairing, generated graph verification,
and git diff --check passed.
Independent ACP rereview found no remaining issue, passed seven focused
turn/disposal cases, and verified a prepended idle listener can start a
new hanging root before ACP handles the old idle event without retaining
the cancelled prompt.