Merge branch 'codex/simp-ui-identity-residue' into codex/simp-hide-concrete-agent-loop

# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	packages/core/agent-loop/tests/agent.spec.ts
#	packages/core/agent-loop/tests/contract-regressions.spec.ts
This commit is contained in:
Tianyi Cui
2026-07-14 18:58:29 +08:00
507 changed files with 3451 additions and 9268 deletions

View File

@@ -1,21 +1,7 @@
/**
* The model-facing `todo_write` tool: the agent's whole task list, replaced
* wholesale on each call. Every call appends a `todo/write` event (the full
* list snapshot) to the calling agent's session log via
* `exec.agent.session.append('todo/write', { todos })`; the current list is the
* most recent such event (last-write-wins on replay). UIs render off
* `session/event`: the stdio UI prints the checklist, the ACP bridge maps it to
* a `plan` sessionUpdate.
*
* Single owner: the list belongs to the ONE agent session that called the tool.
* There is no subagent/shared/swarm scope — a non-agent caller (no
* `exec.agent`) has nowhere to write the list and is rejected.
*
* Plugin export shape: named exports, NO default. The cordis Loader's
* `unwrapExports` does `exports.default ?? exports`, so a stray default would
* collapse the module to the bare `apply` and drop `inject`, crashing at load
* (see docs/postmortem/0001).
*
* Model-facing whole-list replacement. Each call appends a `todo/write` snapshot to the calling
* agent's session; replay is last-write-wins, and UIs render from session events. A non-agent
* caller has no owning list and is rejected. Named exports preserve loader injection metadata.
* @module @deepseek-ai/dsh-tool-todo
*/
@@ -42,20 +28,9 @@ const DESCRIPTION =
+ '(not started), `in_progress` (being worked on now), `completed` (finished).'
/**
* Validate the value constraints the SchemaSpec can't express and build the
* canonical {@link TodoItem}[].
*
* `defineTool` already validates type/required/enum before `execute` runs (a
* bad `status` is rejected by the registry's `validateArgs`, never reaching
* here), so `status` is guaranteed to be one of the three enum literals. But
* `InferArgs` maps an `enum` string prop to plain `string`, so the compiler sees
* `args.todos` as `{ content: string; status: string }[]`; the
* `status as TodoItem['status']` narrowing records that registry guarantee
* rather than re-checking it (an unreachable re-check would be dead code the
* coverage gate would flag). What remains is the
* value rules the DSL has no vocabulary for: non-empty unique content (stored
* trimmed, so the persisted value matches the dedupe/length key), and at most
* one `in_progress` task.
* Validate the value constraints the SchemaSpec can't express and build the canonical {@link
* TodoItem}[]: trimmed non-empty unique content and at most one in-progress item. The registry
* has already enforced the status enum; the cast below records that guarantee.
*/
function toTodoList(raw: { content: string; status: string }[]): TodoItem[] {
const todos: TodoItem[] = []

View File

@@ -150,10 +150,7 @@ describe('dsh-tool-todo', () => {
})
it('has the namespace-plugin export shape (no stray default) so the Loader keeps name/inject/apply', () => {
// Postmortem 0001 guard: this plugin HAS `inject = ['tools']`, so a stray
// `export default apply` would collapse the module via `unwrapExports`
// (`exports.default ?? exports`), DROP `inject`, and crash at load with
// "cannot get property … without inject". Guard the shape directly.
// A default export would make Loader unwrap only apply and drop `inject`.
expect('default' in tool).toBe(false)
expect(tool.name).toBe('tool-todo')
expect(tool.inject).toEqual(['tools'])