Codex's no-ship was a completeness/docs-sync gap, not loop behavior:
- docs/architecture.md: drop the public abort() handle row; the teardown
signal is now cancel() then await whenIdle().
- cancel.spec.ts: the module doc and the turn-start comment contrasted
cancel() against a public abort() verb that no longer exists — reword to
name the loop's private step AbortController.
- packages/ui/acp/src/index.ts: the post-resume-leak comment cited abort();
cancel() is the surviving stop verb that likewise does not unregister.
- Move the RFC proposed -> implemented/simplification with amended text:
Status flips, the both-removal proposal is narrowed to abort-only, and an
implementation note records why whenIdle() is retained (load-bearing
quiescence primitive with live ACP consumers). Update docs/rfc/README.md.
- AGENTS.md "RFCs are proposals, not golden truth": add the concrete
abort/whenIdle worked example now that the implemented RFC exists to link.
- Regenerate the cordis catalog (line-number drift from the rebase).
The public Agent handle exposed abort() (step-only) and cancel() (queue-aware).
No production caller used abort() — ACP maps session/cancel to cancel(), and
lifecycle owners tear down via AgentHandle.dispose(); the loop's own stop paths
abort their per-step AbortController directly. So abort() is latent generality
that keeps a private loop mechanic public.
RFC-premise correction: the public-agent-stop-surface RFC proposed removing
whenIdle() too. Implementation found whenIdle() load-bearing — a real
quiescence primitive with a deliberate loop contract (settle-without-transition,
the replacement-turn race) and ACP test consumers; its proposed replacement
("observe the running->idle transition") is exactly the async-state race
AGENTS.md warns against. So only abort() is removed; whenIdle() stays. The RFC
is amended on the way to implemented/ to record the narrowed scope, and the new
AGENTS.md "RFCs are proposals, not golden truth" principle (PR1) gets its
worked example.
- Remove Agent.abort() from the interface + the ReactLoopAgent impl; the no-arg
'aborted' default goes with it (cancel() keeps its 'cancelled' default).
- Migrate tests: empty-queue abort() -> cancel(reason); the two review-fixes
tests whose subject is the in-flight step's AbortController drive that
controller directly via the private currentAbort field (cancel() would clear
the inbox and destroy the queued steering one of them proves survives a step
abort). The no-arg-default test is dropped (cancel()'s default is already
covered in cancel.spec.ts).
- Resulting public stop surface: cancel() + whenIdle(). Update agent/agent-loop
READMEs, architecture.md, core.md type-equiv, the extension cookbook, the
lifecycle RFC (short note), and the proposed ACP RFC.
Implements docs/rfc/implemented/simplification/2026-06-20-public-agent-stop-surface.md
Type-only change (brands are zero-cost casts; no runtime/wire impact). Closes
the two gaps in the "brand ids that cross package boundaries" policy and fixes
the dependency direction so a capability package never pulls in an unrelated one.
- Extract the `Branded<B>` primitive into a new standalone type-only package
`@deepseek-ai/dsh-brand` (packages/util/brand) with no harness-package deps.
dsh-llm keeps its owned CallId but imports Branded from dsh-brand; dsh-session,
dsh-agent, and dsh-bash all import Branded from there. dsh-bash depends on
dsh-brand ALONE — never on dsh-llm or dsh-session (the architectural fix: a
generic execution backend must not couple to the LLM or session vocabulary).
- Mint BashTaskId + OwnerToken in dsh-bash and thread them through BashTask.id,
the get/ownerOf/list/readOutput/kill seam, the bash-local generation site, and
the dsh-tool-bash validate/access surface. OwnerToken is a DISTINCT brand from
SessionId so the seam stays decoupled; dsh-tool-bash is the single boundary
that casts SessionId -> OwnerToken.
- Brand at the SOURCE, not via mid-pipeline casts: agent-loop's Config types
agents[].id as AgentId and resumeSessionId as SessionId, so the brand enters
at the config boundary and the inner create()/resume casts disappear (only the
genuinely-new per-run session-id string is cast).
- Stop brand erosion: propagate CallId/SessionId/AgentId to the registry/store
Map keys and public params/exports (SessionStore, AgentRegistry + factory
options, the ACP session-id surface + ToolPresenter CallId map, the
persistence coordinator, invariants pendingCalls, the pi-ai tool-call maps).
- Docs: document BashTaskId/OwnerToken in bash.md (type-equiv re-pasted), point
the Branded type-equiv at dsh-brand, fix stale param types in the session/
agent/bash READMEs, regenerate the cordis catalog + module graph.
Implements docs/rfc/proposed/architecture/2026-06-20-branded-ids.md
The LLM service exposed three call surfaces (stream/streamBlocks/generate) but
the only production consumer — the agent loop — uses stream() exclusively,
feeding raw chunks through its own BlockAssembler for replay fidelity. Drop the
speculative convenience surfaces and the registry-change event that no listener
consumed, leaving stream() as the single model-call contract for both
production and tests.
- Remove LlmService.streamBlocks() and generate(), the llm/generate waterfall,
and GenerateResult.
- Remove the llm/adapter-change event (declaration + emits) and the
listener-throw rollback ordering that existed only to protect it; keep the
HMR rollback disposer.
- Remove BlockAssembler.flushReady()/flushRemaining()/result() and the flushed
cursor — the streaming-flush slice existed only for streamBlocks().
- Adapter tests drive a stream()+BlockAssembler helper (tests/assemble.ts)
instead of generate(), exercising the same path production uses.
- Land the AGENTS.md "RFCs are proposals, not golden truth" principle and move
both RFCs proposed -> implemented.
Implements:
- docs/rfc/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md
- docs/rfc/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md
Move the 18 flat packages/<name> packages into role-grouped dirs:
core/, llm/, bash/, session-persistence/, ui/, support/. Group dirs are
pure containers; each package keeps its @deepseek-ai/dsh-* name.
Collapse the per-package tsconfig paths maps (base + typecheck) into one
@deepseek-ai/dsh-* wildcard with a candidate per group, and derive the
publint list from the hierarchy. Update all depth-coupled globs/configs
(workspace, tsdown, vitest, eslint, knip, tsconfig includes/refs,
per-package tsconfigs, generators, doc-script scopes, type-equiv manifest)
and the cross-package/script relative imports in tests.
Fix doc-typecheck's workspacePaths() to parse tsconfig JSONC via the
TypeScript API instead of a regex comment-strip, which corrupted the
new wildcard `/*/` path candidates.
WIP: doc cross-links and package/RFC docs still to update.