Update the capability decision rule, both READMEs, the EventMap JSDoc and generated catalogs. Add the bug-fix Agent Note that records the incident evidence, the decision, rejected alternatives and consequences, and cross-link it from the in-history decision note.
A step's tool set no longer empties when the step is cancelled. The shared
pipeline already settles unstarted calls as cancelled; keeping admission lets
those calls keep their finalizers, and a wrapper result completed at
cancellation now reports cancellation instead of UNKNOWN_TOOL.
exit_plan_mode checks for a missing Agent explicitly instead of asserting it.
Document the activation types on the tools subsystem page, the turn/end code
for a failed tool condition, the replacement and no-set consequences in the
upgrade guide, and the admission rule in the parallel tool-call note.
Two fixture helpers in surface.spec.ts returned ': SessionEvent', a
declaration-merge-extensible mapped union. Spreading such a value (the
rejection cases build '{ ...event, data: ... }') materialises an anonymous
union per fixture, and the following 'as SessionEvent' casts then compare
union against union, which dominates the root type-check program.
Return the single variant each helper actually builds, spelled as the
canonical indexed access SessionEvent<'system/message'> /
SessionEvent<'user/message'>, drop the casts that only existed because data
was a union, and trim the comment of the member count that drifts with every
declaration merge.
Measured on the host build: check 110.9 s -> 84.6 s (median, -23.7 %),
wall -17 % to -24 %; fixture types alone (clean buildinfo, root program):
check 53.4/45.8 s (Extract alias) vs 45.3/49.1 s (indexed access).
- Make the record commit an ES private method, so appendPluginRecord()
is its only runtime entry.
- Document PluginRecordType and PluginRecord on the session subsystem
page, and state in the README and JSDoc that the V3-to-V4 edge renames
unknown ignorable V3 events into the plugin: namespace and that plugins
outside this repository must not call the writer.
- The caller gate parses only files that contain the name or a \u
escape, documents that wildcard re-exports are not reported, and its
spec reads a temporary repository instead of this checkout.
- Cover Session.fromRestore in the record test and name SessionEventMap
in the catalog generator's diagnostic.
Add appendPluginRecord(), which appends one ignorable event whose type is
`plugin:` followed by lowercase slash-separated segments, and
pluginRecordOf(), which reads such an event back. Records are not
SessionEventMap members, so the persistence catalog is unchanged; a build
that does not recognize one retains and skips it, and records never enter
the model-visible surface. Resume and fork carry them with the log, and a
format migration keeps them on a best-effort basis.
Only production source under packages/experimental/ may call the writer.
The verify-plugin-record-callers hygiene gate checks every reference in
repository sources, and the persistence catalog generator rejects a
SessionEventMap member in the plugin: namespace.
Session.append() and the record writer share one commit path, so a record
follows the same reentrancy, detach, and observer-containment rules. The
ignorable-events decision, the session README, the format-version
cookbook, and the plugin-author practices describe the records.
Delete @deepseek-ai/dsh-invariants, every package's ./invariant companion
and its tests, the invariant gates and Vitest setup, the scoped-event
resolver generator, and the INVARIANT-coded listener rethrow paths.
Update docs, READMEs, Agent Notes, and add an upgrade guide.
`ToolCallRecovery.observe` compared `entry?.turn` against the event's turn
before reading `entry.step`. For a `tool/result` whose callId never registered
as pending and whose event carries no turn/step, both sides were `undefined`,
the guard passed, and the `entry.step` read threw. A pending entry is now
required before the turn and step comparison.