Merge refreshed rfc/pty into feature/persistent-pty-sessions
# Conflicts: # docs/architecture.md # docs/capability-seams.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/module-graph.md # docs/tool-catalog.md # examples/acp-agent/tests/acp.snapshot.ts # examples/headless-agent/tests/headless.snapshot.ts # examples/package.json # packages/README.md # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/tools/tests/gen-tool-catalog.spec.ts # pnpm-lock.yaml # pnpm-workspace.yaml # scripts/gen-tool-catalog.ts # scripts/type-equiv.manifest.json # website/.vitepress/config/api-sidebar.json
This commit is contained in:
@@ -15,9 +15,10 @@ Each fact has one home: the tier whose job it is. Elsewhere, link to that home;
|
||||
| [Agent Notes](../.agents/notes/README.md) | Decision records: the why, what-was-given-up, and concise verification contract; `implemented/` notes describe shipped reality in present tense | Migration plans, acceptance-task checklists, fixture walkthroughs, and spec-speak ("should…") once the decision has shipped |
|
||||
| [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — |
|
||||
| [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the Agent Note each guide links) |
|
||||
| [user/](user/index.md) | Product-facing guides published by the documentation website | Generated reference tables, contributor procedures, decision history |
|
||||
| Package README | The per-package contract: config, semantics, limitations, extension points, and [Model Experience](cookbook/adding-a-package.md#4-write-the-package-readme) | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns |
|
||||
| [development.md](development.md) | First-stop contributor onboarding: local setup, daily workflow, and CI shape at summary level; a bilingual pair under the [i18n contract](i18n/README.md) | Runtime/version rationale (→ Agent Notes), gate-by-gate enumerations that drift from `package.json` scripts |
|
||||
| Generated catalogs: [cordis events](cordis-catalog/events.md), [cordis services](cordis-catalog/services.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind |
|
||||
| Generated catalogs: [cordis events](cordis-catalog/events.md), [cordis services](cordis-catalog/services.md), [Cordis core API](cordis-catalog/core/context.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind |
|
||||
| Skills (`.agents/skills/`) | Reusable workflows and specialized decision standards | Product and runtime contracts (→ docs or source) |
|
||||
|
||||
Placement: bugs → postmortems; rationale → Agent Notes; procedures → cookbooks; type shapes → core data; package contracts → READMEs; standing orders → root `AGENTS.md` with a rationale link.
|
||||
|
||||
@@ -64,7 +64,7 @@ sequenceDiagram
|
||||
|
||||
The `assistant/message` edge records every successful provider call, including content-less and `max-tokens` finishes. Empty content stays out of derived history while the durable anchor retains usage and exact chunk provenance, including an explicit empty source set.
|
||||
|
||||
`dsh-compact-basic` uses `agent/post-step` for pressure after those durable facts and `agent/request-error` only for canonical context overflow. Recovery compacts between the closed failed step and a fresh retry step, and returns retry only when the surface replacement generation advances; otherwise the original request error remains authoritative.
|
||||
`dsh-compact-basic` uses `agent/post-step` for pressure after those durable facts and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and a fresh retry step, and returns retry only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.
|
||||
|
||||
SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.
|
||||
|
||||
|
||||
6
docs/architecture.i18n.yaml
Normal file
6
docs/architecture.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
architecture.md: 1db749ec8b6c6a8872bdc1280cd50dbf0583e159
|
||||
architecture.zh.md: 8d05765eb94853076cf8f13c390cef01da07281f
|
||||
@@ -1,18 +1,20 @@
|
||||
# DeepSeek Harness Architecture
|
||||
|
||||
The **DeepSeek Harness SDK** builds agent harnesses on Cordis; **everything, including the shipped loop, is a plugin**.
|
||||
English | [中文](architecture.zh.md)
|
||||
|
||||
**DeepSeek Harness SDK** uses Cordis: **everything is a plugin**, including the loop.
|
||||
|
||||
## Overview
|
||||
|
||||
A harness is one [Cordis](cordis-primer.md) context. Packages add services (`ctx.llm`, `ctx.tools`, `ctx.sessions`), typed events (`agent/request`, `tools/pre-execute`, `session/event`), and disposable prompt, tool, provider, adapter, and listener registrations.
|
||||
Harnesses are [Cordis](cordis-primer.md) contexts whose packages contribute services, typed events, and disposable registrations.
|
||||
|
||||
`packages/core/` groups the default agent flow; surrounding capabilities are equally first-class Cordis plugins.
|
||||
`packages/core/` groups the default agent flow; capabilities remain plugins.
|
||||
|
||||
### Default Services
|
||||
|
||||
| ctx key | Package | Role |
|
||||
|---|---|---|
|
||||
| — | [`dsh-scope`](../packages/core/scope/README.md) | scoped-context registration primitive (library) |
|
||||
| — | [`dsh-scope`](../packages/core/scope/README.md) | scoped-context registration and shared layer storage (library) |
|
||||
| `ctx.sessions` | `dsh-session` | in-memory event-sourced sessions |
|
||||
| `ctx.systemPrompt` | `dsh-system-prompt` | ordered prompt sections, tool schemas, and prompt variables |
|
||||
| `ctx.tools` | `dsh-tools` | tool registry and [execution pipeline](tool-execution-pipeline.md) |
|
||||
@@ -31,14 +33,19 @@ A harness is one [Cordis](cordis-primer.md) context. Packages add services (`ctx
|
||||
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | shared sandbox policy home |
|
||||
| `ctx.codeRuntime` | [`code-runtime/`](../packages/code-runtime/README.md) | model-written program execution |
|
||||
| `ctx.fs` | [`fs/`](../packages/fs/README.md) | filesystem provider primitives and policy events |
|
||||
| `ctx.lsp` | [`lsp/`](../packages/lsp/README.md) | semantic navigation registry |
|
||||
| `ctx.skills` | [`skill/`](../packages/skill/README.md) | skill provider registry and progressive disclosure |
|
||||
| `ctx.web` | [`web/`](../packages/web/README.md) | search/fetch provider registries |
|
||||
| `ctx.compact` | [`compact/`](../packages/compact/README.md) | session-log compaction |
|
||||
| `ctx.compact`, `ctx.toolResultPrune` | [`compact/`](../packages/compact/README.md)/[`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune/README.md) | summary compaction; optional model-free result pruning |
|
||||
| `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | named delegation providers |
|
||||
| `ctx.planMode` | [`plan/`](../packages/plan/README.md) | logged plan collaboration state |
|
||||
| `ctx.tasks` | [`tasks/`](../packages/tasks/README.md) | background task registry + generic `task_*` control tools |
|
||||
| `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | script-driven multi-agent orchestration |
|
||||
| `ctx.goals` | [`goal/`](../packages/goal/README.md) | persisted same-session goals |
|
||||
| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable storage for session logs |
|
||||
| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | live-preferred logical-corpus exact reads and relationship traces |
|
||||
| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | log-backed fallback titles and one optional asynchronous provider |
|
||||
| `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | registry and package-name selection for package-owned runtime checks |
|
||||
|
||||
## Event
|
||||
|
||||
@@ -46,9 +53,9 @@ Events form the service extension API; see the exhaustive [events catalog](cordi
|
||||
|
||||
### Event Domains
|
||||
|
||||
- **Session events** are durable, replayable facts: boundaries, messages, tool activity, steering, compaction, and tool-owned records append to the log and flow through `session/event`.
|
||||
- **Agent events** carry the live `Agent` handle for status, diagnostics, prompt admission, request shaping, result validation, and continuation policy.
|
||||
- **Capability events** belong to their owning seam; `tools/*`, `llm/*`, `system-prompt/*`, `fs/*`, and `subagent/*` attach policy and adapters without importing the loop.
|
||||
- **Session events** are durable facts appended to the log and emitted through `session/event`.
|
||||
- **Agent events** carry the live `Agent` for status, prompt admission, request shaping, validation, and continuation.
|
||||
- **Capability events** let owning seams attach policy and adapters without importing the loop.
|
||||
|
||||
### Interception Semantics
|
||||
|
||||
@@ -56,11 +63,11 @@ Waterfall events behave like around-middleware: a listener delegates by calling
|
||||
|
||||
## Default Loop Lifecycle
|
||||
|
||||
The shipped loop drains work from prompt through checkpoint. Every pause is a service call or event available to plugins.
|
||||
The shipped loop drains prompt-to-checkpoint work through plugin-visible services and events.
|
||||
|
||||
A **session** is an append-only event log. A **turn** drains queued input until the model stops asking for tools and no plugin requests continuation. A **step** is one model request plus the tool executions caused by that response. In the flow below ([sequence companion](agent-lifecycle.md)), quoted names are durable session events and event names are extension points.
|
||||
A **session** is append-only. Each ordinary **turn** claims one queued `send()` item; injection claims none. A successor awaits the preceding claimed turn's checkpoint but may share its `running` interval ([decision](../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md)). A turn ends when model and plugins stop it; a **step** is one model request plus tools. In the [sequence below](agent-lifecycle.md), quotes mark durable events.
|
||||
|
||||
Startup resolves identity. No id mints `<config-id>-session-<uuid>`; `sessionId` resumes or creates; `resumeSessionId` requires history. Active failures emit `agent-loop/config-start-failed(sessionId, error)`, so front doors reject work; teardown stays silent.
|
||||
Without an id, creation mints `<config-id>-session-<uuid>`; `sessionId` resumes or creates, while `resumeSessionId` requires history. Resume restores lineage and delegation depth before publication. Setup failures emit `agent-loop/config-start-failed`; teardown is silent.
|
||||
|
||||
### Turn Flow
|
||||
|
||||
@@ -70,13 +77,13 @@ choose declarative identity and fresh/resume path
|
||||
-> enter session + agent -> session/created -> agent/created
|
||||
-> enable driving -> agent/session-start(source) -> start driver
|
||||
forever:
|
||||
wait for queued messages
|
||||
wait for a queued message
|
||||
emit agent/status(running)
|
||||
TURN:
|
||||
'turn/start'
|
||||
each queued message -> agent/prompt-submit
|
||||
claimed message -> agent/prompt-submit
|
||||
allowed prompt -> 'user/message' plus injected context
|
||||
every prompt blocked -> 'turn/end'(rejected)
|
||||
blocked prompt -> 'prompt/blocked' -> 'turn/end'(rejected)
|
||||
STEP loop:
|
||||
drain steering
|
||||
assemble system prompt and tool schemas
|
||||
@@ -87,7 +94,7 @@ forever:
|
||||
agent/request (config only) -> log request/header -> llm/stream (frozen)
|
||||
on final adapter-path or terminal in-band failure:
|
||||
'step/end'
|
||||
agent/request-error(original error, consecutive retry attempt, signal)
|
||||
agent/request-error(original error, failure facts, immutable prior failures, signal)
|
||||
retry in the next numbered step or preserve the original error
|
||||
otherwise:
|
||||
'assistant/chunk'
|
||||
@@ -108,84 +115,88 @@ forever:
|
||||
checkpoint persistence and notify idle/running status
|
||||
```
|
||||
|
||||
Each step assembles ordered prompt sections, tool schemas, and `{{name}}` variables; unknown or valueless references fail the turn. `dsh-system-prompt` owns the harness identity and default persona, which an agent scope may shadow. The loop supplies `model` and `cwd` ([prompt ownership](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md)).
|
||||
Each step assembles ordered prompt sections, tool schemas, and variables; unknown references fail the turn. `dsh-system-prompt` owns identity and persona, while the loop supplies `model` and `cwd` ([prompt ownership](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md)).
|
||||
|
||||
Tool-time context—including async `agent.inject()` notices and post-tool `additionalContexts`—settles, then follows recorded results. Steering drains before `agent/post-step`, which observes durable output, results, context, and steering before signal closure. Leftovers become queued input. Terminal `agent/turn-stop` runs after continuation and steering folding, stays authoritative through turn close and flush, and discards later steering but preserves queued prompts.
|
||||
Tool-time context—including async `inject()` and post-tool `additionalContexts`—settles after results. Steering drains before `agent/post-step`, which sees durable output, results, context, and steering. Leftovers queue. Terminal `agent/turn-stop` remains authoritative through close/flush; later steering is discarded while queued prompts remain.
|
||||
|
||||
`dsh-compact-basic` handles pressure and canonical overflow at checkpoints; retry requires a balanced surface replacement ([decision](../.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md)).
|
||||
Pruning precedes summaries; overflow retries require durable progress. Bounded transient retries compose on `agent/request-error`; cancellation wins ([compaction](../.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md), [retry](../.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md)).
|
||||
|
||||
### Failure Boundaries
|
||||
|
||||
The turn is the containment boundary. Final adapter-path and terminal in-band failures close the step before `agent/request-error`; retry opens a numbered step; otherwise, the provider error survives. Attempts reset on success.
|
||||
The turn contains failures. Adapter failures close the step before `agent/request-error`, which receives exact `Error`, `LlmFailure`, and history. Retry opens another step; success clears history; exhaustion stores failure on `turn/end`. Failed chunks commit no message/tool.
|
||||
|
||||
Other failures use `agent/error`. Cancellation and disposal beat recovery; undispatched model tool calls receive synthetic `tool/call` and `ABORTED` result pairs before `turn/end`. `cancel()` clears queues and aborts active work; disposal awaits quiescence before unregistering.
|
||||
Other failures use `agent/error`. Cancellation and disposal beat recovery; undispatched tool calls get synthetic `tool/call`/`ABORTED_BEFORE_DISPATCH` pairs. The turn signal retires before `turn/end`. Effective `cancel()` emits its typed cause before clearing queues and aborting; observers cannot veto, idle calls emit nothing, and durability records `aborted`. Disposal awaits quiescence ([decision](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md)).
|
||||
|
||||
Every session event is turn-enclosed. Reloading preserves an interrupted tail and closes it with a synthetic `interrupted` turn end. Failures after durable turn close report only through `agent/error` because no safe in-turn position remains. Each turn has one `TurnEndReason`; [TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap) owns the variants.
|
||||
|
||||
### Agent Handles
|
||||
|
||||
`ctx.agents` owns live agents and returns `AgentHandle { agent, dispose() }`. Plugins drive `Agent` through `send()`, `steer()`, `inject()`, `cancel()`, and `whenIdle()`. The caller fiber and factory provider structurally co-own programmatic lifecycles; the consumer handle is the only other teardown capability. All owners await one disposer.
|
||||
`ctx.agents` owns live agents and returns `AgentHandle { agent, dispose() }`. Plugins use `send()`, `steer()`, `inject()`, `cancel()`, and `whenIdle()`. The caller fiber, factory provider, and consumer handle co-own teardown through one awaited disposer.
|
||||
|
||||
### Agent Scope
|
||||
|
||||
Every live agent owns a scoped `agent.ctx`. Its registrations shadow globals, receive only that agent's dispatches, and unwind with it; async effects such as background-task cleanup are awaited. `CreateAgentOptions.setup(agentCtx)` composes the scope before publication. Typed resolvers derive carrier checks from merged `Events` signatures and `scopeTarget` ([semantic gates](../.agents/notes/implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md)). See [agent scope](../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md) and [subagent composition controls](../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md). `AgentLoop` runs drivers inside `ctx.agents.withInitiator()`; private orchestration derives `agent.session`; other identities stay explicit ([decision](../.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md)).
|
||||
Each agent owns a scoped `agent.ctx`; shared storage overlays global tool, prompt, and command entries while preserving domain views ([decision](../.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md)). Scoped listeners filter dispatch, and every scoped contribution unwinds with awaited cleanup. `CreateAgentOptions.setup(agentCtx)` composes before publication. Typed resolvers derive carrier checks from merged `Events` and `scopeTarget` ([semantic gates](../.agents/notes/implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md)). See [agent scope](../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md) and [subagent composition](../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md). `AgentLoop` runs inside `ctx.agents.withInitiator()`; private orchestration derives `agent.session`, while turn, step, signal, cwd, and authority remain explicit ([decision](../.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md)).
|
||||
|
||||
## State
|
||||
|
||||
### Session Log
|
||||
|
||||
The session log is the source of truth. `deriveMessages()` projects session events into the `Message[]` sent to the model; raw `assistant/chunk` events stay in the log for replay and UI fidelity. Replay, fork, resume, transcript rendering, telemetry, and persistence all derive from the same event stream.
|
||||
The session log is authoritative. `deriveMessages()` projects model history; raw `assistant/chunk` events remain for replay and UI fidelity. Fork, resume, transcript rendering, telemetry, and persistence derive from the same stream.
|
||||
|
||||
**Model-visible ⟺ logged**: the log reconstructs every request — messages at `step/start` fronted by the header's session prefix, headers by folding `request/header` — and dev invariants assert this ([reconstructability](../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)).
|
||||
**Model-visible ⟺ logged**: the log reconstructs every request — messages at `step/start` fronted by the header's session prefix, and headers by folding `request/header` — and the package-owned `dsh-agent-loop/invariant` can assert it through `ctx.invariants` ([reconstructability](../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)).
|
||||
|
||||
Durability is a plugin concern. Persistence backends buffer synchronous `session/event` notifications and the loop awaits a turn-end checkpoint before moving on. The `SessionPersistence` seam stores `SessionEvent` directly, with metadata in `SessionHeader`; JSONL and SQLite share one contract suite.
|
||||
Durability is a plugin concern. Backends buffer synchronous `session/event` notifications; the loop awaits a turn-end checkpoint. `SessionPersistence` stores `SessionEvent` directly and metadata in `SessionHeader`; JSONL defaults to checksummed Zstandard, with SQLite under one contract.
|
||||
|
||||
`ctx.sessions.appendOutOfBand()` joins plugin-owned log-only events to an open turn or creates a balanced, flushed zero-step turn. `session/title` folds latest-wins with source seqs and provenance; its immediate fallback and sole optional async provider never delay the agent response. Forks inherit titles ([decision](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md)).
|
||||
|
||||
### Model Content
|
||||
|
||||
Messages contain typed blocks (`text`, `reasoning`, `tool-call`, `tool-result`) derived from merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New block types coordinate adapters, UI bridges, compaction pricing, token metering, and persistence as one repo-wide contract; replay measurement types live in [token-meter.md](core-data-structures/token-meter.md).
|
||||
Messages use typed blocks from merge-extensible `ContentBlockMap`; the same pattern types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New blocks coordinate adapters, UI, compaction, token metering, and persistence; replay measurements live in [token-meter.md](core-data-structures/token-meter.md).
|
||||
|
||||
Streaming uses raw chunks (`block-start` through `finish`) and `BlockAssembler`. The loop logs and assembles chunks, storing provider/model provenance plus replay state. An `LlmAdapter` implements `stream()`, registers provider routes, and may expose selector metadata; it resolves and validates model ids. Replay state reaches targets only when both routes map to one adapter instance, which owns validation and conversion. The contract lives in [llm-streaming.md](core-data-structures/llm-streaming.md).
|
||||
Streaming uses raw chunks and `BlockAssembler`. Each `LlmAdapter.stream()` is one provider attempt; adapters report facts and `agent/request-error` owns recovery. The loop logs chunks and successful provenance/replay state. Remote adapters use per-read idle watchdogs. Replay state crosses routes only when they share an adapter instance ([contract](core-data-structures/llm-streaming.md)).
|
||||
|
||||
## Extension And Composition
|
||||
|
||||
### Capability Pattern
|
||||
|
||||
A swappable capability usually splits into **interface / implementation / consumer**: the interface owns its `ctx` key and events, an implementation registers a backend, and a consumer exposes model behavior through tools or prompts. Bash is the reference; the [capability graph](capability-seams.md) shows every family.
|
||||
A swappable capability usually splits into **interface / implementation / consumer**: service/events, a backend, and model-facing tools/prompts. Bash is the reference; the [capability graph](capability-seams.md) maps each family.
|
||||
|
||||
Exceptions combine layers deliberately: LLM joins interface and consumer; filesystem wraps providers with policy; web unifies search/fetch registries; skills and subagents use named providers. Subagents spawn fresh, fork a completed-turn prefix, or use ACP children ([subagent.md](core-data-structures/subagent.md)).
|
||||
Exceptions combine layers: LLM interface/consumer; filesystem policy; web registries; named skill/subagent providers. Subagents spawn fresh, fork a completed-turn prefix, or use ACP children ([subagent.md](core-data-structures/subagent.md)).
|
||||
|
||||
`dsh-workspace-context` composes baselines on `agent/session-prefix` and appends `ctx.fs`-discovered nested changes on `tools/post-execute`; its [decision](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) records isolation. `dsh-paths` owns shared paths.
|
||||
|
||||
### Bundles And Apps
|
||||
|
||||
`dsh-agent-spine-demo` bundles the default spine ([README](../packages/examples/agent-spine-demo/README.md)). `dsh-stdio-demo` selects `dsh-tui` for interactive terminals and line-oriented `dsh-stdio` for pipes; `dsh-cli-demo` runs one persisted headless turn with format-pure stdout; `dsh-acp-demo` adds stdout-pure ACP over JSON-RPC ([ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK supplies its default only without an explicit config channel and drives `dsh-jsonrpc` over line-delimited JSON-RPC ([Python SDK](../python/README.md)). Deployments remain thin leaves with swappable backends and optional product tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)).
|
||||
`dsh-agent-spine-demo` bundles a spine and optional goals. App packages own the TUI, one-shot CLI, and ACP/JSON-RPC front doors ([README](../packages/examples/agent-spine-demo/README.md), [ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK supplies a default only without explicit config ([Python SDK](../python/README.md)). Thin deployments use swappable backends and optional tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)).
|
||||
|
||||
### Where New Behavior Goes
|
||||
|
||||
New behavior should attach to a documented extension point; changing the shipped loop requires updating this map.
|
||||
New behavior attaches to a documented extension point; a loop change updates this map.
|
||||
|
||||
| Goal | Mechanism |
|
||||
|---|---|
|
||||
| Add a model provider | register an adapter on `ctx.llm` |
|
||||
| Add a model-facing capability | register a tool on `ctx.tools`; schemas flow into prompt assembly |
|
||||
| Add command execution | implement and register a `ctx.bash` backend |
|
||||
| Add a model-facing capability | register on `ctx.tools`; schemas enter prompt assembly |
|
||||
| Add shell execution | implement and register a `ctx.bash` backend |
|
||||
| Add persistent terminal execution | register a `ctx.pty` backend and `dsh-tool-pty` |
|
||||
| Add a long-running/background capability | register the work on `ctx.tasks`; the generic `task_*` tools collect/stop it |
|
||||
| Add a human command | register on `ctx.commands`; adapters discover and dispatch it without a model turn |
|
||||
| Add background work | register on `ctx.tasks`; generic `task_*` tools collect or stop it |
|
||||
| Add filesystem access or policy | implement a `ctx.fs` provider or listen on `fs/*` policy events |
|
||||
| Confine spawned processes | a `ctx.sandbox` backend; consumers wrap their argv before spawning |
|
||||
| Intercept prompts, requests, model completion/failure, tool use, or continuation | listen on the relevant `agent/*` or `tools/*` event; use serial `agent/turn-stop` for a monotonic terminal stop |
|
||||
| Add a session-stable request prefix outside history | compose it on `agent/session-prefix`, once per loop instance; logged on the request header |
|
||||
| Intercept a request, tool, or turn | use its `agent/*` or `tools/*` event; `agent/turn-stop` is the serial terminal stop |
|
||||
| Add a session-stable prefix outside history | compose `agent/session-prefix`; the request header logs it |
|
||||
| Add UI or editor integration | drive `ctx.agents` and render from `session/event` |
|
||||
| Add durable session state | add a `SessionEventMap` member and render/replay from the log |
|
||||
| Add asynchronous session-title generation | register the sole provider on `ctx.sessionTitle` |
|
||||
| Manage a same-session objective | use `ctx.goals`; continue through `Agent` and `agent/*` |
|
||||
| Fork a live session | use `ctx.sessions.fork(source, boundary?, childSessionId?)` |
|
||||
| Scope a tool, prompt section, or listener to ONE agent | register it through that agent's `agent.ctx` (see Agent Scope) |
|
||||
| Scope a registration to one agent | use that agent's `agent.ctx` (see Agent Scope) |
|
||||
|
||||
The [extension cookbook](cookbook/extension-cookbook.md) carries plugin skeletons and the feature-to-seam map; step-by-step guides cover [packages](cookbook/adding-a-package.md), [tools](cookbook/adding-a-tool.md), [LLM adapters](cookbook/adding-an-llm-adapter.md), and [vendored packages](cookbook/adding-a-vendored-package.md).
|
||||
|
||||
## Quick Reference
|
||||
- Domain terms in the [glossary](glossary.md)
|
||||
- Type definitions in [core-data-structures/](core-data-structures/core.md)
|
||||
- Exact event and service signatures in [events](cordis-catalog/events.md)
|
||||
- [services](cordis-catalog/services.md) catalogs
|
||||
- Exact signatures in the [event](cordis-catalog/events.md) and [service](cordis-catalog/services.md) catalogs
|
||||
- package contracts in the [package map](../packages/README.md)
|
||||
- [Agent Notes](../.agents/notes/README.md)
|
||||
|
||||
202
docs/architecture.zh.md
Normal file
202
docs/architecture.zh.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# DeepSeek Harness 架构
|
||||
|
||||
[English](architecture.md) | 中文
|
||||
|
||||
**DeepSeek Harness SDK** 使用 Cordis:**一切皆插件**,循环也不例外。
|
||||
|
||||
## 概览
|
||||
|
||||
每个 harness 都是一个 [Cordis](cordis-primer.md) 上下文,由各包(package)贡献服务、类型化事件和可释放的注册项。
|
||||
|
||||
`packages/core/` 汇集默认的 agent(智能体)流程;各项功能仍以插件形式存在。
|
||||
|
||||
### 默认服务
|
||||
|
||||
| ctx 键 | 包 | 职责 |
|
||||
|---|---|---|
|
||||
| — | [`dsh-scope`](../packages/core/scope/README.md) | 作用域上下文注册与共享层存储(库) |
|
||||
| `ctx.sessions` | `dsh-session` | 内存中的事件溯源会话 |
|
||||
| `ctx.systemPrompt` | `dsh-system-prompt` | 有序提示词片段、工具 schema 和提示词变量 |
|
||||
| `ctx.tools` | `dsh-tools` | 工具注册表和[执行流水线](tool-execution-pipeline.md) |
|
||||
| `ctx.agents` | `dsh-agent` | 活跃 agent、委托创建、`agent/*` 事件和进程内发起方作用域 |
|
||||
| `ctx.agentLoop` | `dsh-agent-loop` | 实体 `Agent` 驱动器 |
|
||||
|
||||
### 功能服务
|
||||
|
||||
| ctx 键 | 包族 | 职责 |
|
||||
|---|---|---|
|
||||
| `ctx.llm` | [`llm/`](../packages/llm/README.md) | 适配器注册表和模型流式调用 |
|
||||
| `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | 感知回放的单实例请求压力和会话表面压力 |
|
||||
| `ctx.bash` | [`bash/`](../packages/bash/README.md) | 前台和后台命令执行 |
|
||||
| `ctx.pty` | [`pty/`](../packages/pty/README.md) | 按 owner 隔离的持久化终端会话 |
|
||||
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | 同一执行环境内的进程限制(argv 包装、逐调用策略) |
|
||||
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | 共享沙箱策略归属点 |
|
||||
| `ctx.codeRuntime` | [`code-runtime/`](../packages/code-runtime/README.md) | 执行模型编写的程序 |
|
||||
| `ctx.fs` | [`fs/`](../packages/fs/README.md) | 文件系统提供方原语和策略事件 |
|
||||
| `ctx.lsp` | [`lsp/`](../packages/lsp/README.md) | 语义导航注册表 |
|
||||
| `ctx.skills` | [`skill/`](../packages/skill/README.md) | skill(技能)提供方注册表和渐进式披露 |
|
||||
| `ctx.web` | [`web/`](../packages/web/README.md) | 搜索与抓取提供方注册表 |
|
||||
| `ctx.compact`,`ctx.toolResultPrune` | [`compact/`](../packages/compact/README.md)/[`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune/README.md) | 摘要压缩(compaction);可选的无模型结果裁剪 |
|
||||
| `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | 具名委托提供方 |
|
||||
| `ctx.planMode` | [`plan/`](../packages/plan/README.md) | 落日志的 plan 协作状态 |
|
||||
| `ctx.tasks` | [`tasks/`](../packages/tasks/README.md) | 后台任务注册表和通用 `task_*` 控制工具 |
|
||||
| `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | 脚本驱动的多 agent 编排 |
|
||||
| `ctx.goals` | [`goal/`](../packages/goal/README.md) | 持久化的同会话目标 |
|
||||
| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | 会话日志的持久存储 |
|
||||
| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | 实时优先的逻辑语料精确读取和关系追踪 |
|
||||
| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | 基于日志的回退标题和单个可选异步提供方 |
|
||||
| `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | 按包名选择包自有运行时检查的注册表 |
|
||||
|
||||
## 事件
|
||||
|
||||
事件构成服务的扩展 API;完整清单见[事件目录](cordis-catalog/events.md)和[生产方与消费方映射](event-producer-consumer.md)。
|
||||
|
||||
### 事件域
|
||||
|
||||
- **会话事件**是追加到日志并通过 `session/event` 发出的持久事实。
|
||||
- **Agent 事件**携带活跃 `Agent`,用于状态、提示词准入、请求塑形、验证和续跑。
|
||||
- **功能事件**让所属服务边界无需导入循环即可附加策略和适配器。
|
||||
|
||||
### 拦截语义
|
||||
|
||||
waterfall(瀑布式事件)的行为类似环绕中间件:监听器调用 `next()` 即表示委托,直接返回而不调用它则会否决或接管。完整规则见 [Cordis waterfall 语义](cordis-primer.md#cordis-waterfall-semantics)。
|
||||
|
||||
## 默认循环生命周期
|
||||
|
||||
已交付的循环通过插件可见的服务和事件,持续处理从提示词到检查点的工作。
|
||||
|
||||
**会话**采用仅追加方式。每个普通**轮次**领取一项已排队的 `send()` 输入;注入不领取输入。后续轮次会等待前一个已领取轮次的检查点,但可以与其共用同一个 `running` 区间([决策](../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md))。模型和插件停止轮次时,该轮次结束;一个**步骤**包含一次模型请求及其工具。在[下文时序](agent-lifecycle.md)中,引号标记持久事件。
|
||||
|
||||
未提供 id 时,创建流程会生成 `<config-id>-session-<uuid>`;`sessionId` 用于恢复或创建会话,而 `resumeSessionId` 要求已有历史。恢复流程在发布前还原沿袭关系和委托深度。初始化失败会发出 `agent-loop/config-start-failed`;拆卸过程保持静默。
|
||||
|
||||
### 轮次流程
|
||||
|
||||
```text
|
||||
choose declarative identity and fresh/resume path
|
||||
-> prepare private session + agent.ctx -> await unpublished setup
|
||||
-> enter session + agent -> session/created -> agent/created
|
||||
-> enable driving -> agent/session-start(source) -> start driver
|
||||
forever:
|
||||
wait for a queued message
|
||||
emit agent/status(running)
|
||||
TURN:
|
||||
'turn/start'
|
||||
claimed message -> agent/prompt-submit
|
||||
allowed prompt -> 'user/message' plus injected context
|
||||
blocked prompt -> 'prompt/blocked' -> 'turn/end'(rejected)
|
||||
STEP loop:
|
||||
drain steering
|
||||
assemble system prompt and tool schemas
|
||||
agent/session-prefix (first step)
|
||||
agent/pre-step
|
||||
snapshot the derived messages (the reconstruction boundary)
|
||||
'step/start'
|
||||
agent/request (config only) -> log request/header -> llm/stream (frozen)
|
||||
on final adapter-path or terminal in-band failure:
|
||||
'step/end'
|
||||
agent/request-error(original error, failure facts, immutable prior failures, signal)
|
||||
retry in the next numbered step or preserve the original error
|
||||
otherwise:
|
||||
'assistant/chunk'
|
||||
agent/step-result
|
||||
'assistant/message' (transformed content or empty success anchor after step-result rejection)
|
||||
schedule tool calls by ctx.tools.executionMode:
|
||||
exclusive -> one-call barrier
|
||||
parallel -> rolling pool, <= maxParallelToolCalls in flight; reclassify before start
|
||||
each start -> 'tool/call' -> ordered tools/pre-execute -> concurrent tools/execute
|
||||
each model-order result -> ordered tools/post-execute -> 'tool/result'
|
||||
append accepted tool-batch context after all recorded results, then steering
|
||||
agent/post-step
|
||||
'step/end'
|
||||
agent/turn-continuation
|
||||
agent/turn-stop (terminal policy)
|
||||
stop unless tools or continuation policy ask for another step
|
||||
'turn/end'
|
||||
checkpoint persistence and notify idle/running status
|
||||
```
|
||||
|
||||
每个步骤都会组装有序提示词片段、工具 schema 和变量;未知引用会使该轮次失败。`dsh-system-prompt` 负责身份和角色设定,循环则提供 `model` 和 `cwd`([提示词归属](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md))。
|
||||
|
||||
工具执行阶段的上下文,包括异步 `inject()` 和工具执行后的 `additionalContexts`,会在结果产生后稳定。steering(中途引导)会在 `agent/post-step` 前排空;该事件会观察持久输出、结果、上下文和 steering。余留内容进入队列。终止型 `agent/turn-stop` 在关闭和刷写期间始终具有最终决定权;后续 steering 会被丢弃,排队提示词仍予保留。
|
||||
|
||||
裁剪先于摘要;溢出重试必须取得持久进展。有界的瞬态重试在 `agent/request-error` 上组合;取消优先([压缩](../.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md)、[重试](../.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md))。
|
||||
|
||||
### 失败边界
|
||||
|
||||
轮次负责隔离故障。适配器故障会先关闭步骤,再进入 `agent/request-error`;该事件会收到准确的 `Error`、`LlmFailure` 和历史记录。重试会开启另一个步骤;成功会清除历史记录;重试耗尽后,故障存入 `turn/end`。失败分片不会提交消息或工具。
|
||||
|
||||
其他故障使用 `agent/error`。取消和资源释放均优先于恢复;尚未分派的工具调用会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。轮次信号会在 `turn/end` 前失效。实际生效的 `cancel()` 会在清空队列和中止前发出类型化原因;观察方不能否决该操作,空闲状态下的调用不发出任何事件,持久化会记录 `aborted`。dispose(资源释放)会等待系统停稳([决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。
|
||||
|
||||
每个会话事件都包围在轮次内。重新加载会保留中断的日志尾部,并用合成的 `interrupted` 轮次结束事件将其闭合。持久轮次关闭后的故障只通过 `agent/error` 报告,因为此时已没有安全的轮次内位置。每个轮次有一个 `TurnEndReason`;各变体由 [TurnEndReasonMap](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap) 统一定义。
|
||||
|
||||
### Agent 句柄
|
||||
|
||||
`ctx.agents` 拥有活跃 agent,并返回 `AgentHandle { agent, dispose() }`。插件使用 `send()`、`steer()`、`inject()`、`cancel()` 和 `whenIdle()`。调用方 fiber、工厂提供方和消费方句柄通过同一个需等待完成的 disposer 共同拥有拆卸过程。
|
||||
|
||||
### Agent 作用域
|
||||
|
||||
每个 agent 都拥有一个作用域化的 `agent.ctx`;共享存储会在全局工具、提示词和命令条目之上叠加作用域条目,同时保留各领域视图([决策](../.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md))。作用域监听器会过滤分派,每项作用域贡献都会在撤销时等待清理完成。`CreateAgentOptions.setup(agentCtx)` 在发布前完成组合。类型化解析器从合并后的 `Events` 和 `scopeTarget` 推导载体检查([语义门禁](../.agents/notes/implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md))。参见 [agent 作用域](../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md)和 [subagent 组合](../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md)。`AgentLoop` 在 `ctx.agents.withInitiator()` 内运行;私有编排会派生 `agent.session`,而轮次、步骤、信号、cwd 和权限仍保持显式([决策](../.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md))。
|
||||
|
||||
## 状态
|
||||
|
||||
### 会话日志
|
||||
|
||||
会话日志是权威依据。`deriveMessages()` 投影出模型历史;原始 `assistant/chunk` 事件留在日志中,以保证回放和 UI 保真。fork、恢复、transcript(文本记录)渲染、遥测和持久化均派生自同一个事件流。
|
||||
|
||||
**模型可见 ⟺ 已记录**:日志可以重建每个请求,包括由请求头会话前缀置于开头的 `step/start` 时消息,以及通过折叠 `request/header` 得到的请求头;开发期不变量会断言这一点([可重建性](../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md))。
|
||||
|
||||
持久性由插件负责。后端会缓冲同步的 `session/event` 通知;循环等待轮次结束检查点。`SessionPersistence` 直接存储 `SessionEvent`,并将元数据存入 `SessionHeader`;JSONL 默认采用带校验和的 Zstandard,SQLite 则遵循同一契约。
|
||||
|
||||
`ctx.sessions.appendOutOfBand()` 会把插件所属的纯日志事件加入开放轮次,或创建一个平衡且已刷写的零步骤轮次。`session/title` 按后写覆盖方式折叠,并携带源 seq 和来源信息;其即时回退标题和唯一可选异步提供方都不会延迟 agent 响应。fork 会继承标题([决策](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md))。
|
||||
|
||||
### 模型内容
|
||||
|
||||
消息使用从可合并扩展的 `ContentBlockMap` 派生的类型化块;`MessageSource`、`FinishReason`、`TurnTrigger` 和 `TurnEndReason` 也采用同一模式定义类型。新增块会协调适配器、UI、压缩、token 计量和持久化;回放计量见 [token-meter.md](core-data-structures/token-meter.md)。
|
||||
|
||||
流式输出使用原始分片和 `BlockAssembler`。每次 `LlmAdapter.stream()` 调用代表一次提供方尝试;适配器报告事实,`agent/request-error` 负责恢复。循环会记录分片及成功结果的来源信息和回放状态。远程适配器使用逐次读取空闲看门狗。只有当路由共用同一个适配器实例时,回放状态才会跨路由传递([契约](core-data-structures/llm-streaming.md))。
|
||||
|
||||
## 扩展与组合
|
||||
|
||||
### 功能模式
|
||||
|
||||
可替换功能通常拆分为**接口/实现/消费方**:服务和事件、后端,以及面向模型的工具和提示词。Bash 是参考实现;[功能图](capability-seams.md)映射了每个包族。
|
||||
|
||||
例外情况会合并不同层次:LLM(大语言模型)合并接口和消费方,文件系统整合策略,web 使用注册表,skill 和 subagent 使用具名提供方。subagent 可以通过 spawn 创建全新实例、fork 一个已完成轮次的前缀,或使用 ACP(Agent Client Protocol)子 agent([subagent.md](core-data-structures/subagent.md))。
|
||||
|
||||
`dsh-workspace-context` 在 `agent/session-prefix` 上组合基线,并在通过 `ctx.fs` 发现嵌套变更后,于 `tools/post-execute` 追加这些变更;其[决策](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)记录了隔离方式。`dsh-paths` 负责共享路径。
|
||||
|
||||
### 组合包与应用
|
||||
|
||||
`dsh-agent-spine-demo` 组合一套主干和可选目标。应用包负责 TUI、单次运行的 CLI(命令行界面)以及 ACP/JSON-RPC 入口([README](../packages/examples/agent-spine-demo/README.md)、[ui/](../packages/ui/README.md))。`dsh-jsonrpc-agent` 启动外部 `cordis.yml`;Python SDK 仅在没有显式配置时提供默认项([Python SDK](../python/README.md))。轻量部署使用可替换后端和可选工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[图谱](graph-atlas.md))。
|
||||
|
||||
### 新行为的归属位置
|
||||
|
||||
新行为附加到已有文档记录的扩展点;循环发生变更时,本架构图随之更新。
|
||||
|
||||
| 目标 | 机制 |
|
||||
|---|---|
|
||||
| 添加模型提供方 | 在 `ctx.llm` 上注册适配器 |
|
||||
| 添加面向模型的功能 | 在 `ctx.tools` 上注册;schema 进入提示词组装流程 |
|
||||
| 添加 shell 执行 | 实现并注册 `ctx.bash` 后端 |
|
||||
| 添加持久化终端执行 | 注册 `ctx.pty` 后端和 `dsh-tool-pty` |
|
||||
| 添加用户命令 | 在 `ctx.commands` 上注册;适配器无需模型轮次即可发现并分派该命令 |
|
||||
| 添加后台工作 | 在 `ctx.tasks` 上注册;通用 `task_*` 工具负责收集或停止 |
|
||||
| 添加文件系统访问或策略 | 实现 `ctx.fs` 提供方,或监听 `fs/*` 策略事件 |
|
||||
| 限制生成的进程 | 使用 `ctx.sandbox` 后端;消费方在生成进程前包装 argv |
|
||||
| 拦截请求、工具或轮次 | 使用相应的 `agent/*` 或 `tools/*` 事件;`agent/turn-stop` 是串行终止判定点 |
|
||||
| 添加历史记录之外的会话稳定前缀 | 组合 `agent/session-prefix`;请求头会记录该前缀 |
|
||||
| 添加 UI 或编辑器集成 | 驱动 `ctx.agents` 并从 `session/event` 渲染 |
|
||||
| 添加持久会话状态 | 添加一个 `SessionEventMap` 成员,并从日志渲染和回放 |
|
||||
| 添加异步会话标题生成 | 在 `ctx.sessionTitle` 上注册唯一提供方 |
|
||||
| 管理同会话目标 | 使用 `ctx.goals`;通过 `Agent` 和 `agent/*` 续跑 |
|
||||
| fork 活跃会话 | 使用 `ctx.sessions.fork(source, boundary?, childSessionId?)` |
|
||||
| 将注册项限定到单个 agent | 使用该 agent 的 `agent.ctx`(参见 Agent 作用域) |
|
||||
|
||||
[扩展实操手册(cookbook)](cookbook/extension-cookbook.md)提供插件骨架和功能到服务边界的映射;分步指南涵盖[包](cookbook/adding-a-package.md)、[工具](cookbook/adding-a-tool.md)、[LLM 适配器](cookbook/adding-an-llm-adapter.md)和 [vendored 包](cookbook/adding-a-vendored-package.md)。
|
||||
|
||||
## 快速参考
|
||||
- [术语表](glossary.md)中的领域术语
|
||||
- [core-data-structures/](core-data-structures/core.md) 中的类型定义
|
||||
- [事件](cordis-catalog/events.md)和[服务](cordis-catalog/services.md)目录中的准确签名
|
||||
- [包索引](../packages/README.md)中的包契约
|
||||
- [Agent Note(agent 决策记录)](../.agents/notes/README.md)
|
||||
@@ -16,6 +16,8 @@ flowchart LR
|
||||
pkg_compact_basic["compact-basic"]
|
||||
pkg_token_meter["token-meter"]
|
||||
svc_tokenMeter["ctx.tokenMeter<br/>Replay token measurement"]
|
||||
pkg_compact_tool_result_prune["compact-tool-result-prune"]
|
||||
svc_toolResultPrune["ctx.toolResultPrune<br/>Model-free tool-result pruning"]
|
||||
pkg_session["session"]
|
||||
svc_sessions["ctx.sessions<br/>In-memory session store"]
|
||||
pkg_agent["agent"]
|
||||
@@ -24,6 +26,8 @@ flowchart LR
|
||||
pkg_session_query["session-query"]
|
||||
pkg_subagent_inprocess["subagent-inprocess"]
|
||||
pkg_invariants["invariants"]
|
||||
svc_invariants["ctx.invariants<br/>Package-owned invariant registry"]
|
||||
pkg_scope["scope"]
|
||||
svc_sessionPersistence["ctx.sessionPersistence<br/>Durable session persistence seam"]
|
||||
pkg_session_persistence_jsonl["session-persistence-jsonl"]
|
||||
pkg_session_persistence_sqlite["session-persistence-sqlite"]
|
||||
@@ -32,6 +36,10 @@ flowchart LR
|
||||
pkg_hooks_codex["hooks-codex"]
|
||||
pkg_acp["acp"]
|
||||
svc_sessionQuery["ctx.sessionQuery<br/>Exact session-history reads and traces"]
|
||||
pkg_session_title["session-title"]
|
||||
svc_sessionTitle["ctx.sessionTitle<br/>Log-backed session titles"]
|
||||
pkg_session_title_first_message_llm["session-title-first-message-llm"]
|
||||
pkg_session_title_all_messages_llm["session-title-all-messages-llm"]
|
||||
pkg_system_prompt["system-prompt"]
|
||||
svc_systemPrompt["ctx.systemPrompt<br/>System prompt assembly registry"]
|
||||
pkg_tools["tools"]
|
||||
@@ -46,13 +54,20 @@ flowchart LR
|
||||
pkg_tool_todo["tool-todo"]
|
||||
pkg_user_interaction["user-interaction"]
|
||||
svc_userInteraction["ctx.userInteraction<br/>Human question/answer seam"]
|
||||
pkg_stdio_demo["stdio-demo"]
|
||||
pkg_tui["tui"]
|
||||
pkg_plan_mode["plan-mode"]
|
||||
svc_planMode["ctx.planMode<br/>Plan collaboration state"]
|
||||
pkg_commands["commands"]
|
||||
svc_commands["ctx.commands<br/>Human command registry"]
|
||||
pkg_skill["skill"]
|
||||
svc_skills["ctx.skills<br/>Skill provider registry"]
|
||||
pkg_skill_local["skill-local"]
|
||||
svc_agents["ctx.agents<br/>Agent service"]
|
||||
pkg_tui_demo["tui-demo"]
|
||||
svc_agentLoop["ctx.agentLoop<br/>Concrete loop driver"]
|
||||
pkg_agent_spine_demo["agent-spine-demo"]
|
||||
pkg_goal["goal"]
|
||||
svc_goals["ctx.goals<br/>Same-session goal domain"]
|
||||
pkg_bash["bash"]
|
||||
svc_bash["ctx.bash<br/>Bash executor seam"]
|
||||
pkg_bash_local["bash-local"]
|
||||
@@ -85,6 +100,7 @@ flowchart LR
|
||||
pkg_subagent_spawn["subagent-spawn"]
|
||||
pkg_subagent_fork["subagent-fork"]
|
||||
pkg_subagent_acp["subagent-acp"]
|
||||
pkg_tool_ralph["tool-ralph"]
|
||||
pkg_tasks["tasks"]
|
||||
svc_tasks["ctx.tasks<br/>Background task registry"]
|
||||
pkg_tool_tasks["tool-tasks"]
|
||||
@@ -112,16 +128,21 @@ flowchart LR
|
||||
pkg_bash_sandbox --> svc_bash
|
||||
pkg_code_runtime --> svc_codeRuntime
|
||||
pkg_code_runtime_worker --> svc_codeRuntime
|
||||
pkg_commands --> svc_commands
|
||||
pkg_compact --> svc_compact
|
||||
pkg_compact_basic --> svc_compact
|
||||
pkg_compact_tool_result_prune --> svc_toolResultPrune
|
||||
pkg_fs --> svc_fs
|
||||
pkg_fs_local --> svc_fs
|
||||
pkg_fs_sandbox --> svc_fs
|
||||
pkg_goal --> svc_goals
|
||||
pkg_invariants --> svc_invariants
|
||||
pkg_llm --> svc_llm
|
||||
pkg_llm_deepseek --> svc_llm
|
||||
pkg_llm_pi_ai --> svc_llm
|
||||
pkg_llm_replay --> svc_llm
|
||||
pkg_permission --> svc_permission
|
||||
pkg_plan_mode --> svc_planMode
|
||||
pkg_pty --> svc_pty
|
||||
pkg_pty_local --> svc_pty
|
||||
pkg_sandbox --> svc_sandbox
|
||||
@@ -132,11 +153,13 @@ flowchart LR
|
||||
pkg_session_persistence_jsonl --> svc_sessionPersistence
|
||||
pkg_session_persistence_sqlite --> svc_sessionPersistence
|
||||
pkg_session_query --> svc_sessionQuery
|
||||
pkg_session_title --> svc_sessionTitle
|
||||
pkg_session_title_all_messages_llm --> svc_sessionTitle
|
||||
pkg_session_title_first_message_llm --> svc_sessionTitle
|
||||
pkg_skill --> svc_skills
|
||||
pkg_skill_local --> svc_skills
|
||||
pkg_spill --> svc_spillStore
|
||||
pkg_spill_local --> svc_spillStore
|
||||
pkg_stdio_demo --> svc_userInteraction
|
||||
pkg_subagent --> svc_subagents
|
||||
pkg_subagent_acp --> svc_subagents
|
||||
pkg_subagent_fork --> svc_subagents
|
||||
@@ -146,6 +169,7 @@ flowchart LR
|
||||
pkg_token_meter --> svc_tokenMeter
|
||||
pkg_tool_bash --> svc_bashEnv
|
||||
pkg_tools --> svc_tools
|
||||
pkg_tui --> svc_userInteraction
|
||||
pkg_user_interaction --> svc_userInteraction
|
||||
pkg_web --> svc_web
|
||||
pkg_web_fetch_local --> svc_web
|
||||
@@ -158,20 +182,26 @@ flowchart LR
|
||||
svc_agents --> pkg_acp
|
||||
svc_agents --> pkg_agent_loop
|
||||
svc_agents --> pkg_cli_demo
|
||||
svc_agents --> pkg_invariants
|
||||
svc_agents --> pkg_stdio_demo
|
||||
svc_agents --> pkg_subagent_inprocess
|
||||
svc_agents --> pkg_tui_demo
|
||||
svc_approval --> pkg_tool_bash
|
||||
svc_approval --> pkg_tools
|
||||
svc_bash --> pkg_hooks_claude
|
||||
svc_bash --> pkg_hooks_codex
|
||||
svc_bash --> pkg_tool_bash
|
||||
svc_codeRuntime --> pkg_tools
|
||||
svc_commands --> pkg_acp
|
||||
svc_commands --> pkg_tui
|
||||
svc_compact --> pkg_compact_basic
|
||||
svc_fs --> pkg_tool_fs
|
||||
svc_invariants --> pkg_agent
|
||||
svc_invariants --> pkg_agent_loop
|
||||
svc_invariants --> pkg_scope
|
||||
svc_invariants --> pkg_session
|
||||
svc_llm --> pkg_agent_loop
|
||||
svc_llm --> pkg_compact_basic
|
||||
svc_permission --> pkg_acp
|
||||
svc_planMode --> pkg_acp
|
||||
svc_pty --> pkg_tool_pty
|
||||
svc_sandbox --> pkg_bash_sandbox
|
||||
svc_sandbox --> pkg_pty_local
|
||||
@@ -187,12 +217,12 @@ flowchart LR
|
||||
svc_sessions --> pkg_agent
|
||||
svc_sessions --> pkg_agent_loop
|
||||
svc_sessions --> pkg_cli_demo
|
||||
svc_sessions --> pkg_invariants
|
||||
svc_sessions --> pkg_session_persistence
|
||||
svc_sessions --> pkg_session_query
|
||||
svc_sessions --> pkg_subagent_inprocess
|
||||
svc_skills --> pkg_tool_skill
|
||||
svc_spillStore --> pkg_spill_policy
|
||||
svc_subagents --> pkg_tool_ralph
|
||||
svc_subagents --> pkg_tool_subagent
|
||||
svc_systemPrompt --> pkg_agent_loop
|
||||
svc_systemPrompt --> pkg_tool_fs
|
||||
@@ -204,6 +234,7 @@ flowchart LR
|
||||
svc_tasks --> pkg_tool_subagent
|
||||
svc_tasks --> pkg_tool_tasks
|
||||
svc_tokenMeter --> pkg_compact_basic
|
||||
svc_toolResultPrune --> pkg_compact_basic
|
||||
svc_tools --> pkg_acp
|
||||
svc_tools --> pkg_agent_loop
|
||||
svc_tools --> pkg_tool_ask_user
|
||||
@@ -216,9 +247,10 @@ flowchart LR
|
||||
svc_tools --> pkg_tool_todo
|
||||
svc_tools --> pkg_tool_web
|
||||
svc_userInteraction --> pkg_acp
|
||||
svc_userInteraction --> pkg_stdio_demo
|
||||
svc_userInteraction --> pkg_tool_ask_user
|
||||
svc_userInteraction --> pkg_tui
|
||||
svc_web --> pkg_tool_web
|
||||
svc_workflows --> pkg_tool_ralph
|
||||
svc_workflows --> pkg_tool_workflow
|
||||
svc_fs -. event gate .-> pkg_fs_policy
|
||||
```
|
||||
@@ -227,15 +259,21 @@ flowchart LR
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compact-basic`](../packages/compact/compact-basic) | - | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. |
|
||||
| `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compact-basic`](../packages/compact/compact-basic) | - | Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements. |
|
||||
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`cli-demo`](../packages/examples/cli-demo), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-query`](../packages/session-query/session-query), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`invariants`](../packages/support/invariants) | - | Owns append-only Session instances and emits the durable session event feed. |
|
||||
| `ctx.toolResultPrune` | `core` | [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | - | [`compact-basic`](../packages/compact/compact-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. |
|
||||
| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`cli-demo`](../packages/examples/cli-demo), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-query`](../packages/session-query/session-query), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | - | Owns append-only Session instances and emits the durable session event feed. |
|
||||
| `ctx.invariants` | `core` | [`invariants`](../packages/support/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures. |
|
||||
| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session-persistence/session-persistence) | [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`acp`](../packages/ui/acp), [`session-query`](../packages/session-query/session-query) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. |
|
||||
| `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | - | - | - | Resolves live and optional persisted logs into one logical corpus for exact reads and relationship traces. |
|
||||
| `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session-title/session-title) | [`session-title-first-message-llm`](../packages/session-title/session-title-first-message-llm), [`session-title-all-messages-llm`](../packages/session-title/session-title-all-messages-llm) | - | - | Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration. |
|
||||
| `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. |
|
||||
| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tool-bash`](../packages/bash/tool-bash), [`tool-cordis`](../packages/cordis/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web), [`acp`](../packages/ui/acp) | - | Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. |
|
||||
| `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/ui/user-interaction) | [`stdio-demo`](../packages/examples/stdio-demo), [`acp`](../packages/ui/acp) | [`tool-ask-user`](../packages/ui/tool-ask-user), [`stdio-demo`](../packages/examples/stdio-demo), [`acp`](../packages/ui/acp) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. |
|
||||
| `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/ui/user-interaction) | [`tui`](../packages/ui/tui), [`acp`](../packages/ui/acp) | [`tool-ask-user`](../packages/ui/tool-ask-user), [`tui`](../packages/ui/tui), [`acp`](../packages/ui/acp) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. |
|
||||
| `ctx.planMode` | `core` | [`plan-mode`](../packages/plan/plan-mode) | - | [`acp`](../packages/ui/acp) | - | Folds logged plan/mode state, flushes user selections at turn boundaries, renders deployment-owned guidance, registers /plan, and keeps the plan-exit schema stable across transitions. |
|
||||
| `ctx.commands` | `core` | [`commands`](../packages/ui/commands) | - | [`tui`](../packages/ui/tui), [`acp`](../packages/ui/acp) | - | Plugins register direct human commands; TUI and ACP consume the same effective per-agent catalog without sending invocations to the model. |
|
||||
| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. |
|
||||
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`stdio-demo`](../packages/examples/stdio-demo), [`invariants`](../packages/support/invariants) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
|
||||
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`tui-demo`](../packages/examples/tui-demo) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
|
||||
| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. |
|
||||
| `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. |
|
||||
| `ctx.bash` | `seam` | [`bash`](../packages/bash/bash) | [`bash-local`](../packages/bash/bash-local), [`bash-sandbox`](../packages/bash/bash-sandbox) | [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors replace bash-local without touching them. |
|
||||
| `ctx.bashEnv` | `core` | [`tool-bash`](../packages/bash/tool-bash) | - | - | - | Plugins declare effect-scoped DSH_* facts; tool-bash collects one trusted snapshot per execution and the executor rebuilds the namespace. |
|
||||
| `ctx.pty` | `seam` | [`pty`](../packages/pty/pty) | [`pty-local`](../packages/pty/pty-local) | [`tool-pty`](../packages/pty/tool-pty) | - | The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-pty exposes the owner-scoped model surface. |
|
||||
@@ -246,10 +284,10 @@ flowchart LR
|
||||
| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | [`tools`](../packages/core/tools) | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for Code Mode). |
|
||||
| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-policy`](../packages/fs/fs-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-policy contributes observed-state checks through the fs/* event gate. |
|
||||
| `ctx.compact` | `seam` | [`compact`](../packages/compact/compact) | [`compact-basic`](../packages/compact/compact-basic) | [`compact-basic`](../packages/compact/compact-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; a model-facing compact tool remains deferred. |
|
||||
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp) | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name. |
|
||||
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; tool-subagent exposes configured delegation while tool-ralph requires one fresh structured-output route. |
|
||||
| `ctx.tasks` | `core` | [`tasks`](../packages/tasks/tasks) | - | [`tool-bash`](../packages/bash/tool-bash), [`tool-pty`](../packages/pty/tool-pty), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-tasks`](../packages/tasks/tool-tasks) | - | Producers (background bash, PTY sends, and subagent delegations) register running work; tool-tasks is the model-facing control surface that reads, lists, and kills it. |
|
||||
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-local`](../packages/web/web-fetch-local) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. |
|
||||
| `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | The backend saves oversized tool text and returns a model-facing locator plus retrieval hint; spill-policy is the tools/post-execute consumer that decides when to spill. |
|
||||
| `ctx.workflows` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | [`tool-workflow`](../packages/workflow/tool-workflow) | - | One engine per context (bash shape, no named-provider registry); the worker-thread engine fans agent() calls out through ctx.subagents. |
|
||||
| `ctx.workflows` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | One engine per context (bash shape, no named-provider registry); the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. |
|
||||
|
||||
Maintenance mode: hybrid: services are discovered from Cordis declarations; interface/implementation/consumer roles are classified in `scripts/gen-doc-graphs.ts` with a completeness guard.
|
||||
|
||||
@@ -11,7 +11,7 @@ A `Requires:` line lists the service keys the plugin `inject`s: its `cordis.yml`
|
||||
|
||||
## `@deepseek-ai/dsh-acp`
|
||||
|
||||
Requires: `agents` · `sessionPersistence` · `tools` · `userInteraction` · `llm` · `systemPrompt`
|
||||
Requires: `agents` · `commands` · `sessionPersistence` · `tools` · `userInteraction` · `llm` · `systemPrompt`
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin config: the agent template ACP sessions are created from. */
|
||||
@@ -27,7 +27,7 @@ export interface AcpConfig {
|
||||
|
||||
Depends on: `Stream` (`@agentclientprotocol/sdk`)
|
||||
|
||||
Source: [`packages/ui/acp/src/index.ts:206`](../packages/ui/acp/src/index.ts)
|
||||
Source: [`packages/ui/acp/src/index.ts:275`](../packages/ui/acp/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-acp-demo`
|
||||
|
||||
@@ -56,8 +56,12 @@ export interface Config {
|
||||
tools?: ToolsConfig
|
||||
/** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
|
||||
dshHome?: string
|
||||
/** Fallback session-title limits forwarded through agent-spine-demo. */
|
||||
sessionTitle?: NonNullable<agentCore.Config['sessionTitle']>
|
||||
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
|
||||
persistenceRoot?: string
|
||||
/** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
|
||||
persistenceCompression?: JsonlCompression
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
workspaceContext: agentCore.Config['workspaceContext']
|
||||
/** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
|
||||
@@ -66,12 +70,16 @@ export interface Config {
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and command. */
|
||||
goals?: agentCore.GoalConfig | false
|
||||
/** Bounded transient model-request retry policy forwarded through agent-core. */
|
||||
llmRetry?: NonNullable<agentCore.Config['llmRetry']>
|
||||
}
|
||||
```
|
||||
|
||||
Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools)
|
||||
Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`JsonlCompression`](../packages/session-persistence/session-persistence-jsonl/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools)
|
||||
|
||||
Source: [`packages/examples/acp-demo/src/index.ts:33`](../packages/examples/acp-demo/src/index.ts)
|
||||
Source: [`packages/examples/acp-demo/src/index.ts:38`](../packages/examples/acp-demo/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-agent-loop`
|
||||
|
||||
@@ -101,7 +109,7 @@ export interface Config {
|
||||
|
||||
Depends on: [`AgentOptions`](core-data-structures/core.md) · [`SessionId`](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/agent-loop/src/index.ts:369`](../packages/core/agent-loop/src/index.ts)
|
||||
Source: [`packages/core/agent-loop/src/index.ts:360`](../packages/core/agent-loop/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-agent-spine-demo`
|
||||
|
||||
@@ -112,10 +120,14 @@ Source: [`packages/core/agent-loop/src/index.ts:369`](../packages/core/agent-loo
|
||||
* bridge, simply omits it), `persona` and `toolOrder` to the system-prompt
|
||||
* plugin (the deployment's persona section and the explicit model-facing tool
|
||||
* order), the `tools` object to the tool registry (its presentation `mode`),
|
||||
* `dshHome` to bash environment and local skill discovery, `skills` to the
|
||||
* `dshHome` to bash environment and local skill discovery, `sessionTitle` to
|
||||
* the fallback title service, `skills` to the
|
||||
* skill registry/local provider/tool consumer, `workspaceContext` to the
|
||||
* workspace-context loader, and `toolBash`/`toolTasks` to the model-facing tool
|
||||
* plugins this bundle owns. Owner schemas supply defaults for optional input;
|
||||
* workspace-context loader, `llmRetry` to the bounded request-recovery policy,
|
||||
* and `toolBash`/`toolTasks` to the model-facing tool plugins this bundle owns.
|
||||
* `goals` opts into and configures the persisted goal domain plus its model tool
|
||||
* and same-session driver; `invariants` configures global and package-filtered
|
||||
* relational checks. Owner schemas supply defaults for optional input;
|
||||
* workspace context instead requires an explicit byte budget or `false` because
|
||||
* it changes model-visible input. Producer opt-in stays producer-local:
|
||||
* `toolBash` configures bash only; independently composed producers keep their
|
||||
@@ -134,6 +146,8 @@ export interface Config {
|
||||
tools?: ToolsConfig
|
||||
/** DeepSeek Harness home directory shared by shell context and local skill discovery. */
|
||||
dshHome?: string
|
||||
/** Deterministic fallback and accepted-title limits; omission uses the bundle's example policy. */
|
||||
sessionTitle?: SessionTitleConfig
|
||||
/** Workspace-context loader controls with an explicit byte budget; set `false` for hermetic prompts. */
|
||||
workspaceContext: workspaceContext.Config | false
|
||||
/** Skill registry, local provider, and model-facing consumer config. */
|
||||
@@ -142,6 +156,12 @@ export interface Config {
|
||||
toolBash?: toolBash.Config
|
||||
/** Generic background-task controls; set false to keep the task service without model-facing task tools. */
|
||||
toolTasks?: toolTasks.Config | false
|
||||
/** Global enablement and package-name filters for invariant companions. */
|
||||
invariants?: InvariantConfig
|
||||
/** Opt-in persisted same-session goal stack; set false or omit to leave it unmounted. */
|
||||
goals?: GoalConfig | false
|
||||
/** Bounded transient model-request retry policy. */
|
||||
llmRetry?: llmRetry.Config
|
||||
}
|
||||
|
||||
/** Skill bundle config forwarded to the registry, local provider, and model-facing consumer. */
|
||||
@@ -155,11 +175,19 @@ export interface SkillConfig {
|
||||
/** Model-facing skill catalog and tool settings. */
|
||||
tool?: toolSkill.Config
|
||||
}
|
||||
|
||||
/** Persisted goal domain, model-tool policy, and same-session driver config. */
|
||||
export interface GoalConfig {
|
||||
/** Goal-domain creation defaults. */
|
||||
domain?: GoalDomainConfig
|
||||
/** Model-facing goal-tool authority policy. */
|
||||
tool?: toolGoal.Config
|
||||
}
|
||||
```
|
||||
|
||||
Depends on: [`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`SkillLocal`](../packages/skill/skill-local/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`toolBash`](../packages/bash/tool-bash/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`toolTasks`](../packages/tasks/tool-tasks/src/index.ts) · [`workspaceContext`](../packages/context/workspace-context/src/index.ts)
|
||||
Depends on: [`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfig`](#deepseek-aidsh-goal) · [`InvariantConfig`](#deepseek-aidsh-invariants) · [`llmRetry`](../packages/llm/llm-retry/src/index.ts) · [`SessionTitleConfig`](#deepseek-aidsh-session-title) · [`SkillLocal`](../packages/skill/skill-local/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`toolBash`](../packages/bash/tool-bash/src/index.ts) · [`toolGoal`](../packages/goal/tool-goal/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`toolTasks`](../packages/tasks/tool-tasks/src/index.ts) · [`workspaceContext`](../packages/context/workspace-context/src/index.ts)
|
||||
|
||||
Source: [`packages/examples/agent-spine-demo/src/index.ts:59`](../packages/examples/agent-spine-demo/src/index.ts)
|
||||
Source: [`packages/examples/agent-spine-demo/src/index.ts:87`](../packages/examples/agent-spine-demo/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-bash-local`
|
||||
|
||||
@@ -222,22 +250,28 @@ export interface Config {
|
||||
tools?: ToolsConfig
|
||||
/** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
|
||||
dshHome?: string
|
||||
/** Fallback session-title limits forwarded through agent-spine-demo. */
|
||||
sessionTitle?: NonNullable<agentCore.Config['sessionTitle']>
|
||||
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
|
||||
persistenceRoot?: string
|
||||
/** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
|
||||
persistenceCompression?: JsonlCompression
|
||||
/** Skill registry, local-provider, and model-facing consumer config. */
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-spine-demo. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task control-tool config forwarded through agent-spine-demo. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Bounded transient model-request retry policy forwarded through agent-spine-demo. */
|
||||
llmRetry?: NonNullable<agentCore.Config['llmRetry']>
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
workspaceContext: agentCore.Config['workspaceContext']
|
||||
}
|
||||
```
|
||||
|
||||
Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools)
|
||||
Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`JsonlCompression`](../packages/session-persistence/session-persistence-jsonl/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools)
|
||||
|
||||
Source: [`packages/examples/cli-demo/src/index.ts:22`](../packages/examples/cli-demo/src/index.ts)
|
||||
Source: [`packages/examples/cli-demo/src/index.ts:25`](../packages/examples/cli-demo/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-code-runtime-worker`
|
||||
|
||||
@@ -280,15 +314,25 @@ Source: [`packages/code-runtime/code-runtime-worker/src/index.ts:21`](../package
|
||||
Requires: `llm` · `tokenMeter`
|
||||
|
||||
```ts config-catalog
|
||||
/** Basic compaction configuration; every common field has a deployment default. */
|
||||
export interface BasicCompactConfig {
|
||||
/** Compact at this fraction of the token meter's context window. Defaults to `0.8`. */
|
||||
/** Basic compaction configuration with an optional exact-target policy table. */
|
||||
export interface BasicCompactConfig extends CompactPolicyConfig {
|
||||
/** Exact provider/model overrides; duplicate targets fail plugin load. */
|
||||
modelPolicies?: ModelCompactPolicyConfig[]
|
||||
/** Enable automatic post-step pressure and overflow-recovery listeners. Defaults to `true`. */
|
||||
auto?: boolean
|
||||
}
|
||||
|
||||
/** Policy fields shared by the default policy and exact model overrides. */
|
||||
export interface CompactPolicyConfig {
|
||||
/** Compact at this fraction of the model's context window. Defaults to `0.8`. */
|
||||
thresholdRatio?: number
|
||||
/** Recent surface tokens retained verbatim. Defaults to `floor(contextWindow * 0.16)`. */
|
||||
/** Recent context retained as a fraction of the model's window. Defaults to `0.16`. */
|
||||
retainRatio?: number
|
||||
/** Absolute recent-context budget; mutually exclusive with `retainRatio`. */
|
||||
retainTokens?: number
|
||||
/** Summary provider; `''` resolves the latest routed pair, then the agent pair. Defaults to `''`. */
|
||||
/** Summary provider; set together with `summarizationModel`, or inherit the conversation target. */
|
||||
summarizationProvider?: string
|
||||
/** Summary model; `''` resolves the latest routed pair, then the agent pair. Defaults to `''`. */
|
||||
/** Summary model; set together with `summarizationProvider`, or inherit the conversation target. */
|
||||
summarizationModel?: string
|
||||
/** Provider generation cap for summarization. Defaults to `8192`. */
|
||||
maxTokens?: number
|
||||
@@ -296,12 +340,34 @@ export interface BasicCompactConfig {
|
||||
compactionRetries?: number
|
||||
/** Maximum retries after canonical context overflow; `0` disables recovery. Defaults to `1`. */
|
||||
maxOverflowRetries?: number
|
||||
/** Enable automatic post-step pressure and overflow-recovery listeners. Defaults to `true`. */
|
||||
auto?: boolean
|
||||
}
|
||||
|
||||
/** Exact provider/model override merged over the default compaction policy. */
|
||||
export interface ModelCompactPolicyConfig extends CompactPolicyConfig {
|
||||
/** Registered provider route to match. */
|
||||
provider: string
|
||||
/** Exact routed model id to match within `provider`. */
|
||||
model: string
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/compact/compact-basic/src/types.ts:8`](../packages/compact/compact-basic/src/types.ts)
|
||||
Source: [`packages/compact/compact-basic/src/types.ts:38`](../packages/compact/compact-basic/src/types.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-compact-tool-result-prune`
|
||||
|
||||
```ts config-catalog
|
||||
/** Character-budget policy for deterministic tool-result pruning. */
|
||||
export interface ToolResultPruneConfig {
|
||||
/** Prune when total text exceeds this many Unicode code points. Defaults to `8192`. */
|
||||
thresholdChars?: number
|
||||
/** Maximum leading Unicode code points retained. Defaults to `4096`. */
|
||||
headChars?: number
|
||||
/** Maximum trailing Unicode code points retained. Defaults to `1024`. */
|
||||
tailChars?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/compact/compact-tool-result-prune/src/types.ts:4`](../packages/compact/compact-tool-result-prune/src/types.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-fs-local`
|
||||
|
||||
@@ -333,6 +399,20 @@ Depends on: [`LocalConfig`](#deepseek-aidsh-fs-local)
|
||||
|
||||
Source: [`packages/fs/fs-sandbox/src/index.ts:49`](../packages/fs/fs-sandbox/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-goal`
|
||||
|
||||
Requires: `agents`
|
||||
|
||||
```ts config-catalog
|
||||
/** Deployment defaults for goal creation. */
|
||||
export interface Config {
|
||||
/** Total rounds used when a create request omits its own cap. */
|
||||
defaultMaxGoalRounds?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/goal/goal/src/index.ts:56`](../packages/goal/goal/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-hooks-claude`
|
||||
|
||||
Requires: `bash`
|
||||
@@ -394,6 +474,22 @@ export interface Config {
|
||||
|
||||
Source: [`packages/hooks/hooks-codex/src/index.ts:42`](../packages/hooks/hooks-codex/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-invariants`
|
||||
|
||||
```ts config-catalog
|
||||
/** Runtime invariant selection configured on the service plugin. */
|
||||
export interface Config {
|
||||
/** Global switch; defaults to `true`. */
|
||||
readonly enabled?: boolean
|
||||
/** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */
|
||||
readonly package_allowlist?: string[]
|
||||
/** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */
|
||||
readonly package_blocklist?: string[]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/support/invariants/src/index.ts:15`](../packages/support/invariants/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-jsonrpc`
|
||||
|
||||
Requires: `agents`
|
||||
@@ -438,6 +534,8 @@ export interface Config {
|
||||
reasoningEffort?: 'high' | 'max'
|
||||
/** Advisory models shown by discovery consumers; defaults to V4 Flash and V4 Pro. */
|
||||
models?: DeepSeekCatalogModel[]
|
||||
/** Maximum provider idle time while one stream read is outstanding (default five minutes). */
|
||||
streamIdleTimeoutMs?: number
|
||||
}
|
||||
|
||||
/** One optional model entry advertised by the hand-written adapter. */
|
||||
@@ -448,10 +546,12 @@ export interface DeepSeekCatalogModel {
|
||||
name?: string
|
||||
/** Optional selector detail for deployments with similar model variants. */
|
||||
description?: string
|
||||
/** Known combined request/response context capacity; omitted when deployment metadata is unavailable. */
|
||||
contextWindow?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/llm/llm-deepseek/src/index.ts:33`](../packages/llm/llm-deepseek/src/index.ts)
|
||||
Source: [`packages/llm/llm-deepseek/src/index.ts:34`](../packages/llm/llm-deepseek/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-llm-pi-ai`
|
||||
|
||||
@@ -486,16 +586,14 @@ export interface PiAiProviderProfile {
|
||||
timeoutMs?: number
|
||||
/** WebSocket connection timeout in milliseconds. */
|
||||
websocketConnectTimeoutMs?: number
|
||||
/** Provider SDK retry count. */
|
||||
maxRetries?: number
|
||||
/** Maximum provider-requested retry delay in milliseconds. */
|
||||
maxRetryDelayMs?: number
|
||||
/** Maximum provider idle time while one stream read is outstanding. */
|
||||
streamIdleTimeoutMs?: number
|
||||
}
|
||||
```
|
||||
|
||||
Depends on: `CacheRetention` (`@earendil-works/pi-ai`) · `ThinkingBudgets` (`@earendil-works/pi-ai`) · `ThinkingLevel` (`@earendil-works/pi-ai`) · `Transport` (`@earendil-works/pi-ai`)
|
||||
|
||||
Source: [`packages/llm/llm-pi-ai/src/config.ts:40`](../packages/llm/llm-pi-ai/src/config.ts)
|
||||
Source: [`packages/llm/llm-pi-ai/src/config.ts:48`](../packages/llm/llm-pi-ai/src/config.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-llm-replay`
|
||||
|
||||
@@ -536,10 +634,74 @@ export interface ReplayModelConfig {
|
||||
name?: string
|
||||
/** Optional selector description. */
|
||||
description?: string
|
||||
/** Optional positive integer context capacity published by the replay adapter. */
|
||||
contextWindow?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/support/llm-replay/src/index.ts:375`](../packages/support/llm-replay/src/index.ts)
|
||||
Source: [`packages/support/llm-replay/src/index.ts:385`](../packages/support/llm-replay/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-llm-retry`
|
||||
|
||||
Requires: `agents`
|
||||
|
||||
```ts config-catalog
|
||||
/** Deployment-owned limits and classification for transient request recovery. */
|
||||
export interface Config {
|
||||
/** Maximum transient retries after the first request (default 2). */
|
||||
maxTransientRetries?: number
|
||||
/** Initial local exponential-backoff delay in milliseconds (default 500). */
|
||||
initialDelayMs?: number
|
||||
/** Maximum accepted or locally scheduled delay in milliseconds (default 10000). */
|
||||
maxDelayMs?: number
|
||||
/** Symmetric random multiplier range around one (default 0.1). */
|
||||
jitterRatio?: number
|
||||
/** Stable failure codes eligible for this policy. */
|
||||
retryableCodes?: string[]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/llm/llm-retry/src/index.ts:39`](../packages/llm/llm-retry/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-lsp-local`
|
||||
|
||||
Requires: `lsp`
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin configuration: provider id → local language-server configuration. */
|
||||
export interface Config {
|
||||
/** Non-empty table of stable provider ids to independent local server configurations. */
|
||||
servers: Record<string, LspLocalServerConfig>
|
||||
}
|
||||
|
||||
/** One configured local language server and its host bounds. */
|
||||
export interface LspLocalServerConfig {
|
||||
/** Executable to spawn (absolute, or resolved on PATH at load). */
|
||||
command: string
|
||||
/** Lowercase leading-dot extension → LSP language id (e.g. `{ '.ts': 'typescript' }`). */
|
||||
extensionToLanguage: Record<string, string>
|
||||
/** Arguments passed to the executable (no shell). Default `[]`. */
|
||||
args?: string[]
|
||||
/** Extra env vars merged on top of the scrubbed ambient env. Default `{}`. */
|
||||
env?: Record<string, string>
|
||||
/** Static `initialize` options forwarded to the server. Default `null`. */
|
||||
initializationOptions?: unknown
|
||||
/** Static answer to every `workspace/configuration` item. Default `null`. */
|
||||
configuration?: unknown
|
||||
/** Largest single framed message accepted from the server (bytes). Default 16000000. */
|
||||
maxMessageBytes?: number
|
||||
/** Largest stderr tail retained for diagnostics (bytes). Default 1000000. */
|
||||
maxStderrBytes?: number
|
||||
/** Largest source file this host will open (bytes). Default 4000000. */
|
||||
maxDocumentBytes?: number
|
||||
/** Graceful `shutdown`/`exit` budget before escalation (ms). Default 5000. */
|
||||
shutdownTimeoutMs?: number
|
||||
/** Request-cancel and SIGTERM→SIGKILL grace (ms). Default 2000. */
|
||||
killGraceMs?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/lsp/lsp-local/src/index.ts:85`](../packages/lsp/lsp-local/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-mcp-client`
|
||||
|
||||
@@ -624,6 +786,20 @@ Depends on: [`ApprovalPolicy`](core-data-structures/approval.md) · [`SandboxMod
|
||||
|
||||
Source: [`packages/ui/permission/src/index.ts:83`](../packages/ui/permission/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-plan-mode`
|
||||
|
||||
Requires: `tools` · `systemPrompt`
|
||||
|
||||
```ts config-catalog
|
||||
/** Deployment-owned plan guidance. */
|
||||
export interface PlanModeConfig {
|
||||
/** Guidance rendered as the `plan:policy` prompt section while plan mode is active. */
|
||||
section: string
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/plan/plan-mode/src/index.ts:57`](../packages/plan/plan-mode/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-pty-local`
|
||||
|
||||
Requires: `pty` · `sandbox` · `sandboxPolicy`
|
||||
@@ -760,10 +936,15 @@ export interface Config {
|
||||
* (bash calls, subprocesses). Sessions group under per-cwd subdirectories.
|
||||
*/
|
||||
root: string
|
||||
/** Physical encoding; defaults to checksummed Zstandard frames. */
|
||||
compression?: JsonlCompression
|
||||
}
|
||||
|
||||
/** Physical encoding selected for JSONL session artifacts. */
|
||||
export type JsonlCompression = 'zstd' | 'none'
|
||||
```
|
||||
|
||||
Source: [`packages/session-persistence/session-persistence-jsonl/src/index.ts:24`](../packages/session-persistence/session-persistence-jsonl/src/index.ts)
|
||||
Source: [`packages/session-persistence/session-persistence-jsonl/src/index.ts:37`](../packages/session-persistence/session-persistence-jsonl/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-session-persistence-sqlite`
|
||||
|
||||
@@ -818,6 +999,50 @@ export interface Config {
|
||||
|
||||
Source: [`packages/session-query/session-query/src/config.ts:9`](../packages/session-query/session-query/src/config.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-session-title`
|
||||
|
||||
Requires: `sessions`
|
||||
|
||||
```ts config-catalog
|
||||
/** Required deterministic fallback and accepted-title limits. */
|
||||
export interface Config {
|
||||
/** Maximum whitespace-delimited words in the built-in fallback. */
|
||||
readonly fallbackMaxWords: number
|
||||
/** Maximum UTF-8 bytes in the built-in fallback. */
|
||||
readonly fallbackMaxBytes: number
|
||||
/** Maximum UTF-8 bytes in any accepted title. */
|
||||
readonly maxTitleBytes: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/session-title/session-title/src/index.ts:69`](../packages/session-title/session-title/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-session-title-all-messages-llm`
|
||||
|
||||
Requires: `sessionTitle` · `llm` · `sessions`
|
||||
|
||||
```ts config-catalog
|
||||
/** Required LLM policy; this plugin adds no defaults. */
|
||||
export type Config = SessionTitleLlmConfig
|
||||
```
|
||||
|
||||
Depends on: [`SessionTitleLlmConfig`](../packages/session-title/session-title-llm/src/index.ts)
|
||||
|
||||
Source: [`packages/session-title/session-title-all-messages-llm/src/index.ts:15`](../packages/session-title/session-title-all-messages-llm/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-session-title-first-message-llm`
|
||||
|
||||
Requires: `sessionTitle` · `llm` · `sessions`
|
||||
|
||||
```ts config-catalog
|
||||
/** Required LLM policy; this plugin adds no defaults. */
|
||||
export type Config = SessionTitleLlmConfig
|
||||
```
|
||||
|
||||
Depends on: [`SessionTitleLlmConfig`](../packages/session-title/session-title-llm/src/index.ts)
|
||||
|
||||
Source: [`packages/session-title/session-title-first-message-llm/src/index.ts:15`](../packages/session-title/session-title-first-message-llm/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-skill`
|
||||
|
||||
```ts config-catalog
|
||||
@@ -882,88 +1107,6 @@ export interface Config {
|
||||
|
||||
Source: [`packages/spill/spill-policy/src/index.ts:45`](../packages/spill/spill-policy/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-stdio`
|
||||
|
||||
Requires: `agents` · `userInteraction`
|
||||
|
||||
```ts config-catalog
|
||||
/** Serializable plugin configuration (cordis-native, schemastery). */
|
||||
export interface Config {
|
||||
/** Banner printed once on start, before the first `> ` prompt. */
|
||||
welcome?: string
|
||||
/** Exact shared agent/session identity stdin drives. Defaults to `'main'`. */
|
||||
sessionId?: string
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/ui/stdio/src/index.ts:33`](../packages/ui/stdio/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-stdio-demo`
|
||||
|
||||
```ts config-catalog
|
||||
/**
|
||||
* App config: the swappable per-demo values, each routed to where the app wires
|
||||
* it. `provider`/`model`/`resumeSessionId` configure the pre-created `main` agent (through
|
||||
* {@link @deepseek-ai/dsh-agent-spine-demo}'s forwarded `agents` list); `persona` is
|
||||
* the deployment persona (forwarded to the system-prompt plugin); `toolOrder`
|
||||
* is the explicit model-facing tool order (forwarded to the system-prompt plugin);
|
||||
* fresh sessions use `process.cwd()` as their workspace cwd; resumed sessions
|
||||
* keep their persisted cwd. `persistenceRoot` is the JSONL backend's directory;
|
||||
* `welcome` is the UI banner and `ui` configures terminal mode/presentation.
|
||||
*/
|
||||
export interface Config {
|
||||
/** Provider route for the `main` agent. */
|
||||
provider: string
|
||||
/** Model name for the `main` agent (must have a registered adapter). */
|
||||
model: string
|
||||
/** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */
|
||||
maxParallelToolCalls?: number
|
||||
/** Deployment persona (the system-prompt plugin's `persona` config). */
|
||||
persona?: string
|
||||
/** Explicit model-facing tool order (the system-prompt plugin's `toolOrder` config; see dsh-system-prompt). */
|
||||
toolOrder?: string[]
|
||||
/** Tool-registry config — its presentation `mode` (forwarded through agent-spine-demo; see dsh-tools). */
|
||||
tools?: ToolsConfig
|
||||
/** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
|
||||
dshHome?: string
|
||||
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
|
||||
persistenceRoot?: string
|
||||
/** stdin-chat banner printed once on start. Defaults to `'ready.'`. */
|
||||
welcome?: string
|
||||
/** Terminal front-door selection and pi-tui presentation settings. */
|
||||
ui?: UiConfig
|
||||
/** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-core. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/**
|
||||
* If set, the pre-created agent RESUMES this persisted session id instead of
|
||||
* starting fresh. Sourced from an env var in the leaf `cordis.yml`
|
||||
* (`resumeSessionId: !!js process.env.RESUME_SESSION_ID`).
|
||||
*/
|
||||
resumeSessionId?: string
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
workspaceContext: agentCore.Config['workspaceContext']
|
||||
}
|
||||
|
||||
/** App-level terminal selection with nested TUI presentation settings. */
|
||||
export interface UiConfig {
|
||||
/** Select a concrete front door or infer it from the process streams. */
|
||||
mode?: TerminalMode
|
||||
/** Settings forwarded only when the pi-tui front door is selected. */
|
||||
tui?: uiTui.TuiConfig
|
||||
}
|
||||
|
||||
/** Terminal front door selected by the app bundle. */
|
||||
export type TerminalMode = 'auto' | 'readline' | 'tui'
|
||||
```
|
||||
|
||||
Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`uiTui`](../packages/ui/tui/src/index.ts)
|
||||
|
||||
Source: [`packages/examples/stdio-demo/src/index.ts:75`](../packages/examples/stdio-demo/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-subagent-acp`
|
||||
|
||||
Requires: `subagents`
|
||||
@@ -978,8 +1121,11 @@ export interface Config {
|
||||
/** Arguments passed to {@link command}. */
|
||||
args: string[]
|
||||
/**
|
||||
* Working directory for the child process and its ACP session. Defaults to
|
||||
* the parent process's cwd when omitted.
|
||||
* Working directory override for the child process and its ACP session.
|
||||
* Must be non-empty; a relative path resolves against the harness launch
|
||||
* directory at load, and the result must be an existing directory. When
|
||||
* omitted, each child inherits its delegating parent session's cwd — and
|
||||
* starting one from a parent session that has no cwd fails.
|
||||
*/
|
||||
cwd?: string
|
||||
/**
|
||||
@@ -1001,7 +1147,7 @@ export interface Config {
|
||||
* before the parent escalates to a signal.
|
||||
*/
|
||||
disposeEofGraceMs?: number
|
||||
/** Grace period (ms) between `SIGTERM` and the `SIGKILL` escalation on dispose. */
|
||||
/** Termination confirmation window (ms), including forced exit on every platform. */
|
||||
disposeGraceMs?: number
|
||||
}
|
||||
|
||||
@@ -1009,7 +1155,7 @@ export interface Config {
|
||||
export type PermissionPolicy = 'allow' | 'reject'
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent-acp/src/index.ts:18`](../packages/subagent/subagent-acp/src/index.ts)
|
||||
Source: [`packages/subagent/subagent-acp/src/index.ts:21`](../packages/subagent/subagent-acp/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-subagent-fork`
|
||||
|
||||
@@ -1058,7 +1204,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/core/system-prompt/src/index.ts:143`](../packages/core/system-prompt/src/index.ts)
|
||||
Source: [`packages/core/system-prompt/src/index.ts:147`](../packages/core/system-prompt/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-time-context`
|
||||
|
||||
@@ -1079,11 +1225,8 @@ Source: [`packages/context/time-context/src/index.ts:19`](../packages/context/ti
|
||||
## `@deepseek-ai/dsh-token-meter`
|
||||
|
||||
```ts config-catalog
|
||||
/** Token-meter plugin configuration. */
|
||||
export interface TokenMeterConfig {
|
||||
/** Service-wide context-window capacity in tokens. Defaults to `128000`. */
|
||||
contextWindow?: number
|
||||
}
|
||||
/** Token-meter plugin configuration; the fixed estimator has no settings. */
|
||||
export type TokenMeterConfig = Record<string, never>
|
||||
```
|
||||
|
||||
Source: [`packages/llm/token-meter/src/types.ts:10`](../packages/llm/token-meter/src/types.ts)
|
||||
@@ -1164,6 +1307,58 @@ export interface Config {
|
||||
|
||||
Source: [`packages/fs/tool-fs-search/src/index.ts:62`](../packages/fs/tool-fs-search/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-tool-goal`
|
||||
|
||||
Requires: `agents` · `goals` · `tools` · `systemPrompt`
|
||||
|
||||
```ts config-catalog
|
||||
/** Model policy and hard lower bounds for goal-state updates. */
|
||||
export interface Config {
|
||||
/** Minimum admitted goal rounds before the model may self-report `blocked`. */
|
||||
blockedAfterConsecutiveRounds?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/goal/tool-goal/src/index.ts:27`](../packages/goal/tool-goal/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-tool-lsp`
|
||||
|
||||
Requires: `tools` · `lsp` · `systemPrompt`
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin configuration: result caps and the timeout budget. */
|
||||
export interface Config {
|
||||
/** Largest number of rendered locations before an omission marker (default 100). */
|
||||
maxLocations?: number
|
||||
/** Largest complete rendered result in characters, including truncation metadata (default 16000). */
|
||||
maxResultChars?: number
|
||||
/** Tool-call timeout budget in ms (default 60000). */
|
||||
timeoutMs?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/lsp/tool-lsp/src/index.ts:58`](../packages/lsp/tool-lsp/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-tool-ralph`
|
||||
|
||||
Requires: `tools` · `workflows` · `subagents` · `systemPrompt`
|
||||
|
||||
```ts config-catalog
|
||||
/** Deployment policy for the fixed Ralph workflow. */
|
||||
export interface Config {
|
||||
/** Fresh structured-output provider used for every round (default `spawn`). */
|
||||
subagentProvider?: string
|
||||
/** Default and deployment ceiling for one call's round count (default 256). */
|
||||
maxRounds?: number
|
||||
/** Maximum serialized characters in one structured handoff (default 16384). */
|
||||
maxHandoffChars?: number
|
||||
/** Maximum characters in a successful parent-facing terminal text (default 16384). */
|
||||
maxResultChars?: number
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/workflow/tool-ralph/src/index.ts:22`](../packages/workflow/tool-ralph/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-tool-skill`
|
||||
|
||||
Requires: `tools` · `skills`
|
||||
@@ -1209,8 +1404,7 @@ export interface Config {
|
||||
/**
|
||||
* Tool filter applied to every child. Filtered tools disappear from its
|
||||
* prompt and reject execution. Requires the provider's `toolFilter`
|
||||
* capability; unknown names fail startup. Children otherwise see this tool,
|
||||
* so deny it or set `maxDepth` to bound recursion.
|
||||
* capability; unknown names fail startup.
|
||||
*/
|
||||
toolFilter?: {
|
||||
/** Global tool names the child keeps; everything else is removed. */
|
||||
@@ -1219,10 +1413,15 @@ export interface Config {
|
||||
deny?: string[]
|
||||
}
|
||||
/**
|
||||
* Maximum child depth. Requires the provider's `depthLimit` capability and a
|
||||
* non-negative safe integer. Omission is unbounded.
|
||||
* Maximum child depth: a non-negative safe integer (default `3`; `0` forbids
|
||||
* delegation entirely), or `'provider-managed'` to send no cap. A numeric cap
|
||||
* requires the provider's `depthLimit` capability (mount fails loud
|
||||
* otherwise). The provider checks the calling agent's current depth at every
|
||||
* start; the tool remains model-visible so runtime policy owns rejection.
|
||||
* `'provider-managed'` is for an out-of-process provider (ACP) whose
|
||||
* recursion budget belongs to the child harness's own deployment.
|
||||
*/
|
||||
maxDepth?: number
|
||||
maxDepth?: number | 'provider-managed'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1304,11 +1503,11 @@ export interface Config {
|
||||
export type ToolPresentationMode = 'native' | 'code' | 'both'
|
||||
```
|
||||
|
||||
Source: [`packages/core/tools/src/index.ts:382`](../packages/core/tools/src/index.ts)
|
||||
Source: [`packages/core/tools/src/index.ts:419`](../packages/core/tools/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-tui`
|
||||
|
||||
Requires: `agents` · `userInteraction` · `tools`
|
||||
Requires: `agents` · `commands` · `userInteraction` · `tools` · `llm` · `systemPrompt` · `tokenMeter`
|
||||
|
||||
```ts config-catalog
|
||||
/** Serializable plugin configuration. */
|
||||
@@ -1323,14 +1522,20 @@ export interface Config extends TuiConfig {
|
||||
export interface TuiConfig {
|
||||
/** Render model reasoning blocks. */
|
||||
showReasoning?: boolean
|
||||
/** Maximum tool-output lines shown before the card is collapsed. */
|
||||
/** Maximum tool-card body lines retained in its collapsed head/tail preview. */
|
||||
maxToolOutputLines?: number
|
||||
/** Maximum options visible at once in a user-question dialog. */
|
||||
/** Maximum options visible at once in a user-question panel. */
|
||||
maxQuestionOptions?: number
|
||||
/** User-question dialog width in terminal columns. */
|
||||
/** Maximum models visible at once in the model selector. */
|
||||
maxModelOptions?: number
|
||||
/** User-question panel width in terminal columns, clamped to the terminal. */
|
||||
questionDialogWidth?: number
|
||||
/** User-question dialog maximum height in terminal rows. */
|
||||
/** User-question panel maximum height in terminal rows. */
|
||||
questionDialogMaxHeight?: number
|
||||
/** Model-selector width in terminal columns. */
|
||||
modelDialogWidth?: number
|
||||
/** Model-selector maximum height in terminal rows. */
|
||||
modelDialogMaxHeight?: number
|
||||
/** Show the terminal's hardware cursor at the pi editor's IME marker. */
|
||||
showHardwareCursor?: boolean
|
||||
/** Apply the built-in ANSI color palette. */
|
||||
@@ -1340,7 +1545,55 @@ export interface TuiConfig {
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/ui/tui/src/index.ts:100`](../packages/ui/tui/src/index.ts)
|
||||
Source: [`packages/ui/tui/src/index.ts:129`](../packages/ui/tui/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-tui-demo`
|
||||
|
||||
```ts config-catalog
|
||||
/** App config routed to the spine, TUI, configured agent, and JSONL backend. */
|
||||
export interface Config {
|
||||
/** Provider route for the `main` agent. */
|
||||
provider: string
|
||||
/** Model name for the `main` agent; a matching adapter must be registered. */
|
||||
model: string
|
||||
/** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */
|
||||
maxParallelToolCalls?: number
|
||||
/** Deployment persona forwarded to the system-prompt plugin. */
|
||||
persona?: string
|
||||
/** Explicit model-facing tool order forwarded to the system-prompt plugin. */
|
||||
toolOrder?: string[]
|
||||
/** Tool-registry presentation config forwarded through agent-spine-demo. */
|
||||
tools?: ToolsConfig
|
||||
/** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
|
||||
dshHome?: string
|
||||
/** Fallback session-title limits forwarded through agent-spine-demo. */
|
||||
sessionTitle?: NonNullable<agentCore.Config['sessionTitle']>
|
||||
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
|
||||
persistenceRoot?: string
|
||||
/** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
|
||||
persistenceCompression?: JsonlCompression
|
||||
/** TUI subtitle rendered on start. Defaults to `ready.`. */
|
||||
welcome?: string
|
||||
/** Full-screen TUI presentation settings. */
|
||||
ui?: uiTui.TuiConfig
|
||||
/** Skill registry, local-provider, and model-facing consumer config. */
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-spine-demo. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task controls forwarded through agent-spine-demo; set false to omit them. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and command. */
|
||||
goals?: agentCore.GoalConfig | false
|
||||
/** Persisted session id to resume instead of creating a fresh session. */
|
||||
resumeSessionId?: string
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
workspaceContext: agentCore.Config['workspaceContext']
|
||||
}
|
||||
```
|
||||
|
||||
Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`JsonlCompression`](../packages/session-persistence/session-persistence-jsonl/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`uiTui`](../packages/ui/tui/src/index.ts)
|
||||
|
||||
Source: [`packages/examples/tui-demo/src/index.ts:33`](../packages/examples/tui-demo/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-user-approval`
|
||||
|
||||
@@ -1371,7 +1624,7 @@ export interface Config {
|
||||
export type ApprovalPolicy = 'ask' | 'never'
|
||||
```
|
||||
|
||||
Source: [`packages/ui/user-approval/src/index.ts:214`](../packages/ui/user-approval/src/index.ts)
|
||||
Source: [`packages/ui/user-approval/src/index.ts:198`](../packages/ui/user-approval/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-web`
|
||||
|
||||
@@ -1537,9 +1790,20 @@ Source: [`packages/context/workspace-context/src/config.ts:16`](../packages/cont
|
||||
These load from a `cordis.yml` entry with no `config:` block; they declare no config surface.
|
||||
|
||||
- `@deepseek-ai/dsh-agent` ([`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-connection` ([`packages/client/connection/src/index.ts`](../packages/client/connection/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-i18n` ([`packages/client/i18n/src/index.ts`](../packages/client/i18n/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-runtime` ([`packages/client/runtime/src/index.ts`](../packages/client/runtime/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-ui-conversation` ([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-ui-layout` ([`packages/client/ui-layout/src/index.ts`](../packages/client/ui-layout/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-ui-sidebar` ([`packages/client/ui-sidebar/src/index.ts`](../packages/client/ui-sidebar/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-ui-theme` ([`packages/client/ui-theme/src/index.ts`](../packages/client/ui-theme/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-ui-trajectory` ([`packages/client/ui-trajectory/src/index.ts`](../packages/client/ui-trajectory/src/index.ts))
|
||||
- `@deepseek-ai/dsh-command-goal` — requires `commands` · `goals` ([`packages/goal/command-goal/src/index.ts`](../packages/goal/command-goal/src/index.ts))
|
||||
- `@deepseek-ai/dsh-commands` ([`packages/ui/commands/src/index.ts`](../packages/ui/commands/src/index.ts))
|
||||
- `@deepseek-ai/dsh-fs-policy` ([`packages/fs/fs-policy/src/index.ts`](../packages/fs/fs-policy/src/index.ts))
|
||||
- `@deepseek-ai/dsh-invariants` — requires `sessions` ([`packages/support/invariants/src/index.ts`](../packages/support/invariants/src/index.ts))
|
||||
- `@deepseek-ai/dsh-goal-session` — requires `agents` · `goals` · `sessions` ([`packages/goal/goal-session/src/index.ts`](../packages/goal/goal-session/src/index.ts))
|
||||
- `@deepseek-ai/dsh-llm` ([`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts))
|
||||
- `@deepseek-ai/dsh-lsp` ([`packages/lsp/lsp/src/index.ts`](../packages/lsp/lsp/src/index.ts))
|
||||
- `@deepseek-ai/dsh-pty` ([`packages/pty/pty/src/index.ts`](../packages/pty/pty/src/index.ts))
|
||||
- `@deepseek-ai/dsh-session` ([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts))
|
||||
- `@deepseek-ai/dsh-subagent` ([`packages/subagent/subagent/src/index.ts`](../packages/subagent/subagent/src/index.ts))
|
||||
@@ -1572,15 +1836,22 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
|
||||
- `@deepseek-ai/dsh-agent-loop-testkit` ([`packages/support/agent-loop-testkit/src/index.ts`](../packages/support/agent-loop-testkit/src/index.ts))
|
||||
- `@deepseek-ai/dsh-app-boot` ([`packages/ui/app-boot/src/index.ts`](../packages/ui/app-boot/src/index.ts))
|
||||
- `@deepseek-ai/dsh-brand` ([`packages/util/brand/src/index.ts`](../packages/util/brand/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-ui-primitives` ([`packages/client/ui-primitives/src/index.ts`](../packages/client/ui-primitives/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-ui-slots` ([`packages/client/ui-slots/src/index.ts`](../packages/client/ui-slots/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-web` ([`packages/client/web/src/index.ts`](../packages/client/web/src/index.ts))
|
||||
- `@deepseek-ai/dsh-client-web-react` ([`packages/client/web-react/src/index.ts`](../packages/client/web-react/src/index.ts))
|
||||
- `@deepseek-ai/dsh-helper` ([`packages/sdk/helper/src/index.ts`](../packages/sdk/helper/src/index.ts))
|
||||
- `@deepseek-ai/dsh-home` ([`packages/util/home/src/index.ts`](../packages/util/home/src/index.ts))
|
||||
- `@deepseek-ai/dsh-hook-protocol` ([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts))
|
||||
- `@deepseek-ai/dsh-host-apiproxy` ([`packages/host/apiproxy/src/index.ts`](../packages/host/apiproxy/src/index.ts))
|
||||
- `@deepseek-ai/dsh-host-runtime` ([`packages/host/runtime/src/index.ts`](../packages/host/runtime/src/index.ts))
|
||||
- `@deepseek-ai/dsh-host-webserver` ([`packages/host/webserver/src/index.ts`](../packages/host/webserver/src/index.ts))
|
||||
- `@deepseek-ai/dsh-jsonrpc-demo` ([`packages/examples/jsonrpc-demo/src/index.ts`](../packages/examples/jsonrpc-demo/src/index.ts))
|
||||
- `@deepseek-ai/dsh-loader-smoke` ([`packages/support/loader-smoke/src/index.ts`](../packages/support/loader-smoke/src/index.ts))
|
||||
- `@deepseek-ai/dsh-paths` ([`packages/util/paths/src/index.ts`](../packages/util/paths/src/index.ts))
|
||||
- `@deepseek-ai/dsh-retention` ([`packages/util/retention/src/index.ts`](../packages/util/retention/src/index.ts))
|
||||
- `@deepseek-ai/dsh-scope` ([`packages/core/scope/src/index.ts`](../packages/core/scope/src/index.ts))
|
||||
- `@deepseek-ai/dsh-scripts` ([`packages/sdk/scripts/src/index.ts`](../packages/sdk/scripts/src/index.ts))
|
||||
- `@deepseek-ai/dsh-session-title-llm` ([`packages/session-title/session-title-llm/src/index.ts`](../packages/session-title/session-title-llm/src/index.ts))
|
||||
- `@deepseek-ai/dsh-subagent-inprocess` ([`packages/subagent/subagent-inprocess/src/index.ts`](../packages/subagent/subagent-inprocess/src/index.ts))
|
||||
- `@deepseek-ai/dsh-subagent-subprocess` ([`packages/subagent/subagent-subprocess/src/index.ts`](../packages/subagent/subagent-subprocess/src/index.ts))
|
||||
- `@deepseek-ai/dsh-telemetry` ([`packages/sdk/telemetry/src/index.ts`](../packages/sdk/telemetry/src/index.ts))
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
adding-a-tool.md: 68a8449bc189497b917efe678837d757f85aaf75
|
||||
adding-a-tool.zh.md: 003534e04550bfbee6740aa3b6bee02ac2cdc237
|
||||
adding-a-tool.md: 9e8fa1287c854f33f62a4c6a1ed93adfccc19471
|
||||
adding-a-tool.zh.md: be4e0800036ac9bde949a11d8a35e49cfb92efd7
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](adding-a-tool.zh.md)
|
||||
|
||||
How to give the model a new capability. Reference implementations: `examples/echo-agent/src/echo-tool.ts` (minimal) and `packages/bash/tool-bash` (production-grade, three-package seam).
|
||||
How to give the model a new capability. The minimal shape below shows the contract; `packages/bash/tool-bash` is the production-grade three-package seam.
|
||||
|
||||
## The minimal shape
|
||||
|
||||
@@ -25,7 +25,7 @@ export function apply(ctx: Context) {
|
||||
async execute(args, exec) {
|
||||
// args is TYPED from the schema: { path: string; limit?: number }
|
||||
// exec carries immutable identity + token; signal is the operational field
|
||||
return [{ type: 'text', text: await readFile(args.path, 'utf8') }]
|
||||
return [{ type: 'text', text: await readFile(args.path, { encoding: 'utf8', signal: exec.signal }) }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -37,7 +37,7 @@ Registration is effect-based: disposing the plugin fiber unregisters the tool (w
|
||||
|
||||
- **Args are validated for you.** `defineTool` validates the model-generated `arguments` against the `SchemaSpec` before `execute` runs (type, required keys, enum membership, nested objects/arrays — [runtime arg validation](../../.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.md)), so inside `execute` the args already match `InferArgs`. You still hand-check value constraints the DSL can't express (non-empty strings, positive numbers, cross-field rules); throw a descriptive Error for those. Raw JSON-Schema tools registered directly (MCP) are NOT validated by the harness — they validate their own input.
|
||||
- **Registration borrows your readonly definition.** A typed same-process contribution is not a serialization boundary; do not mutate its schema or replace callbacks after registration. `schemas()` materializes only the explicit model-facing projection. To hot-swap a tool, dispose its owning effect and register the replacement; mutable state inside the callback's closure remains ordinary plugin state.
|
||||
- **Execution identity is protected.** The registry materializes `arguments` as detached lossless JSON in one recursive pass, freezes that value before policy starts, and assigns an opaque `exec.token`; `callId`, `name`, `arguments`, `agent`, `token`, and an optional enclosing-transport `parent` token stay immutable through dispatch. `parent` is identity-only and exposes no live outer execution. Treat `args` as readonly input. An around-dispatch wrapper may add, replace, or remove only `exec.signal` to impose cancellation or a deadline.
|
||||
- **Execution identity is protected.** The registry materializes `arguments` as detached lossless JSON in one recursive pass, freezes that value before policy starts, and assigns an opaque `exec.token`; `callId`, `name`, `arguments`, `agent`, `token`, the required caller-owned `signal`, and an optional enclosing-transport `parent` token stay immutable through dispatch. `parent` is identity-only and exposes no live outer execution. Treat `args` as readonly input. Only an around-dispatch wrapper receives a mutable view, and it may replace and restore the required `exec.signal` to impose a deadline but cannot remove it.
|
||||
- **Throwing or returning non-JSON data means `isError`.** The registry catches throws and materializes the final result before observers run. A malformed or non-JSON result becomes `{ isError: true }`, preventing a live success that cannot be logged. Throw for infrastructure failures; report domain failures in result text when the model must interpret them.
|
||||
- **Honor `exec.signal`.** Cancel in-flight work when it fires.
|
||||
- **Attach durable card data with `meta` (optional).** `execute` may return `{ content, meta }` instead of a bare `ContentBlock[]` — `meta` is a JSON-serializable payload the core treats as opaque, persisted on the `tool/result` event and handed back to your `presentResult` (so a card that needs more than `args`, like `write`/`edit`'s applied-hunk diff, survives a session replay). Keep UI-only data here, never in the model-facing `content`.
|
||||
@@ -45,7 +45,7 @@ Registration is effect-based: disposing the plugin fiber unregisters the tool (w
|
||||
|
||||
## Long-running work
|
||||
|
||||
Gate `run_in_background` with producer config, reject a pre-aborted call, then register through `ctx.tasks.start({ kind, label, owner: exec.agent, run })`. The runtime validates ownership and control-surface availability before `run()` starts work, then supplies the id, session fence, generic control tools, notices, and owner cleanup.
|
||||
Gate `run_in_background` with producer config, then register through `ctx.tasks.start({ kind, label, owner: exec.agent, run })`. The registry skips a pre-aborted invocation before the producer body; the runtime validates ownership and control-surface availability before `run()` starts work, then supplies the id, session fence, generic control tools, notices, and owner cleanup.
|
||||
|
||||
The producer supplies synchronous `cancel`, non-rejecting `done` that settles after resource cleanup, and optional consuming `readOutput` with bounded-output formatting. Once the id is returned, use a task-owned cancellation signal rather than `exec.signal`. See the [background task runtime Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) and `dsh-tool-bash` for a stream producer.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](adding-a-tool.md) | 中文
|
||||
|
||||
如何为模型赋予一项新能力。参考实现:`examples/echo-agent/src/echo-tool.ts`(最小化)和 `packages/bash/tool-bash`(生产级,由三个包(package)构成的 seam)。
|
||||
如何为模型赋予一项新能力。下文的最小形态展示这项契约;`packages/bash/tool-bash` 是生产级、由三个包(package)构成的 seam。
|
||||
|
||||
## 最小形态
|
||||
|
||||
@@ -25,7 +25,7 @@ export function apply(ctx: Context) {
|
||||
async execute(args, exec) {
|
||||
// args is TYPED from the schema: { path: string; limit?: number }
|
||||
// exec carries immutable identity + token; signal is the operational field
|
||||
return [{ type: 'text', text: await readFile(args.path, 'utf8') }]
|
||||
return [{ type: 'text', text: await readFile(args.path, { encoding: 'utf8', signal: exec.signal }) }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export function apply(ctx: Context) {
|
||||
|
||||
- **参数已为你校验。** `defineTool` 在 `execute` 运行前,会根据 `SchemaSpec` 校验模型生成的 `arguments`(类型、必填键、枚举成员、嵌套对象/数组——见[运行时参数校验](../../.agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.md)),因此 `execute` 内部的 args 已匹配 `InferArgs`。你仍需手动检查 DSL 无法表达的值约束(非空字符串、正数、跨字段规则),对这些情况抛出描述性 Error。直接注册的原始 JSON-Schema 工具(MCP)不由 harness 校验,它们自行校验输入。
|
||||
- **注册借用你的只读定义。** 类型化的同进程贡献不是序列化边界;注册后不要修改其 schema 或替换回调。`schemas()` 只物化显式的模型可见投影。如需热替换工具,请 dispose 其所属副作用并注册替代品;回调闭包内的可变状态仍是普通的插件状态。
|
||||
- **执行身份受保护。** 注册表在一次递归遍历中将 `arguments` 物化为分离的无损 JSON,在策略开始前冻结该值,并分配一个不透明的 `exec.token`;`callId`、`name`、`arguments`、`agent`、`token` 以及可选的外层传输 `parent` token 在整个分发过程中保持不可变。`parent` 仅用于身份标识,不暴露活跃的外层执行。请将 `args` 视为只读输入。around-dispatch 包装器只能添加、替换或移除 `exec.signal`,以施加取消或截止时间。
|
||||
- **执行身份受保护。** 注册表在一次递归遍历中将 `arguments` 物化为分离的无损 JSON,在策略开始前冻结该值,并分配一个不透明的 `exec.token`;`callId`、`name`、`arguments`、`agent`、`token`、必填且由调用方持有的 `signal`,以及可选的外层传输 `parent` token 在整个分发过程中保持不可变。`parent` 仅用于身份标识,不暴露活跃的外层执行。请将 `args` 视为只读输入。只有 around-dispatch 包装器会收到可变视图;它可以替换并恢复必填的 `exec.signal` 以施加截止时间,但不能移除该信号。
|
||||
- **抛出异常或返回非 JSON 数据意味着 `isError`。** 注册表捕获异常,并在观察者运行前物化最终结果。格式错误或非 JSON 的结果变为 `{ isError: true }`,防止出现无法记录的活跃成功。基础设施故障请抛异常;当模型需要解读领域失败时,请在结果文本中报告。
|
||||
- **遵守 `exec.signal`。** 信号触发时取消进行中的工作。
|
||||
- **使用 `meta` 附加持久化的卡片数据(可选)。** `execute` 可以返回 `{ content, meta }` 而非裸的 `ContentBlock[]`。`meta` 是 JSON 可序列化的载荷,核心将其视为不透明数据,持久化在 `tool/result` 事件上并回传给你的 `presentResult`(这样需要 `args` 之外信息的卡片——如 `write`/`edit` 的已应用 hunk diff——在会话回放中依然存活)。仅在此处放 UI 数据,绝不放入模型可见的 `content`。
|
||||
@@ -45,7 +45,7 @@ export function apply(ctx: Context) {
|
||||
|
||||
## 长时间运行的工作
|
||||
|
||||
通过 producer 配置控制 `run_in_background`,拒绝已预先中止的调用,然后使用 `ctx.tasks.start({ kind, label, owner: exec.agent, run })` 注册任务。运行时会在 `run()` 启动工作前校验 owner 和控制面是否可用,随后提供 id、会话围栏、通用控制工具、通知和 owner cleanup。
|
||||
通过 producer 配置控制 `run_in_background`,然后使用 `ctx.tasks.start({ kind, label, owner: exec.agent, run })` 注册任务。注册表会在进入 producer 主体前跳过已预先中止的调用;运行时会在 `run()` 启动工作前校验 owner 和控制面是否可用,随后提供 id、会话围栏、通用控制工具、通知和 owner cleanup。
|
||||
|
||||
producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及可选的消费式 `readOutput`(负责有界输出的格式化)。返回 id 后,应使用 task 自有的取消信号,而不是 `exec.signal`。流式 producer 的示例和完整契约见[后台 task 运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)与 `dsh-tool-bash`。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
extension-cookbook.md: 37793e4e76bf5171c759ca78be473912101bd9f4
|
||||
extension-cookbook.zh.md: 8f170f225b55721c78ef27c0e87e481b5cb00f64
|
||||
extension-cookbook.md: 8873cac21960e2e2efe0e8c6c5868c3a8e7ee75c
|
||||
extension-cookbook.zh.md: f34e9f2fa707be69b13ac408cc1ede1a86310fae
|
||||
|
||||
@@ -87,7 +87,7 @@ export function apply(ctx: Context) {
|
||||
|
||||
## Runnable wirings
|
||||
|
||||
Six runnable leaves load their plugin trees from `cordis.yml`: [`examples/echo-agent`](../../examples/echo-agent) (mock model + echo tool, `pnpm run demo:echo`), [`examples/repl-agent`](../../examples/repl-agent) (DeepSeek V4 + coding tools through a line-oriented readline REPL, `pnpm run demo:repl`), [`examples/tui-agent`](../../examples/tui-agent) (the same coding composition through full-screen pi-tui, `pnpm run demo:tui`), [`examples/headless-agent`](../../examples/headless-agent) (the same capability class behind a one-shot task and DSH-native output, `pnpm run demo:headless -- "task"`), [`examples/cordis-agent`](../../examples/cordis-agent) (self-inspection and dynamic plugin mounting, `pnpm run demo:cordis`), and [`examples/acp-agent`](../../examples/acp-agent) (an ACP server over JSON-RPC stdio, `pnpm run demo:acp`). The terminal leaves load [`@deepseek-ai/dsh-stdio-demo`](../../packages/examples/stdio-demo), the headless leaf loads [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo), the ACP leaf loads [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and all three app packages share [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo).
|
||||
Runnable leaves load their plugin trees from `examples/*/cordis.yml`; the root `demo:*` scripts and those leaf directories are the authoritative inventory. Interactive leaves use [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo), non-interactive leaves use [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo), ACP leaves use [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and the app packages share [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo).
|
||||
|
||||
## The feature → mechanism map
|
||||
|
||||
@@ -98,7 +98,7 @@ Every product feature maps to a listener on a documented extension seam — the
|
||||
| Product feature | Plugin mechanism |
|
||||
|---|---|
|
||||
| Hook system (user + project level) | listeners on `agent/session-start`, `agent/prompt-submit`, `agent/request`, `agent/step-result`, `tools/pre-execute`, `tools/post-execute`, `agent/turn-continuation` — each interception waterfall returns a typed Decision; the `dsh-hooks-claude` / `dsh-hooks-codex` bridges map hook config files onto these seams |
|
||||
| `/goal` | force-continue via `agent/turn-continuation` + `steer()` reminders |
|
||||
| `/goal` | `ctx.goals` owns durable state, `dsh-goal-session` schedules same-session rounds through the public `Agent`, and separate command/tool producers expose human/model control |
|
||||
| `/loop` | on the `turn/end` session event, `send()` the next iteration; or force-continue |
|
||||
| Dynamic workflow | `ctx.workflows` + the worker-thread engine + the `workflow` tool; structured in-process children enforce output with scoped prompt/tool registrations, a monotonic tool guard, final `tools/result` commit (including enclosing `run_code`), and terminal `agent/turn-stop` |
|
||||
| Queued + steering messages | core `Agent.send()` / `Agent.steer()` |
|
||||
@@ -113,7 +113,7 @@ Every product feature maps to a listener on a documented extension seam — the
|
||||
| Monotonic terminal turn policy | return `{ action: 'stop' }` from serial `agent/turn-stop`, after continuation and steering have already been folded |
|
||||
| Subprocess sandbox (landlock / sandbox-exec) | use a `ctx.sandbox` backend through `dsh-bash-sandbox`; use `tools/pre-execute` for capability-level denial |
|
||||
| Permission system / AskUserQuestion | return `ask` from `tools/pre-execute` and answer through `ctx.approval`; register a separate model-facing ask tool for ordinary user questions |
|
||||
| Plan mode | `tools/pre-execute` (deny writes) + a mode prompt section via `ctx.systemPrompt.section()` or `agent.inject()` (model-visible ⟺ logged: `agent/request` shapes call config only) |
|
||||
| Plan mode | Shipped: [`@deepseek-ai/dsh-plan-mode`](../../packages/plan/plan-mode/README.md) — logged `plan/mode` state, the `plan:policy` guidance section, `/plan [message]`, and the user-reviewed `exit_plan_mode` exit; enforcement stays on the independent sandbox/approval axes |
|
||||
| Sub-agent delegation | the `ctx.subagents` provider registry (`dsh-subagent-spawn`/`-fork`/`-acp`) + `dsh-tool-subagent` exposing one configured provider to the model |
|
||||
| MCP | one plugin per server: discover tools → `ctx.tools.register()` |
|
||||
| Skills | section + tool registration; `inject()` skill content on invocation |
|
||||
|
||||
@@ -87,7 +87,7 @@ export function apply(ctx: Context) {
|
||||
|
||||
## 可运行的组装示例
|
||||
|
||||
六个可运行叶子从 `cordis.yml` 加载各自的插件树:[`examples/echo-agent`](../../examples/echo-agent)(mock 模型 + echo 工具,`pnpm run demo:echo`)、[`examples/repl-agent`](../../examples/repl-agent)(DeepSeek V4 + coding 工具,通过面向行的 readline REPL 交互,`pnpm run demo:repl`)、[`examples/tui-agent`](../../examples/tui-agent)(通过全屏 pi-tui 复用相同的 coding 组装,`pnpm run demo:tui`)、[`examples/headless-agent`](../../examples/headless-agent)(同类能力通过单次任务和 DSH 原生输出运行,`pnpm run demo:headless -- "task"`)、[`examples/cordis-agent`](../../examples/cordis-agent)(自我检查和动态插件挂载,`pnpm run demo:cordis`)与 [`examples/acp-agent`](../../examples/acp-agent)(通过 JSON-RPC stdio 暴露的 ACP 服务器,`pnpm run demo:acp`)。终端叶子加载 [`@deepseek-ai/dsh-stdio-demo`](../../packages/examples/stdio-demo),headless 叶子加载 [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo),ACP 叶子加载 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),三个 app 包都通过 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 共享主干。
|
||||
可运行叶子从 `examples/*/cordis.yml` 加载各自的插件树;根目录的 `demo:*` 脚本和这些叶子目录是权威清单。交互式叶子使用 [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo),非交互式叶子使用 [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo),ACP 叶子使用 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),应用包共享 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo)。
|
||||
|
||||
## 功能→机制映射
|
||||
|
||||
@@ -98,7 +98,7 @@ export function apply(ctx: Context) {
|
||||
| 产品功能 | 插件机制 |
|
||||
|---|---|
|
||||
| 钩子系统(用户级 + 项目级) | `agent/session-start`、`agent/prompt-submit`、`agent/request`、`agent/step-result`、`tools/pre-execute`、`tools/post-execute`、`agent/turn-continuation` 上的监听器——每个拦截 waterfall 返回一个类型化 Decision;`dsh-hooks-claude` / `dsh-hooks-codex` 桥接器将钩子配置文件映射到这些 seam 上 |
|
||||
| `/goal` | 通过 `agent/turn-continuation` 强制继续 + `steer()` 提醒 |
|
||||
| `/goal` | `ctx.goals` 管理持久状态,`dsh-goal-session` 通过公共 `Agent` 调度同会话回合,独立的命令/工具生产方分别提供人类/模型控制 |
|
||||
| `/loop` | 在 `turn/end` 会话事件上 `send()` 下一次迭代;或强制继续 |
|
||||
| 动态工作流 | `ctx.workflows` + worker-thread 引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的 prompt/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和终端 `agent/turn-stop` 来强制输出 |
|
||||
| 排队消息 + steering(中途引导) | 核心 `Agent.send()` / `Agent.steer()` |
|
||||
@@ -113,7 +113,7 @@ export function apply(ctx: Context) {
|
||||
| 单调终端轮次策略 | 从串行 `agent/turn-stop` 返回 `{ action: 'stop' }`,此时 continuation 和 steering 已折叠完毕 |
|
||||
| 子进程沙箱(landlock / sandbox-exec) | 通过 `dsh-bash-sandbox` 使用 `ctx.sandbox` 后端;能力级别的拒绝使用 `tools/pre-execute` |
|
||||
| 权限系统 / AskUserQuestion | 从 `tools/pre-execute` 返回 `ask` 并通过 `ctx.approval` 应答;为普通用户提问注册一个独立的面向模型的 ask 工具 |
|
||||
| Plan mode | `tools/pre-execute`(拒绝写操作)+ 通过 `ctx.systemPrompt.section()` 或 `agent.inject()` 注入模式提示词段(model-visible ⟺ logged:`agent/request` 仅塑形调用配置) |
|
||||
| Plan mode | 已交付:[`@deepseek-ai/dsh-plan-mode`](../../packages/plan/plan-mode/README.md) — 落日志的 `plan/mode` 状态、`plan:policy` 引导段、`/plan [message]`,以及经用户评审的 `exit_plan_mode` 出口;强制约束留在独立的沙箱/审批轴上 |
|
||||
| 子 agent 委派 | `ctx.subagents` 提供方注册表(`dsh-subagent-spawn`/`-fork`/`-acp`)+ `dsh-tool-subagent` 向模型暴露一个已配置的提供方 |
|
||||
| MCP | 每个服务器一个插件:发现工具 → `ctx.tools.register()` |
|
||||
| Skill(技能) | section + 工具注册;调用时通过 `inject()` 注入 skill 内容 |
|
||||
|
||||
364
docs/cordis-catalog/core/context.md
Normal file
364
docs/cordis-catalog/core/context.md
Normal file
@@ -0,0 +1,364 @@
|
||||
<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.
|
||||
Run `pnpm run gen-cordis-catalog` to regenerate. -->
|
||||
|
||||
# Context
|
||||
|
||||
The context is the core Cordis object: every service, event, and lifecycle API is reached through `ctx`. Event methods are documented on [Events](events.md), effects and the current fiber on [Fiber](fiber.md), and plugin loading on [Registry](registry.md).
|
||||
|
||||
Root and child dependency containers for Cordis plugins.
|
||||
|
||||
A context is a proxy: normal property reads go through the service resolver, while `extend()`, `isolate()`, and `intercept()` create scoped child contexts without mutating their parent.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L42)
|
||||
|
||||
### ctx.extend(meta?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Create a child context with extra metadata on top of the current scope.
|
||||
*
|
||||
* The child prototypally inherits every property of this context; own
|
||||
* properties of `meta` shadow the inherited ones. The parent is not mutated.
|
||||
*
|
||||
* @param meta — own properties (including symbol keys) to define on the child.
|
||||
* @returns a child context inheriting from this one.
|
||||
*/
|
||||
extend(meta = {}): this
|
||||
```
|
||||
|
||||
Create a child context with extra metadata on top of the current scope.
|
||||
|
||||
The child prototypally inherits every property of this context; own properties of `meta` shadow the inherited ones. The parent is not mutated.
|
||||
|
||||
- `meta` — own properties (including symbol keys) to define on the child.
|
||||
|
||||
**Returns** a child context inheriting from this one.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L99)
|
||||
|
||||
### ctx.isolate(name, label?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Create a child context with an independent service scope for `name`.
|
||||
*
|
||||
* Below the returned context, reads and writes of the service `name`
|
||||
* resolve against the new label instead of the parent's, so a different
|
||||
* implementation can be provided without affecting the parent scope.
|
||||
* Passing the same `label` to two `isolate()` calls joins their scopes.
|
||||
*
|
||||
* @param name — the service name to isolate.
|
||||
* @param label — scope label to join; defaults to a fresh unique symbol.
|
||||
* @returns a child context whose `name` service resolves in the new scope.
|
||||
*/
|
||||
isolate(name: string, label?: symbol)
|
||||
```
|
||||
|
||||
Create a child context with an independent service scope for `name`.
|
||||
|
||||
Below the returned context, reads and writes of the service `name` resolve against the new label instead of the parent's, so a different implementation can be provided without affecting the parent scope. Passing the same `label` to two `isolate()` calls joins their scopes.
|
||||
|
||||
- `name` — the service name to isolate.
|
||||
- `label` — scope label to join; defaults to a fresh unique symbol.
|
||||
|
||||
**Returns** a child context whose `name` service resolves in the new scope.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L121)
|
||||
|
||||
### ctx.intercept(name, config)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Add service-specific intercept config for plugins started below this
|
||||
* context.
|
||||
*
|
||||
* Plugins loaded under the returned context see `config` merged into the
|
||||
* service's resolved config (ancestor entries first; see
|
||||
* `Service[symbols.resolveConfig]`). The parent context is not affected.
|
||||
*
|
||||
* @param name — the service name whose config to intercept.
|
||||
* @param config — the intercept config to merge for that service.
|
||||
* @returns a child context carrying the additional intercept entry.
|
||||
*/
|
||||
intercept<K extends InjectKey>(name: K, config: Context[K] extends { [symbols.config]: infer T } ? T : never): this
|
||||
intercept(name: string, config: any): this
|
||||
```
|
||||
|
||||
Add service-specific intercept config for plugins started below this context.
|
||||
|
||||
Plugins loaded under the returned context see `config` merged into the service's resolved config (ancestor entries first; see `Service[symbols.resolveConfig]`). The parent context is not affected.
|
||||
|
||||
- `name` — the service name whose config to intercept.
|
||||
- `config` — the intercept config to merge for that service.
|
||||
|
||||
**Returns** a child context carrying the additional intercept entry.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L139)
|
||||
|
||||
### ctx.root
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The root context of the application (every child context shares it). @experimental */
|
||||
root: this
|
||||
```
|
||||
|
||||
The root context of the application (every child context shares it). @experimental
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L22)
|
||||
|
||||
### ctx.baseUrl
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Base URL used to resolve relative plugin/module specifiers, if the runtime sets one. */
|
||||
baseUrl?: string
|
||||
```
|
||||
|
||||
Base URL used to resolve relative plugin/module specifiers, if the runtime sets one.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L24)
|
||||
|
||||
### ctx.events
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The event bus. Its methods are also mixed onto `ctx` (`ctx.on`, `ctx.emit`, ...). */
|
||||
events: EventsService
|
||||
```
|
||||
|
||||
The event bus. Its methods are also mixed onto `ctx` (`ctx.on`, `ctx.emit`, ...).
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L26)
|
||||
|
||||
### ctx.logger
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The logging service. Call `ctx.logger(name)` for a named logger. */
|
||||
logger: LoggerService
|
||||
```
|
||||
|
||||
The logging service. Call `ctx.logger(name)` for a named logger.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L28)
|
||||
|
||||
### ctx.reflect
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The reflection layer backing the context proxy (`ctx.get`, `ctx.provide`, ...). */
|
||||
reflect: ReflectService
|
||||
```
|
||||
|
||||
The reflection layer backing the context proxy (`ctx.get`, `ctx.provide`, ...).
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L30)
|
||||
|
||||
### ctx.registry
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The plugin registry. Its methods are mixed onto `ctx` (`ctx.plugin`, `ctx.inject`). */
|
||||
registry: RegistryService
|
||||
```
|
||||
|
||||
The plugin registry. Its methods are mixed onto `ctx` (`ctx.plugin`, `ctx.inject`).
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L32)
|
||||
|
||||
## Static members
|
||||
|
||||
### Context.effect
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key under which a disposer exposes its {@link EffectMeta} diagnostics tree. */
|
||||
static readonly effect: unique symbol
|
||||
```
|
||||
|
||||
Symbol key under which a disposer exposes its EffectMeta diagnostics tree.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L44)
|
||||
|
||||
### Context.filter
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key for a context's listener filter, consulted on every event dispatch. */
|
||||
static readonly filter: unique symbol
|
||||
```
|
||||
|
||||
Symbol key for a context's listener filter, consulted on every event dispatch.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L46)
|
||||
|
||||
### Context.isolate
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the isolation map (see the `Context[symbols.isolate]` property). */
|
||||
static readonly isolate: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the isolation map (see the `Context[symbols.isolate]` property).
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L48)
|
||||
|
||||
### Context.intercept
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the intercept map (see the `Context[symbols.intercept]` property). */
|
||||
static readonly intercept: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the intercept map (see the `Context[symbols.intercept]` property).
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L50)
|
||||
|
||||
### Context.is(value)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Returns true for Cordis context proxies and context prototypes.
|
||||
*
|
||||
* Works across realms and across multiple copies of cordis, because the
|
||||
* brand is keyed by a global symbol rather than by `instanceof`.
|
||||
*
|
||||
* @param value — the value to test.
|
||||
* @returns `true` if `value` is a Cordis context, narrowing its type.
|
||||
*/
|
||||
static is(value: any): value is Context
|
||||
```
|
||||
|
||||
Returns true for Cordis context proxies and context prototypes.
|
||||
|
||||
Works across realms and across multiple copies of cordis, because the brand is keyed by a global symbol rather than by `instanceof`.
|
||||
|
||||
- `value` — the value to test.
|
||||
|
||||
**Returns** `true` if `value` is a Cordis context, narrowing its type.
|
||||
|
||||
[Source](../../../vendor/cordis/src/context.ts#L61)
|
||||
|
||||
## Service store and mixins
|
||||
|
||||
### ctx.get(name, strict?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Read a service from the store without the inject requirement.
|
||||
*
|
||||
* @param name — the service name.
|
||||
* @param strict — when `true` (default), only return implementations
|
||||
* whose providing fiber is currently active.
|
||||
* @returns the service value, or `undefined` when not (yet) provided.
|
||||
*/
|
||||
get<K extends string & keyof this>(name: K, strict?: boolean): undefined | this[K]
|
||||
get(name: string, strict?: boolean): any
|
||||
```
|
||||
|
||||
Read a service from the store without the inject requirement.
|
||||
|
||||
- `name` — the service name.
|
||||
- `strict` — when `true` (default), only return implementations whose providing fiber is currently active.
|
||||
|
||||
**Returns** the service value, or `undefined` when not (yet) provided.
|
||||
|
||||
[Source](../../../vendor/cordis/src/reflect.ts#L16)
|
||||
|
||||
### ctx.set(name, value)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Overwrite a provided service's value.
|
||||
*
|
||||
* Only the fiber that provided the service may set it; setting an
|
||||
* unprovided name throws.
|
||||
*
|
||||
* @param name — the service name.
|
||||
* @param value — the new service value.
|
||||
*/
|
||||
set<K extends string & keyof this>(name: K, value: undefined | this[K]): void
|
||||
set(name: string, value: any): void
|
||||
```
|
||||
|
||||
Overwrite a provided service's value.
|
||||
|
||||
Only the fiber that provided the service may set it; setting an unprovided name throws.
|
||||
|
||||
- `name` — the service name.
|
||||
- `value` — the new service value.
|
||||
|
||||
[Source](../../../vendor/cordis/src/reflect.ts#L28)
|
||||
|
||||
### ctx.provide(name, value)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register a service implementation owned by the current fiber.
|
||||
*
|
||||
* The service becomes visible to dependents in the same isolation scope
|
||||
* once the fiber is active; it is unregistered (waking dependents) when
|
||||
* the returned disposer runs or the fiber unloads. Throws if the name is
|
||||
* already provided in this scope or declared as an accessor.
|
||||
*
|
||||
* @param name — the service name.
|
||||
* @param value — the service value.
|
||||
* @returns a disposer that unregisters the service.
|
||||
*/
|
||||
provide<K extends string & keyof this>(name: K, value: undefined | this[K]): () => void
|
||||
provide(name: string, value?: any): () => void
|
||||
```
|
||||
|
||||
Register a service implementation owned by the current fiber.
|
||||
|
||||
The service becomes visible to dependents in the same isolation scope once the fiber is active; it is unregistered (waking dependents) when the returned disposer runs or the fiber unloads. Throws if the name is already provided in this scope or declared as an accessor.
|
||||
|
||||
- `name` — the service name.
|
||||
- `value` — the service value.
|
||||
|
||||
**Returns** a disposer that unregisters the service.
|
||||
|
||||
[Source](../../../vendor/cordis/src/reflect.ts#L43)
|
||||
|
||||
### ctx.accessor(name, options)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Define a computed context property backed by get/set hooks.
|
||||
*
|
||||
* The accessor is removed when the current fiber unloads. Throws if the
|
||||
* name is already declared.
|
||||
*
|
||||
* @param name — the context property name.
|
||||
* @param options — the `get` hook and optional `set` hook.
|
||||
*/
|
||||
accessor(name: string, options: Omit<Property.Accessor, 'type'>): void
|
||||
```
|
||||
|
||||
Define a computed context property backed by get/set hooks.
|
||||
|
||||
The accessor is removed when the current fiber unloads. Throws if the name is already declared.
|
||||
|
||||
- `name` — the context property name.
|
||||
- `options` — the `get` hook and optional `set` hook.
|
||||
|
||||
[Source](../../../vendor/cordis/src/reflect.ts#L55)
|
||||
|
||||
### ctx.mixin(name, mixins)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Expose selected members of a service directly on `ctx`.
|
||||
*
|
||||
* Each mixed-in key becomes an accessor that forwards to the service
|
||||
* (binding methods to it), so e.g. `ctx.on` forwards to `ctx.events.on`.
|
||||
* Mixins are removed when the current fiber unloads.
|
||||
*
|
||||
* @param name — the context property holding the source service.
|
||||
* @param mixins — keys to forward, or a source-key → ctx-key map.
|
||||
*/
|
||||
mixin<K extends string & keyof this>(name: K, mixins: (keyof this & keyof this[K])[] | Dict<string>): void
|
||||
mixin<T extends {}>(source: T, mixins: (keyof this & keyof T)[] | Dict<string>): void
|
||||
```
|
||||
|
||||
Expose selected members of a service directly on `ctx`.
|
||||
|
||||
Each mixed-in key becomes an accessor that forwards to the service (binding methods to it), so e.g. `ctx.on` forwards to `ctx.events.on`. Mixins are removed when the current fiber unloads.
|
||||
|
||||
- `name` — the context property holding the source service.
|
||||
- `mixins` — keys to forward, or a source-key → ctx-key map.
|
||||
|
||||
[Source](../../../vendor/cordis/src/reflect.ts#L66)
|
||||
207
docs/cordis-catalog/core/events.md
Normal file
207
docs/cordis-catalog/core/events.md
Normal file
@@ -0,0 +1,207 @@
|
||||
<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.
|
||||
Run `pnpm run gen-cordis-catalog` to regenerate. -->
|
||||
|
||||
# Events
|
||||
|
||||
The event-dispatch API mixed into every context. Harness event declarations and their dispatch modes are generated separately in the [Cordis events catalog](../events.md).
|
||||
|
||||
### ctx.parallel(name, ...args)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Dispatch an event, running all listeners concurrently.
|
||||
*
|
||||
* @param name — the event name.
|
||||
* @param args — arguments passed to every listener.
|
||||
* @returns a promise resolving once every listener has settled.
|
||||
*/
|
||||
parallel<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promise<void>
|
||||
parallel<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): Promise<void>
|
||||
```
|
||||
|
||||
Dispatch an event, running all listeners concurrently.
|
||||
|
||||
- `name` — the event name.
|
||||
- `args` — arguments passed to every listener.
|
||||
|
||||
**Returns** a promise resolving once every listener has settled.
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L43)
|
||||
|
||||
### ctx.emit(name, ...args)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Dispatch an event synchronously, ignoring listener return values.
|
||||
*
|
||||
* @param name — the event name.
|
||||
* @param args — arguments passed to every listener.
|
||||
*/
|
||||
emit<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): void
|
||||
emit<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): void
|
||||
```
|
||||
|
||||
Dispatch an event synchronously, ignoring listener return values.
|
||||
|
||||
- `name` — the event name.
|
||||
- `args` — arguments passed to every listener.
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L52)
|
||||
|
||||
### ctx.serial(name, ...args)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Dispatch an event, awaiting listeners in order until one bails.
|
||||
*
|
||||
* @param name — the event name.
|
||||
* @param args — arguments passed to each listener.
|
||||
* @returns the first bail value (non-null, non-false, non-undefined), if any.
|
||||
*/
|
||||
serial<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promisify<ReturnType<Events[K]>>
|
||||
serial<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): Promisify<ReturnType<Events[K]>>
|
||||
```
|
||||
|
||||
Dispatch an event, awaiting listeners in order until one bails.
|
||||
|
||||
- `name` — the event name.
|
||||
- `args` — arguments passed to each listener.
|
||||
|
||||
**Returns** the first bail value (non-null, non-false, non-undefined), if any.
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L62)
|
||||
|
||||
### ctx.bail(name, ...args)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Dispatch an event, calling listeners in order until one bails.
|
||||
*
|
||||
* @param name — the event name.
|
||||
* @param args — arguments passed to each listener.
|
||||
* @returns the first bail value (non-null, non-false, non-undefined), if any.
|
||||
*/
|
||||
bail<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
|
||||
bail<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
|
||||
```
|
||||
|
||||
Dispatch an event, calling listeners in order until one bails.
|
||||
|
||||
- `name` — the event name.
|
||||
- `args` — arguments passed to each listener.
|
||||
|
||||
**Returns** the first bail value (non-null, non-false, non-undefined), if any.
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L72)
|
||||
|
||||
### ctx.waterfall(name, ...args)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Dispatch an event whose last argument is a `next` continuation.
|
||||
*
|
||||
* Each listener wraps the rest of the chain: calling `next()` invokes the
|
||||
* next listener (finally the built-in behavior); not calling it vetoes.
|
||||
*
|
||||
* @param name — the event name.
|
||||
* @param args — listener arguments; the final one is the innermost `next`.
|
||||
* @returns the outermost listener's return value.
|
||||
*/
|
||||
waterfall<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
|
||||
waterfall<K extends keyof Events>(thisArg: NoInfer<ThisType<Events[K]>>, name: K, ...args: Parameters<Events[K]>): ReturnType<Events[K]>
|
||||
```
|
||||
|
||||
Dispatch an event whose last argument is a `next` continuation.
|
||||
|
||||
Each listener wraps the rest of the chain: calling `next()` invokes the next listener (finally the built-in behavior); not calling it vetoes.
|
||||
|
||||
- `name` — the event name.
|
||||
- `args` — listener arguments; the final one is the innermost `next`.
|
||||
|
||||
**Returns** the outermost listener's return value.
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L85)
|
||||
|
||||
### ctx.on(name, listener, options?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register an event listener owned by the current fiber.
|
||||
*
|
||||
* @param name — the event name to listen for.
|
||||
* @param listener — called with the dispatch arguments.
|
||||
* @param options — listener options; a boolean is shorthand for `prepend`.
|
||||
* @returns a disposer removing the listener; `true` if it was still registered.
|
||||
*/
|
||||
on<K extends keyof Events>(name: K, listener: Events[K], options?: boolean | EventOptions): () => boolean
|
||||
```
|
||||
|
||||
Register an event listener owned by the current fiber.
|
||||
|
||||
- `name` — the event name to listen for.
|
||||
- `listener` — called with the dispatch arguments.
|
||||
- `options` — listener options; a boolean is shorthand for `prepend`.
|
||||
|
||||
**Returns** a disposer removing the listener; `true` if it was still registered.
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L96)
|
||||
|
||||
### ctx.once(name, listener, options?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Same as `on()`, but the listener disposes itself after its first call.
|
||||
*
|
||||
* @param name — the event name to listen for.
|
||||
* @param listener — called at most once with the dispatch arguments.
|
||||
* @param options — listener options; a boolean is shorthand for `prepend`.
|
||||
* @returns a disposer removing the listener; `true` if it was still registered.
|
||||
*/
|
||||
once<K extends keyof Events>(name: K, listener: Events[K], options?: boolean | EventOptions): () => boolean
|
||||
```
|
||||
|
||||
Same as `on()`, but the listener disposes itself after its first call.
|
||||
|
||||
- `name` — the event name to listen for.
|
||||
- `listener` — called at most once with the dispatch arguments.
|
||||
- `options` — listener options; a boolean is shorthand for `prepend`.
|
||||
|
||||
**Returns** a disposer removing the listener; `true` if it was still registered.
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L105)
|
||||
|
||||
## EventOptions
|
||||
|
||||
Options accepted by `ctx.on()` and `ctx.once()`.
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Options accepted by `ctx.on()` and `ctx.once()`. */
|
||||
interface EventOptions {
|
||||
/** Add the listener before existing listeners for the same event. */
|
||||
prepend?: boolean
|
||||
/** Receive the event regardless of context filter checks. */
|
||||
global?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L111)
|
||||
|
||||
## DispatchMode
|
||||
|
||||
Event dispatch strategy used by the event service.
|
||||
|
||||
`emit` runs synchronous listeners without awaiting them, `parallel` awaits all listeners together, `serial` awaits them in order until one bails, `bail` stops on the first synchronous bail value, and `waterfall` composes listeners around a final `next` callback.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Event dispatch strategy used by the event service.
|
||||
*
|
||||
* `emit` runs synchronous listeners without awaiting them, `parallel` awaits
|
||||
* all listeners together, `serial` awaits them in order until one bails,
|
||||
* `bail` stops on the first synchronous bail value, and `waterfall` composes
|
||||
* listeners around a final `next` callback.
|
||||
*/
|
||||
type DispatchMode = 'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall'
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/events.ts#L31)
|
||||
375
docs/cordis-catalog/core/fiber.md
Normal file
375
docs/cordis-catalog/core/fiber.md
Normal file
@@ -0,0 +1,375 @@
|
||||
<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.
|
||||
Run `pnpm run gen-cordis-catalog` to regenerate. -->
|
||||
|
||||
# Fiber
|
||||
|
||||
A fiber is one loaded plugin instance: its lifecycle state, validated config, and registered effects. `ctx.fiber` is the current fiber, and `ctx.effect()` delegates to it.
|
||||
|
||||
### ctx.effect(execute, label?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register a cleanup-aware effect on this fiber.
|
||||
*
|
||||
* `execute` runs immediately; the disposers it produces are collected and
|
||||
* run (in reverse order) either when the returned disposer is called or
|
||||
* when the fiber unloads, whichever comes first. Calling the disposer twice
|
||||
* is a no-op. Throws `CordisError('INACTIVE_EFFECT')` if the fiber is
|
||||
* already disposed, and `TypeError` if `execute` returns an invalid shape.
|
||||
*
|
||||
* @param execute — the effect body; see {@link Effect} for accepted shapes.
|
||||
* @param label — effect label shown in `getEffects()` diagnostics.
|
||||
* @returns a disposer that tears the effect down and settles once done.
|
||||
*/
|
||||
effect(execute: () => SyncEffect, label?: string): Disposable<Promise<void>>
|
||||
effect(execute: () => Effect, label?: string): AsyncDisposable<Promise<void>>
|
||||
```
|
||||
|
||||
Register a cleanup-aware effect on this fiber.
|
||||
|
||||
`execute` runs immediately; the disposers it produces are collected and run (in reverse order) either when the returned disposer is called or when the fiber unloads, whichever comes first. Calling the disposer twice is a no-op. Throws `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed, and `TypeError` if `execute` returns an invalid shape.
|
||||
|
||||
- `execute` — the effect body; see `Effect` for accepted shapes.
|
||||
- `label` — effect label shown in `getEffects()` diagnostics.
|
||||
|
||||
**Returns** a disposer that tears the effect down and settles once done.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L419)
|
||||
|
||||
### ctx.fiber
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The fiber (plugin runtime instance) that owns this context. */
|
||||
fiber: Fiber
|
||||
```
|
||||
|
||||
The fiber (plugin runtime instance) that owns this context.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L11)
|
||||
|
||||
## The Fiber class
|
||||
|
||||
Runtime instance of one plugin application.
|
||||
|
||||
A fiber tracks dependency state, validated config, lifecycle effects, and cleanup for the plugin context returned by `ctx.plugin()`.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L183)
|
||||
|
||||
### fiber.uid
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Unique id within the registry; 0 for the root fiber, `null` once disposed. */
|
||||
public uid: number | null
|
||||
```
|
||||
|
||||
Unique id within the registry; 0 for the root fiber, `null` once disposed.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L185)
|
||||
|
||||
### fiber.ctx
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The context this fiber's plugin runs in (extends the parent context). */
|
||||
public readonly ctx: Context
|
||||
```
|
||||
|
||||
The context this fiber's plugin runs in (extends the parent context).
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L187)
|
||||
|
||||
### fiber.config
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The validated plugin config (updated by `update()`). */
|
||||
public config: any
|
||||
```
|
||||
|
||||
The validated plugin config (updated by `update()`).
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L189)
|
||||
|
||||
### fiber.state
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Current lifecycle state; transitions emit `internal/status`. */
|
||||
public state
|
||||
```
|
||||
|
||||
Current lifecycle state; transitions emit `internal/status`.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L191)
|
||||
|
||||
### fiber.dispose
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Dispose this fiber: unload the plugin, then settle once cleanup finished. */
|
||||
public readonly dispose: () => Promise<void>
|
||||
```
|
||||
|
||||
Dispose this fiber: unload the plugin, then settle once cleanup finished.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L193)
|
||||
|
||||
### fiber.store
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Snapshot of required service implementations while loaded; `undefined` otherwise. */
|
||||
public store: Dict<Impl> | undefined
|
||||
```
|
||||
|
||||
Snapshot of required service implementations while loaded; `undefined` otherwise.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L195)
|
||||
|
||||
### fiber.inertia
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The in-flight load/unload transition, if one is currently running. */
|
||||
public inertia: Promise<void> | undefined
|
||||
```
|
||||
|
||||
The in-flight load/unload transition, if one is currently running.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L197)
|
||||
|
||||
### fiber.name
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The plugin's display name, inherited from the nearest named ancestor, else `'root'`. */
|
||||
get name()
|
||||
```
|
||||
|
||||
The plugin's display name, inherited from the nearest named ancestor, else `'root'`.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L340)
|
||||
|
||||
### fiber.assertActive()
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Throw if the fiber has already been disposed.
|
||||
*
|
||||
* @returns nothing when the fiber is still active.
|
||||
* @throws {CordisError} `INACTIVE_EFFECT` when the fiber's uid has been cleared.
|
||||
*/
|
||||
assertActive()
|
||||
```
|
||||
|
||||
Throw if the fiber has already been disposed.
|
||||
|
||||
**Returns** nothing when the fiber is still active.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L355)
|
||||
|
||||
### fiber.effect(execute, label?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register a cleanup-aware effect on this fiber.
|
||||
*
|
||||
* `execute` runs immediately; the disposers it produces are collected and
|
||||
* run (in reverse order) either when the returned disposer is called or
|
||||
* when the fiber unloads, whichever comes first. Calling the disposer twice
|
||||
* is a no-op. Throws `CordisError('INACTIVE_EFFECT')` if the fiber is
|
||||
* already disposed, and `TypeError` if `execute` returns an invalid shape.
|
||||
*
|
||||
* @param execute — the effect body; see {@link Effect} for accepted shapes.
|
||||
* @param label — effect label shown in `getEffects()` diagnostics.
|
||||
* @returns a disposer that tears the effect down and settles once done.
|
||||
*/
|
||||
effect(execute: () => SyncEffect, label?: string): Disposable<Promise<void>>
|
||||
effect(execute: () => Effect, label?: string): AsyncDisposable<Promise<void>>
|
||||
```
|
||||
|
||||
Register a cleanup-aware effect on this fiber.
|
||||
|
||||
`execute` runs immediately; the disposers it produces are collected and run (in reverse order) either when the returned disposer is called or when the fiber unloads, whichever comes first. Calling the disposer twice is a no-op. Throws `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed, and `TypeError` if `execute` returns an invalid shape.
|
||||
|
||||
- `execute` — the effect body; see `Effect` for accepted shapes.
|
||||
- `label` — effect label shown in `getEffects()` diagnostics.
|
||||
|
||||
**Returns** a disposer that tears the effect down and settles once done.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L419)
|
||||
|
||||
### fiber.getEffects()
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Return metadata for currently registered effects.
|
||||
*
|
||||
* @returns one {@link EffectMeta} tree per labeled live effect.
|
||||
*/
|
||||
getEffects()
|
||||
```
|
||||
|
||||
Return metadata for currently registered effects.
|
||||
|
||||
**Returns** one `EffectMeta` tree per labeled live effect.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L572)
|
||||
|
||||
### fiber.await()
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Wait for current lifecycle work and rethrow startup errors.
|
||||
*
|
||||
* @returns this fiber, once it has settled into a stable state.
|
||||
* @throws the config-validation or plugin-startup error, if any.
|
||||
*/
|
||||
async await()
|
||||
```
|
||||
|
||||
Wait for current lifecycle work and rethrow startup errors.
|
||||
|
||||
**Returns** this fiber, once it has settled into a stable state.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L701)
|
||||
|
||||
### fiber.restart()
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Dispose and immediately reload this plugin with its current config.
|
||||
*
|
||||
* @returns a promise resolving once the reload settled.
|
||||
* @throws {CordisError} `INACTIVE_EFFECT` when the fiber is already disposed.
|
||||
*/
|
||||
async restart()
|
||||
```
|
||||
|
||||
Dispose and immediately reload this plugin with its current config.
|
||||
|
||||
**Returns** a promise resolving once the reload settled.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L715)
|
||||
|
||||
### fiber.update(config, noSave?)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Validate and apply new config, then restart the plugin.
|
||||
*
|
||||
* Runs the `internal/update` waterfall first, so update hooks (and HMR)
|
||||
* can veto or replace the restart.
|
||||
*
|
||||
* @param config — the new raw config; validated before anything restarts.
|
||||
* @param noSave — hint for persistence hooks not to write the change back.
|
||||
* @returns nothing; the restart runs behind the `internal/update` waterfall.
|
||||
* @throws {ValidationError} when the new config fails validation.
|
||||
*/
|
||||
update(config: any, noSave = false)
|
||||
```
|
||||
|
||||
Validate and apply new config, then restart the plugin.
|
||||
|
||||
Runs the `internal/update` waterfall first, so update hooks (and HMR) can veto or replace the restart.
|
||||
|
||||
- `config` — the new raw config; validated before anything restarts.
|
||||
- `noSave` — hint for persistence hooks not to write the change back.
|
||||
|
||||
**Returns** nothing; the restart runs behind the `internal/update` waterfall.
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L733)
|
||||
|
||||
## Effect
|
||||
|
||||
Effect body result accepted by `ctx.effect()` and plugin startup.
|
||||
|
||||
Either a single disposer, a promise of one, or a (possibly async) iterable yielding several — generator effects register each yielded disposer as it is produced.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Effect body result accepted by `ctx.effect()` and plugin startup.
|
||||
*
|
||||
* Either a single disposer, a promise of one, or a (possibly async) iterable
|
||||
* yielding several — generator effects register each yielded disposer as it
|
||||
* is produced.
|
||||
*/
|
||||
type Effect<T = any> =
|
||||
| SyncEffect<T>
|
||||
| AsyncEffect<T>
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L82)
|
||||
|
||||
## Disposable
|
||||
|
||||
Function returned by an effect to release resources during disposal.
|
||||
|
||||
Disposers run in reverse registration order when the owning fiber unloads; they may be async, in which case unloading awaits them.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Function returned by an effect to release resources during disposal.
|
||||
*
|
||||
* Disposers run in reverse registration order when the owning fiber unloads;
|
||||
* they may be async, in which case unloading awaits them.
|
||||
*/
|
||||
type Disposable<T = any> = () => T
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L73)
|
||||
|
||||
## EffectMeta
|
||||
|
||||
Tree node used to expose nested effect labels for diagnostics.
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Tree node used to expose nested effect labels for diagnostics. */
|
||||
interface EffectMeta {
|
||||
/** Human-readable effect label, e.g. `ctx.on("event")` or `ctx.provide("name")`. */
|
||||
label: string
|
||||
/** Metadata of nested effects registered while this effect ran. */
|
||||
children: EffectMeta[]
|
||||
}
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L95)
|
||||
|
||||
## CordisError
|
||||
|
||||
Framework error with a stable machine-readable code.
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Framework error with a stable machine-readable code. */
|
||||
class CordisError extends Error {
|
||||
/**
|
||||
* @param code — the stable error code; also the default message.
|
||||
* @param message — optional human-readable override.
|
||||
*/
|
||||
constructor(public code: CordisError.Code, message?: string)
|
||||
}
|
||||
|
||||
/** Cordis error code definitions. */
|
||||
namespace CordisError {
|
||||
export type Code = keyof typeof Code
|
||||
|
||||
export const Code = {
|
||||
INACTIVE_EFFECT: 'cannot create effect on inactive context',
|
||||
} as const
|
||||
}
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L156)
|
||||
|
||||
## ValidationError
|
||||
|
||||
Error raised when plugin configuration fails standard-schema validation.
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Error raised when plugin configuration fails standard-schema validation. */
|
||||
class ValidationError extends TypeError {
|
||||
name = 'ValidationError'
|
||||
|
||||
/**
|
||||
* Build the aggregated message from schema issues.
|
||||
*
|
||||
* @param issues — the standard-schema issues, one message line each.
|
||||
*/
|
||||
constructor(issues: readonly StandardSchemaV1.Issue[])
|
||||
}
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/fiber.ts#L18)
|
||||
152
docs/cordis-catalog/core/registry.md
Normal file
152
docs/cordis-catalog/core/registry.md
Normal file
@@ -0,0 +1,152 @@
|
||||
<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.
|
||||
Run `pnpm run gen-cordis-catalog` to regenerate. -->
|
||||
|
||||
# Registry
|
||||
|
||||
Plugin loading and dependency injection.
|
||||
|
||||
### ctx.inject(deps, callback)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Run a callback once the requested services are available.
|
||||
*
|
||||
* Shorthand for `ctx.plugin({ inject, apply: callback })`: the callback
|
||||
* is unloaded and re-run whenever a required service changes.
|
||||
*
|
||||
* @param deps — required services, as an array or a name → config map.
|
||||
* @param callback — plugin body called with `(ctx, config)`.
|
||||
* @returns the fiber; awaiting it settles once loading finished.
|
||||
*/
|
||||
inject(deps: Inject, callback: Plugin.Function<void>): Fiber & PromiseLike<Fiber>
|
||||
```
|
||||
|
||||
Run a callback once the requested services are available.
|
||||
|
||||
Shorthand for `ctx.plugin({ inject, apply: callback })`: the callback is unloaded and re-run whenever a required service changes.
|
||||
|
||||
- `deps` — required services, as an array or a name → config map.
|
||||
- `callback` — plugin body called with `(ctx, config)`.
|
||||
|
||||
**Returns** the fiber; awaiting it settles once loading finished.
|
||||
|
||||
[Source](../../../vendor/cordis/src/registry.ts#L175)
|
||||
|
||||
### ctx.plugin(plugin, ...args)
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Load a plugin in the current context.
|
||||
*
|
||||
* @param plugin — a function, class, or `{ apply }` object plugin.
|
||||
* @param args — the plugin config, validated against its `Config` schema.
|
||||
* @returns the fiber; awaiting it settles once loading finished
|
||||
* (rejecting on config or startup errors).
|
||||
*/
|
||||
plugin<P extends Plugin>(plugin: P, ...args: Spread<GetPluginConfig<P>>): Fiber & PromiseLike<Fiber>
|
||||
```
|
||||
|
||||
Load a plugin in the current context.
|
||||
|
||||
- `plugin` — a function, class, or `{ apply }` object plugin.
|
||||
- `args` — the plugin config, validated against its `Config` schema.
|
||||
|
||||
**Returns** the fiber; awaiting it settles once loading finished (rejecting on config or startup errors).
|
||||
|
||||
[Source](../../../vendor/cordis/src/registry.ts#L184)
|
||||
|
||||
## Plugin
|
||||
|
||||
Supported plugin entrypoint shapes.
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Supported plugin entrypoint shapes. */
|
||||
type Plugin<T = any> =
|
||||
| Plugin.Function<T>
|
||||
| Plugin.Constructor<T>
|
||||
| Plugin.Object<T>
|
||||
|
||||
/** Types associated with plugin entrypoints and runtime records. */
|
||||
namespace Plugin {
|
||||
/** Shared metadata understood by the plugin registry and related tooling. */
|
||||
export interface Base<T = any> {
|
||||
/** Display name used for fiber diagnostics and logger names. */
|
||||
name?: string
|
||||
/** Standard-schema validator applied to config before the plugin starts. */
|
||||
Config?: StandardSchemaV1<any, T>
|
||||
/** Services the plugin requires; it only loads while all are available. */
|
||||
inject?: Inject
|
||||
/** Service name(s) the plugin provides (read by `Service` and by loaders). */
|
||||
provide?: string | string[]
|
||||
/** Service names whose intercept config the plugin declares it consumes. */
|
||||
intercept?: Dict<boolean>
|
||||
}
|
||||
|
||||
export interface Transform<S, T> {
|
||||
/** Marks the transform object as a schema/config transform. */
|
||||
schema?: true
|
||||
/** Convert user-facing config to runtime config. */
|
||||
Config: (config: S) => T
|
||||
}
|
||||
|
||||
/** Function plugin called with `(ctx, config)`. */
|
||||
export interface Function<T = any> extends Base<T> {
|
||||
(ctx: Context, config: T): any
|
||||
}
|
||||
|
||||
/** Class plugin constructed with `(ctx, config)`. */
|
||||
export interface Constructor<T = any> extends Base<T> {
|
||||
new (ctx: Context, config: T): any
|
||||
}
|
||||
|
||||
/** Object plugin with an `apply(ctx, config)` method. */
|
||||
export interface Object<T = any> extends Base<T> {
|
||||
apply(ctx: Context, config: T): any
|
||||
}
|
||||
|
||||
/** Mutable registry record shared by all fibers of one plugin callback. */
|
||||
export interface Runtime {
|
||||
/** Display name copied from the first registered plugin shape. */
|
||||
name?: string
|
||||
/** Every live fiber of this plugin (one per `ctx.plugin()` call). */
|
||||
fibers: DisposableList<Fiber>
|
||||
/** The executable entrypoint all fibers share (registry identity key). */
|
||||
callback: globalThis.Function
|
||||
/** Standard-schema validator applied to each fiber's config. */
|
||||
Config?: StandardSchemaV1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/registry.ts#L91)
|
||||
|
||||
## Inject
|
||||
|
||||
Service dependency declaration accepted by plugins and the `@Inject` decorator.
|
||||
|
||||
Array form requests services without intercept config. Object form maps each service name to optional intercept config for the plugin context.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Service dependency declaration accepted by plugins and the `@Inject`
|
||||
* decorator.
|
||||
*
|
||||
* Array form requests services without intercept config. Object form maps each
|
||||
* service name to optional intercept config for the plugin context.
|
||||
*/
|
||||
type Inject<M = Dict> = (keyof M)[] | { [K in keyof M]?: M[K] }
|
||||
|
||||
/** Utilities for normalizing plugin dependency declarations. */
|
||||
namespace Inject {
|
||||
/**
|
||||
* Convert array/object/class-inherited inject metadata into a plain map.
|
||||
*
|
||||
* @param inject — the declaration to normalize; `null`/`undefined` add nothing.
|
||||
* @param result — the map to fill (service name → intercept config or `null`).
|
||||
* @returns `result`.
|
||||
*/
|
||||
export function resolve(inject: Inject | null | undefined, result: Dict = Object.create(null))
|
||||
}
|
||||
```
|
||||
|
||||
[Source](../../../vendor/cordis/src/registry.ts#L18)
|
||||
102
docs/cordis-catalog/core/service.md
Normal file
102
docs/cordis-catalog/core/service.md
Normal file
@@ -0,0 +1,102 @@
|
||||
<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.
|
||||
Run `pnpm run gen-cordis-catalog` to regenerate. -->
|
||||
|
||||
# Service
|
||||
|
||||
The base class for context services. A subclass loaded as a plugin registers itself as `ctx.<name>`.
|
||||
|
||||
Base class for services that expose a named API on `ctx`.
|
||||
|
||||
Subclasses call `super(ctx, name)` from their constructor. The service is registered immediately and is automatically removed with the owning fiber.
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L11)
|
||||
|
||||
### service.name
|
||||
|
||||
```ts cordis-catalog
|
||||
/** The service name this instance is registered under. */
|
||||
public name!: string
|
||||
```
|
||||
|
||||
The service name this instance is registered under.
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L30)
|
||||
|
||||
## Static members
|
||||
|
||||
### Service.init
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of an instance method run after construction (class plugins). */
|
||||
static readonly init: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of an instance method run after construction (class plugins).
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L13)
|
||||
|
||||
### Service.check
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the availability predicate passed to `ctx.provide()`. */
|
||||
static readonly check: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the availability predicate passed to `ctx.provide()`.
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L15)
|
||||
|
||||
### Service.config
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the phantom intercept-config type parameter. */
|
||||
static readonly config: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the phantom intercept-config type parameter.
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L17)
|
||||
|
||||
### Service.invoke
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the call body making a service callable (e.g. `ctx.logger()`). */
|
||||
static readonly invoke: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the call body making a service callable (e.g. `ctx.logger()`).
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L19)
|
||||
|
||||
### Service.extend
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the helper deriving an extended service instance. */
|
||||
static readonly extend: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the helper deriving an extended service instance.
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L21)
|
||||
|
||||
### Service.tracker
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the tracker metadata used for context tracing. */
|
||||
static readonly tracker: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the tracker metadata used for context tracing.
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L23)
|
||||
|
||||
### Service.resolveConfig
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Symbol key of the intercept-config resolution helper below. */
|
||||
static readonly resolveConfig: unique symbol
|
||||
```
|
||||
|
||||
Symbol key of the intercept-config resolution helper below.
|
||||
|
||||
[Source](../../../vendor/cordis/src/service.ts#L25)
|
||||
@@ -7,12 +7,33 @@ Every cordis event a plugin can listen to: exact signature, dispatch mode, and o
|
||||
|
||||
This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence and include the original source JSDoc immediately before each event or service method. doc-typecheck skips these bare declaration fragments; type names in a signature link to the page that documents them.
|
||||
|
||||
The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely.
|
||||
The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely. The event-dispatch methods themselves are generated in the [Cordis core Events API](core/events.md).
|
||||
|
||||
Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).
|
||||
|
||||
## `agent/*`
|
||||
|
||||
### `agent/cancel-requested` — emit
|
||||
|
||||
Effective broad cancellation was requested, before queued/steering work is cleared or the active turn is aborted. This observe-only notification cannot veto cancellation; listener failures are contained.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Effective broad cancellation was requested, before queued/steering work
|
||||
* is cleared or the active turn is aborted. This observe-only notification
|
||||
* cannot veto cancellation; listener failures are contained.
|
||||
* @param agent - the agent whose current work is being cancelled.
|
||||
* @param cause - resolved typed cancellation cause, including the default.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @mode emit
|
||||
*/
|
||||
'agent/cancel-requested'(this: Scoped<Agent>, agent: Agent, cause: AgentCancelCause): void
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [AgentCancelCause](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:201`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/created` — emit
|
||||
|
||||
A fully configured agent and live session were published. Setup is composition-only; `agent/session-start` is the first startup-driving seam. Synchronous listener failure vetoes publication, while returned-promise rejection is reported. Detach requested during dispatch waits until every creation listener has observed the stable entry.
|
||||
@@ -33,7 +54,7 @@ A fully configured agent and live session were published. Setup is composition-o
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:143`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:163`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/disposed` — emit
|
||||
|
||||
@@ -53,7 +74,7 @@ An agent left the registry; AgentLoop emits this after driver quiescence but bef
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:152`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:172`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/error` — emit
|
||||
|
||||
@@ -75,7 +96,7 @@ A step or turn errored. The loop reports a failure here (plus the logger) even w
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:307`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:346`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/post-step` — serial
|
||||
|
||||
@@ -98,7 +119,7 @@ Awaited serial checkpoint after the response, real or synthetic tool results, in
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:260`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:296`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/pre-step` — serial
|
||||
|
||||
@@ -121,28 +142,31 @@ Awaited serial checkpoint before `step/start`; appends land outside the pending
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:200`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:230`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/prompt-submit` — waterfall
|
||||
|
||||
Allow, rewrite, or block one drained prompt before it becomes a user message. Call `next()` for the unchanged default.
|
||||
Allow, rewrite, or block one claimed prompt before it becomes a user message. Call `next()` for the unchanged default. The signal controls only this turn; listeners may cooperate with it but must not retain it to control another turn.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Allow, rewrite, or block one drained prompt before it becomes a user
|
||||
* message. Call `next()` for the unchanged default.
|
||||
* @param agent - the agent draining its inbox.
|
||||
* @param content - the drained message's blocks, as queued.
|
||||
* Allow, rewrite, or block one claimed prompt before it becomes a user
|
||||
* message. Call `next()` for the unchanged default. The signal controls only
|
||||
* this turn; listeners may cooperate with it but must not retain it to
|
||||
* control another turn.
|
||||
* @param agent - the agent whose turn claimed the message.
|
||||
* @param content - the claimed message's blocks, as queued.
|
||||
* @param source - the message's resolved source.
|
||||
* @param signal - the current turn's explicit abort signal.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'agent/prompt-submit'(this: Scoped<Agent>, agent: Agent, content: ContentBlock[], source: MessageSource, next: () => Promise<PromptDecision>): Promise<PromptDecision>
|
||||
'agent/prompt-submit'(this: Scoped<Agent>, agent: Agent, content: ContentBlock[], source: MessageSource, signal: AbortSignal, next: () => Promise<PromptDecision>): Promise<PromptDecision>
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md) · [PromptDecision](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:210`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/queued` — emit
|
||||
|
||||
@@ -163,7 +187,7 @@ Detached, frozen content entered the agent's inbox. Source defaults have already
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [MessageSource](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:171`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:191`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/request` — waterfall
|
||||
|
||||
@@ -178,15 +202,17 @@ Replace the frozen call configuration. Model-visible content must use logged cha
|
||||
* @param turn - the open turn number.
|
||||
* @param step - the step whose request this is.
|
||||
* @param config - the config the loop would use (frozen); return a replacement to switch.
|
||||
* @param signal - the current turn's explicit abort signal; ambient
|
||||
* initiator identity does not imply liveness or cancellation authority.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'agent/request'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, config: LlmCallConfig, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>
|
||||
'agent/request'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, config: LlmCallConfig, signal: AbortSignal, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [LlmCallConfig](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:222`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:257`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/request-error` — waterfall
|
||||
|
||||
@@ -201,17 +227,18 @@ Recover a model-request failure after its failed step has closed. `retry` opens
|
||||
* @param turn - the open turn number.
|
||||
* @param step - the failed step number.
|
||||
* @param error - the original model-request failure.
|
||||
* @param retryAttempt - zero-based number of prior recovery retries.
|
||||
* @param failure - serializable facts normalized at the final adapter boundary.
|
||||
* @param priorFailures - immutable failures that already authorized another request in this consecutive sequence.
|
||||
* @param signal - the turn abort signal.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'agent/request-error'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, error: RequestError, retryAttempt: number, signal: AbortSignal, next: () => Promise<RequestErrorDecision>): Promise<RequestErrorDecision>
|
||||
'agent/request-error'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, error: RequestError, failure: LlmFailure, priorFailures: readonly LlmFailure[], signal: AbortSignal, next: () => Promise<RequestErrorDecision>): Promise<RequestErrorDecision>
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [RequestError](../core-data-structures/core.md) · [RequestErrorDecision](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
Types: [Agent](../core-data-structures/core.md) · [LlmFailure](../core-data-structures/llm-streaming.md) · [RequestError](../core-data-structures/core.md) · [RequestErrorDecision](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:274`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:311`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/session-prefix` — waterfall
|
||||
|
||||
@@ -229,7 +256,7 @@ Compose request-only messages placed before derived history. The frozen result i
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @param agent - the agent whose session prefix is being composed.
|
||||
* @param prefix - the frozen seed; return an extended replacement.
|
||||
* @param signal - aborts composition when the step is torn down.
|
||||
* @param signal - the current turn's explicit abort signal.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'agent/session-prefix'(this: Scoped<Agent>, agent: Agent, prefix: Message[], signal: AbortSignal, next: () => Promise<Message[]>): Promise<Message[]>
|
||||
@@ -237,7 +264,7 @@ Compose request-only messages placed before derived history. The frozen result i
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:237`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:272`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/session-start` — emit
|
||||
|
||||
@@ -259,7 +286,7 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [SessionStartSource](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:184`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:214`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/status` — emit
|
||||
|
||||
@@ -279,7 +306,7 @@ Agent status changed (`idle` ⇄ `running`, or → `disposed`). `send()` does no
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [AgentStatus](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:161`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:181`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/step-result` — waterfall
|
||||
|
||||
@@ -293,15 +320,16 @@ Waterfall: post-process the assembled assistant Message before tool dispatch (va
|
||||
* @param turn - the open turn number.
|
||||
* @param step - the step that produced the message.
|
||||
* @param message - the assistant message as assembled from the stream.
|
||||
* @param signal - the current turn's explicit abort signal.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'agent/step-result'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, message: Message, next: () => Promise<Message>): Promise<Message>
|
||||
'agent/step-result'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, message: Message, signal: AbortSignal, next: () => Promise<Message>): Promise<Message>
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [Message](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:248`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:284`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/turn-continuation` — waterfall
|
||||
|
||||
@@ -314,15 +342,16 @@ Override whether the turn continues. The default continues after tool calls or s
|
||||
* @param agent - the agent deciding whether to run another step.
|
||||
* @param turn - the turn being continued or stopped.
|
||||
* @param defaultDecision - what the loop would do absent an override.
|
||||
* @param signal - the current turn's explicit abort signal.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'agent/turn-continuation'(this: Scoped<Agent>, agent: Agent, turn: number, defaultDecision: ContinuationDecision, next: () => Promise<ContinuationDecision>): Promise<ContinuationDecision>
|
||||
'agent/turn-continuation'(this: Scoped<Agent>, agent: Agent, turn: number, defaultDecision: ContinuationDecision, signal: AbortSignal, next: () => Promise<ContinuationDecision>): Promise<ContinuationDecision>
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [ContinuationDecision](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:284`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:322`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
### `agent/turn-stop` — serial
|
||||
|
||||
@@ -335,15 +364,16 @@ Monotonic terminal-stop checkpoint after continuation and steering are folded; a
|
||||
* steering queued in that window is discarded, while ordinary sends survive.
|
||||
* @param agent - the agent whose composed continuation outcome may be stopped.
|
||||
* @param turn - the turn at its terminal-stop checkpoint.
|
||||
* @param signal - the current turn's explicit abort signal.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @mode serial
|
||||
*/
|
||||
'agent/turn-stop'(this: Scoped<Agent>, agent: Agent, turn: number): ContinuationStop | undefined
|
||||
'agent/turn-stop'(this: Scoped<Agent>, agent: Agent, turn: number, signal: AbortSignal): Promise<ContinuationStop | undefined> | ContinuationStop | undefined
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [ContinuationStop](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/core/agent/src/types.ts:294`](../../packages/core/agent/src/types.ts)
|
||||
Source: [`packages/core/agent/src/types.ts:333`](../../packages/core/agent/src/types.ts)
|
||||
|
||||
## `agent-loop/*`
|
||||
|
||||
@@ -366,7 +396,7 @@ A declarative agent entry failed before it could publish a live agent. Consumers
|
||||
|
||||
Types: [SessionId](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/agent-loop/src/index.ts:362`](../../packages/core/agent-loop/src/index.ts)
|
||||
Source: [`packages/core/agent-loop/src/index.ts:353`](../../packages/core/agent-loop/src/index.ts)
|
||||
|
||||
## `approval/*`
|
||||
|
||||
@@ -387,7 +417,25 @@ Ask composed answerers for one decision. Return an outcome to claim the request
|
||||
|
||||
Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [ApprovalService](../core-data-structures/approval.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/ui/user-approval/src/index.ts:31`](../../packages/ui/user-approval/src/index.ts)
|
||||
Source: [`packages/ui/user-approval/src/index.ts:30`](../../packages/ui/user-approval/src/index.ts)
|
||||
|
||||
## `commands/*`
|
||||
|
||||
### `commands/change` — emit
|
||||
|
||||
A command was registered or unregistered. This is an unfiltered registry notification because a global or scoped change may affect any UI view. Observer failures are contained and cannot veto the registry mutation.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* A command was registered or unregistered. This is an unfiltered registry
|
||||
* notification because a global or scoped change may affect any UI view.
|
||||
* Observer failures are contained and cannot veto the registry mutation.
|
||||
* @mode emit
|
||||
*/
|
||||
'commands/change'(): void
|
||||
```
|
||||
|
||||
Source: [`packages/ui/commands/src/index.ts:103`](../../packages/ui/commands/src/index.ts)
|
||||
|
||||
## `fs/*`
|
||||
|
||||
@@ -450,6 +498,29 @@ Types: [FsTarget](../core-data-structures/filesystem.md) · [FsWriteIntent](../c
|
||||
|
||||
Source: [`packages/fs/fs/src/index.ts:54`](../../packages/fs/fs/src/index.ts)
|
||||
|
||||
## `goal/*`
|
||||
|
||||
### `goal/changed` — emit
|
||||
|
||||
Goal mutation accepted by one live agent. The matching context event is already appended or queued in that agent's active tool-batch FIFO. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Goal mutation accepted by one live agent. The matching context event is
|
||||
* already appended or queued in that agent's active tool-batch FIFO.
|
||||
* Listener failures are contained.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
||||
* @param agent - agent whose session owns the goal.
|
||||
* @param change - fresh current projection or clear tombstone.
|
||||
* @mode emit
|
||||
*/
|
||||
'goal/changed'(this: import('@deepseek-ai/dsh-scope').Scoped<Agent>, agent: Agent, change: GoalChanged): void
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [GoalChanged](../core-data-structures/goal.md) · [Scoped](../core-data-structures/scope.md)
|
||||
|
||||
Source: [`packages/goal/goal/src/types.ts:167`](../../packages/goal/goal/src/types.ts)
|
||||
|
||||
## `llm/*`
|
||||
|
||||
### `llm/stream` — waterfall
|
||||
@@ -461,11 +532,11 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t
|
||||
* Waterfall around every streaming model call (retry, replay, routing).
|
||||
* Bound to the {@link LlmService}; call `next()` to reach the resolved
|
||||
* adapter's stream, or yield your own chunks to short-circuit.
|
||||
* @param options - the full request. A LOOP-built request arrives
|
||||
* deep-frozen (mutation throws): its content is a pure function of the
|
||||
* session log (the reconstructability Agent Note), so listeners read it, never
|
||||
* rewrite it. A hand-built one-shot (compaction summarize) is the
|
||||
* caller's own object and stays mutable here.
|
||||
* @param options - the full request. A LOOP-built request carries the
|
||||
* process-local {@link markAgentLoopRequest} identity and arrives deep-frozen
|
||||
* (mutation throws): its content is a pure function of the session log (the
|
||||
* reconstructability Agent Note), so listeners read it, never rewrite it.
|
||||
* Hand-built calls own their mutability policy and do not carry that marker.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable<StreamChunk>): AsyncIterable<StreamChunk>
|
||||
@@ -473,7 +544,7 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t
|
||||
|
||||
Types: [GenerateOptions](../core-data-structures/core.md) · [LlmService](../core-data-structures/llm-streaming.md) · [StreamChunk](../core-data-structures/llm-streaming.md)
|
||||
|
||||
Source: [`packages/llm/llm/src/index.ts:43`](../../packages/llm/llm/src/index.ts)
|
||||
Source: [`packages/llm/llm/src/index.ts:52`](../../packages/llm/llm/src/index.ts)
|
||||
|
||||
## `session/*`
|
||||
|
||||
@@ -498,7 +569,7 @@ Creation announcement during session publication. A synchronous throw vetoes and
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:47`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:68`](../../packages/core/session/src/index.ts)
|
||||
|
||||
### `session/disposed` — emit
|
||||
|
||||
@@ -519,7 +590,7 @@ Emitted once when an announced session leaves the store, including publication r
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:57`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:78`](../../packages/core/session/src/index.ts)
|
||||
|
||||
### `session/event` — emit
|
||||
|
||||
@@ -542,7 +613,7 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:69`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:90`](../../packages/core/session/src/index.ts)
|
||||
|
||||
### `session/flush` — parallel
|
||||
|
||||
@@ -563,7 +634,7 @@ Awaited parallel durability checkpoint: every listener runs and the caller await
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:79`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:100`](../../packages/core/session/src/index.ts)
|
||||
|
||||
## `subagent/*`
|
||||
|
||||
@@ -585,7 +656,7 @@ A ready child settled. Scope-filtered dispatch uses the same delegating parent c
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:112`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:139`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/provider-added` — emit
|
||||
|
||||
@@ -602,7 +673,7 @@ A provider became resolvable in the registry.
|
||||
|
||||
Types: [SubagentProvider](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:86`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:113`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/provider-removed` — emit
|
||||
|
||||
@@ -617,7 +688,7 @@ A provider left the registry. Accepted runs remain holder-owned.
|
||||
'subagent/provider-removed'(name: string): void
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:92`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:119`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/start` — emit
|
||||
|
||||
@@ -639,19 +710,21 @@ A provider established a ready child. For in-process providers, `ctx.agents.get(
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:103`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:130`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
## `system-prompt/*`
|
||||
|
||||
### `system-prompt/assemble` — waterfall
|
||||
|
||||
Expert waterfall over the assembled sections, tools, and variables. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners receive only that scope's assemblies. The returned value is authoritative.
|
||||
Expert waterfall over the assembled sections, tools, and variables. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners receive only that scope's assemblies. The returned value is authoritative. A supplied signal controls only this explicit assembly request and must not be retained to control later turns.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Expert waterfall over the assembled sections, tools, and variables.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners
|
||||
* receive only that scope's assemblies. The returned value is authoritative.
|
||||
* A supplied signal controls only this explicit assembly request and must not
|
||||
* be retained to control later turns.
|
||||
* @param assembly - the mutable assembly built from registered providers.
|
||||
* @param context - the caller's per-assembly context.
|
||||
* @mode waterfall
|
||||
@@ -661,7 +734,7 @@ Expert waterfall over the assembled sections, tools, and variables. Scope-filter
|
||||
|
||||
Types: [AssembleContext](../core-data-structures/system-prompt.md) · [Scoped](../core-data-structures/scope.md) · [SystemPrompt](../core-data-structures/system-prompt.md)
|
||||
|
||||
Source: [`packages/core/system-prompt/src/index.ts:27`](../../packages/core/system-prompt/src/index.ts)
|
||||
Source: [`packages/core/system-prompt/src/index.ts:29`](../../packages/core/system-prompt/src/index.ts)
|
||||
|
||||
### `system-prompt/change` — emit
|
||||
|
||||
@@ -676,7 +749,7 @@ Emitted when any prompt provider changes. This registry notification is unfilter
|
||||
'system-prompt/change'(): void
|
||||
```
|
||||
|
||||
Source: [`packages/core/system-prompt/src/index.ts:33`](../../packages/core/system-prompt/src/index.ts)
|
||||
Source: [`packages/core/system-prompt/src/index.ts:35`](../../packages/core/system-prompt/src/index.ts)
|
||||
|
||||
## `tools/*`
|
||||
|
||||
@@ -697,36 +770,41 @@ A tool was registered or unregistered, or a scoped restriction changed (the avai
|
||||
'tools/change'(): void
|
||||
```
|
||||
|
||||
Source: [`packages/core/tools/src/index.ts:116`](../../packages/core/tools/src/index.ts)
|
||||
Source: [`packages/core/tools/src/index.ts:123`](../../packages/core/tools/src/index.ts)
|
||||
|
||||
### `tools/execute` — waterfall
|
||||
|
||||
Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a normalized result; wrappers may change only `exec.signal`, while call identity remains immutable. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a normalized result; wrappers may change only `exec.signal`, while call identity remains immutable. The registry re-fuses the original caller signal before the body, so replacement cannot detach caller cancellation; wrappers must still restore their signal and reach quiescence. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns
|
||||
* a normalized result; wrappers may change only `exec.signal`, while call
|
||||
* identity remains immutable.
|
||||
* identity remains immutable. The registry re-fuses the original caller
|
||||
* signal before the body, so replacement cannot detach caller cancellation;
|
||||
* wrappers must still restore their signal and reach quiescence.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
* @param exec - the allowed call about to dispatch (name, parsed arguments, caller agent, signal).
|
||||
* @mode waterfall
|
||||
*/
|
||||
'tools/execute'(this: Scoped<ToolRegistry>, exec: ToolExecution, next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>
|
||||
'tools/execute'(this: Scoped<ToolRegistry>, exec: ToolDispatchExecution, next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult>
|
||||
```
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md)
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [ToolDispatchExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md)
|
||||
|
||||
Source: [`packages/core/tools/src/index.ts:89`](../../packages/core/tools/src/index.ts)
|
||||
Source: [`packages/core/tools/src/index.ts:93`](../../packages/core/tools/src/index.ts)
|
||||
|
||||
### `tools/post-execute` — waterfall
|
||||
|
||||
Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Async listeners must observe `exec.signal`; after they settle, caller cancellation replaces only a successful accepted outcome with the code selected by whether the tool body was invoked. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Accept, replace, enrich, or block a normalized dispatch result. `next()`
|
||||
* accepts it unchanged; thrown tools still reach this seam as errors.
|
||||
* accepts it unchanged; thrown tools still reach this seam as errors. Async
|
||||
* listeners must observe `exec.signal`; after they settle, caller
|
||||
* cancellation replaces only a successful accepted outcome with the code
|
||||
* selected by whether the tool body was invoked.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
* @param exec - the call that just ran (name, parsed arguments, caller agent).
|
||||
* @param result - the dispatch outcome a listener may accept, replace, or block.
|
||||
@@ -737,16 +815,18 @@ Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts
|
||||
|
||||
Types: [PostToolDecision](../core-data-structures/tools.md) · [Scoped](../core-data-structures/scope.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md)
|
||||
|
||||
Source: [`packages/core/tools/src/index.ts:98`](../../packages/core/tools/src/index.ts)
|
||||
Source: [`packages/core/tools/src/index.ts:105`](../../packages/core/tools/src/index.ts)
|
||||
|
||||
### `tools/pre-execute` — waterfall
|
||||
|
||||
Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approval support turns `ask` into denial. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approval support turns `ask` into denial. Async gates must observe `exec.signal`; the registry rechecks cancellation after they settle but never abandons their promise. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Allow, deny, or ask before dispatch. `next()` delegates to allow; missing
|
||||
* approval support turns `ask` into denial.
|
||||
* approval support turns `ask` into denial. Async gates must observe
|
||||
* `exec.signal`; the registry rechecks cancellation after they settle but
|
||||
* never abandons their promise.
|
||||
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls.
|
||||
* @param exec - the pending call (name, parsed arguments, caller agent).
|
||||
* @mode waterfall
|
||||
@@ -756,7 +836,7 @@ Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approv
|
||||
|
||||
Types: [PreToolDecision](../core-data-structures/tools.md) · [Scoped](../core-data-structures/scope.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md)
|
||||
|
||||
Source: [`packages/core/tools/src/index.ts:80`](../../packages/core/tools/src/index.ts)
|
||||
Source: [`packages/core/tools/src/index.ts:82`](../../packages/core/tools/src/index.ts)
|
||||
|
||||
### `tools/result` — emit
|
||||
|
||||
@@ -775,7 +855,7 @@ Observe the frozen, lossless-JSON final outcome. Listener failures are contained
|
||||
|
||||
Types: [Scoped](../core-data-structures/scope.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md)
|
||||
|
||||
Source: [`packages/core/tools/src/index.ts:106`](../../packages/core/tools/src/index.ts)
|
||||
Source: [`packages/core/tools/src/index.ts:113`](../../packages/core/tools/src/index.ts)
|
||||
|
||||
## `workflow/*`
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Every `ctx.<key>` service a plugin can call: the exact public interface with ori
|
||||
|
||||
This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence and include the original source JSDoc immediately before each event or service method. doc-typecheck skips these bare declaration fragments; type names in a signature link to the page that documents them.
|
||||
|
||||
The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer `ctx` surface a plugin also sees — pinned vendor source, summarized tersely.
|
||||
The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer `ctx` surface a plugin also sees — pinned vendor source, summarized tersely. Detailed Context, Fiber, Registry, and Service APIs are generated in the [Cordis core API](core/context.md).
|
||||
|
||||
## `ctx.agentLoop` — `AgentLoop`
|
||||
|
||||
@@ -44,7 +44,7 @@ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandl
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [AgentOptions](../core-data-structures/core.md) · [SessionHeader](../core-data-structures/persistence.md) · [SessionId](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/agent-loop/src/index.ts:407`](../../packages/core/agent-loop/src/index.ts)
|
||||
Source: [`packages/core/agent-loop/src/index.ts:398`](../../packages/core/agent-loop/src/index.ts)
|
||||
|
||||
## `ctx.agents` — `AgentRegistry`
|
||||
|
||||
@@ -216,7 +216,7 @@ roots(): Agent[]
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/agent/src/index.ts:217`](../../packages/core/agent/src/index.ts)
|
||||
Source: [`packages/core/agent/src/index.ts:225`](../../packages/core/agent/src/index.ts)
|
||||
|
||||
## `ctx.approval` — `ApprovalService`
|
||||
|
||||
@@ -246,7 +246,7 @@ async request(req: ApprovalRequest): Promise<ApprovalOutcome>
|
||||
|
||||
Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md)
|
||||
|
||||
Source: [`packages/ui/user-approval/src/index.ts:229`](../../packages/ui/user-approval/src/index.ts)
|
||||
Source: [`packages/ui/user-approval/src/index.ts:213`](../../packages/ui/user-approval/src/index.ts)
|
||||
|
||||
## `ctx.bash` — `BashExecutor` (abstract seam)
|
||||
|
||||
@@ -340,6 +340,47 @@ Types: [CodeRunRequest](../core-data-structures/code-runtime.md) · [CodeRunResu
|
||||
|
||||
Source: [`packages/code-runtime/code-runtime/src/index.ts:30`](../../packages/code-runtime/code-runtime/src/index.ts)
|
||||
|
||||
## `ctx.commands` — `CommandService`
|
||||
|
||||
Human-command registry. Plain-context definitions are global; definitions registered through a command-injected child of an agent context shadow globals for that agent.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register a global or calling-agent-scoped command.
|
||||
* @param definition - discovery metadata and direct UI handler.
|
||||
* @returns the exact effect disposer that unregisters this definition.
|
||||
*/
|
||||
register(definition: CommandDefinition): () => void
|
||||
|
||||
/**
|
||||
* List the effective immutable command descriptors for one agent.
|
||||
* @param agent - exact receiving agent and scoped-layer key.
|
||||
* @returns name-sorted descriptors after scoped shadowing.
|
||||
*/
|
||||
list(agent: Agent): readonly CommandDescriptor[]
|
||||
|
||||
/**
|
||||
* Resolve one effective command definition.
|
||||
* @param agent - exact receiving agent and scoped-layer key.
|
||||
* @param name - command name without a slash.
|
||||
* @returns the scoped shadow or global definition.
|
||||
*/
|
||||
find(agent: Agent, name: string): CommandDefinition | undefined
|
||||
|
||||
/**
|
||||
* Parse and execute a known command without sending it to the model.
|
||||
* @param agent - exact receiving agent.
|
||||
* @param line - complete slash-command line.
|
||||
* @param signal - cancellation signal owned by the UI request.
|
||||
* @returns a detached result, or `undefined` when syntax or name does not resolve.
|
||||
*/
|
||||
async execute( agent: Agent, line: string, signal: AbortSignal, ): Promise<CommandResult | undefined>
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [CommandDefinition](../core-data-structures/commands.md) · [CommandDescriptor](../core-data-structures/commands.md) · [CommandResult](../core-data-structures/commands.md)
|
||||
|
||||
Source: [`packages/ui/commands/src/index.ts:227`](../../packages/ui/commands/src/index.ts)
|
||||
|
||||
## `ctx.compact` — `CompactService` (abstract seam)
|
||||
|
||||
Abstract compaction service. Implementations own trigger policy, retention, and summarization, and may consume a separate measurement service. A successful run replaces the selected surface span with one summary node and prevents concurrent compaction of the same session. Load one implementation per context as `ctx.compact`.
|
||||
@@ -381,7 +422,7 @@ abstract compactRegion( start: number, end: number, agent: CompactAgentContext,
|
||||
|
||||
Types: [CompactionResult](../core-data-structures/compaction.md) · [CompactionTrigger](../core-data-structures/compaction.md)
|
||||
|
||||
Source: [`packages/compact/compact/src/index.ts:40`](../../packages/compact/compact/src/index.ts)
|
||||
Source: [`packages/compact/compact/src/index.ts:39`](../../packages/compact/compact/src/index.ts)
|
||||
|
||||
## `ctx.fs` — `FileSystem` (abstract seam)
|
||||
|
||||
@@ -485,6 +526,111 @@ Types: [FsDirEntry](../core-data-structures/filesystem.md) · [FsEditOutcome](..
|
||||
|
||||
Source: [`packages/fs/fs/src/index.ts:81`](../../packages/fs/fs/src/index.ts)
|
||||
|
||||
## `ctx.goals` — `GoalService`
|
||||
|
||||
Goal service (`ctx.goals`) backed exclusively by the owning session log.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Read the current goal for one exact live agent.
|
||||
* @param agent - owning live agent.
|
||||
* @returns a fresh view or `undefined` when no goal is current.
|
||||
* @throws {@link GoalError} when the agent is not the registry's live instance.
|
||||
*/
|
||||
get(agent: Agent): GoalView | undefined
|
||||
|
||||
/**
|
||||
* Remove process-local continuation authority without changing durable goal
|
||||
* phase or revision. Lifecycle owners use this before unloading a driver;
|
||||
* a later human-authorized {@link resume} records the new activation edge.
|
||||
* @param agent - owning live agent.
|
||||
* @returns a fresh disarmed view, or `undefined` when no goal is current.
|
||||
*/
|
||||
disarm(agent: Agent): GoalView | undefined
|
||||
|
||||
/**
|
||||
* Create and arm a goal. A completed goal may be replaced; every other
|
||||
* current phase must be cleared or resumed instead.
|
||||
* @param agent - owning live agent.
|
||||
* @param request - objective and optional round cap.
|
||||
* @returns the created live view.
|
||||
*/
|
||||
create(agent: Agent, request: CreateGoalRequest): GoalView
|
||||
|
||||
/**
|
||||
* Edit objective and/or round cap without changing phase.
|
||||
* @param agent - owning live agent.
|
||||
* @param ref - expected current revision.
|
||||
* @param request - at least one replacement field.
|
||||
* @returns the edited view.
|
||||
*/
|
||||
edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView
|
||||
|
||||
/**
|
||||
* Pause an active goal and disarm automatic continuation.
|
||||
* @param agent - owning live agent.
|
||||
* @param ref - expected current revision.
|
||||
* @returns the paused view.
|
||||
*/
|
||||
pause(agent: Agent, ref: GoalRef): GoalView
|
||||
|
||||
/**
|
||||
* Resume and arm a stopped goal, or rearm an active goal after a
|
||||
* session-start edge, while its round budget still has capacity.
|
||||
* @param agent - owning live agent.
|
||||
* @param ref - expected current revision.
|
||||
* @returns the active view.
|
||||
*/
|
||||
resume(agent: Agent, ref: GoalRef): GoalView
|
||||
|
||||
/**
|
||||
* Mark a current non-complete goal complete and disarm it.
|
||||
* @param agent - owning live agent.
|
||||
* @param ref - expected current revision.
|
||||
* @returns the completed view.
|
||||
*/
|
||||
complete(agent: Agent, ref: GoalRef): GoalView
|
||||
|
||||
/**
|
||||
* Mark an active goal blocked and disarm it.
|
||||
* @param agent - owning live agent.
|
||||
* @param ref - expected current revision.
|
||||
* @param reason - policy-owned stable code and human-readable explanation.
|
||||
* @returns the blocked view with its durable reason.
|
||||
*/
|
||||
block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView
|
||||
|
||||
/**
|
||||
* Clear the current goal while retaining a durable tombstone and history.
|
||||
* @param agent - owning live agent.
|
||||
* @param ref - expected current revision.
|
||||
* @returns the tombstone ref whose revision is one past the cleared snapshot.
|
||||
*/
|
||||
clear(agent: Agent, ref: GoalRef): GoalRef
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md) · [CreateGoalRequest](../core-data-structures/goal.md) · [EditGoalRequest](../core-data-structures/goal.md) · [GoalBlockReason](../core-data-structures/goal.md) · [GoalRef](../core-data-structures/goal.md) · [GoalView](../core-data-structures/goal.md)
|
||||
|
||||
Source: [`packages/goal/goal/src/index.ts:135`](../../packages/goal/goal/src/index.ts)
|
||||
|
||||
## `ctx.invariants` — `InvariantService`
|
||||
|
||||
Package-owned invariant registry with global and regex-based selection.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Register one package's invariant installer. The package name is reserved
|
||||
* even when filtering disables its checks. Enabled installers run in a child
|
||||
* fiber; failure disposes that fiber and releases the reservation.
|
||||
* @param packageName - full npm package name that owns the contribution.
|
||||
* @param installer - listener or startup-check installer for the child context.
|
||||
* @returns an effect-scoped disposer for the registration.
|
||||
*/
|
||||
register(packageName: string, installer: InvariantInstaller): () => void
|
||||
```
|
||||
|
||||
Source: [`packages/support/invariants/src/index.ts:94`](../../packages/support/invariants/src/index.ts)
|
||||
|
||||
## `ctx.llm` — `LlmService`
|
||||
|
||||
The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall.
|
||||
@@ -514,6 +660,16 @@ listProviders(): LlmProviderInfo[]
|
||||
*/
|
||||
async listModels(provider: string): Promise<LlmModelInfo[]>
|
||||
|
||||
/**
|
||||
* Resolve context capacity from the adapter that owns one exact route.
|
||||
* This query is independent of the advisory model catalog: an unlisted model
|
||||
* may return metadata, while `undefined` never rejects later routing.
|
||||
* @param provider - registered provider route to inspect.
|
||||
* @param model - exact model id passed to the adapter.
|
||||
* @returns detached context metadata, or `undefined` when the adapter has none.
|
||||
*/
|
||||
async resolveModelContext( provider: string, model: string, ): Promise<LlmModelContext | undefined>
|
||||
|
||||
/**
|
||||
* Stream one model call as raw chunks (token-level deltas). Throws
|
||||
* `LlmError` with code `NO_ADAPTER` if no adapter is registered for
|
||||
@@ -529,9 +685,9 @@ async listModels(provider: string): Promise<LlmModelInfo[]>
|
||||
stream(options: GenerateOptions): AsyncIterable<StreamChunk>
|
||||
```
|
||||
|
||||
Types: [GenerateOptions](../core-data-structures/core.md) · [LlmAdapter](../core-data-structures/llm-streaming.md) · [LlmModelInfo](../core-data-structures/core.md) · [LlmProviderInfo](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md)
|
||||
Types: [GenerateOptions](../core-data-structures/core.md) · [LlmAdapter](../core-data-structures/llm-streaming.md) · [LlmModelContext](../core-data-structures/core.md) · [LlmModelInfo](../core-data-structures/core.md) · [LlmProviderInfo](../core-data-structures/core.md) · [StreamChunk](../core-data-structures/llm-streaming.md)
|
||||
|
||||
Source: [`packages/llm/llm/src/index.ts:97`](../../packages/llm/llm/src/index.ts)
|
||||
Source: [`packages/llm/llm/src/index.ts:159`](../../packages/llm/llm/src/index.ts)
|
||||
|
||||
## `ctx.permission` — `PermissionService`
|
||||
|
||||
@@ -577,6 +733,33 @@ Types: [Session](../core-data-structures/session.md) · [SessionEvent](../core-d
|
||||
|
||||
Source: [`packages/ui/permission/src/index.ts:97`](../../packages/ui/permission/src/index.ts)
|
||||
|
||||
## `ctx.planMode` — `PlanModeService`
|
||||
|
||||
`ctx.planMode`: owns logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool. UIs observe committed flips through `session/event`; there is no live mirror.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Read the logged plan state and any selected state awaiting a boundary.
|
||||
*
|
||||
* @param agent The agent to read.
|
||||
* @returns Current logged state plus a pending selection, when present.
|
||||
*/
|
||||
get(agent: Agent): { active: boolean; pending?: boolean }
|
||||
|
||||
/**
|
||||
* Select whether plan mode should be active from the next turn boundary.
|
||||
* Repeated selection of the current or already-pending state is a no-op.
|
||||
*
|
||||
* @param agent The agent to switch.
|
||||
* @param active Whether plan mode should be active.
|
||||
*/
|
||||
set(agent: Agent, active: boolean): void
|
||||
```
|
||||
|
||||
Types: [Agent](../core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/plan/plan-mode/src/index.ts:141`](../../packages/plan/plan-mode/src/index.ts)
|
||||
|
||||
## `ctx.pty` — `PtyService`
|
||||
|
||||
In-process registry for replaceable PTY backends and exact-Agent sessions.
|
||||
@@ -747,6 +930,13 @@ Live-preferred logical-corpus exact-read and relationship-tracing service.
|
||||
*/
|
||||
listSessions(): Promise<SessionRecord[]>
|
||||
|
||||
/**
|
||||
* Fold the latest log-backed title from one live-preferred logical session.
|
||||
* @param sessionId - live or persisted session id to read.
|
||||
* @returns latest title snapshot, or `undefined` when the log has no title event.
|
||||
*/
|
||||
async readTitle(sessionId: SessionId): Promise<SessionTitleSnapshot | undefined>
|
||||
|
||||
/**
|
||||
* List lightweight raw-log event records for one logical session.
|
||||
* @param sessionId - live-preferred session id to read.
|
||||
@@ -778,9 +968,9 @@ async traceEvent(request: SessionEventTraceRequest): Promise<SessionEventTrace>
|
||||
async readEvent(request: SessionEventReadRequest): Promise<SessionEventWindow>
|
||||
```
|
||||
|
||||
Types: [SessionEventReadRequest](../core-data-structures/session-query.md) · [SessionEventRecord](../core-data-structures/session-query.md) · [SessionEventTrace](../core-data-structures/session-query.md) · [SessionEventTraceRequest](../core-data-structures/session-query.md) · [SessionEventWindow](../core-data-structures/session-query.md) · [SessionId](../core-data-structures/core.md) · [SessionLineageTrace](../core-data-structures/session-query.md) · [SessionRecord](../core-data-structures/session-query.md)
|
||||
Types: [SessionEventReadRequest](../core-data-structures/session-query.md) · [SessionEventRecord](../core-data-structures/session-query.md) · [SessionEventTrace](../core-data-structures/session-query.md) · [SessionEventTraceRequest](../core-data-structures/session-query.md) · [SessionEventWindow](../core-data-structures/session-query.md) · [SessionId](../core-data-structures/core.md) · [SessionLineageTrace](../core-data-structures/session-query.md) · [SessionRecord](../core-data-structures/session-query.md) · [SessionTitleSnapshot](../core-data-structures/session-title.md)
|
||||
|
||||
Source: [`packages/session-query/session-query/src/index.ts:38`](../../packages/session-query/session-query/src/index.ts)
|
||||
Source: [`packages/session-query/session-query/src/index.ts:40`](../../packages/session-query/session-query/src/index.ts)
|
||||
|
||||
## `ctx.sessions` — `SessionStore`
|
||||
|
||||
@@ -793,9 +983,9 @@ Persistence is intentionally not implemented here — persistence plugins subscr
|
||||
* Create a session owned by the calling fiber: disposing that fiber stops
|
||||
* event notification and removes the session from the store. `options.seed`
|
||||
* populates the session with a copy of those events (replay/fork);
|
||||
* `options.meta` attaches creation metadata (validated absolute `cwd`,
|
||||
* `parentSession` lineage) as the immutable {@link SessionHeader} (the store
|
||||
* fills `version`/`id`/`createdAt`).
|
||||
* `options.meta` attaches creation metadata (validated absolute `cwd`, seed
|
||||
* and parent lineage, and delegation depth) as the immutable
|
||||
* {@link SessionHeader} (the store fills `version`/`id`/`createdAt`).
|
||||
*
|
||||
* For an agent whose session must be torn down IN ORDER with its loop (so the
|
||||
* loop's final flush is captured before the store attachment ends), do NOT use this
|
||||
@@ -876,6 +1066,28 @@ announce(session: Session): void
|
||||
*/
|
||||
async flush(session: Session): Promise<void>
|
||||
|
||||
/**
|
||||
* Append one plugin-declared log-only event without borrowing the agent
|
||||
* loop's lifecycle. An open turn receives the event directly and remains
|
||||
* responsible for its ordinary checkpoint. A closed log receives one
|
||||
* zero-step turn around the event, followed by an awaited flush.
|
||||
*
|
||||
* Once the synthetic `turn/start` commits, this method always attempts its
|
||||
* matching `turn/end` and flush, including when the target append fails.
|
||||
* Detachment requested by an event or flush listener is deferred until that
|
||||
* sequence settles, so publication cannot switch from a live scoped session
|
||||
* to an unobserved bare `Session` halfway through the update.
|
||||
*
|
||||
* @param session - exact live session that owns the target log.
|
||||
* @param type - event type opted into {@link OutOfBandSessionEventMap} by its owner.
|
||||
* @param data - typed JSON payload for the target event.
|
||||
* @param trigger - plugin-owned turn trigger used only when the log is closed.
|
||||
* @returns the accepted target event with its assigned sequence and timestamp.
|
||||
* @throws when the session is detached, another out-of-band append is active,
|
||||
* event acceptance fails, the synthetic turn cannot close, or flushing fails.
|
||||
*/
|
||||
async appendOutOfBand<T extends OutOfBandSessionEventType>( session: Session, type: T, data: SessionEventMap[T], trigger: TurnTrigger, ): Promise<SessionEvent<T>>
|
||||
|
||||
/**
|
||||
* Look up a live session.
|
||||
* @param id - the session id to look up.
|
||||
@@ -905,9 +1117,43 @@ list(): Session[]
|
||||
fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session
|
||||
```
|
||||
|
||||
Types: [CreateSessionOptions](../core-data-structures/persistence.md) · [Session](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md)
|
||||
Types: [CreateSessionOptions](../core-data-structures/persistence.md) · [OutOfBandSessionEventType](../core-data-structures/session.md) · [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) · [SessionEventMap](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md) · [TurnTrigger](../core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/index.ts:549`](../../packages/core/session/src/index.ts)
|
||||
Source: [`packages/core/session/src/index.ts:592`](../../packages/core/session/src/index.ts)
|
||||
|
||||
## `ctx.sessionTitle` — `SessionTitleService`
|
||||
|
||||
Log-backed title fold plus asynchronous fallback generation.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Read the latest folded title from one live or replayed session.
|
||||
* @param session - session whose log is the title source of truth.
|
||||
* @returns latest title snapshot, or `undefined` before eligible input.
|
||||
*/
|
||||
get(session: Session): SessionTitleSnapshot | undefined
|
||||
|
||||
/**
|
||||
* Explicitly retry the registered provider, or materialize the built-in
|
||||
* fallback when no provider is registered.
|
||||
* @param session - exact live session to refresh.
|
||||
* @param signal - optional caller cancellation; an in-progress fallback append may finish durably before rejection.
|
||||
* @returns latest accepted title, or `undefined` when no eligible text exists.
|
||||
*/
|
||||
async refresh(session: Session, signal?: AbortSignal): Promise<SessionTitleSnapshot | undefined>
|
||||
|
||||
/**
|
||||
* Register the sole optional title provider. Disposal aborts its pending and
|
||||
* active work before another provider may register.
|
||||
* @param provider - provider identity, cadence, and generation function.
|
||||
* @returns exact Cordis effect disposer, which settles after active calls quiesce.
|
||||
*/
|
||||
register(provider: SessionTitleProvider): () => Promise<void>
|
||||
```
|
||||
|
||||
Types: [Session](../core-data-structures/session.md) · [SessionTitleProvider](../core-data-structures/session-title.md) · [SessionTitleSnapshot](../core-data-structures/session-title.md)
|
||||
|
||||
Source: [`packages/session-title/session-title/src/index.ts:282`](../../packages/session-title/session-title/src/index.ts)
|
||||
|
||||
## `ctx.skills` — `SkillService`
|
||||
|
||||
@@ -1021,7 +1267,7 @@ async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>
|
||||
|
||||
Types: [SubagentProvider](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md)
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:180`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
## `ctx.systemPrompt` — `SystemPrompt`
|
||||
|
||||
@@ -1069,7 +1315,7 @@ async assemble(context: AssembleContext = {}): Promise<PromptAssembly>
|
||||
|
||||
Types: [AssembleContext](../core-data-structures/system-prompt.md) · [PromptSection](../core-data-structures/system-prompt.md) · [ToolProviderResult](../core-data-structures/system-prompt.md)
|
||||
|
||||
Source: [`packages/core/system-prompt/src/index.ts:209`](../../packages/core/system-prompt/src/index.ts)
|
||||
Source: [`packages/core/system-prompt/src/index.ts:246`](../../packages/core/system-prompt/src/index.ts)
|
||||
|
||||
## `ctx.tasks` — `TaskService`
|
||||
|
||||
@@ -1193,7 +1439,44 @@ estimateMessage(message: Message): number
|
||||
|
||||
Types: [EpochHeader](../core-data-structures/session.md) · [Message](../core-data-structures/core.md) · [Session](../core-data-structures/session.md) · [TokenMeasurement](../core-data-structures/token-meter.md)
|
||||
|
||||
Source: [`packages/llm/token-meter/src/index.ts:106`](../../packages/llm/token-meter/src/index.ts)
|
||||
Source: [`packages/llm/token-meter/src/index.ts:82`](../../packages/llm/token-meter/src/index.ts)
|
||||
|
||||
## `ctx.toolResultPrune` — `ToolResultPruneService`
|
||||
|
||||
Deterministic head/middle/tail pruning for current tool-result surface nodes.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Measure text content in Unicode code points; non-text blocks cost zero.
|
||||
* @param blocks - tool-result content to measure.
|
||||
* @returns total Unicode code points across text blocks.
|
||||
*/
|
||||
measureContent(blocks: readonly ContentBlock[]): number
|
||||
|
||||
/**
|
||||
* Replace an over-budget text middle while retaining rich-block order.
|
||||
* Text slicing is by Unicode code point, not UTF-16 code unit, so a retained
|
||||
* boundary cannot split a surrogate pair. Grapheme clusters may still split.
|
||||
* @param blocks - original tool-result content.
|
||||
* @returns pruned content, or `null` when the text is within budget.
|
||||
*/
|
||||
pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null
|
||||
|
||||
/**
|
||||
* Prune every over-budget tool result from one stable current-surface snapshot.
|
||||
* Each replacement preserves the complete event data except for `content`,
|
||||
* and points at the shadowed node for durable provenance and replay.
|
||||
* @param session - session whose current surface is rewritten.
|
||||
* @returns landed replacements and aggregate Unicode-code-point savings.
|
||||
* @throws when the session rejects a replacement; replacements committed
|
||||
* earlier in the pass remain durable.
|
||||
*/
|
||||
pruneSession(session: Session): PruneResult
|
||||
```
|
||||
|
||||
Types: [ContentBlock](../core-data-structures/core.md) · [PruneResult](../core-data-structures/compaction.md) · [Session](../core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/compact/compact-tool-result-prune/src/index.ts:39`](../../packages/compact/compact-tool-result-prune/src/index.ts)
|
||||
|
||||
## `ctx.tools` — `ToolRegistry`
|
||||
|
||||
@@ -1261,7 +1544,11 @@ executionMode(exec: ToolExecutionInput): ToolExecutionMode
|
||||
* Execute through pre-policy, guards, around-dispatch, post-policy, and final
|
||||
* notification. Tool and listener failures resolve as materialized error
|
||||
* results; an invisible tool reports `UNKNOWN_TOOL`. The returned outcome is
|
||||
* the same lossless, frozen snapshot final observers receive.
|
||||
* the same lossless, frozen snapshot final observers receive. Cancellation
|
||||
* arriving after entry and before final result materialization skips a
|
||||
* not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a
|
||||
* successful started outcome with `ABORTED`; already-started work is still
|
||||
* drained and may retain a tool-owned structured error.
|
||||
* @param exec - the typed same-process call input. The registry assigns its
|
||||
* correlation token before policy begins.
|
||||
* @returns the materialized final result.
|
||||
@@ -1271,7 +1558,7 @@ async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>
|
||||
|
||||
Types: [ScopeKey](../core-data-structures/scope.md) · [ToolDefinition](../core-data-structures/tools.md) · [ToolExecutionInput](../core-data-structures/tools.md) · [ToolExecutionMode](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolGuard](../core-data-structures/tools.md) · [ToolRestriction](../core-data-structures/tools.md) · [ToolSchema](../core-data-structures/tools.md)
|
||||
|
||||
Source: [`packages/core/tools/src/index.ts:438`](../../packages/core/tools/src/index.ts)
|
||||
Source: [`packages/core/tools/src/index.ts:524`](../../packages/core/tools/src/index.ts)
|
||||
|
||||
## `ctx.userInteraction` — `UserInteractionService`
|
||||
|
||||
@@ -1297,7 +1584,7 @@ async ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>
|
||||
|
||||
Types: [AskUserQuestionAnswer](../core-data-structures/user-interaction.md) · [AskUserQuestionRequest](../core-data-structures/user-interaction.md) · [UserInteractionProvider](../core-data-structures/user-interaction.md)
|
||||
|
||||
Source: [`packages/ui/user-interaction/src/index.ts:82`](../../packages/ui/user-interaction/src/index.ts)
|
||||
Source: [`packages/ui/user-interaction/src/index.ts:50`](../../packages/ui/user-interaction/src/index.ts)
|
||||
|
||||
## `ctx.web` — `WebService`
|
||||
|
||||
|
||||
84
docs/core-data-structures/commands.md
Normal file
84
docs/core-data-structures/commands.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Human Commands
|
||||
|
||||
The human-command seam of [`dsh-commands`](../../packages/ui/commands). TUI and ACP adapters use it to discover and directly execute plugin-owned commands for an exact agent without creating a model message. The [command Agent Note](../../.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md) owns dispatch and lifecycle rationale; the [package README](../../packages/ui/commands/README.md) owns composition and limitations.
|
||||
|
||||
Source: [`packages/ui/commands/src/index.ts`](../../packages/ui/commands/src/index.ts)
|
||||
|
||||
## Input metadata
|
||||
|
||||
ACP currently exposes one unstructured-input hint. Command availability follows plugin composition: every adapter consuming the registry sees every effective definition.
|
||||
|
||||
```ts type-equiv
|
||||
/** Immutable command input metadata compatible with ACP unstructured input. */
|
||||
interface CommandInputDescriptor {
|
||||
/** Placeholder shown before the user supplies free-form input. */
|
||||
readonly hint: string
|
||||
}
|
||||
```
|
||||
|
||||
## Definition
|
||||
|
||||
`CommandDefinition` is the plugin-authored registration. The registry validates and freezes a detached effective definition.
|
||||
|
||||
```ts type-equiv
|
||||
/** Plugin-owned command registration. */
|
||||
interface CommandDefinition {
|
||||
/** Lowercase command name without the leading slash. */
|
||||
readonly name: string
|
||||
/** Human-readable summary used in discovery UI. */
|
||||
readonly description: string
|
||||
/** Optional free-form input hint advertised to capable clients. */
|
||||
readonly input?: CommandInputDescriptor
|
||||
/** Execute against the receiving agent without sending the command to the model. */
|
||||
readonly handler: (invocation: CommandInvocation) => CommandResult | Promise<CommandResult>
|
||||
}
|
||||
```
|
||||
|
||||
## Invocation and result
|
||||
|
||||
The adapter owns cancellation and passes the exact target agent. `rawInput` begins immediately after the parsed name and retains the adapter-delivered separator and suffix. Results are direct UI outcomes, not tool results or session events.
|
||||
|
||||
```ts type-equiv
|
||||
/** Invocation passed to one registered command handler. */
|
||||
interface CommandInvocation {
|
||||
/** Exact agent whose human-facing surface received the command. */
|
||||
readonly agent: Agent
|
||||
/** Exact text following the registered command name, including separator whitespace. */
|
||||
readonly rawInput: string
|
||||
/** Cancellation signal owned by the dispatching UI request. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Expected command outcome rendered directly by the dispatching UI. */
|
||||
type CommandResult =
|
||||
| { readonly kind: 'success'; readonly text?: string }
|
||||
| { readonly kind: 'error'; readonly text: string }
|
||||
```
|
||||
|
||||
## Discovery and parsing views
|
||||
|
||||
Adapters receive handler-free immutable descriptors after scope resolution. `parseCommand()` returns `ParsedCommand` before registry resolution; syntax-valid input can still name an unavailable command.
|
||||
|
||||
```ts type-equiv
|
||||
/** Handler-free immutable command view returned to UI adapters. */
|
||||
interface CommandDescriptor {
|
||||
/** Lowercase command name without the leading slash. */
|
||||
readonly name: string
|
||||
/** Human-readable summary used in discovery UI. */
|
||||
readonly description: string
|
||||
/** Optional free-form input hint advertised to capable clients. */
|
||||
readonly input?: CommandInputDescriptor
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Syntactically valid slash command before registry resolution. */
|
||||
interface ParsedCommand {
|
||||
/** Lowercase command name without the leading slash. */
|
||||
readonly name: string
|
||||
/** Exact text following the command name. */
|
||||
readonly rawInput: string
|
||||
}
|
||||
```
|
||||
@@ -6,7 +6,7 @@ Source: [`packages/compact/compact/src/types.ts`](../../packages/compact/compact
|
||||
|
||||
## The `compact/*` session events
|
||||
|
||||
Compaction extends [`SessionEventMap`](session.md) with three event types via declaration merging. All three are **log-only** — they record the compaction lock and its provenance, and never join the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', start, end }` — the only surface mutation. See the Agent Note for why reusing `user/message` is honest rather than a workaround.
|
||||
Compaction extends [`SessionEventMap`](session.md) with three event types via declaration merging. All three are **log-only** — they record the compaction lock and its provenance, and never join the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', start, end }` — the only surface mutation performed by summary compaction. See the Agent Note for why reusing `user/message` is honest rather than a workaround.
|
||||
|
||||
| Event | Payload | Role |
|
||||
|---|---|---|
|
||||
@@ -60,6 +60,36 @@ type CompactionTrigger = 'pressure' | 'context-overflow'
|
||||
|
||||
`CompactService` exposes `compactIfNeeded(agent, trigger, signal)` for automatic `pressure` or `context-overflow` policy, returning `null` when no safe work exists, and `compactRegion(...)` for an explicit inclusive surface range. Implementations must forward the supplied signal to summarization. The seam owns no pricing API: the singleton [`ctx.tokenMeter`](token-meter.md) directly owns estimation and replay, while `dsh-compact-basic` owns retention, event sequencing, routed summarization calls, and their configuration.
|
||||
|
||||
Pressure compaction runs at serial `agent/post-step`, after successful assistant output, tool results, buffered context, and steering are durable but before `step/end`. Failed-request recovery runs through `agent/request-error` after the failed step closes, and authorizes a fresh numbered-step retry only when the surface replacement generation advances. Region boundaries preserve tool-call/result pairing but do not preserve whole turns, allowing early closed steps of one oversized turn to compact. `dsh-compact-basic` owns thresholds, retained-tail policy, overflow caps, and failure handling.
|
||||
Pressure compaction runs at serial `agent/post-step`, after successful assistant output, tool results, buffered context, and steering are durable but before `step/end`. Once pressure or canonical overflow qualifies, compact-basic invokes optional [`ctx.toolResultPrune`](../../packages/compact/compact-tool-result-prune/README.md) before range selection, remeasures through `ctx.tokenMeter`, and can advance the surface without a summary. Failed-request recovery runs through `agent/request-error` after the failed step closes and authorizes a fresh numbered-step retry only when the surface replacement generation advances, even if later summary work throws after pruning; cancellation still wins. Region boundaries preserve tool-call/result pairing but not whole turns, allowing early closed steps of one oversized turn to compact. `dsh-compact-basic` owns thresholds, retained-tail policy, overflow caps, and failure handling.
|
||||
|
||||
The seam exports `toolPairingBalancedBefore(session, seq)` and `toolPairingBalancedAfter(session, seq)` for those edge checks. Both validate current surface membership and reject missing seqs and orphan results; the [package contract](../../packages/compact/compact/README.md#tool-pairing-boundaries) owns their cache semantics.
|
||||
|
||||
## Tool-result pruning outcomes
|
||||
|
||||
The optional tool-result pruning service reports each durable content replacement and the aggregate Unicode-code-point reduction. Its public result types live in [`compact-tool-result-prune/src/types.ts`](../../packages/compact/compact-tool-result-prune/src/types.ts).
|
||||
|
||||
```ts type-equiv
|
||||
/** Provenance and size accounting for one landed surface replacement. */
|
||||
interface PrunedEntry {
|
||||
/** Full-fidelity tool-result event shadowed by the replacement. */
|
||||
readonly originalSeq: number
|
||||
/** Newly appended pruned tool-result event. */
|
||||
readonly replacementSeq: number
|
||||
/** Tool call shared by the original and replacement. */
|
||||
readonly callId: CallId
|
||||
/** Original text size in Unicode code points. */
|
||||
readonly charsBefore: number
|
||||
/** Replacement text size in Unicode code points. */
|
||||
readonly charsAfter: number
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Aggregate outcome of one stable-surface pruning pass. */
|
||||
interface PruneResult {
|
||||
/** Replacements in the snapshotted surface order. */
|
||||
readonly pruned: readonly PrunedEntry[]
|
||||
/** Total Unicode code points removed across replacements. */
|
||||
readonly charsRemoved: number
|
||||
}
|
||||
```
|
||||
|
||||
@@ -18,9 +18,12 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
|
||||
| [llm-streaming.md](llm-streaming.md) | the `StreamChunk` wire protocol + adapter contract, `BlockAssembler`, the `LlmAdapter` seam |
|
||||
| [token-meter.md](token-meter.md) | immutable scalar and positional replay measurements with consumed-log revisions |
|
||||
| [scope.md](scope.md) | scoped registration identity, dispatch carriers, and the owned `Scope` context |
|
||||
| [goal.md](goal.md) | persisted goal identity, lifecycle snapshots, activation, change records, and round attribution |
|
||||
| [commands.md](commands.md) | the human-command seam: definitions, adapter discovery, direct invocation, results, and parsing views |
|
||||
| [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnTrigger`/`TurnEndReason`, `deriveMessages()`, the turn-enclosure invariant |
|
||||
| [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` |
|
||||
| [session-query.md](session-query.md) | logical records, bounded exact-event reads, and relationship traces |
|
||||
| [session-title.md](session-title.md) | durable title snapshots, source provenance, and the asynchronous provider contract |
|
||||
| [system-prompt.md](system-prompt.md) | per-assembly context, tool-provider results, prompt sections, and cooperative assembly |
|
||||
| [tools.md](tools.md) | `ToolDefinition` full fields, the schema DSL, `ToolExecution`/`ToolResult`, tool-presentation UI types, and the guarded execution pipeline |
|
||||
| [user-interaction.md](user-interaction.md) | the UI-backed human question/answer seam: `AskUserQuestionRequest`, answer/options vocabulary, provider API, error taxonomy |
|
||||
@@ -30,6 +33,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
|
||||
| [sandbox.md](sandbox.md) | the process-confinement seam: file-effect modes, `SandboxPolicy`, `ConfinedArgv`, enforcement and fail-closed errors |
|
||||
| [code-runtime.md](code-runtime.md) | the code-execution seam: `CodeRunRequest`/`Result`, binding namespaces, captured logs, the `CodeRunFailure` taxonomy |
|
||||
| [filesystem.md](filesystem.md) | the filesystem seam: `FsTarget`, read/write/edit outcomes, observed-file state, `FsErrorCode` |
|
||||
| [lsp.md](lsp.md) | the LSP navigation seam: `LspQueryRequest`/`Result`, `LspProvider`/`Service`, four operations, `LspError` |
|
||||
| [skills.md](skills.md) | the skill service: discovery priority, `SkillSummary`/`SkillDefinition`, session-prefix catalog, model-facing `skill` loading |
|
||||
| [compaction.md](compaction.md) | the compaction seam: the `compact/*` session events, `CompactionResult`, the `CompactService` interface |
|
||||
| [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split |
|
||||
@@ -191,6 +195,16 @@ interface LlmModelInfo {
|
||||
}
|
||||
```
|
||||
|
||||
Correctness-sensitive model capacity is queried separately from the advisory catalog and is owned by the adapter serving the exact route.
|
||||
|
||||
```ts type-equiv
|
||||
/** Provider-owned context capacity for one exact provider/model route. */
|
||||
interface LlmModelContext {
|
||||
/** Maximum combined request and response context in tokens. */
|
||||
contextWindow: number
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** A single model request, fully assembled. */
|
||||
interface GenerateOptions {
|
||||
@@ -222,10 +236,16 @@ interface GenerateOptions {
|
||||
* it; replay uses it to keep concurrent parent and child cursors independent.
|
||||
*/
|
||||
sessionId?: Branded<'SessionId'>
|
||||
/**
|
||||
* Provider-neutral classification for an auxiliary model call. Adapters may
|
||||
* map the purpose to model-hidden transport metadata. Ordinary conversation
|
||||
* requests leave it unset.
|
||||
*/
|
||||
purpose?: 'compaction'
|
||||
}
|
||||
```
|
||||
|
||||
Why a model response stopped is a merge-extensible reason:
|
||||
Why a model response stopped is a merge-extensible reason. Terminal provider failures carry the streaming contract's [`LlmFailure`](llm-streaming.md#llmfailure):
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -236,8 +256,8 @@ interface FinishReasonMap {
|
||||
'stop': { kind: 'stop' }
|
||||
'tool-calls': { kind: 'tool-calls' }
|
||||
'max-tokens': { kind: 'max-tokens' }
|
||||
'aborted': { kind: 'aborted' }
|
||||
'error': { kind: 'error'; message: string; code?: string }
|
||||
'aborted': { kind: 'aborted'; failure: LlmFailure }
|
||||
'error': { kind: 'error'; failure: LlmFailure }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -267,7 +287,7 @@ The model-facing `ToolSchema` is the wire shape; the registered `ToolDefinition`
|
||||
|
||||
The loop builds each request from logged state. `EpochHeader` records call config, rendered prompt, authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset), and session prefix through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md).
|
||||
|
||||
`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, or sampling. `agent/session-prefix` composes request-only prefix messages once per loop instance, and the header records the exact result used. Requests reaching `llm/stream` are deep-frozen, so mutation throws.
|
||||
`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, or sampling. `agent/session-prefix` composes request-only prefix messages once per loop instance, and the header records the exact result used. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests.
|
||||
|
||||
On the wire, a loop-built request reads in this order: the `system` slot (the rendered prompt assembly) → `messagePrefix` (the frozen session prefix) → the derived history — the boundary snapshot, whose tail is the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The prefix never enters the derived history; its durable record is the header events, and the dev invariant recomputes exactly this equation against every loop-built request.
|
||||
|
||||
@@ -349,6 +369,13 @@ interface InjectOptions extends SendOptions {
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Stable runtime cause accepted by {@link Agent.cancel}. */
|
||||
type AgentCancelCause =
|
||||
| { readonly kind: 'user' }
|
||||
| { readonly kind: 'parent' }
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Public agent handle; its concrete implementation is internal to `@deepseek-ai/dsh-agent-loop`. */
|
||||
interface Agent {
|
||||
@@ -361,15 +388,20 @@ interface Agent {
|
||||
readonly ctx: Context
|
||||
|
||||
/**
|
||||
* Queue detached, frozen lossless-JSON input; starts a turn when idle.
|
||||
* Queue one detached, frozen lossless-JSON item. If claimed, it is the sole
|
||||
* ordinary message in its FIFO-ordered turn; the next claimed item waits for
|
||||
* that turn's checkpoint.
|
||||
* Invalid input throws synchronously before notification or enqueue.
|
||||
*/
|
||||
send(content: ContentBlock[], options?: SendOptions): void
|
||||
|
||||
/**
|
||||
* Steer a running turn: content is injected between steps of the current
|
||||
* turn. Uses the same owned-value and synchronous-validation boundary as
|
||||
* {@link send}; when idle, behaves exactly like that method.
|
||||
* Submit steering while the agent is `running`. An open turn records it at
|
||||
* the next steering checkpoint before a request or continuation decision;
|
||||
* policy may stop before another step. After turn close and its checkpoint,
|
||||
* any remainder is queued for a later turn; terminal `agent/turn-stop`,
|
||||
* cancellation, or disposal may discard it. Uses the same synchronous
|
||||
* snapshot-and-validation boundary as {@link send}; when idle, delegates to it.
|
||||
*/
|
||||
steer(content: ContentBlock[], options?: SendOptions): void
|
||||
|
||||
@@ -383,12 +415,15 @@ interface Agent {
|
||||
inject(content: ContentBlock[], options?: InjectOptions): void
|
||||
|
||||
/**
|
||||
* Clear queued and steering work, including work waiting to start, and abort
|
||||
* the active step. The supplied reason is preserved across pre-step and active
|
||||
* cancellation windows, and `whenIdle()` resolves after cancellation reaches
|
||||
* quiescence. Idle cancellation is a no-op and does not arm a later cancel.
|
||||
* Clear all queued and steering work, including items waiting to start, and
|
||||
* abort the active turn. An effective call first emits
|
||||
* `agent/cancel-requested` with the resolved typed cause. The first cause wins
|
||||
* for the active turn, and `whenIdle()` resolves after cancellation reaches
|
||||
* quiescence. Omission means `{ kind: 'user' }`. Idle cancellation is a no-op
|
||||
* and does not arm later work. The active turn snapshots and freezes the cause.
|
||||
* @param cause - the stable caller intent carried by the current turn signal.
|
||||
*/
|
||||
cancel(reason?: string): void
|
||||
cancel(cause?: AgentCancelCause): void
|
||||
|
||||
/** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */
|
||||
whenIdle(): Promise<void>
|
||||
@@ -396,7 +431,9 @@ interface Agent {
|
||||
}
|
||||
```
|
||||
|
||||
`AgentStatus` is `'idle' | 'running' | 'disposed'`, and `SessionId` is branded. `AgentOptions` is merge-extensible and currently includes `provider?` and `model?`; dispatch requires both after `agent/request`. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default.
|
||||
`AgentStatus` is `'idle' | 'running' | 'disposed'`, and `SessionId` is branded. `running` describes the driver-wide drain interval, which can span turn close, its durability checkpoint, and consecutive queued turns; it does not prove a turn is still open. `AgentOptions` is merge-extensible: core declares `provider?` and `model?` (dispatch requires both after `agent/request`). Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default.
|
||||
|
||||
The cause is a TypeScript-enforced same-process input. An active holder copies its discriminant into the runtime-only `AbortSignal.reason`; it is retired before `turn/end` publication. `agentInterruptReasonOf(signal)` recognizes `user`, `parent`, and lifecycle-only `disposed` without consulting ambient initiator state. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result.
|
||||
|
||||
The [event taxonomy](../architecture.md#event) owns the `agent/*` lifecycle, checkpoint, and waterfall contracts. Turn and step boundaries are durable session events rather than agent emits.
|
||||
|
||||
@@ -420,13 +457,14 @@ interface HookContext {
|
||||
}
|
||||
```
|
||||
|
||||
`agent/prompt-submit` returns a `PromptDecision` (allow a drained queued message — optionally rewriting its `content` or attaching `additionalContexts` — or block it; a batch whose every prompt is blocked opens a zero-step turn that ends `rejected`):
|
||||
`agent/prompt-submit` returns a `PromptDecision` (allow the turn's claimed queued message — optionally rewriting its `content` or attaching `additionalContexts` — or record `prompt/blocked` and end that zero-step turn as `rejected`):
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Prompt interception result. `allow.content` replaces the prompt and each
|
||||
* `additionalContexts` entry becomes a separate context message. `block` records a
|
||||
* durable `prompt/blocked`; an all-blocked batch ends a zero-step rejected turn.
|
||||
* `additionalContexts` entry becomes a separate context message. `block`
|
||||
* records a durable `prompt/blocked` and ends the claimed prompt's zero-step
|
||||
* turn as rejected.
|
||||
*/
|
||||
type PromptDecision =
|
||||
| { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: HookContext[] }
|
||||
@@ -442,14 +480,14 @@ type ContinuationDecision =
|
||||
| { action: 'continue'; reason?: { content: ContentBlock[]; source: MessageSource } }
|
||||
```
|
||||
|
||||
`agent/request-error` receives the original `RequestError`, whose optional provider-neutral `code` supports stable routing without message parsing:
|
||||
`agent/request-error` receives the exact original `RequestError` beside its immutable `LlmFailure`, an immutable list of failures that already authorized another request in the consecutive sequence, the turn signal, and `next()`. Recovery plugins route on `failure.code`, not the live error's message; each policy counts only its own codes, and a successful request clears the history:
|
||||
|
||||
```ts type-equiv
|
||||
/** Model-request failure with an optional machine-routable provider code. */
|
||||
type RequestError = Error & { code?: string }
|
||||
```
|
||||
|
||||
It returns a `RequestErrorDecision`; `retry` opens a new numbered step after the recovery listener's durable mutation, while `fail` preserves that error:
|
||||
It returns a `RequestErrorDecision`; `retry` opens a new numbered step after the recovery listener's durable mutation, while `fail` retains the structured failure on `turn/end`:
|
||||
|
||||
```ts type-equiv
|
||||
/** Failed-request recovery decision; `retry` opens another numbered step while listeners delegate by calling `next()`. */
|
||||
|
||||
143
docs/core-data-structures/goal.md
Normal file
143
docs/core-data-structures/goal.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Same-session goals
|
||||
|
||||
Types shared by the event-sourced goal domain and its policy consumers. The [goal-domain Agent Note](../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) owns the persistence and activation decisions; this page records the literal shapes from [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts).
|
||||
|
||||
## Identity and lifecycle
|
||||
|
||||
`GoalId` is a [branded id](core.md#branded-ids). A caller mutates one exact revision through `GoalRef`; every accepted durable mutation increments the revision.
|
||||
|
||||
```ts type-equiv
|
||||
/** Compare-and-set identity for one exact goal revision. */
|
||||
interface GoalRef {
|
||||
/** Stable goal identity. */
|
||||
readonly id: GoalId
|
||||
/** Positive revision; every durable mutation increments it. */
|
||||
readonly revision: number
|
||||
}
|
||||
```
|
||||
|
||||
The durable phase answers what happened to the objective. Process-local activation separately answers whether a continuation consumer may start another round.
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable continuation phase. Activation is process-local and separate. */
|
||||
type GoalPhase =
|
||||
| 'active'
|
||||
| 'paused'
|
||||
| 'blocked'
|
||||
| 'complete'
|
||||
```
|
||||
|
||||
Blocking is the single durable stopped-by-a-problem state. Its policy-owned reason carries a stable lower-kebab-case code for routing and a free-form explanation for humans and models.
|
||||
|
||||
```ts type-equiv
|
||||
/** Machine-routable and human-readable explanation for a blocked goal. */
|
||||
interface GoalBlockReason {
|
||||
/** Stable lower-kebab-case classification chosen by the blocking policy. */
|
||||
readonly code: string
|
||||
/** Non-empty explanation shown to humans and models. */
|
||||
readonly message: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Full durable state written by every non-clear goal mutation. */
|
||||
interface GoalSnapshot extends GoalRef {
|
||||
/** Human-requested completion objective. */
|
||||
readonly objective: string
|
||||
/** Durable lifecycle phase. */
|
||||
readonly phase: GoalPhase
|
||||
/** Present exactly while `phase` is `blocked`. */
|
||||
readonly blockedReason?: GoalBlockReason
|
||||
/** Total admitted goal-round cap. */
|
||||
readonly maxGoalRounds: number
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Current goal projection, including values derived from the session log. */
|
||||
interface GoalView extends GoalSnapshot {
|
||||
/** Highest admitted round number for this goal. */
|
||||
readonly roundsStarted: number
|
||||
/** Epoch milliseconds of the create mutation. */
|
||||
readonly createdAt: number
|
||||
/** Epoch milliseconds of the latest mutation. */
|
||||
readonly updatedAt: number
|
||||
/** Process-local continuation eligibility; never persisted. */
|
||||
readonly activation: GoalActivation
|
||||
}
|
||||
```
|
||||
|
||||
## Durable changes
|
||||
|
||||
Every mutation is a `context/message` whose metadata is either a complete snapshot or a clear tombstone. The version, metadata, goal source, and verbatim rendered content form one replay invariant.
|
||||
|
||||
```ts type-equiv
|
||||
/** Full-snapshot goal mutation retained in a model-visible context event. */
|
||||
interface GoalSnapshotChangeMeta {
|
||||
readonly kind: 'goal/change'
|
||||
readonly version: 1
|
||||
readonly operation: Exclude<GoalOperation, 'clear'>
|
||||
readonly goal: GoalSnapshot
|
||||
readonly roundsStarted: number
|
||||
readonly createdAt: number
|
||||
readonly updatedAt: number
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Tombstone retained when the current goal is cleared. */
|
||||
interface GoalClearChangeMeta {
|
||||
readonly kind: 'goal/change'
|
||||
readonly version: 1
|
||||
readonly operation: 'clear'
|
||||
readonly cleared: GoalRef
|
||||
readonly clearedAt: number
|
||||
}
|
||||
```
|
||||
|
||||
Goal state changes use round `0`. A continuation consumer attributes each admitted user-message turn with a positive, sequential round number and the current revision; replay rejects gaps, stale revisions, stopped phases, and cap overflow.
|
||||
|
||||
```ts type-equiv
|
||||
/** Message attribution for durable goal state and continuation rounds. */
|
||||
interface GoalMessageSource {
|
||||
readonly kind: 'goal'
|
||||
readonly goalId: GoalId
|
||||
readonly revision: number
|
||||
/** Zero for state changes; positive for admitted continuation rounds. */
|
||||
readonly round: number
|
||||
}
|
||||
```
|
||||
|
||||
## Requests and notifications
|
||||
|
||||
Creation separates caller omission from the deployment choice, which `create()` resolves internally. An edit is a partial replacement whose runtime validator requires at least one field. Every mutation notification carries the accepted operation and exact revision; clear omits `goal`.
|
||||
|
||||
```ts type-equiv
|
||||
/** Input whose omitted round cap is resolved by the service configuration. */
|
||||
interface CreateGoalRequest {
|
||||
readonly objective: string
|
||||
readonly maxGoalRounds?: number
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Fields changed by an edit; at least one must be present. */
|
||||
interface EditGoalRequest {
|
||||
readonly objective?: string
|
||||
readonly maxGoalRounds?: number
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Live notification after one goal mutation has been accepted for logging. */
|
||||
interface GoalChanged {
|
||||
readonly operation: GoalOperation
|
||||
readonly ref: GoalRef
|
||||
/** Absent for a clear tombstone. */
|
||||
readonly goal?: GoalView
|
||||
}
|
||||
```
|
||||
|
||||
## Service behavior
|
||||
|
||||
[`GoalService`](../../packages/goal/goal/src/index.ts) resolves creation defaults, folds strict replay, enforces exact-live-agent identity and compare-and-set mutations, overlays deferred injections, and emits contained `goal/changed` notifications. The package [README](../../packages/goal/goal/README.md) owns the callable and model-visible contract.
|
||||
@@ -31,18 +31,40 @@ type StreamChunk =
|
||||
}
|
||||
```
|
||||
|
||||
## `LlmFailure`
|
||||
|
||||
Every thrown or in-band final-adapter failure normalizes to one serializable provider-neutral payload. `providerRetryAfterMs` is a validated positive delay requested by the provider, not a retry decision; `ProviderRequestId` is an opaque branded string for diagnostics.
|
||||
|
||||
```ts type-equiv
|
||||
/** Serializable provider-boundary facts; policy decides whether they are retryable. */
|
||||
interface LlmFailure {
|
||||
/** Human-readable provider or transport failure. */
|
||||
readonly message: string
|
||||
/** Stable provider-neutral machine-routing code. */
|
||||
readonly code: string
|
||||
/** HTTP status observed at the provider boundary, when available. */
|
||||
readonly status?: number
|
||||
/** Provider-requested delay in milliseconds, when valid and available. */
|
||||
readonly providerRetryAfterMs?: number
|
||||
/** Opaque provider-issued request identifier for diagnostics. */
|
||||
readonly requestId?: ProviderRequestId
|
||||
}
|
||||
```
|
||||
|
||||
## The adapter contract
|
||||
|
||||
Every adapter MUST obey these, and every consumer may rely on them:
|
||||
|
||||
- **`usage` before `finish`, nothing after `finish`.** Defer both to the provider's end-of-stream marker so a trailing usage-only chunk can't violate the ordering.
|
||||
- **Tool-call `arguments` stay raw JSON strings end-to-end.** Partial fragments stream via `argumentsDelta`; a provider that hands back parsed objects re-stringifies at `block-end`.
|
||||
- **Two sanctioned error paths.** A failure may either THROW from `stream()` (transport/protocol errors) **or** end the stream with `finish {kind:'error'|'aborted'}` (provider in-band errors, for adapters that can't throw mid-stream). Consumers must handle *both*. The agent loop closes the failed step and offers either form to `agent/request-error`; absent recovery it becomes a turn error, and no normal completed assistant message is logged for that request.
|
||||
- **Two sanctioned error paths, one fact shape.** A failure may either THROW from `stream()` (transport/protocol errors) **or** end the stream with `finish {kind:'error'|'aborted', failure}` (provider in-band errors, for adapters that can't throw mid-stream). `LlmError.failure` carries the same `LlmFailure`. The final adapter boundary preserves the exact thrown `Error` object and associates immutable facts with that call; the agent loop closes the failed step and offers the error, facts, and immutable prior-retried facts to `agent/request-error`. Absent recovery the structured failure becomes the turn error, and no normal assistant message or tool side effect is committed for that attempt.
|
||||
- **One adapter call is one provider attempt.** Adapters disable library retries. Agent-level recovery opens another durable numbered step; direct `ctx.llm.stream()` callers remain single-attempt.
|
||||
- **Provider stalls are bounded at the transport.** Both shipping remote adapters expose positive finite `streamIdleTimeoutMs` with a five-minute default. The watchdog arms only while iterator `next()` is outstanding, uses one stable signal for the whole request, maps its own expiry to `TIMEOUT`, and keeps an earlier caller abort as `ABORTED`.
|
||||
- **Context overflow has one canonical code.** Both DeepSeek adapters classify explicit provider detail through `isContextWindowExceededError()` and surface `CONTEXT_WINDOW_EXCEEDED`, whether the failure arrives as a thrown HTTP `LlmError` or an in-band finish error. Consumers route on the code, never provider text.
|
||||
- **Every provider HTTP request carries the app-attribution header.** Adapters send `attributionHeaders()` (below) - the `User-Agent` baseline - and prove it with a wire-level test (mock server asserting the received header, or the library's header hook for a library-backed adapter).
|
||||
- **Replay state is adapter-owned.** A successful `finish` may carry lossless-JSON state needed to reconstruct a native provider response. The loop stores it with the assembled assistant message unless an `agent/step-result` listener rewrote the content. On a later request, `LlmService` passes the state only when the historical provider and target provider are currently registered to the exact same adapter instance. That adapter validates the state and owns any cross-model or cross-provider conversion; other adapters receive the provider-neutral content and provenance without the private state.
|
||||
|
||||
This contract was pinned down by two deliberately independent implementations: `dsh-llm-deepseek` (hand-rolled fetch/SSE) and `dsh-llm-pi-ai` (a generic multi-provider adapter through `@earendil-works/pi-ai`). The library-backed adapter cannot throw mid-stream, so it exercises the finish-chunk error path the hand-rolled one might not.
|
||||
This contract is pinned down by two deliberately independent implementations: `dsh-llm-deepseek` (hand-rolled fetch/SSE) and `dsh-llm-pi-ai` (a generic multi-provider adapter through `@earendil-works/pi-ai`). The library-backed adapter exercises the finish-chunk error path, while transport-boundary tests prove each idle watchdog stops its actual request.
|
||||
|
||||
## `AppIdentity` — app attribution
|
||||
|
||||
@@ -132,7 +154,7 @@ declare class BlockAssembler {
|
||||
|
||||
## The seam
|
||||
|
||||
`LlmAdapter` is the provider seam: subclass, implement `stream()`, and register one adapter instance with `ctx.llm.registerAdapter(providers, adapter)`. `GenerateOptions.provider` selects the registered adapter; `GenerateOptions.model` is passed to that adapter and need not be registered at lifecycle start. Duplicate provider routes fail atomically. Optional `providerInfo()` and asynchronous `listModels()` methods feed `LlmService.listProviders()` / `listModels()` with detached selector metadata. That catalog is advisory rather than a request whitelist: the adapter remains authoritative and may accept unlisted model ids. Adapter lookup happens at the terminal continuation of the `llm/stream` waterfall, so a listener may short-circuit the call or route a mutable one-shot request before lookup. The `block-start` / `block-end` `index` correlation and the assembler together mean an adapter only has to emit well-formed chunks — block reassembly is not each adapter's problem. The consumer surface (`ctx.llm.stream()`) and the `llm/stream` waterfall are described in [architecture.md § Content blocks and streaming](../architecture.md#content-blocks-and-streaming-dsh-llm).
|
||||
`LlmAdapter` is the provider seam: subclass, implement `stream()`, and register one adapter instance with `ctx.llm.registerAdapter(providers, adapter)`. `GenerateOptions.provider` selects the registered adapter; `GenerateOptions.model` is passed to that adapter and need not be registered at lifecycle start. Duplicate provider routes fail atomically. Optional `providerInfo()` and asynchronous `listModels()` methods feed `LlmService.listProviders()` / `listModels()` with detached selector metadata. That catalog is advisory rather than a request whitelist: the adapter remains authoritative and may accept unlisted model ids. The separate `resolveModelContext()` query exposes correctness-sensitive capacity for an exact route without making catalog membership authoritative; absence means unknown metadata, not invalid routing. Adapter lookup happens at the terminal continuation of the `llm/stream` waterfall, so a listener may short-circuit the call or route a mutable one-shot request before lookup. The `block-start` / `block-end` `index` correlation and the assembler together mean an adapter only has to emit well-formed chunks — block reassembly is not each adapter's problem. The consumer surface (`ctx.llm.stream()`) and the `llm/stream` waterfall are described in [architecture.md § Content blocks and streaming](../architecture.md#content-blocks-and-streaming-dsh-llm).
|
||||
|
||||
```ts public-api
|
||||
/**
|
||||
@@ -156,6 +178,17 @@ declare abstract class LlmAdapter {
|
||||
* @returns discoverable models in adapter-preferred order.
|
||||
*/
|
||||
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
||||
/**
|
||||
* Resolve context capacity for one model accepted by this adapter. Absence
|
||||
* means the adapter does not know the capacity, not that routing is invalid.
|
||||
* @param _provider - one provider route owned by this adapter.
|
||||
* @param _model - exact model id passed to {@link GenerateOptions.model}.
|
||||
* @returns provider-owned context metadata, or `undefined` when unavailable.
|
||||
*/
|
||||
resolveModelContext(
|
||||
_provider: string,
|
||||
_model: string,
|
||||
): Promise<LlmModelContext | undefined>;
|
||||
/**
|
||||
* Stream one model call as raw chunks. The only required method.
|
||||
* @param options - the fully-assembled request; implementations must honor `options.signal`.
|
||||
|
||||
163
docs/core-data-structures/lsp.md
Normal file
163
docs/core-data-structures/lsp.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# LSP navigation
|
||||
|
||||
The LSP seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md) exposing semantic code navigation on one `ctx.lsp` service, split across packages: interface ([dsh-lsp](../../packages/lsp/lsp), `ctx.lsp` + the provider registry), a generic implementation ([dsh-lsp-local](../../packages/lsp/lsp-local), a configured stdio language-server host), and consumer ([dsh-tool-lsp](../../packages/lsp/tool-lsp), the `lsp` tool schema). LSP is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). A provider swap does not change how the model asks for navigation.
|
||||
|
||||
Source: [`packages/lsp/lsp/src/types.ts`](../../packages/lsp/lsp/src/types.ts)
|
||||
|
||||
## Operations and coordinates
|
||||
|
||||
The seam and model expose exactly four semantic queries; the union is closed, so adding one is a compile-enforced change across the seam, providers, and the tool. Positions and ranges are zero-based UTF-16, matching the protocol; the model-facing tool owns the one-based cursor convention and converts on the way in and out.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The four semantic queries the seam and model expose. A closed union: adding an operation is a
|
||||
* compile-enforced change across the seam, providers, and the tool. Symbols and call hierarchy are
|
||||
* deliberately deferred (they need different schemas).
|
||||
*/
|
||||
type LspOperation = 'goToDefinition' | 'findReferences' | 'goToImplementation' | 'hover'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** A zero-based UTF-16 cursor coordinate, matching the LSP wire convention. */
|
||||
interface LspPosition {
|
||||
/** Zero-based line. */
|
||||
readonly line: number
|
||||
/** Zero-based UTF-16 code-unit offset within the line. */
|
||||
readonly character: number
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** A zero-based UTF-16 half-open range `[start, end)`. */
|
||||
interface LspRange {
|
||||
readonly start: LspPosition
|
||||
readonly end: LspPosition
|
||||
}
|
||||
```
|
||||
|
||||
## Request
|
||||
|
||||
Every field is required: `workspaceRoot` is caller-supplied, `languageId` comes from the provider's registration (not the request), and consumers own timeouts and result limits — so no field needs implementation defaulting and there is no `resolve()` step. The provider receives the caller's request plus the derived `languageId`, which only synchronizes the transient document and never participates in selection.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A caller's normalized query. Every field is required: `workspaceRoot` is caller-supplied,
|
||||
* `languageId` comes from the provider registration (not here), and consumers own timeouts and
|
||||
* result limits — so no field needs implementation defaulting and there is no `resolve()` step.
|
||||
*/
|
||||
interface LspQueryRequest {
|
||||
/** Which semantic query to run. */
|
||||
readonly operation: LspOperation
|
||||
/** The source file to query (relative to `workspaceRoot` or absolute; the provider canonicalizes). */
|
||||
readonly filePath: string
|
||||
/** The zero-based UTF-16 cursor position to query at. */
|
||||
readonly position: LspPosition
|
||||
/** The workspace root the provider resolves against and indexes; required, never defaulted. */
|
||||
readonly workspaceRoot: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A request as a provider receives it: the caller's {@link LspQueryRequest} plus the `languageId`
|
||||
* the seam derived from the provider's extension mapping. The language id only synchronizes the
|
||||
* transient document; it does not participate in selection.
|
||||
*/
|
||||
interface LspProviderQuery extends LspQueryRequest {
|
||||
/** The LSP language id for `filePath`, from this provider's extension mapping. */
|
||||
readonly languageId: string
|
||||
}
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
A CLOSED discriminated union: navigation operations normalize to `locations`, `hover` to content or `null`. Consumers `switch` on `kind` to exhaustiveness so a new arm breaks compilation until handled. `findReferences` always includes declarations — the provider enforces this internally, so callers get no flag. The `locations` variant carries `resolvedWorkspaceRoot`: the provider's canonical form of the request's `workspaceRoot` and the root its `file:` URIs are relative to, so a caller relativizing display paths uses it rather than the possibly-symlinked request root.
|
||||
|
||||
```ts type-equiv
|
||||
/** One resolved location: a document URI and the range within it. */
|
||||
interface LspLocation {
|
||||
/** The target document URI (`file:` or otherwise), verbatim from the server. */
|
||||
readonly uri: string
|
||||
/** The range within the target document. */
|
||||
readonly range: LspRange
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Normalized hover content, or `null` for no hover at the position. */
|
||||
interface LspHover {
|
||||
/** The normalized hover text (markdown or plaintext, provider-joined). */
|
||||
readonly contents: string
|
||||
/** The range the hover applies to, when the server supplied one. */
|
||||
readonly range?: LspRange
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The closed result union. Navigation operations (`goToDefinition`, `findReferences`,
|
||||
* `goToImplementation`) normalize to `locations`; `hover` normalizes to content or `null`.
|
||||
* Consumers `switch` on `kind` to exhaustiveness so a new arm breaks compilation until handled.
|
||||
*
|
||||
* The `locations` variant carries `resolvedWorkspaceRoot`: the provider's canonical form of the
|
||||
* request's `workspaceRoot`, and the root its `file:` location URIs are relative to. A caller that
|
||||
* relativizes display paths MUST use this, not the request's (possibly symlinked) `workspaceRoot`;
|
||||
* otherwise a symlinked workspace misclassifies in-workspace results as external.
|
||||
*/
|
||||
type LspQueryResult =
|
||||
| { readonly kind: 'locations'; readonly locations: readonly LspLocation[]; readonly resolvedWorkspaceRoot: string }
|
||||
| { readonly kind: 'hover'; readonly hover: LspHover | null }
|
||||
```
|
||||
|
||||
## Provider and service
|
||||
|
||||
A provider owns a stable branded `id` and an exclusive lowercase leading-dot extension map. `registerProvider` reserves the id and every extension atomically — an invalid or conflicting registration publishes nothing — and its disposer releases all reservations. Selection is per query and order-independent; no match throws `LspError` `LSP_UNAVAILABLE`. The seam exposes no protocol types, process/document controls, or generic JSON-RPC escape hatch.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A language-server backend registered on `ctx.lsp`. Each provider owns a stable {@link
|
||||
* LspProviderId} and an extension-to-language-id map (lowercase, leading-dot keys).
|
||||
* `findReferences` always includes declarations — the provider enforces this internally; callers
|
||||
* get no flag.
|
||||
*/
|
||||
interface LspProvider {
|
||||
/** Stable provider identity, reserved atomically with the extension mappings. */
|
||||
readonly id: LspProviderId
|
||||
/** Lowercase leading-dot extension → LSP language id (e.g. `{ '.ts': 'typescript' }`). */
|
||||
readonly extensionToLanguage: Readonly<Record<string, string>>
|
||||
/**
|
||||
* Run one query. The seam has already selected this provider and derived `languageId`.
|
||||
* @param request - the resolved provider query (caller request + derived language id).
|
||||
* @param signal - optional cancellation; the provider stops its own work when it aborts.
|
||||
* @returns the normalized, closed-union result.
|
||||
*/
|
||||
query(request: LspProviderQuery, signal?: AbortSignal): Promise<LspQueryResult>
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The LSP capability seam (`ctx.lsp`). Owns provider registration/selection and normalized query
|
||||
* execution; exposes exactly the four operations and no protocol escape hatch.
|
||||
*/
|
||||
interface LspService {
|
||||
/**
|
||||
* Register a provider, atomically reserving its id and every normalized extension. Any conflict
|
||||
* or invalid input publishes nothing and throws `LspError`; the returned disposer releases all
|
||||
* reservations. Disposed with the calling fiber.
|
||||
* @param provider - the backend to register.
|
||||
* @returns a synchronous disposer releasing the id and all extension reservations.
|
||||
*/
|
||||
registerProvider(provider: LspProvider): () => void
|
||||
/**
|
||||
* Select a provider by the file's extension and run one query. Selection is per-query and
|
||||
* order-independent; no match throws `LspError` `LSP_UNAVAILABLE`.
|
||||
* @param request - the normalized query.
|
||||
* @param signal - optional cancellation forwarded to the selected provider.
|
||||
* @returns the normalized, closed-union result.
|
||||
*/
|
||||
query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>
|
||||
}
|
||||
```
|
||||
|
||||
`LspProviderId` is the seam's branded id (`Branded<'LspProviderId'>` from [dsh-brand](../../packages/util/brand)); `LspError` extends `HarnessError` with stable codes such as `LSP_INVALID_PROVIDER`, `LSP_CONFLICT`, `LSP_UNAVAILABLE`, `LSP_DISPOSED`, `LSP_UNSUPPORTED_OPERATION`, and `LSP_MALFORMED_RESPONSE`, which callers route on instead of parsing `message`.
|
||||
@@ -6,7 +6,7 @@ The seam is a textbook [capability seam](../../.agents/notes/implemented/archite
|
||||
|
||||
## The flush checkpoint
|
||||
|
||||
`session/event` is a *synchronous* notification; persistence plugins buffer it (write-behind) and drain at the awaited `session/flush` checkpoint the loop fires at every turn end. Flush is `ctx.parallel` (awaited): a turn's events are durably committed before the next turn starts, and the turn boundary is the commit boundary. A rejecting flush is reported via `agent/error` and the logger — never as a session event (it would land past the commit boundary), so the backend keeps its buffered events for the next flush.
|
||||
`session/event` is a *synchronous* notification; persistence plugins buffer it (write-behind) until `session/flush`. The loop awaits an ordinary turn's checkpoint before claiming the next queue item; synchronous idle `inject()` schedules its checkpoint without blocking `send()`, and disposal still drains it. A successful flush durably commits the closed turn as one unit; a rejecting flush is reported through `agent/error` and the logger — never as a session event past the closed turn — while the backend keeps its buffered events for the next flush.
|
||||
|
||||
## Crash recovery preserves an interrupted turn
|
||||
|
||||
@@ -60,12 +60,18 @@ interface SessionHeader {
|
||||
* boundary lets resume and replay distinguish parent history from child work.
|
||||
*/
|
||||
readonly seedLength?: number
|
||||
/**
|
||||
* Delegation depth: absent (zero) for a top-level session, parent depth + 1
|
||||
* for a subagent child. Persisted so a recursion budget survives restart and
|
||||
* resume — a runtime-only depth would reset a resumed child to top-level.
|
||||
*/
|
||||
readonly delegationDepth?: number
|
||||
}
|
||||
```
|
||||
|
||||
## `CreateSessionOptions` — seeding and metadata
|
||||
|
||||
Creating a `Session` through the store takes a `seed` (replay/fork an existing event log) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, and — only when reconstructing a persisted session — the original `createdAt` to preserve it.
|
||||
Creating a `Session` through the store takes a `seed` (replay/fork an existing event log) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the `delegationDepth`, and — only when reconstructing a persisted session — the original `createdAt` to preserve it.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -85,6 +91,7 @@ interface CreateSessionOptions {
|
||||
readonly parentSession?: SessionId
|
||||
readonly createdAt?: number
|
||||
readonly seedLength?: number
|
||||
readonly delegationDepth?: number
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -95,7 +102,7 @@ Replay/fork is therefore `ctx.sessions.create(id, { seed: seedEvents })`; resumi
|
||||
|
||||
Both implement the same abstract `SessionPersistence` (locate/create/append/load/list over `SessionEvent`) and pass `runPersistenceContract`, proving the seam is genuinely backend-agnostic:
|
||||
|
||||
- **[dsh-session-persistence-jsonl](../../packages/session-persistence/session-persistence-jsonl)** — an append-only JSONL log per session with crash-safe atomic writes, the interrupted-turn crash recovery above, and a read/replay path.
|
||||
- **[dsh-session-persistence-jsonl](../../packages/session-persistence/session-persistence-jsonl)** — an append-only logical JSONL log per session, stored as checksummed concatenated Zstandard frames by default or raw lines by configuration, with crash-safe atomic writes, interrupted-turn recovery, and a read/replay path.
|
||||
- **[dsh-session-persistence-sqlite](../../packages/session-persistence/session-persistence-sqlite)** — `node:sqlite`, one row per `SessionEvent`. The row shape `(session_id, seq, type, time, data, source_event_seqs, surface_op)` maps 1:1 onto the event, including optional surface metadata, so there is no parallel persisted schema to keep in sync.
|
||||
|
||||
Multiple backends sharing one on-disk session coordinate writes through the [shared persistence write-coordinator](../../.agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md).
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Scoped Registration
|
||||
|
||||
The [scope package](../../packages/core/scope) supplies the identity and carrier vocabulary that makes one registration context mean both per-agent visibility and shared lifetime ownership. It is a library primitive rather than a Cordis service; the [agent-scope runtime-design Agent Note](../../.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md#scope-routing-one-opaque-key-selects-one-layer) owns the implementation rationale, while the package [README](../../packages/core/scope/README.md) owns the callable API and filtering semantics.
|
||||
The [scope package](../../packages/core/scope) supplies the identity, carrier, and scoped-layer vocabulary that makes one registration context mean both per-agent visibility and shared lifetime ownership. It is a library primitive rather than a Cordis service; the [agent-scope runtime-design Agent Note](../../.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md#scope-routing-one-opaque-key-selects-one-layer) owns the lifecycle rationale, the [shared-storage Agent Note](../../.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md) owns the registry-layer decision, and the package [README](../../packages/core/scope/README.md) owns the callable API and filtering semantics.
|
||||
|
||||
Source: [`packages/core/scope/src/index.ts`](../../packages/core/scope/src/index.ts).
|
||||
Sources: [`packages/core/scope/src/index.ts`](../../packages/core/scope/src/index.ts) and [`packages/core/scope/src/store.ts`](../../packages/core/scope/src/store.ts).
|
||||
|
||||
## Identity and dispatch carrier
|
||||
|
||||
@@ -39,3 +39,19 @@ interface Scope {
|
||||
dispose(): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
## Scoped registry layer
|
||||
|
||||
`ScopeLayer` represents one registry's complete contribution at the global or exact-scope level. A concrete layer may aggregate multiple named and anonymous tables; whole-layer emptiness lets `ScopedLayers` reclaim scoped state without discarding a sibling table.
|
||||
|
||||
```ts type-equiv
|
||||
/** One scope's aggregate contribution to a registry. */
|
||||
interface ScopeLayer {
|
||||
/** Whether every table in this layer is empty. */
|
||||
isEmpty(): boolean
|
||||
}
|
||||
```
|
||||
|
||||
`ScopedLayers<L>` owns the eager global layer and lazily created exact-scope layers. Reads do not create layers: `peek(undefined)` means no overlay, while `merge()` materializes insertion-ordered global named entries followed by scoped shadows. Registrations use one context for both visibility and Cordis effect ownership, collect one synchronous undo before optional notification, return Cordis's exact disposer, and reclaim a scoped layer only when its complete `ScopeLayer` is empty.
|
||||
|
||||
`NamedEntries<V>` supplies insertion-ordered lookup and live iteration with caller-owned duplicate errors. `AnonymousEntries<V>` gives every append a unique identity so equal values remain independent. Iteration stays live within one nonempty table generation; draining the table detaches existing iterators from later insertions. Both return idempotent exact-entry undos; the shared `EntryValues` implementation interface is not public.
|
||||
|
||||
140
docs/core-data-structures/session-title.md
Normal file
140
docs/core-data-structures/session-title.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Session Titles
|
||||
|
||||
Durable latest-wins title state and the optional asynchronous provider vocabulary owned by [`@deepseek-ai/dsh-session-title`](../../packages/session-title/session-title). The shared LLM helper owns the exact auxiliary request record. Package READMEs own timing, fallback, failure, and fork behavior; the generated [persistence catalog](../persistence-catalog.md) owns the complete event declarations.
|
||||
|
||||
Sources: [`packages/session-title/session-title/src/index.ts`](../../packages/session-title/session-title/src/index.ts), [`packages/session-title/session-title-llm/src/index.ts`](../../packages/session-title/session-title-llm/src/index.ts)
|
||||
|
||||
## Durable title state
|
||||
|
||||
`SessionTitleProviderId` is recorded for provider-produced revisions. `SessionTitleEventData` carries exact human-message provenance, while `SessionTitleSnapshot` adds the durable event envelope facts selected by `foldSessionTitle()`.
|
||||
|
||||
```ts type-equiv
|
||||
/** Identifies one session-title provider registration. */
|
||||
type SessionTitleProviderId = Branded<'SessionTitleProviderId'>
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact auxiliary model route that produced a title. */
|
||||
interface SessionTitleModelProvenance {
|
||||
/** Registered LLM provider route. */
|
||||
readonly provider: string
|
||||
/** Provider model id. */
|
||||
readonly model: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable ownership record for an accepted session title. */
|
||||
type SessionTitleSource =
|
||||
| { readonly kind: 'fallback' }
|
||||
| {
|
||||
readonly kind: 'provider'
|
||||
readonly provider: SessionTitleProviderId
|
||||
readonly model?: SessionTitleModelProvenance
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Payload of the log-only `session/title` event. */
|
||||
interface SessionTitleEventData {
|
||||
/** Normalized non-empty title text. */
|
||||
readonly title: string
|
||||
/** Exact human `user/message` seqs used to derive this title. */
|
||||
readonly messageSeqs: number[]
|
||||
/** Built-in fallback or registered-provider provenance. */
|
||||
readonly source: SessionTitleSource
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Latest folded title plus the title event's durable envelope facts. */
|
||||
interface SessionTitleSnapshot extends SessionTitleEventData {
|
||||
/** Seq of the latest `session/title` event. */
|
||||
readonly eventSeq: number
|
||||
/** Timestamp of the latest `session/title` event. */
|
||||
readonly updatedAt: number
|
||||
}
|
||||
```
|
||||
|
||||
## Auxiliary request record
|
||||
|
||||
The shared LLM helper records each validated, dispatchable title request before calling the model. The payload reproduces the model-visible system and message input, routing, output limit, provider ownership, and source-message attribution even when generation later fails.
|
||||
|
||||
```ts type-equiv
|
||||
/** Exact model-visible request recorded before one auxiliary title dispatch. */
|
||||
interface SessionTitleLlmRequestEventData {
|
||||
/** Registered title-provider identity responsible for the request. */
|
||||
readonly titleProvider: SessionTitleProviderId
|
||||
/** Exact human `user/message` seqs represented in `messages`. */
|
||||
readonly messageSeqs: number[]
|
||||
/** Exact auxiliary LLM route. */
|
||||
readonly route: SessionTitleModelProvenance
|
||||
/** Exact auxiliary system prompt. */
|
||||
readonly system: string
|
||||
/** Exact auxiliary message list. */
|
||||
readonly messages: Message[]
|
||||
/** Exact auxiliary output-token cap. */
|
||||
readonly maxTokens: number
|
||||
}
|
||||
```
|
||||
|
||||
## Provider input and output
|
||||
|
||||
The service snapshots eligible messages through one revision. A provider returns only seqs from that request; service-owned acceptance verifies ordering, normalizes the title, enforces the byte limit, and appends provenance.
|
||||
|
||||
```ts type-equiv
|
||||
/** One eligible human text message exposed to title providers. */
|
||||
interface SessionTitleUserMessage {
|
||||
/** Source `user/message` event seq. */
|
||||
readonly seq: number
|
||||
/** Exact concatenated text-block content. */
|
||||
readonly text: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Automatic generation cadence owned by a registered provider. */
|
||||
type SessionTitleAutomaticMode = 'first-message' | 'all-user-messages'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Immutable input supplied to one title-provider call. */
|
||||
interface SessionTitleProviderRequest {
|
||||
/** Live session being titled. */
|
||||
readonly session: Session
|
||||
/** All eligible human messages through this generation revision. */
|
||||
readonly messages: readonly SessionTitleUserMessage[]
|
||||
/** Exact current logged main-request route, when one has been recorded. */
|
||||
readonly route?: SessionTitleModelProvenance
|
||||
/** Cancellation for supersession, disposal, timeout composition, or the explicit caller. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Provider output before service-owned normalization and durable acceptance. */
|
||||
interface SessionTitleProviderResult {
|
||||
/** Proposed title text. */
|
||||
readonly title: string
|
||||
/** Exact seqs from `request.messages` used by this result. */
|
||||
readonly messageSeqs: readonly number[]
|
||||
/** Auxiliary LLM route, when generation used a model. */
|
||||
readonly model?: SessionTitleModelProvenance
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** One optional asynchronous title implementation registered with the service. */
|
||||
interface SessionTitleProvider {
|
||||
/** Stable provider identity recorded in title provenance. */
|
||||
readonly id: SessionTitleProviderId
|
||||
/** When new human prompts start automatic generation. */
|
||||
readonly automatic: SessionTitleAutomaticMode
|
||||
/**
|
||||
* Produce one title revision.
|
||||
* @param request - message snapshot, current route, session, and cancellation.
|
||||
* @returns proposed title plus exact input seqs and optional model provenance.
|
||||
*/
|
||||
generate(request: SessionTitleProviderRequest): Promise<SessionTitleProviderResult>
|
||||
}
|
||||
```
|
||||
@@ -17,27 +17,28 @@ The append-only event types. Merge-extensible: a plugin declares extra event typ
|
||||
*/
|
||||
interface SessionEventMap {
|
||||
/**
|
||||
* Opens turn `turn`. `trigger` records what started it — a drained message
|
||||
* batch or an idle-time injection. The turn is the durability/replay
|
||||
* Opens turn `turn`. `trigger` records what started it — one claimed queued
|
||||
* message or an idle-time injection. The turn is the durability/replay
|
||||
* boundary: every event sits between a `turn/start` and its matching
|
||||
* `turn/end` (the turn-enclosure invariant).
|
||||
*/
|
||||
'turn/start': { turn: number; trigger: TurnTrigger }
|
||||
/**
|
||||
* Closes turn `turn` with the {@link TurnEndReason} that ended it. The loop
|
||||
* fires the awaited `session/flush` checkpoint at every turn end, so the turn
|
||||
* boundary is also the durable-commit boundary.
|
||||
* awaits `session/flush` after an ordinary turn ends before claiming the next
|
||||
* queued item. Success commits the turn; rejection is reported live and does
|
||||
* not prevent later work.
|
||||
*/
|
||||
'turn/end': { turn: number; reason: TurnEndReason }
|
||||
/** Opens step `step` of turn `turn` — one model call plus the tool executions it requested. */
|
||||
'step/start': { turn: number; step: number }
|
||||
/** Closes step `step` of turn `turn`. */
|
||||
'step/end': { turn: number; step: number }
|
||||
/** A user-visible prompt (queued message drained at turn start). */
|
||||
/** A user-visible prompt (the queued message claimed for this turn). */
|
||||
'user/message': { content: ContentBlock[]; source: MessageSource }
|
||||
/**
|
||||
* Durable record of a prompt veto and its reason. It is log-only: the blocked
|
||||
* prompt never enters the model-visible surface, including in a mixed batch.
|
||||
* prompt never enters the model-visible surface, and its turn runs zero steps.
|
||||
*/
|
||||
'prompt/blocked': { content: ContentBlock[]; source: MessageSource; reason: string }
|
||||
/**
|
||||
@@ -93,6 +94,20 @@ interface SessionEventMap {
|
||||
}
|
||||
```
|
||||
|
||||
### `OutOfBandSessionEventMap` — narrow late-append opt-in
|
||||
|
||||
`SessionEventMap` membership alone does not authorize an event outside the agent loop's ordinary lifecycle. An event owner declaration-merges the same key into this empty marker map before `ctx.sessions.appendOutOfBand()` accepts it; the derived type additionally excludes every surface event. An accepted update joins an open turn or receives a balanced, flushed zero-step turn.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Marker map for plugin-owned log-only events accepted by
|
||||
* `SessionStore.appendOutOfBand()`. A plugin extends this map with the same key
|
||||
* it adds to {@link SessionEventMap}; surface and lifecycle events stay
|
||||
* ineligible unless their owner explicitly opts them into this narrow seam.
|
||||
*/
|
||||
interface OutOfBandSessionEventMap {}
|
||||
```
|
||||
|
||||
### `TodoItem` — one todo-list entry
|
||||
|
||||
The unit of the `todo/write` event's whole-list snapshot. Deliberately minimal — a `content` line and a three-state `status` (no id, priority, or `activeForm`): the list is replaced wholesale on every write, so entries need no stable identity, and the status triple is exactly the ACP `PlanEntryStatus`, so a UI bridge can map a todo list onto an ACP `plan` 1:1 (synthesizing the priority ACP additionally requires). See the [todo_write Agent Note](../../.agents/notes/implemented/feature/2026-06-29-todo-write-tool.md).
|
||||
@@ -429,7 +444,7 @@ declare class Session {
|
||||
- `context/message` → a user-role message carrying its `content` verbatim at its chronological position. Optional JSON `meta` remains in the event log and is never rendered.
|
||||
- `steering/message` → a user-role message carrying its content verbatim at its chronological position.
|
||||
|
||||
Everything else (`turn/*`, `step/*`) is structural and does not project into a message. Token usage is observed on `assistant/message.usage` (the step that produced it); an operational error's step number is on `turn/end.reason` for `kind: 'error'`. Because this unreleased format intentionally has no compatibility promise, seed/load validation rejects request headers without provider+model and assistant messages without provider/model provenance instead of guessing a route for historical data.
|
||||
Everything else (`turn/*`, `step/*`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting reads per-step `assistant/chunk { type: 'usage' }` records and treats `assistant/message.usage` as the committed-step fallback when no usage chunk exists; failed model-request attempts have no assistant message, so their usage chunk is the durable accounting record. An operational error's step number is on `turn/end.reason` for `kind: 'error'`, with normalized `LlmFailure` facts for a final model-request failure and message/code for other live errors. Because this unreleased format intentionally has no compatibility promise, seed/load validation rejects request headers without provider+model and assistant messages without provider/model provenance instead of guessing a route for historical data.
|
||||
|
||||
## Live-session fork API
|
||||
|
||||
@@ -462,26 +477,33 @@ interface TurnTriggerMap {
|
||||
|
||||
## Why a turn ended: `TurnEndReasonMap`
|
||||
|
||||
`aborted` is intentionally a coarse durable outcome: it records that cancellation interrupted the live turn, not which runtime caller requested it. The runtime-only caller vocabulary belongs to [`AgentCancelCause`](core.md#the-agent-handle); a future audit requirement would use a separate control-request event rather than overloading the terminal result.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Why a turn ended. Merge-extensible sum type.
|
||||
*/
|
||||
interface TurnEndReasonMap {
|
||||
completed: { kind: 'completed' }
|
||||
aborted: { kind: 'aborted'; reason?: string }
|
||||
/** A cancellation request interrupted the live turn. */
|
||||
aborted: { kind: 'aborted' }
|
||||
/**
|
||||
* The turn failed: a step threw or the model reported a failure. `step` is the
|
||||
* step number the failure occurred on (the operational error's location — the
|
||||
* single durable record of an in-turn failure; live diagnostics also fire via
|
||||
* `agent/error`). `code` is the error's code when one was attached.
|
||||
* `agent/error`). Final model-request failures retain their normalized facts
|
||||
* as one `failure`; other turn failures retain their live Error message/code.
|
||||
*/
|
||||
error: { kind: 'error'; step: number; message: string; code?: string }
|
||||
error: { kind: 'error'; step: number } & (
|
||||
| { failure: LlmFailure; message?: never; code?: never }
|
||||
| { message: string; code?: string; failure?: never }
|
||||
)
|
||||
disposed: { kind: 'disposed' }
|
||||
/** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
|
||||
'max-tokens': { kind: 'max-tokens' }
|
||||
/**
|
||||
* Policy blocked every prompt before the first step. The zero-step turn still
|
||||
* records a balanced durable boundary and the veto reason.
|
||||
* Policy blocked the turn's claimed prompt before the first step. The
|
||||
* zero-step turn still records a balanced durable boundary and veto reason.
|
||||
*/
|
||||
rejected: { kind: 'rejected'; reason: string }
|
||||
/**
|
||||
@@ -492,11 +514,11 @@ interface TurnEndReasonMap {
|
||||
}
|
||||
```
|
||||
|
||||
`max-tokens` mirrors the model-call `FinishReason` of the same name: any `max-tokens` step in a turn makes the whole turn end `max-tokens` rather than `completed` (the cut-short fact wins over a later continuation), so a consumer can tell a clean stop from a truncated one — but only over `completed`: the `disposed`/`aborted`/`error` outcomes take precedence. `rejected` is a zero-step turn whose whole prompt batch an `agent/prompt-submit` hook blocked (the ACP bridge maps it to `cancelled`). `interrupted` is the one reason no loop emits — it is synthesized by crash recovery (see [persistence.md](persistence.md)). Both maps are merge-extensible.
|
||||
`max-tokens` mirrors the model-call `FinishReason` of the same name: any `max-tokens` step in a turn makes the whole turn end `max-tokens` rather than `completed` (the cut-short fact wins over a later continuation), so a consumer can tell a clean stop from a truncated one — but only over `completed`: the `disposed`/`aborted`/`error` outcomes take precedence. `rejected` is a zero-step turn whose claimed prompt an `agent/prompt-submit` hook blocked (the ACP bridge maps it to `cancelled`). `interrupted` is the one reason no loop emits — it is synthesized by crash recovery (see [persistence.md](persistence.md)). Both maps are merge-extensible.
|
||||
|
||||
## The turn-enclosure invariant
|
||||
|
||||
Every session event lives **inside** a turn (between a `turn/start` and its `turn/end`). The loop appends queued `user/message` events *after* `turn/start`, and an idle `agent.inject()` wraps its `context/message` in a one-shot `injection` turn. This makes the turn the single durability/replay boundary: a backend can treat anything after the last `turn/end` as an interrupted-crash tail without risking the loss of legitimately-recorded between-turn context. The `dsh-invariants` plugin enforces it in dev (a message event outside an open turn throws). See [the turn-enclosure invariant Agent Note](../../.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.md).
|
||||
Every session event lives **inside** a turn (between a `turn/start` and its `turn/end`). The loop appends queued `user/message` events *after* `turn/start`, an idle `agent.inject()` wraps its `context/message` in a one-shot `injection` turn, and `appendOutOfBand()` similarly wraps an eligible log-only event when no turn is open. This makes the turn the single durability/replay boundary: a backend can treat anything after the last `turn/end` as an interrupted-crash tail without risking the loss of legitimately-recorded between-turn context. The optional `dsh-session/invariant` companion enforces it in dev through `ctx.invariants` (a message event outside an open turn throws). See [the turn-enclosure invariant Agent Note](../../.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.md).
|
||||
|
||||
## Plugin-contributed log-only events
|
||||
|
||||
@@ -506,6 +528,6 @@ The hook bridges' `hook/invoked` / `hook/result` provenance pairs (from `@deepse
|
||||
|
||||
## Durability contract
|
||||
|
||||
What a persistence backend relies on: the durable log persists every event verbatim, **including** `assistant/chunk` — `seq` must stay contiguous, so chunks cannot be filtered out of the canonical log. All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.events` always equals what a backend can persist. Adding an event type that carries non-serializable data, or that breaks the turn/step nesting the invariants plugin checks, is a breaking change to the on-disk format.
|
||||
What a persistence backend relies on: the durable log persists every event verbatim, **including** `assistant/chunk` — `seq` must stay contiguous, so chunks cannot be filtered out of the canonical log. All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.events` always equals what a backend can persist. Adding an event type that carries non-serializable data, or that breaks the turn/step nesting checked by the session invariant companion, is a breaking change to the on-disk format.
|
||||
|
||||
The backends that consume this contract are on [persistence.md](persistence.md).
|
||||
|
||||
@@ -49,7 +49,10 @@ interface SubagentStartRequest {
|
||||
* The spawning ("parent") agent — the one whose tool call started this
|
||||
* subagent. REQUIRED: in-process backends read `parent.session.header` for
|
||||
* the working directory, the `parentSession` lineage to stamp on the child,
|
||||
* and the parent's delegation depth. Out-of-process backends (ACP) ignore it.
|
||||
* and the parent's delegation depth. The out-of-process backend (ACP) reads
|
||||
* exactly one field — the session header's cwd, the child's workspace when
|
||||
* no deployment `cwd` override is configured; nothing else crosses the
|
||||
* process boundary.
|
||||
*/
|
||||
readonly parent: Agent
|
||||
/**
|
||||
@@ -237,5 +240,5 @@ interface SubagentProvider {
|
||||
|
||||
The spawn and fork backends create an ordinary agent through `parent.ctx`, pass cancellation into core creation, and dispose through `AgentHandle`. Provider removal blocks new starts without revoking accepted runs. Each child gets a new flat scope rather than inheriting parent registrations. Depth and fork seeding reuse existing agent and session vocabulary:
|
||||
|
||||
- **Delegation depth** is a merge-extensible `AgentOptions.subagentDepth` field (`0` for a top-level agent, parent + 1 for a child). Only `undefined` means top level; every stored present value must be a non-negative safe integer. The seam owns it — the loop neither sets nor reads it — so a nested spawn validates its parent's stored depth, rejects a derived child depth outside the safe-integer domain, and applies a defined absolute `request.maxDepth` cap to that child.
|
||||
- **Delegation depth** is durable `SessionHeader.delegationDepth` plus the merge-extensible runtime field `AgentOptions.subagentDepth`; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absolute `request.maxDepth` cap.
|
||||
- **Fork seeding** uses `CreateAgentOptions.seed` (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `resume` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/support/invariants) replay accepts it (the in-flight, unbalanced turn is excluded).
|
||||
|
||||
@@ -6,7 +6,7 @@ Source: [`packages/core/system-prompt/src/index.ts`](../../packages/core/system-
|
||||
|
||||
## Assembly context
|
||||
|
||||
`AssembleContext` identifies the scope layer one assembly resolves. It is merge-extensible: `dsh-agent` adds the optional live `agent` field, and `assembleContextFor(agent)` sets that field and `scope` together.
|
||||
`AssembleContext` identifies the scope layer one assembly resolves and may carry the explicit control signal for that request. It is merge-extensible: `dsh-agent` adds the optional live `agent` field, and `assembleContextFor(agent, signal)` sets the explicit fields together. A bare assembly has neither scope nor signal.
|
||||
|
||||
```ts type-equiv
|
||||
/** Merge-extensible context for one prompt assembly. */
|
||||
@@ -16,6 +16,8 @@ interface AssembleContext {
|
||||
* only global providers and subject-less listeners participate.
|
||||
*/
|
||||
scope?: ScopeKey
|
||||
/** Explicit control signal for the turn that requested this assembly, when any. */
|
||||
signal?: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -11,6 +11,15 @@ A `ToolSchema` (the model-facing fields) plus the `execute` function, host-only
|
||||
```ts type-equiv
|
||||
/** A registered tool: its schema plus the execution function. */
|
||||
interface ToolDefinition extends ToolSchema {
|
||||
/**
|
||||
* Run one accepted call. Async work must observe or forward `exec.signal` and
|
||||
* settle only after its owned work reaches quiescence. The registry preserves
|
||||
* caller cancellation through around-dispatch signal replacement and does
|
||||
* not abandon this promise, but it cannot hard-kill same-process code.
|
||||
* @param args - losslessly snapshotted, frozen model arguments.
|
||||
* @param exec - execution identity, cancellation signal, and context deferral.
|
||||
* @returns model-facing content plus optional private presentation metadata.
|
||||
*/
|
||||
execute(args: unknown, exec: ToolRunContext): Promise<ToolExecuteReturn>
|
||||
/**
|
||||
* Cooperative tool-call timeout budget in milliseconds. Omit for no deadline.
|
||||
@@ -138,7 +147,7 @@ interface ToolRestriction {
|
||||
|
||||
## Execution: extensible waterfalls plus monotonic policy
|
||||
|
||||
`ctx.tools.execute()` accepts a caller-owned `ToolExecutionInput`, materializes its parsed JSON arguments once into a pipeline-owned `ToolExecution`, and runs that call through `tools/pre-execute` (the reorderable allow/deny/ask waterfall) → registered monotonic guards → `tools/execute` (around-dispatch wrappers) → `tools/post-execute` (inspect/replace the result) → `tools/result` (the immutable authoritative outcome). The outcome is a `ToolExecutionResult`.
|
||||
`ctx.tools.execute()` accepts a caller-owned `ToolExecutionInput` with a required readonly `signal`, materializes its parsed JSON arguments once into a pipeline-owned `ToolExecution`, and runs that call through `tools/pre-execute` (the reorderable allow/deny/ask waterfall) → registered monotonic guards → `tools/execute` (around-dispatch wrappers) → `tools/post-execute` (inspect/replace the result) → `tools/result` (the immutable authoritative outcome). Only the `tools/execute` view may replace the required signal. The outcome is a `ToolExecutionResult`.
|
||||
|
||||
```ts type-equiv
|
||||
/** Opaque call identity that permits correlation without exposing mutable execution state. */
|
||||
@@ -161,10 +170,11 @@ interface ToolExecutionInput {
|
||||
/**
|
||||
* Opaque token of the enclosing transport execution, when one exists. Code
|
||||
* Mode sets this on SDK sub-dispatches so commit-style observers can wait for
|
||||
* the outer `run_code` outcome without receiving its live mutable execution.
|
||||
*/
|
||||
* the outer `run_code` outcome without receiving its live mutable execution.
|
||||
*/
|
||||
readonly parent?: ToolExecutionToken
|
||||
signal?: AbortSignal
|
||||
/** Required caller-owned cancellation for this invocation. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
@@ -203,9 +213,9 @@ type ToolExecutionMode =
|
||||
/**
|
||||
* One pending tool call inside the registry pipeline. Parsed arguments cross
|
||||
* one lossless-JSON materialization boundary before policy and are deep-frozen;
|
||||
* call identity and the registry-assigned {@link token} are readonly. An
|
||||
* around-dispatch wrapper may set, replace, or remove `signal`. The registry
|
||||
* freezes the complete object before `tools/result` observers run.
|
||||
* call identity, the caller signal, and the registry-assigned {@link token} are
|
||||
* readonly. The registry freezes the complete object before `tools/result`
|
||||
* observers run.
|
||||
*/
|
||||
interface ToolExecution extends ToolExecutionInput {
|
||||
/** Registry-assigned identity shared with nested calls only as their opaque `parent` token. */
|
||||
@@ -213,7 +223,19 @@ interface ToolExecution extends ToolExecutionInput {
|
||||
}
|
||||
```
|
||||
|
||||
`ToolExecutionToken` is an opaque runtime `Symbol` used only for identity comparison. Before policy, `execute()` materializes and freezes arguments, rejects non-JSON input, and assigns the token. Identity fields and the optional parent token remain readonly; only `signal` may change around dispatch. Final observers receive the frozen execution identity.
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Around-dispatch view of a {@link ToolExecution}. A `tools/execute` wrapper
|
||||
* may replace the signal for its delegated lifetime, but it cannot remove it.
|
||||
* The registry fuses every replacement with the captured caller signal.
|
||||
*/
|
||||
interface ToolDispatchExecution extends Omit<ToolExecution, 'signal'> {
|
||||
/** Cancellation signal visible to the next wrapper or tool body. */
|
||||
signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
`ToolExecutionToken` is an opaque runtime `Symbol` used only for identity comparison. Before policy, `execute()` materializes and freezes arguments, rejects non-JSON input, and assigns the token. Identity fields, the required caller signal, and the optional parent token remain readonly. A `ToolDispatchExecution` wrapper may replace but not remove the signal; the registry re-fuses the caller signal before invoking the body. Final observers receive the frozen execution identity.
|
||||
|
||||
A `ToolGuard` is scope-aware final pre-dispatch policy. Its shape deliberately has no allow result: `undefined` preserves the waterfall decision, while a returned reason can only reduce permission, so a later listener cannot undo it.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# User Interaction
|
||||
|
||||
The user-interaction seam of [dsh-user-interaction](../../packages/ui/user-interaction). It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active `UserInteractionProvider`: `dsh-stdio-demo` selects keyboard-driven `dsh-tui` overlays or `dsh-stdio` readline prompts, and `dsh-acp` maps questions to ACP form elicitations.
|
||||
The user-interaction seam of [dsh-user-interaction](../../packages/ui/user-interaction). It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active `UserInteractionProvider`: `dsh-tui` uses keyboard-driven overlays, and `dsh-acp` maps questions to ACP form elicitations.
|
||||
|
||||
Source: [`packages/ui/user-interaction/src/index.ts`](../../packages/ui/user-interaction/src/index.ts)
|
||||
|
||||
@@ -20,15 +20,17 @@ interface AskUserQuestionOption {
|
||||
|
||||
## Question item
|
||||
|
||||
`AskUserQuestionItem` is one question in a request. The model supplies a stable `id`, which is echoed back with the answer so batched questions remain routable.
|
||||
`AskUserQuestionItem` is one question in a request. The caller supplies a stable `id`, which is echoed back with the answer so batched questions remain routable. Optional `detail` carries supporting text that providers render with the question but keep out of selectable option labels.
|
||||
|
||||
```ts type-equiv
|
||||
/** One question in an ask_user_question request. */
|
||||
/** One question in a user-interaction request. */
|
||||
interface AskUserQuestionItem {
|
||||
/** Stable model-provided question id, echoed in the answer. */
|
||||
/** Stable caller-provided question id, echoed in the answer. */
|
||||
id: string
|
||||
/** The question to display. */
|
||||
question: string
|
||||
/** Optional supporting detail rendered with the question but kept out of option labels. */
|
||||
detail?: string
|
||||
/** Optional short heading/group label. */
|
||||
header?: string
|
||||
/** Optional choices the UI can render as a menu. */
|
||||
|
||||
@@ -8,7 +8,7 @@ Source: [`packages/workflow/workflow/src/types.ts`](../../packages/workflow/work
|
||||
|
||||
## The start request
|
||||
|
||||
What a caller asks for when starting a run. The tool layer builds this from the model's `{ script, meta, args }` call plus the calling agent; `meta` and `args` are plain JSON DATA (the engine shape-validates `meta` and rejects loud BEFORE anything runs — no script text is ever evaluated to obtain it). `parent` is REQUIRED — every child the script spawns is attributed to it (cwd, lineage, and depth flow through the [subagent seam](subagent.md)).
|
||||
What a caller asks for when starting a run. The ordinary workflow tool builds this from the model's `{ script, meta, args }` call plus the calling agent; specialized consumers may also select one engine-wide `subagentProvider` and lower `maxTotalAgents` for the run, but the script cannot observe or replace either policy. `meta` and `args` are plain JSON DATA (the engine shape-validates `meta` and rejects loud BEFORE anything runs — no script text is ever evaluated to obtain it). `parent` is REQUIRED — every child the script spawns is attributed to it (cwd, lineage, and depth flow through the [subagent seam](subagent.md)).
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -26,6 +26,17 @@ interface WorkflowStartRequest {
|
||||
meta: WorkflowMeta
|
||||
/** Optional input exposed verbatim to the script as the `args` global. */
|
||||
args?: unknown
|
||||
/**
|
||||
* Optional engine-wide child-provider override for this run. The workflow
|
||||
* script cannot observe or replace it; omission uses the engine's configured
|
||||
* provider.
|
||||
*/
|
||||
subagentProvider?: string
|
||||
/**
|
||||
* Optional per-run total-child ceiling. Implementations reject values above
|
||||
* their deployment ceiling before publishing the run.
|
||||
*/
|
||||
maxTotalAgents?: number
|
||||
/** The agent on whose behalf the run executes (parent of every child). */
|
||||
parent: Agent
|
||||
/** Cancels the run when aborted (the tool's `exec.signal`). */
|
||||
|
||||
@@ -12,7 +12,7 @@ When an interface documents two valid ways to signal something — an adapter ma
|
||||
|
||||
## Async state is not synchronous state
|
||||
|
||||
`agent.send()` does not flip status before returning; a background task's completion races turn boundaries; `reader.close()` fires for both EOF and disposal. Never gate control flow on a status you only just requested — drive lifecycle off the events/promises that actually fire (`agent/status`, `task.done`), and observe the transition (saw `running` THEN `idle`) rather than counting actions you assume map 1:1 to turns (the loop batches queued messages). The guard cuts both ways: if the awaited transition can never occur (EOF with no work submitted → never `running`), the wait hangs — handle the "nothing to wait for" branch explicitly.
|
||||
`agent.send()` does not flip status before returning; a background task's completion races turn boundaries; `reader.close()` fires for both EOF and disposal. Never gate control flow on a status you only just requested — drive lifecycle off the events/promises that actually fire (`agent/status`, `task.done`), and observe the transition (saw `running` THEN `idle`) instead of treating status as a per-send result: several queued sends run as consecutive turns under one `running` interval, while cancellation or disposal can discard unstarted items. The guard cuts both ways: if the awaited transition can never occur (EOF with no work submitted → never `running`), the wait hangs — handle the "nothing to wait for" branch explicitly.
|
||||
|
||||
## Dispose must reach quiescence, not just request it
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
development.md: 94eb4f03329b574862a1ac1de2f8c1d4db4f4a0a
|
||||
development.zh.md: b533aff43a66ff7cfc5dc61e5b9b224a01c51f12
|
||||
development.md: 10406cebae1bf83fff663903b1478c9acb8476a1
|
||||
development.zh.md: 50051ffd631518b37c3ad96f5fd3830cf6893ec9
|
||||
|
||||
@@ -9,7 +9,7 @@ This onboarding guide helps project contributors get started with the local envi
|
||||
- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).
|
||||
- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.
|
||||
- Git.
|
||||
- Optional: a DeepSeek API key for the REPL/ACP agent demos and real-API e2e tests.
|
||||
- Optional: a DeepSeek API key for the TUI/Headless/ACP agent demos and real-API e2e tests.
|
||||
|
||||
## First-time setup
|
||||
|
||||
@@ -35,13 +35,13 @@ pnpm run typecheck
|
||||
|
||||
That first typecheck runs the package/vendor build graph and the root no-emit `tsconfig.json` graph for examples, tests, and scripts. The root graph uses the same source `paths` map but relies on project references so vendored code is checked under its own tsconfig settings.
|
||||
|
||||
If you are preparing to push from a fresh clone or worktree, also build once:
|
||||
If a relevant local check consumes built package output, build once first:
|
||||
|
||||
```sh
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs.
|
||||
`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs; ordinary commits and pushes do not require that build unless their selected checks consume it.
|
||||
|
||||
## Environment variables
|
||||
|
||||
@@ -56,14 +56,14 @@ DEEPSEEK_BASE_URL=https://... # optional
|
||||
|
||||
## Git hooks
|
||||
|
||||
lefthook is configured in `lefthook.yml` as an early local checkpoint before review:
|
||||
lefthook is configured in `lefthook.yml` as a fast local checkpoint:
|
||||
|
||||
- `pre-commit` runs staged-file ESLint fixes, `pnpm run typecheck`, and the vendor manifest guard.
|
||||
- `pre-push` runs `pnpm run check:pre-push`, whose scheduler runs runtime-closure verification, unit tests, duplication detection, snapshot tests, build, module-graph freshness, and the member gates of `pnpm run hygiene` and `pnpm run doc-sync` concurrently.
|
||||
- `pre-commit` runs staged-file ESLint fixes, checks the staged diff for whitespace errors, and runs the vendor manifest guard.
|
||||
- `pre-push` runs only the incremental repository typecheck.
|
||||
|
||||
The vendor manifest guard checks that changes under `vendor/*/src` are staged with the matching `vendor/README.md` manifest update. See `vendor/README.md` before editing vendored code.
|
||||
|
||||
These hooks do not exactly mirror CI. Notably, `pre-push` runs unit tests without coverage, while CI runs `pnpm run test:coverage`; CI also runs echo-agent and built-bin smoke tests and exercises the compatibility matrix on Node 22.19, 24, and 26.
|
||||
The hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the [checks relevant to the changed behavior](../AGENTS.md#run-relevant-checks-locally) once; CI owns exhaustive coverage, built-artifact smokes, and the Node 22.19, 24, and 26 compatibility matrix.
|
||||
|
||||
## CI gates
|
||||
|
||||
@@ -90,7 +90,7 @@ pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README
|
||||
pnpm run verify-mermaid # fail if a ```mermaid diagram has invalid Mermaid syntax
|
||||
pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type
|
||||
pnpm run verify-doc-budgets # fail if a budgeted standing doc exceeds its word ceiling
|
||||
pnpm run doc-sync # all Markdown/doc gates; see the doc-sync script in package.json for the full list
|
||||
pnpm run doc-sync # all Markdown/doc gates, scheduled concurrently; the doc-sync leaf list in scripts/run-gates.ts is the full list
|
||||
pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps
|
||||
pnpm run verify-module-graph # fail if docs/module-graph.md is stale
|
||||
pnpm run build # emit lib/types intermediates, then bundle lib/index.* runtime files
|
||||
@@ -102,19 +102,13 @@ When changing package public behavior, update the relevant README or JSDoc in th
|
||||
|
||||
## Demos
|
||||
|
||||
The echo demo does not need API credentials:
|
||||
The one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:
|
||||
|
||||
```sh
|
||||
pnpm run demo:echo
|
||||
pnpm run demo:headless "summarize this workspace"
|
||||
```
|
||||
|
||||
The repl-agent demo uses the line-oriented readline front door and needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:
|
||||
|
||||
```sh
|
||||
pnpm run demo:repl
|
||||
```
|
||||
|
||||
The full-screen TUI reuses the repl-agent composition through the pi-tui front door and needs the same credentials:
|
||||
The full-screen interactive coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:
|
||||
|
||||
```sh
|
||||
pnpm run demo:tui
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。
|
||||
- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。
|
||||
- Git。
|
||||
- 可选:一个 DeepSeek API key,用于 REPL/ACP(Agent Client Protocol) agent(智能体)演示和真实 API 的 e2e 测试。
|
||||
- 可选:一个 DeepSeek API key,用于 TUI/Headless/ACP(Agent Client Protocol) agent(智能体)演示和真实 API 的 e2e 测试。
|
||||
|
||||
## 首次搭建
|
||||
|
||||
@@ -35,13 +35,13 @@ pnpm run typecheck
|
||||
|
||||
首次类型检查会执行 package/vendor 的构建图,以及根目录下用于示例、测试和脚本的 no-emit `tsconfig.json` 项目图。根图使用同一份源码 `paths` 映射,但依赖 project references,因此 vendor 代码在它自己的 tsconfig 设置下被检查。
|
||||
|
||||
如果准备从新克隆或新 worktree 推送,还需要构建一次:
|
||||
如果相关的本地检查需要使用构建后的包产物,请先构建一次:
|
||||
|
||||
```sh
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件。
|
||||
`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。
|
||||
|
||||
## 环境变量
|
||||
|
||||
@@ -56,14 +56,14 @@ DEEPSEEK_BASE_URL=https://... # optional
|
||||
|
||||
## Git 钩子
|
||||
|
||||
lefthook 在 `lefthook.yml` 中配置,作为评审前的本地早期检查点:
|
||||
lefthook 在 `lefthook.yml` 中配置,作为快速的本地检查点:
|
||||
|
||||
- `pre-commit` 运行对暂存文件的 ESLint 修复、`pnpm run typecheck` 和 vendor manifest(元数据清单)守卫;
|
||||
- `pre-push` 运行 `pnpm run check:pre-push`,其调度器并发运行 runtime-closure 校验、单元测试、重复代码检查、快照测试、构建、module-graph 新鲜度,以及 `pnpm run hygiene` 与 `pnpm run doc-sync` 的各成员门禁。
|
||||
- `pre-commit` 运行对暂存文件的 ESLint 修复,检查暂存 diff 中的空白错误,并运行 vendor manifest(元数据清单)守卫;
|
||||
- `pre-push` 只运行仓库增量类型检查。
|
||||
|
||||
vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。
|
||||
|
||||
这些钩子并不与 CI 完全一致。特别是:`pre-push` 运行不带覆盖率的单元测试,而 CI 运行 `pnpm run test:coverage`;CI 还会运行 echo-agent 和 built-bin 冒烟测试,并在 Node 22.19、24 和 26 上执行兼容性矩阵。
|
||||
这些钩子有意不运行测试、快照、文档检查、构建或 `hygiene`。贡献者只运行一次[与改动行为相关的检查](../AGENTS.md#run-relevant-checks-locally);CI 负责全量覆盖率门禁、构建产物冒烟测试,以及 Node 22.19、24 和 26 兼容性矩阵。
|
||||
|
||||
## CI 门禁
|
||||
|
||||
@@ -90,7 +90,7 @@ pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README
|
||||
pnpm run verify-mermaid # fail if a ```mermaid diagram has invalid Mermaid syntax
|
||||
pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type
|
||||
pnpm run verify-doc-budgets # fail if a budgeted standing doc exceeds its word ceiling
|
||||
pnpm run doc-sync # all Markdown/doc gates; see the doc-sync script in package.json for the full list
|
||||
pnpm run doc-sync # all Markdown/doc gates, scheduled concurrently; the doc-sync leaf list in scripts/run-gates.ts is the full list
|
||||
pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps
|
||||
pnpm run verify-module-graph # fail if docs/module-graph.md is stale
|
||||
pnpm run build # emit lib/types intermediates, then bundle lib/index.* runtime files
|
||||
@@ -102,19 +102,13 @@ pnpm run hygiene # knip, publint, workspace constraints, and NodeNext dec
|
||||
|
||||
## 演示
|
||||
|
||||
echo 演示不需要 API 凭证:
|
||||
单次运行的 Headless coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:
|
||||
|
||||
```sh
|
||||
pnpm run demo:echo
|
||||
pnpm run demo:headless "summarize this workspace"
|
||||
```
|
||||
|
||||
repl-agent 示例使用面向行的 readline 前端,并需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:
|
||||
|
||||
```sh
|
||||
pnpm run demo:repl
|
||||
```
|
||||
|
||||
全屏 TUI 通过 pi-tui 前端复用 repl-agent 组装,并需要相同的凭证:
|
||||
全屏交互式 coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:
|
||||
|
||||
```sh
|
||||
pnpm run demo:tui
|
||||
|
||||
@@ -7,54 +7,59 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
|
||||
| Event | Mode | Declared in | Dispatchers | Listeners |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:362`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
|
||||
| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:143`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
|
||||
| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:152`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
|
||||
| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:307`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`tui`](../packages/ui/tui) |
|
||||
| `agent/post-step` | `serial` | [`packages/core/agent/src/types.ts:260`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`compact-basic`](../packages/compact/compact-basic) |
|
||||
| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:200`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`time-context`](../packages/context/time-context), [`user-approval`](../packages/ui/user-approval) |
|
||||
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:210`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`acp`](../packages/ui/acp), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) |
|
||||
| `agent/queued` | `emit` | [`packages/core/agent/src/types.ts:171`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | - |
|
||||
| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:222`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`acp`](../packages/ui/acp) |
|
||||
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/types.ts:274`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic) |
|
||||
| `agent/session-prefix` | `waterfall` | [`packages/core/agent/src/types.ts:237`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:184`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`stdio`](../packages/ui/stdio) |
|
||||
| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:161`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`invariants`](../packages/support/invariants), [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
|
||||
| `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:248`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | - |
|
||||
| `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:284`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
|
||||
| `agent/turn-stop` | `serial` | [`packages/core/agent/src/types.ts:294`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
|
||||
| `approval/request` | `waterfall` | [`packages/ui/user-approval/src/index.ts:31`](../packages/ui/user-approval/src/index.ts) | [`user-approval`](../packages/ui/user-approval) (`waterfall`) | [`acp`](../packages/ui/acp) |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:353`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | [`tui`](../packages/ui/tui) |
|
||||
| `agent/cancel-requested` | `emit` | [`packages/core/agent/src/types.ts:201`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-session`](../packages/goal/goal-session) |
|
||||
| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:163`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`goal-session`](../packages/goal/goal-session), [`tui`](../packages/ui/tui) |
|
||||
| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:172`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`goal-session`](../packages/goal/goal-session), [`tui`](../packages/ui/tui) |
|
||||
| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:346`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-session`](../packages/goal/goal-session), `runtime`, [`tui`](../packages/ui/tui) |
|
||||
| `agent/post-step` | `serial` | [`packages/core/agent/src/types.ts:296`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`compact-basic`](../packages/compact/compact-basic) |
|
||||
| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:230`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`time-context`](../packages/context/time-context), [`user-approval`](../packages/ui/user-approval) |
|
||||
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:243`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`acp`](../packages/ui/acp), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) |
|
||||
| `agent/queued` | `emit` | [`packages/core/agent/src/types.ts:191`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-session`](../packages/goal/goal-session) |
|
||||
| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:257`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) |
|
||||
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/types.ts:311`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`llm-retry`](../packages/llm/llm-retry), [`plan-mode`](../packages/plan/plan-mode) |
|
||||
| `agent/session-prefix` | `waterfall` | [`packages/core/agent/src/types.ts:272`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:214`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
|
||||
| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:181`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), [`goal-session`](../packages/goal/goal-session), `runtime`, [`tui`](../packages/ui/tui) |
|
||||
| `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:284`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | - |
|
||||
| `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:322`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode) |
|
||||
| `agent/turn-stop` | `serial` | [`packages/core/agent/src/types.ts:333`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`tool-goal`](../packages/goal/tool-goal) |
|
||||
| `approval/request` | `waterfall` | [`packages/ui/user-approval/src/index.ts:30`](../packages/ui/user-approval/src/index.ts) | [`user-approval`](../packages/ui/user-approval) (`waterfall`) | [`acp`](../packages/ui/acp) |
|
||||
| `commands/change` | `emit` | [`packages/ui/commands/src/index.ts:103`](../packages/ui/commands/src/index.ts) | [`commands`](../packages/ui/commands) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`tui`](../packages/ui/tui) |
|
||||
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:62`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:71`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:54`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:43`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`invariants`](../packages/support/invariants), [`llm-replay`](../packages/support/llm-replay) |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:47`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:57`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:69`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence), [`stdio`](../packages/ui/stdio), [`token-meter`](../packages/llm/token-meter), [`tui`](../packages/ui/tui), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:79`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:112`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc) |
|
||||
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:86`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:92`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:103`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude) |
|
||||
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:27`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`acp`](../packages/ui/acp) |
|
||||
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:33`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
|
||||
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:116`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - |
|
||||
| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:89`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`timeout-policy`](../packages/timeout/timeout-policy) |
|
||||
| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:98`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`spill-policy`](../packages/spill/spill-policy), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:80`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
|
||||
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:106`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:81`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
|
||||
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:70`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
|
||||
| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:91`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
|
||||
| `goal/changed` | `emit` | [`packages/goal/goal/src/types.ts:167`](../packages/goal/goal/src/types.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-session`](../packages/goal/goal-session) |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:52`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/support/llm-replay), [`session-title`](../packages/session-title/session-title) |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:68`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`llm-retry`](../packages/llm/llm-retry), `runtime`, [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`user-approval`](../packages/ui/user-approval) |
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:78`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `runtime`, [`session-persistence`](../packages/session-persistence/session-persistence), [`session-title`](../packages/session-title/session-title) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:90`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), `runtime`, [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-title`](../packages/session-title/session-title), [`token-meter`](../packages/llm/token-meter), [`tui`](../packages/ui/tui), [`user-approval`](../packages/ui/user-approval), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:100`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:139`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc), [`subagent`](../packages/subagent/subagent) |
|
||||
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:113`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:119`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:130`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`subagent`](../packages/subagent/subagent) |
|
||||
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:29`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:35`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
|
||||
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:123`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - |
|
||||
| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:93`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`timeout-policy`](../packages/timeout/timeout-policy) |
|
||||
| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:105`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`spill-policy`](../packages/spill/spill-policy), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:82`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
|
||||
| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:113`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:81`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) |
|
||||
| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:70`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) |
|
||||
| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:91`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) |
|
||||
| `workflow/log` | `emit` | [`packages/workflow/workflow/src/index.ts:60`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
|
||||
| `workflow/phase` | `emit` | [`packages/workflow/workflow/src/index.ts:53`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
|
||||
| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:45`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - |
|
||||
| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:45`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) |
|
||||
|
||||
## Non-harness or undeclared event strings seen in package source
|
||||
|
||||
| Event string | Dispatchers | Listeners |
|
||||
| --- | --- | --- |
|
||||
| `internal/dispatch` | - | [`invariants`](../packages/support/invariants) |
|
||||
| `internal/dispatch` | - | [`compact`](../packages/compact/compact), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), `runtime`, [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`workflow`](../packages/workflow/workflow) |
|
||||
| `internal/plugin` | - | `webserver` |
|
||||
| `internal/status` | - | [`agent`](../packages/core/agent) |
|
||||
| `slots/changed` | `runtime` (`emit`) | - |
|
||||
|
||||
Maintenance mode: generated: Cordis event declarations and producer/listener edges are resolved from the repository TypeScript Program.
|
||||
|
||||
@@ -14,4 +14,28 @@ FIXME(glossary-completeness): Expand this glossary before the first release so i
|
||||
- **shadowing** — most-specific-wins name resolution: a scoped tool/section/variable replaces its same-named global twin for that scope alone. The per-agent persona and per-agent tool-variant mechanism.
|
||||
- **restriction / scope-local registration** — a restriction (`tools.restrict`) filters the GLOBAL tool surface for one scope (compose by intersection); scope-local registrations are merged after that filter. A filtered-away global tool is absent from the prompt AND refuses execution, indistinguishably from a nonexistent one.
|
||||
- **setup window** — the creation slot where a creator composes an agent's scoped world (`CreateAgentOptions.setup`): after the scope and agent object exist but before the agent or session is published, `agent/session-start` fires, or the first prompt is assembled. Setup registers; it never drives the agent.
|
||||
- **lineage** — parent/child facts carried as data (`parentSession`, `subagentDepth`); never affects visibility. <a id="lineage"></a>
|
||||
- **lineage** — parent/child facts carried as data (`parentSession`, durable `delegationDepth`, runtime `subagentDepth`); never affects visibility. <a id="lineage"></a>
|
||||
|
||||
## goal
|
||||
|
||||
- **goal** — one durable completion objective attached to an existing session, with a revisioned `active` / `paused` / `blocked` / `complete` phase and a goal-round cap; `blocked` retains a policy code and explanation. A goal is state, not a scheduler or a separate conversation; the session log remains its source of truth.
|
||||
- **goal round** — one continuation cycle admitted for the current goal. The same-session driver materializes a goal round as one goal-sourced [turn](#turn), which can contain multiple steps; unrelated human turns in the same session do not consume the goal-round cap. <a id="goal-round"></a>
|
||||
- **goal activation** — process-local permission for a continuation consumer to admit another goal round. Activation is either `armed` or `disarmed`; it is deliberately absent from durable replay, so resume and fork require a later human-authorized resume mutation through `/goal` or the model tool before automatic work.
|
||||
|
||||
## human command
|
||||
|
||||
- **human command** — a slash-prefixed instruction interpreted and executed by a human-facing adapter through `ctx.commands`, without becoming a model message. It is distinct from a model-facing tool and from shell command execution through `ctx.bash`.
|
||||
- **command plane** — discovery, parsing, dispatch, cancellation, and result rendering owned by UI adapters and command plugins. Command output is UI state unless the handler separately mutates a durable domain.
|
||||
- **goal command** — the `/goal` human command contributed by `dsh-command-goal`; it observes or mutates the current goal directly while the goal domain owns every durable, model-visible record.
|
||||
|
||||
## loop hierarchy
|
||||
|
||||
- **turn** — one drain of admitted input in a session, ending after the model and its tools stop or a terminal policy intervenes. <a id="turn"></a>
|
||||
- **step** — one model request plus the tool executions caused by its response; a turn contains one or more steps. <a id="step"></a>
|
||||
- **round** — an outer policy iteration containing a turn, such as a [goal round](#goal-round) or one fresh-agent Ralph attempt. Round counters belong to that policy and do not count every turn in a session. <a id="round"></a>
|
||||
|
||||
## Ralph
|
||||
|
||||
- **Ralph loop** — one foreground fresh-agent workflow run toward an immutable objective. It is a model-facing tool policy composed from workflow and subagent primitives, not a same-session goal, agent-loop mode, scheduler, or generic workflow-script feature. <a id="ralph-loop"></a>
|
||||
- **Ralph round** — one fresh child session in a [Ralph loop](#ralph-loop). The child receives no parent or prior-child conversation seed; the shared workspace and one bounded [Ralph handoff](#ralph-handoff) carry cross-round state. <a id="ralph-round"></a>
|
||||
- **Ralph handoff** — the normalized bounded structured report passed from one continuing Ralph round to the next, containing status, summary, evidence, next steps, and blocker text. It supplements the shared workspace rather than replacing it as authority. <a id="ralph-handoff"></a>
|
||||
|
||||
@@ -12,8 +12,6 @@ The process decision behind this index is recorded in [the documentation graph A
|
||||
| [module dependency graph](module-graph.md) | `generated` |
|
||||
| [tool schema catalog and package map](tool-catalog.md) | `generated` |
|
||||
| [capability seams and core services](capability-seams.md) | `hybrid generated` |
|
||||
| [echo-agent app composition](../examples/echo-agent/composition.md) | `hybrid generated` |
|
||||
| [repl-agent app composition](../examples/repl-agent/composition.md) | `hybrid generated` |
|
||||
| [tui-agent app composition](../examples/tui-agent/composition.md) | `hybrid generated` |
|
||||
| [headless-agent app composition](../examples/headless-agent/composition.md) | `hybrid generated` |
|
||||
| [cordis-agent app composition](../examples/cordis-agent/composition.md) | `hybrid generated` |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
README.md: bd5d8c08a4c474a13342b6b60800cfe0d31e110b
|
||||
README.zh.md: a53ab8d9d6053b39def34505038504fefc80a3f9
|
||||
README.md: c4ddf44ad2497b4ff371918356ab1ec0698c7049
|
||||
README.zh.md: 4a31af4fdee4db2d0362cf9117a6eef4fea32393
|
||||
|
||||
@@ -21,7 +21,7 @@ This repo's documentation is read by people and agents both inside and outside t
|
||||
|
||||
## The gate: verify-translation-pairing
|
||||
|
||||
`pnpm run verify-translation-pairing` (part of `doc-sync`, so CI and the pre-push hook run it) enforces the contract mechanically:
|
||||
`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:
|
||||
|
||||
1. Every file listed as `required` in [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) has a complete pair.
|
||||
2. Every pair that exists at all — required or not — is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), both sides carry the language switcher, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and every link target apart from the switcher.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
## 门禁:verify-translation-pairing
|
||||
|
||||
`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,因此 CI 和 pre-push 钩子都会运行)机械地强制执行这份契约:
|
||||
`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份契约:
|
||||
|
||||
1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件都有完整配对。
|
||||
2. 任何已存在的配对——无论是否 required——都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致——标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
|
||||
**dispose(资源释放)必须等待所有任务完全停稳,不能仅下发终止指令就返回**:如果清理过程只发出终止或中断信号,却不等任务停止就返回,就会留下孤儿进程。清理应采用异步方式,等待所有子任务彻底退出(先发出终止信号,再等待退出);发出信号前应先关闭监听器与通知注册表,使延迟到达的完成事件不再触发通知。测试要证明 dispose 的确等到清理完成:执行完 `await fiber.dispose()` 后进程 PID 立即消失,不能只检查进程最终会自行消亡。
|
||||
|
||||
> **Async state is not synchronous state** — `agent.send()` does not flip status before returning; a background task's completion races turn boundaries; `reader.close()` fires for both EOF and disposal. Never gate control flow on a status you only just requested — drive lifecycle off the events/promises that actually fire (`agent/status`, `task.done`), and observe the transition (saw `running` THEN `idle`) rather than counting actions you assume map 1:1 to turns.
|
||||
> **Async state is not synchronous state** — `agent.send()` does not flip status before returning; a background task's completion races turn boundaries; `reader.close()` fires for both EOF and disposal. Never gate control flow on a status you only just requested — drive lifecycle off the events/promises that actually fire (`agent/status`, `task.done`), and observe the transition (saw `running` THEN `idle`) instead of treating status as a per-send result: several queued sends run as consecutive turns under one `running` interval, while cancellation or disposal can discard unstarted items.
|
||||
|
||||
**异步状态不等同于同步瞬时状态**:调用 `agent.send()` 不会在返回前同步更新状态;后台任务的完成时间与轮次边界存在竞态;`reader.close()` 既会在读到文件末尾时触发,也会在资源释放时触发。切勿把刚刚发起的状态变更当成已经生效,据此控制流程;生命周期逻辑应以实际触发的事件和已完成的 promise(`agent/status`、`task.done`)为准,并观察完整的状态变化(先 `running`,再 `idle`),不要根据操作次数推断操作与轮次一一对应。
|
||||
**异步状态不等同于同步瞬时状态**:调用 `agent.send()` 不会在返回前同步更新状态;后台任务的完成时间与轮次边界存在竞态;`reader.close()` 既会在读到文件末尾时触发,也会在资源释放时触发。切勿把刚刚发起的状态变更当成已经生效,据此控制流程;生命周期逻辑应以实际触发的事件和已完成的 promise(`agent/status`、`task.done`)为准,并观察完整的状态变化(先 `running`,再 `idle`),不要把状态当作逐次 `send()` 的结果:多次排队的 `send()` 会作为连续轮次运行,但可能共用一个 `running` 区间;取消或资源释放还可能丢弃尚未启动的队列项。
|
||||
|
||||
## ③ 测试政策清单
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
| waterfall | waterfall | waterfall(瀑布式事件) | | |
|
||||
| wheel | wheel 包 | | | Python 打包格式 |
|
||||
| worktree | worktree | | | git 工作区概念 |
|
||||
| Zstandard | Zstandard | | | RFC 8878 compression format; `zstd` remains a code value. |
|
||||
|
||||
## 双语类(中英文文本各自使用中英文)
|
||||
|
||||
|
||||
@@ -123,9 +123,9 @@ Follow the Good versions; these sentence-level examples illustrate error categor
|
||||
- Good: `A green gate does not mean the translation is correct.`
|
||||
|
||||
### Code block comments — never translate
|
||||
- Source code block contains: `# readline coding agent (needs DEEPSEEK_API_KEY)`
|
||||
- Bad: `# readline 编码 agent(需要 DEEPSEEK_API_KEY)`
|
||||
- Good: `# readline coding agent (needs DEEPSEEK_API_KEY)` (byte-identical)
|
||||
- Source code block contains: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)`
|
||||
- Bad: `# 全屏 TUI coding agent(需要 DEEPSEEK_API_KEY)`
|
||||
- Good: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)` (byte-identical)
|
||||
|
||||
### Language switcher — English to Chinese
|
||||
- Source: `English | [中文](README.zh.md)`
|
||||
|
||||
@@ -9,7 +9,6 @@ Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, deri
|
||||
flowchart TD
|
||||
subgraph group_util["packages/util"]
|
||||
pkg_brand["brand"]
|
||||
pkg_home["home"]
|
||||
pkg_paths["paths"]
|
||||
pkg_retention["retention"]
|
||||
pkg_timeout["timeout"]
|
||||
@@ -18,6 +17,7 @@ flowchart TD
|
||||
pkg_llm["llm"]
|
||||
pkg_llm_deepseek["llm-deepseek"]
|
||||
pkg_llm_pi_ai["llm-pi-ai"]
|
||||
pkg_llm_retry["llm-retry"]
|
||||
pkg_token_meter["token-meter"]
|
||||
end
|
||||
subgraph group_core["packages/core"]
|
||||
@@ -28,6 +28,12 @@ flowchart TD
|
||||
pkg_system_prompt["system-prompt"]
|
||||
pkg_tools["tools"]
|
||||
end
|
||||
subgraph group_goal["packages/goal"]
|
||||
pkg_command_goal["command-goal"]
|
||||
pkg_goal["goal"]
|
||||
pkg_goal_session["goal-session"]
|
||||
pkg_tool_goal["tool-goal"]
|
||||
end
|
||||
subgraph group_bash["packages/bash"]
|
||||
pkg_bash["bash"]
|
||||
pkg_bash_local["bash-local"]
|
||||
@@ -50,6 +56,7 @@ flowchart TD
|
||||
subgraph group_compact["packages/compact"]
|
||||
pkg_compact["compact"]
|
||||
pkg_compact_basic["compact-basic"]
|
||||
pkg_compact_tool_result_prune["compact-tool-result-prune"]
|
||||
end
|
||||
subgraph group_subagent["packages/subagent"]
|
||||
pkg_subagent["subagent"]
|
||||
@@ -79,6 +86,9 @@ flowchart TD
|
||||
subgraph group_todo["packages/todo"]
|
||||
pkg_tool_todo["tool-todo"]
|
||||
end
|
||||
subgraph group_plan["packages/plan"]
|
||||
pkg_plan_mode["plan-mode"]
|
||||
end
|
||||
subgraph group_cordis["packages/cordis"]
|
||||
pkg_tool_cordis["tool-cordis"]
|
||||
end
|
||||
@@ -95,6 +105,12 @@ flowchart TD
|
||||
subgraph group_session_query["packages/session-query"]
|
||||
pkg_session_query["session-query"]
|
||||
end
|
||||
subgraph group_session_title["packages/session-title"]
|
||||
pkg_session_title["session-title"]
|
||||
pkg_session_title_all_messages_llm["session-title-all-messages-llm"]
|
||||
pkg_session_title_first_message_llm["session-title-first-message-llm"]
|
||||
pkg_session_title_llm["session-title-llm"]
|
||||
end
|
||||
subgraph group_support["packages/support"]
|
||||
pkg_acp_snapshot["acp-snapshot"]
|
||||
pkg_agent_loop_testkit["agent-loop-testkit"]
|
||||
@@ -105,14 +121,28 @@ flowchart TD
|
||||
subgraph group_ui["packages/ui"]
|
||||
pkg_acp["acp"]
|
||||
pkg_app_boot["app-boot"]
|
||||
pkg_commands["commands"]
|
||||
pkg_jsonrpc["jsonrpc"]
|
||||
pkg_permission["permission"]
|
||||
pkg_stdio["stdio"]
|
||||
pkg_tool_ask_user["tool-ask-user"]
|
||||
pkg_tui["tui"]
|
||||
pkg_user_approval["user-approval"]
|
||||
pkg_user_interaction["user-interaction"]
|
||||
end
|
||||
subgraph group_client["packages/client"]
|
||||
pkg_client_connection["client-connection"]
|
||||
pkg_client_i18n["client-i18n"]
|
||||
pkg_client_runtime["client-runtime"]
|
||||
pkg_client_ui_conversation["client-ui-conversation"]
|
||||
pkg_client_ui_layout["client-ui-layout"]
|
||||
pkg_client_ui_primitives["client-ui-primitives"]
|
||||
pkg_client_ui_sidebar["client-ui-sidebar"]
|
||||
pkg_client_ui_slots["client-ui-slots"]
|
||||
pkg_client_ui_theme["client-ui-theme"]
|
||||
pkg_client_ui_trajectory["client-ui-trajectory"]
|
||||
pkg_client_web["client-web"]
|
||||
pkg_client_web_react["client-web-react"]
|
||||
end
|
||||
subgraph group_code_runtime["packages/code-runtime"]
|
||||
pkg_code_runtime["code-runtime"]
|
||||
pkg_code_runtime_worker["code-runtime-worker"]
|
||||
@@ -126,11 +156,21 @@ flowchart TD
|
||||
pkg_agent_spine_demo["agent-spine-demo"]
|
||||
pkg_cli_demo["cli-demo"]
|
||||
pkg_jsonrpc_demo["jsonrpc-demo"]
|
||||
pkg_stdio_demo["stdio-demo"]
|
||||
pkg_tui_demo["tui-demo"]
|
||||
end
|
||||
subgraph group_guard["packages/guard"]
|
||||
pkg_repeat_tool_guard["repeat-tool-guard"]
|
||||
end
|
||||
subgraph group_host["packages/host"]
|
||||
pkg_host_apiproxy["host-apiproxy"]
|
||||
pkg_host_runtime["host-runtime"]
|
||||
pkg_host_webserver["host-webserver"]
|
||||
end
|
||||
subgraph group_lsp["packages/lsp"]
|
||||
pkg_lsp["lsp"]
|
||||
pkg_lsp_local["lsp-local"]
|
||||
pkg_tool_lsp["tool-lsp"]
|
||||
end
|
||||
subgraph group_mcp["packages/mcp"]
|
||||
pkg_mcp_client["mcp-client"]
|
||||
end
|
||||
@@ -154,131 +194,268 @@ flowchart TD
|
||||
pkg_tool_tasks["tool-tasks"]
|
||||
end
|
||||
subgraph group_workflow["packages/workflow"]
|
||||
pkg_tool_ralph["tool-ralph"]
|
||||
pkg_tool_workflow["tool-workflow"]
|
||||
pkg_workflow["workflow"]
|
||||
pkg_workflow_workerthread["workflow-workerthread"]
|
||||
end
|
||||
pkg_brand --> pkg_invariants
|
||||
pkg_paths --> pkg_invariants
|
||||
pkg_retention --> pkg_invariants
|
||||
pkg_timeout --> pkg_invariants
|
||||
pkg_scope --> pkg_invariants
|
||||
pkg_skill --> pkg_invariants
|
||||
pkg_subagent_subprocess --> pkg_invariants
|
||||
pkg_acp_snapshot --> pkg_invariants
|
||||
pkg_loader_smoke --> pkg_invariants
|
||||
pkg_app_boot --> pkg_invariants
|
||||
pkg_client_connection --> pkg_invariants
|
||||
pkg_client_i18n --> pkg_invariants
|
||||
pkg_client_runtime --> pkg_invariants
|
||||
pkg_client_ui_conversation --> pkg_invariants
|
||||
pkg_client_ui_layout --> pkg_invariants
|
||||
pkg_client_ui_primitives --> pkg_invariants
|
||||
pkg_client_ui_sidebar --> pkg_invariants
|
||||
pkg_client_ui_slots --> pkg_invariants
|
||||
pkg_client_ui_theme --> pkg_invariants
|
||||
pkg_client_ui_trajectory --> pkg_invariants
|
||||
pkg_client_web --> pkg_invariants
|
||||
pkg_client_web_react --> pkg_invariants
|
||||
pkg_code_runtime --> pkg_invariants
|
||||
pkg_jsonrpc_demo --> pkg_invariants
|
||||
pkg_host_apiproxy --> pkg_invariants
|
||||
pkg_host_runtime --> pkg_invariants
|
||||
pkg_host_webserver --> pkg_invariants
|
||||
pkg_llm --> pkg_brand
|
||||
pkg_llm --> pkg_invariants
|
||||
pkg_code_runtime_worker --> pkg_code_runtime
|
||||
pkg_code_runtime_worker --> pkg_invariants
|
||||
pkg_helper --> pkg_brand
|
||||
pkg_helper --> pkg_invariants
|
||||
pkg_scripts --> pkg_app_boot
|
||||
pkg_scripts --> pkg_invariants
|
||||
pkg_telemetry --> pkg_brand
|
||||
pkg_telemetry --> pkg_invariants
|
||||
pkg_telemetry --> pkg_paths
|
||||
pkg_llm_deepseek --> pkg_invariants
|
||||
pkg_llm_deepseek --> pkg_llm
|
||||
pkg_llm_deepseek --> pkg_timeout
|
||||
pkg_llm_pi_ai --> pkg_invariants
|
||||
pkg_llm_pi_ai --> pkg_llm
|
||||
pkg_llm_pi_ai --> pkg_timeout
|
||||
pkg_session --> pkg_brand
|
||||
pkg_session --> pkg_invariants
|
||||
pkg_session --> pkg_llm
|
||||
pkg_session --> pkg_scope
|
||||
pkg_system_prompt --> pkg_invariants
|
||||
pkg_system_prompt --> pkg_llm
|
||||
pkg_system_prompt --> pkg_scope
|
||||
pkg_web --> pkg_invariants
|
||||
pkg_web --> pkg_llm
|
||||
pkg_lsp --> pkg_brand
|
||||
pkg_lsp --> pkg_invariants
|
||||
pkg_lsp --> pkg_llm
|
||||
pkg_sandbox --> pkg_invariants
|
||||
pkg_sandbox --> pkg_llm
|
||||
pkg_token_meter --> pkg_invariants
|
||||
pkg_token_meter --> pkg_llm
|
||||
pkg_token_meter --> pkg_session
|
||||
pkg_agent --> pkg_brand
|
||||
pkg_agent --> pkg_invariants
|
||||
pkg_agent --> pkg_llm
|
||||
pkg_agent --> pkg_scope
|
||||
pkg_agent --> pkg_session
|
||||
pkg_agent --> pkg_system_prompt
|
||||
pkg_bash --> pkg_invariants
|
||||
pkg_bash --> pkg_sandbox
|
||||
pkg_fs --> pkg_brand
|
||||
pkg_fs --> pkg_invariants
|
||||
pkg_fs --> pkg_llm
|
||||
pkg_fs --> pkg_sandbox
|
||||
pkg_compact --> pkg_invariants
|
||||
pkg_compact --> pkg_llm
|
||||
pkg_compact --> pkg_session
|
||||
pkg_compact_tool_result_prune --> pkg_invariants
|
||||
pkg_compact_tool_result_prune --> pkg_llm
|
||||
pkg_compact_tool_result_prune --> pkg_session
|
||||
pkg_web_fetch_local --> pkg_invariants
|
||||
pkg_web_fetch_local --> pkg_timeout
|
||||
pkg_web_fetch_local --> pkg_web
|
||||
pkg_web_search_deepseek --> pkg_invariants
|
||||
pkg_web_search_deepseek --> pkg_web
|
||||
pkg_web_search_exa --> pkg_invariants
|
||||
pkg_web_search_exa --> pkg_web
|
||||
pkg_web_search_perplexity --> pkg_invariants
|
||||
pkg_web_search_perplexity --> pkg_web
|
||||
pkg_spill --> pkg_brand
|
||||
pkg_spill --> pkg_invariants
|
||||
pkg_spill --> pkg_llm
|
||||
pkg_spill --> pkg_session
|
||||
pkg_session_persistence --> pkg_invariants
|
||||
pkg_session_persistence --> pkg_session
|
||||
pkg_session_title --> pkg_brand
|
||||
pkg_session_title --> pkg_invariants
|
||||
pkg_session_title --> pkg_llm
|
||||
pkg_session_title --> pkg_session
|
||||
pkg_llm_replay --> pkg_invariants
|
||||
pkg_llm_replay --> pkg_llm
|
||||
pkg_llm_replay --> pkg_session
|
||||
pkg_lsp_local --> pkg_brand
|
||||
pkg_lsp_local --> pkg_invariants
|
||||
pkg_lsp_local --> pkg_llm
|
||||
pkg_lsp_local --> pkg_lsp
|
||||
pkg_lsp_local --> pkg_timeout
|
||||
pkg_sandbox_local --> pkg_invariants
|
||||
pkg_sandbox_local --> pkg_llm
|
||||
pkg_sandbox_local --> pkg_sandbox
|
||||
pkg_sandbox_policy --> pkg_invariants
|
||||
pkg_sandbox_policy --> pkg_sandbox
|
||||
pkg_sandbox_policy --> pkg_session
|
||||
pkg_llm_retry --> pkg_agent
|
||||
pkg_llm_retry --> pkg_invariants
|
||||
pkg_llm_retry --> pkg_llm
|
||||
pkg_llm_retry --> pkg_session
|
||||
pkg_llm_retry --> pkg_timeout
|
||||
pkg_goal --> pkg_agent
|
||||
pkg_goal --> pkg_brand
|
||||
pkg_goal --> pkg_invariants
|
||||
pkg_goal --> pkg_llm
|
||||
pkg_goal --> pkg_scope
|
||||
pkg_goal --> pkg_session
|
||||
pkg_bash_local --> pkg_bash
|
||||
pkg_bash_local --> pkg_invariants
|
||||
pkg_bash_local --> pkg_timeout
|
||||
pkg_fs_local --> pkg_fs
|
||||
pkg_fs_local --> pkg_invariants
|
||||
pkg_fs_policy --> pkg_fs
|
||||
pkg_fs_policy --> pkg_invariants
|
||||
pkg_skill_local --> pkg_fs
|
||||
pkg_skill_local --> pkg_home
|
||||
pkg_skill_local --> pkg_invariants
|
||||
pkg_skill_local --> pkg_paths
|
||||
pkg_skill_local --> pkg_skill
|
||||
pkg_compact_basic --> pkg_agent
|
||||
pkg_compact_basic --> pkg_compact
|
||||
pkg_compact_basic --> pkg_compact_tool_result_prune
|
||||
pkg_compact_basic --> pkg_invariants
|
||||
pkg_compact_basic --> pkg_llm
|
||||
pkg_compact_basic --> pkg_session
|
||||
pkg_compact_basic --> pkg_token_meter
|
||||
pkg_spill_local --> pkg_invariants
|
||||
pkg_spill_local --> pkg_spill
|
||||
pkg_hook_protocol --> pkg_bash
|
||||
pkg_hook_protocol --> pkg_invariants
|
||||
pkg_hook_protocol --> pkg_session
|
||||
pkg_session_persistence_jsonl --> pkg_invariants
|
||||
pkg_session_persistence_jsonl --> pkg_session
|
||||
pkg_session_persistence_jsonl --> pkg_session_persistence
|
||||
pkg_session_persistence_sqlite --> pkg_invariants
|
||||
pkg_session_persistence_sqlite --> pkg_session
|
||||
pkg_session_persistence_sqlite --> pkg_session_persistence
|
||||
pkg_session_query --> pkg_invariants
|
||||
pkg_session_query --> pkg_llm
|
||||
pkg_session_query --> pkg_session
|
||||
pkg_session_query --> pkg_session_persistence
|
||||
pkg_invariants --> pkg_agent
|
||||
pkg_invariants --> pkg_llm
|
||||
pkg_invariants --> pkg_scope
|
||||
pkg_invariants --> pkg_session
|
||||
pkg_session_query --> pkg_session_title
|
||||
pkg_session_title_llm --> pkg_invariants
|
||||
pkg_session_title_llm --> pkg_llm
|
||||
pkg_session_title_llm --> pkg_session
|
||||
pkg_session_title_llm --> pkg_session_title
|
||||
pkg_session_title_llm --> pkg_timeout
|
||||
pkg_commands --> pkg_agent
|
||||
pkg_commands --> pkg_invariants
|
||||
pkg_commands --> pkg_scope
|
||||
pkg_user_approval --> pkg_agent
|
||||
pkg_user_approval --> pkg_brand
|
||||
pkg_user_approval --> pkg_invariants
|
||||
pkg_user_approval --> pkg_llm
|
||||
pkg_user_approval --> pkg_scope
|
||||
pkg_user_approval --> pkg_session
|
||||
pkg_user_approval --> pkg_system_prompt
|
||||
pkg_user_interaction --> pkg_agent
|
||||
pkg_user_interaction --> pkg_invariants
|
||||
pkg_user_interaction --> pkg_llm
|
||||
pkg_time_context --> pkg_agent
|
||||
pkg_time_context --> pkg_invariants
|
||||
pkg_time_context --> pkg_session
|
||||
pkg_pty --> pkg_agent
|
||||
pkg_pty --> pkg_brand
|
||||
pkg_pty --> pkg_invariants
|
||||
pkg_tasks --> pkg_agent
|
||||
pkg_tasks --> pkg_brand
|
||||
pkg_tasks --> pkg_invariants
|
||||
pkg_tasks --> pkg_session
|
||||
pkg_tasks --> pkg_timeout
|
||||
pkg_workflow --> pkg_agent
|
||||
pkg_workflow --> pkg_brand
|
||||
pkg_workflow --> pkg_invariants
|
||||
pkg_workflow --> pkg_llm
|
||||
pkg_workflow --> pkg_session
|
||||
pkg_tools --> pkg_agent
|
||||
pkg_tools --> pkg_code_runtime
|
||||
pkg_tools --> pkg_invariants
|
||||
pkg_tools --> pkg_llm
|
||||
pkg_tools --> pkg_scope
|
||||
pkg_tools --> pkg_session
|
||||
pkg_tools --> pkg_system_prompt
|
||||
pkg_tools --> pkg_user_approval
|
||||
pkg_command_goal --> pkg_commands
|
||||
pkg_command_goal --> pkg_goal
|
||||
pkg_command_goal --> pkg_invariants
|
||||
pkg_goal_session --> pkg_agent
|
||||
pkg_goal_session --> pkg_goal
|
||||
pkg_goal_session --> pkg_invariants
|
||||
pkg_goal_session --> pkg_llm
|
||||
pkg_goal_session --> pkg_session
|
||||
pkg_bash_sandbox --> pkg_bash
|
||||
pkg_bash_sandbox --> pkg_bash_local
|
||||
pkg_bash_sandbox --> pkg_invariants
|
||||
pkg_bash_sandbox --> pkg_sandbox
|
||||
pkg_bash_sandbox --> pkg_sandbox_policy
|
||||
pkg_fs_sandbox --> pkg_fs
|
||||
pkg_fs_sandbox --> pkg_fs_local
|
||||
pkg_fs_sandbox --> pkg_invariants
|
||||
pkg_fs_sandbox --> pkg_sandbox
|
||||
pkg_fs_sandbox --> pkg_sandbox_policy
|
||||
pkg_session_title_all_messages_llm --> pkg_invariants
|
||||
pkg_session_title_all_messages_llm --> pkg_llm
|
||||
pkg_session_title_all_messages_llm --> pkg_session
|
||||
pkg_session_title_all_messages_llm --> pkg_session_title
|
||||
pkg_session_title_all_messages_llm --> pkg_session_title_llm
|
||||
pkg_session_title_first_message_llm --> pkg_invariants
|
||||
pkg_session_title_first_message_llm --> pkg_llm
|
||||
pkg_session_title_first_message_llm --> pkg_session
|
||||
pkg_session_title_first_message_llm --> pkg_session_title
|
||||
pkg_session_title_first_message_llm --> pkg_session_title_llm
|
||||
pkg_permission --> pkg_bash
|
||||
pkg_permission --> pkg_invariants
|
||||
pkg_permission --> pkg_sandbox
|
||||
pkg_permission --> pkg_sandbox_policy
|
||||
pkg_permission --> pkg_session
|
||||
pkg_permission --> pkg_user_approval
|
||||
pkg_pty_local --> pkg_invariants
|
||||
pkg_pty_local --> pkg_pty
|
||||
pkg_pty_local --> pkg_sandbox
|
||||
pkg_pty_local --> pkg_sandbox_policy
|
||||
pkg_agent_loop --> pkg_agent
|
||||
pkg_agent_loop --> pkg_invariants
|
||||
pkg_agent_loop --> pkg_llm
|
||||
pkg_agent_loop --> pkg_scope
|
||||
pkg_agent_loop --> pkg_session
|
||||
pkg_agent_loop --> pkg_session_persistence
|
||||
pkg_agent_loop --> pkg_system_prompt
|
||||
pkg_agent_loop --> pkg_tools
|
||||
pkg_tool_goal --> pkg_agent
|
||||
pkg_tool_goal --> pkg_goal
|
||||
pkg_tool_goal --> pkg_invariants
|
||||
pkg_tool_goal --> pkg_llm
|
||||
pkg_tool_goal --> pkg_session
|
||||
pkg_tool_goal --> pkg_system_prompt
|
||||
pkg_tool_goal --> pkg_tools
|
||||
pkg_tool_bash --> pkg_agent
|
||||
pkg_tool_bash --> pkg_bash
|
||||
pkg_tool_bash --> pkg_home
|
||||
pkg_tool_bash --> pkg_invariants
|
||||
pkg_tool_bash --> pkg_llm
|
||||
pkg_tool_bash --> pkg_paths
|
||||
pkg_tool_bash --> pkg_sandbox
|
||||
pkg_tool_bash --> pkg_sandbox_policy
|
||||
pkg_tool_bash --> pkg_session_persistence
|
||||
@@ -287,6 +464,7 @@ flowchart TD
|
||||
pkg_tool_bash --> pkg_tools
|
||||
pkg_tool_bash --> pkg_user_approval
|
||||
pkg_tool_fs --> pkg_fs
|
||||
pkg_tool_fs --> pkg_invariants
|
||||
pkg_tool_fs --> pkg_llm
|
||||
pkg_tool_fs --> pkg_sandbox
|
||||
pkg_tool_fs --> pkg_sandbox_policy
|
||||
@@ -295,6 +473,7 @@ flowchart TD
|
||||
pkg_tool_fs --> pkg_tools
|
||||
pkg_tool_fs --> pkg_user_approval
|
||||
pkg_tool_fs_search --> pkg_bash
|
||||
pkg_tool_fs_search --> pkg_invariants
|
||||
pkg_tool_fs_search --> pkg_llm
|
||||
pkg_tool_fs_search --> pkg_retention
|
||||
pkg_tool_fs_search --> pkg_session
|
||||
@@ -302,153 +481,212 @@ flowchart TD
|
||||
pkg_tool_fs_search --> pkg_system_prompt
|
||||
pkg_tool_fs_search --> pkg_tools
|
||||
pkg_tool_skill --> pkg_agent
|
||||
pkg_tool_skill --> pkg_invariants
|
||||
pkg_tool_skill --> pkg_llm
|
||||
pkg_tool_skill --> pkg_skill
|
||||
pkg_tool_skill --> pkg_tools
|
||||
pkg_subagent --> pkg_agent
|
||||
pkg_subagent --> pkg_brand
|
||||
pkg_subagent --> pkg_invariants
|
||||
pkg_subagent --> pkg_llm
|
||||
pkg_subagent --> pkg_scope
|
||||
pkg_subagent --> pkg_session
|
||||
pkg_subagent --> pkg_tools
|
||||
pkg_tool_web --> pkg_invariants
|
||||
pkg_tool_web --> pkg_llm
|
||||
pkg_tool_web --> pkg_system_prompt
|
||||
pkg_tool_web --> pkg_tools
|
||||
pkg_tool_web --> pkg_web
|
||||
pkg_spill_policy --> pkg_invariants
|
||||
pkg_spill_policy --> pkg_llm
|
||||
pkg_spill_policy --> pkg_retention
|
||||
pkg_spill_policy --> pkg_session
|
||||
pkg_spill_policy --> pkg_spill
|
||||
pkg_spill_policy --> pkg_tools
|
||||
pkg_timeout_policy --> pkg_invariants
|
||||
pkg_timeout_policy --> pkg_llm
|
||||
pkg_timeout_policy --> pkg_timeout
|
||||
pkg_timeout_policy --> pkg_tools
|
||||
pkg_tool_todo --> pkg_agent
|
||||
pkg_tool_todo --> pkg_invariants
|
||||
pkg_tool_todo --> pkg_session
|
||||
pkg_tool_todo --> pkg_tools
|
||||
pkg_plan_mode --> pkg_agent
|
||||
pkg_plan_mode --> pkg_commands
|
||||
pkg_plan_mode --> pkg_invariants
|
||||
pkg_plan_mode --> pkg_session
|
||||
pkg_plan_mode --> pkg_system_prompt
|
||||
pkg_plan_mode --> pkg_tools
|
||||
pkg_plan_mode --> pkg_user_interaction
|
||||
pkg_tool_cordis --> pkg_invariants
|
||||
pkg_tool_cordis --> pkg_scope
|
||||
pkg_tool_cordis --> pkg_tools
|
||||
pkg_hooks_codex --> pkg_agent
|
||||
pkg_hooks_codex --> pkg_hook_protocol
|
||||
pkg_hooks_codex --> pkg_invariants
|
||||
pkg_hooks_codex --> pkg_llm
|
||||
pkg_hooks_codex --> pkg_session
|
||||
pkg_hooks_codex --> pkg_session_persistence
|
||||
pkg_hooks_codex --> pkg_tools
|
||||
pkg_agent_loop_testkit --> pkg_agent
|
||||
pkg_agent_loop_testkit --> pkg_invariants
|
||||
pkg_agent_loop_testkit --> pkg_llm
|
||||
pkg_agent_loop_testkit --> pkg_session
|
||||
pkg_agent_loop_testkit --> pkg_system_prompt
|
||||
pkg_agent_loop_testkit --> pkg_tools
|
||||
pkg_acp --> pkg_agent
|
||||
pkg_acp --> pkg_bash
|
||||
pkg_acp --> pkg_llm
|
||||
pkg_acp --> pkg_permission
|
||||
pkg_acp --> pkg_sandbox
|
||||
pkg_acp --> pkg_session
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_system_prompt
|
||||
pkg_acp --> pkg_tools
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_acp --> pkg_user_interaction
|
||||
pkg_tool_ask_user --> pkg_agent
|
||||
pkg_tool_ask_user --> pkg_invariants
|
||||
pkg_tool_ask_user --> pkg_tools
|
||||
pkg_tool_ask_user --> pkg_user_interaction
|
||||
pkg_workspace_context --> pkg_agent
|
||||
pkg_workspace_context --> pkg_fs
|
||||
pkg_workspace_context --> pkg_invariants
|
||||
pkg_workspace_context --> pkg_llm
|
||||
pkg_workspace_context --> pkg_paths
|
||||
pkg_workspace_context --> pkg_session
|
||||
pkg_workspace_context --> pkg_tools
|
||||
pkg_repeat_tool_guard --> pkg_agent
|
||||
pkg_repeat_tool_guard --> pkg_invariants
|
||||
pkg_repeat_tool_guard --> pkg_tools
|
||||
pkg_tool_lsp --> pkg_invariants
|
||||
pkg_tool_lsp --> pkg_llm
|
||||
pkg_tool_lsp --> pkg_lsp
|
||||
pkg_tool_lsp --> pkg_system_prompt
|
||||
pkg_tool_lsp --> pkg_timeout
|
||||
pkg_tool_lsp --> pkg_tools
|
||||
pkg_mcp_client --> pkg_invariants
|
||||
pkg_mcp_client --> pkg_llm
|
||||
pkg_mcp_client --> pkg_tools
|
||||
pkg_tool_pty --> pkg_agent
|
||||
pkg_tool_pty --> pkg_invariants
|
||||
pkg_tool_pty --> pkg_llm
|
||||
pkg_tool_pty --> pkg_pty
|
||||
pkg_tool_pty --> pkg_system_prompt
|
||||
pkg_tool_pty --> pkg_tasks
|
||||
pkg_tool_pty --> pkg_tools
|
||||
pkg_tool_tasks --> pkg_agent
|
||||
pkg_tool_tasks --> pkg_invariants
|
||||
pkg_tool_tasks --> pkg_system_prompt
|
||||
pkg_tool_tasks --> pkg_tasks
|
||||
pkg_tool_tasks --> pkg_tools
|
||||
pkg_tool_workflow --> pkg_agent
|
||||
pkg_tool_workflow --> pkg_invariants
|
||||
pkg_tool_workflow --> pkg_llm
|
||||
pkg_tool_workflow --> pkg_system_prompt
|
||||
pkg_tool_workflow --> pkg_tools
|
||||
pkg_tool_workflow --> pkg_workflow
|
||||
pkg_subagent_acp --> pkg_agent
|
||||
pkg_subagent_acp --> pkg_invariants
|
||||
pkg_subagent_acp --> pkg_llm
|
||||
pkg_subagent_acp --> pkg_session
|
||||
pkg_subagent_acp --> pkg_subagent
|
||||
pkg_subagent_acp --> pkg_subagent_subprocess
|
||||
pkg_subagent_inprocess --> pkg_agent
|
||||
pkg_subagent_inprocess --> pkg_invariants
|
||||
pkg_subagent_inprocess --> pkg_llm
|
||||
pkg_subagent_inprocess --> pkg_session
|
||||
pkg_subagent_inprocess --> pkg_subagent
|
||||
pkg_subagent_inprocess --> pkg_system_prompt
|
||||
pkg_subagent_inprocess --> pkg_tools
|
||||
pkg_tool_subagent --> pkg_agent
|
||||
pkg_tool_subagent --> pkg_invariants
|
||||
pkg_tool_subagent --> pkg_llm
|
||||
pkg_tool_subagent --> pkg_subagent
|
||||
pkg_tool_subagent --> pkg_tasks
|
||||
pkg_tool_subagent --> pkg_tools
|
||||
pkg_hooks_claude --> pkg_agent
|
||||
pkg_hooks_claude --> pkg_hook_protocol
|
||||
pkg_hooks_claude --> pkg_invariants
|
||||
pkg_hooks_claude --> pkg_llm
|
||||
pkg_hooks_claude --> pkg_session
|
||||
pkg_hooks_claude --> pkg_session_persistence
|
||||
pkg_hooks_claude --> pkg_subagent
|
||||
pkg_hooks_claude --> pkg_tools
|
||||
pkg_acp --> pkg_agent
|
||||
pkg_acp --> pkg_bash
|
||||
pkg_acp --> pkg_commands
|
||||
pkg_acp --> pkg_invariants
|
||||
pkg_acp --> pkg_llm
|
||||
pkg_acp --> pkg_llm_retry
|
||||
pkg_acp --> pkg_permission
|
||||
pkg_acp --> pkg_plan_mode
|
||||
pkg_acp --> pkg_sandbox
|
||||
pkg_acp --> pkg_session
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_session_title
|
||||
pkg_acp --> pkg_system_prompt
|
||||
pkg_acp --> pkg_tools
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_acp --> pkg_user_interaction
|
||||
pkg_jsonrpc --> pkg_agent
|
||||
pkg_jsonrpc --> pkg_invariants
|
||||
pkg_jsonrpc --> pkg_llm
|
||||
pkg_jsonrpc --> pkg_llm_deepseek
|
||||
pkg_jsonrpc --> pkg_scope
|
||||
pkg_jsonrpc --> pkg_session
|
||||
pkg_jsonrpc --> pkg_subagent
|
||||
pkg_stdio --> pkg_agent
|
||||
pkg_stdio --> pkg_agent_loop
|
||||
pkg_stdio --> pkg_llm
|
||||
pkg_stdio --> pkg_session
|
||||
pkg_stdio --> pkg_user_interaction
|
||||
pkg_tui --> pkg_agent
|
||||
pkg_tui --> pkg_agent_loop
|
||||
pkg_tui --> pkg_commands
|
||||
pkg_tui --> pkg_invariants
|
||||
pkg_tui --> pkg_llm
|
||||
pkg_tui --> pkg_llm_retry
|
||||
pkg_tui --> pkg_session
|
||||
pkg_tui --> pkg_session_title
|
||||
pkg_tui --> pkg_system_prompt
|
||||
pkg_tui --> pkg_token_meter
|
||||
pkg_tui --> pkg_tools
|
||||
pkg_tui --> pkg_user_interaction
|
||||
pkg_agent_spine_demo --> pkg_agent
|
||||
pkg_agent_spine_demo --> pkg_agent_loop
|
||||
pkg_agent_spine_demo --> pkg_home
|
||||
pkg_agent_spine_demo --> pkg_goal
|
||||
pkg_agent_spine_demo --> pkg_goal_session
|
||||
pkg_agent_spine_demo --> pkg_invariants
|
||||
pkg_agent_spine_demo --> pkg_llm
|
||||
pkg_agent_spine_demo --> pkg_llm_retry
|
||||
pkg_agent_spine_demo --> pkg_paths
|
||||
pkg_agent_spine_demo --> pkg_scope
|
||||
pkg_agent_spine_demo --> pkg_session
|
||||
pkg_agent_spine_demo --> pkg_session_title
|
||||
pkg_agent_spine_demo --> pkg_skill
|
||||
pkg_agent_spine_demo --> pkg_skill_local
|
||||
pkg_agent_spine_demo --> pkg_system_prompt
|
||||
pkg_agent_spine_demo --> pkg_tasks
|
||||
pkg_agent_spine_demo --> pkg_tool_bash
|
||||
pkg_agent_spine_demo --> pkg_tool_goal
|
||||
pkg_agent_spine_demo --> pkg_tool_skill
|
||||
pkg_agent_spine_demo --> pkg_tool_tasks
|
||||
pkg_agent_spine_demo --> pkg_tools
|
||||
pkg_agent_spine_demo --> pkg_workspace_context
|
||||
pkg_tool_ralph --> pkg_agent
|
||||
pkg_tool_ralph --> pkg_invariants
|
||||
pkg_tool_ralph --> pkg_llm
|
||||
pkg_tool_ralph --> pkg_subagent
|
||||
pkg_tool_ralph --> pkg_system_prompt
|
||||
pkg_tool_ralph --> pkg_tools
|
||||
pkg_tool_ralph --> pkg_workflow
|
||||
pkg_workflow_workerthread --> pkg_agent
|
||||
pkg_workflow_workerthread --> pkg_brand
|
||||
pkg_workflow_workerthread --> pkg_invariants
|
||||
pkg_workflow_workerthread --> pkg_llm
|
||||
pkg_workflow_workerthread --> pkg_session
|
||||
pkg_workflow_workerthread --> pkg_subagent
|
||||
pkg_workflow_workerthread --> pkg_tools
|
||||
pkg_workflow_workerthread --> pkg_workflow
|
||||
pkg_subagent_fork --> pkg_agent
|
||||
pkg_subagent_fork --> pkg_invariants
|
||||
pkg_subagent_fork --> pkg_session
|
||||
pkg_subagent_fork --> pkg_subagent
|
||||
pkg_subagent_fork --> pkg_subagent_inprocess
|
||||
pkg_subagent_spawn --> pkg_invariants
|
||||
pkg_subagent_spawn --> pkg_subagent
|
||||
pkg_subagent_spawn --> pkg_subagent_inprocess
|
||||
pkg_acp_demo --> pkg_acp
|
||||
pkg_acp_demo --> pkg_agent_spine_demo
|
||||
pkg_acp_demo --> pkg_app_boot
|
||||
pkg_acp_demo --> pkg_command_goal
|
||||
pkg_acp_demo --> pkg_commands
|
||||
pkg_acp_demo --> pkg_invariants
|
||||
pkg_acp_demo --> pkg_session_persistence_jsonl
|
||||
pkg_acp_demo --> pkg_tools
|
||||
pkg_acp_demo --> pkg_user_interaction
|
||||
@@ -456,120 +694,152 @@ flowchart TD
|
||||
pkg_cli_demo --> pkg_agent
|
||||
pkg_cli_demo --> pkg_agent_spine_demo
|
||||
pkg_cli_demo --> pkg_app_boot
|
||||
pkg_cli_demo --> pkg_invariants
|
||||
pkg_cli_demo --> pkg_llm
|
||||
pkg_cli_demo --> pkg_session
|
||||
pkg_cli_demo --> pkg_session_persistence_jsonl
|
||||
pkg_cli_demo --> pkg_tools
|
||||
pkg_cli_demo --> pkg_workspace_context
|
||||
pkg_stdio_demo --> pkg_agent
|
||||
pkg_stdio_demo --> pkg_agent_loop
|
||||
pkg_stdio_demo --> pkg_agent_spine_demo
|
||||
pkg_stdio_demo --> pkg_app_boot
|
||||
pkg_stdio_demo --> pkg_llm
|
||||
pkg_stdio_demo --> pkg_session
|
||||
pkg_stdio_demo --> pkg_session_persistence_jsonl
|
||||
pkg_stdio_demo --> pkg_stdio
|
||||
pkg_stdio_demo --> pkg_tool_ask_user
|
||||
pkg_stdio_demo --> pkg_tools
|
||||
pkg_stdio_demo --> pkg_tui
|
||||
pkg_stdio_demo --> pkg_user_interaction
|
||||
pkg_stdio_demo --> pkg_workspace_context
|
||||
pkg_tui_demo --> pkg_agent
|
||||
pkg_tui_demo --> pkg_agent_loop
|
||||
pkg_tui_demo --> pkg_agent_spine_demo
|
||||
pkg_tui_demo --> pkg_app_boot
|
||||
pkg_tui_demo --> pkg_command_goal
|
||||
pkg_tui_demo --> pkg_commands
|
||||
pkg_tui_demo --> pkg_invariants
|
||||
pkg_tui_demo --> pkg_llm
|
||||
pkg_tui_demo --> pkg_session
|
||||
pkg_tui_demo --> pkg_session_persistence_jsonl
|
||||
pkg_tui_demo --> pkg_tool_ask_user
|
||||
pkg_tui_demo --> pkg_tools
|
||||
pkg_tui_demo --> pkg_tui
|
||||
pkg_tui_demo --> pkg_user_interaction
|
||||
pkg_tui_demo --> pkg_workspace_context
|
||||
```
|
||||
|
||||
| Package | Group | Depends on |
|
||||
| --- | --- | --- |
|
||||
| [`brand`](../packages/util/brand) | `util` | — |
|
||||
| [`home`](../packages/util/home) | `util` | — |
|
||||
| [`paths`](../packages/util/paths) | `util` | — |
|
||||
| [`retention`](../packages/util/retention) | `util` | — |
|
||||
| [`timeout`](../packages/util/timeout) | `util` | — |
|
||||
| [`scope`](../packages/core/scope) | `core` | — |
|
||||
| [`skill`](../packages/skill/skill) | `skill` | — |
|
||||
| [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | `subagent` | — |
|
||||
| [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | — |
|
||||
| [`loader-smoke`](../packages/support/loader-smoke) | `support` | — |
|
||||
| [`app-boot`](../packages/ui/app-boot) | `ui` | — |
|
||||
| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | — |
|
||||
| [`jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | — |
|
||||
| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand) |
|
||||
| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime) |
|
||||
| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand) |
|
||||
| [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot) |
|
||||
| [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand) |
|
||||
| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`llm`](../packages/llm/llm) |
|
||||
| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`llm`](../packages/llm/llm) |
|
||||
| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
|
||||
| [`system-prompt`](../packages/core/system-prompt) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
|
||||
| [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) |
|
||||
| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`llm`](../packages/llm/llm) |
|
||||
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`agent`](../packages/core/agent) | `core` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`bash`](../packages/bash/bash) | `bash` | [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`compact`](../packages/compact/compact) | `compact` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
|
||||
| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`web`](../packages/web/web) |
|
||||
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`web`](../packages/web/web) |
|
||||
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`web`](../packages/web/web) |
|
||||
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`session`](../packages/core/session) |
|
||||
| [`llm-replay`](../packages/support/llm-replay) | `support` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
|
||||
| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`timeout`](../packages/util/timeout) |
|
||||
| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) |
|
||||
| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs) |
|
||||
| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`home`](../packages/util/home), [`skill`](../packages/skill/skill) |
|
||||
| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) |
|
||||
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`session`](../packages/core/session) |
|
||||
| [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| [`invariants`](../packages/support/invariants) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session) |
|
||||
| [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) |
|
||||
| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent) |
|
||||
| [`pty`](../packages/pty/pty) | `pty` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand) |
|
||||
| [`tasks`](../packages/tasks/tasks) | `tasks` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
|
||||
| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||
| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||
| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`pty-local`](../packages/pty/pty-local) | `pty` | [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`home`](../packages/util/home), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) |
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) |
|
||||
| [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`tool-cordis`](../packages/cordis/tool-cordis) | `cordis` | [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) |
|
||||
| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) |
|
||||
| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`permission`](../packages/ui/permission), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) |
|
||||
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) |
|
||||
| [`tool-pty`](../packages/pty/tool-pty) | `pty` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`pty`](../packages/pty/pty), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) |
|
||||
| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
|
||||
| [`stdio`](../packages/ui/stdio) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`tui`](../packages/ui/tui) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`home`](../packages/util/home), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tool-bash`](../packages/bash/tool-bash), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
|
||||
| [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
|
||||
| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/ui/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`cli-demo`](../packages/examples/cli-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`stdio-demo`](../packages/examples/stdio-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`stdio`](../packages/ui/stdio), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`invariants`](../packages/support/invariants) | `support` | — |
|
||||
| [`brand`](../packages/util/brand) | `util` | [`invariants`](../packages/support/invariants) |
|
||||
| [`paths`](../packages/util/paths) | `util` | [`invariants`](../packages/support/invariants) |
|
||||
| [`retention`](../packages/util/retention) | `util` | [`invariants`](../packages/support/invariants) |
|
||||
| [`timeout`](../packages/util/timeout) | `util` | [`invariants`](../packages/support/invariants) |
|
||||
| [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/support/invariants) |
|
||||
| [`skill`](../packages/skill/skill) | `skill` | [`invariants`](../packages/support/invariants) |
|
||||
| [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | `subagent` | [`invariants`](../packages/support/invariants) |
|
||||
| [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | [`invariants`](../packages/support/invariants) |
|
||||
| [`loader-smoke`](../packages/support/loader-smoke) | `support` | [`invariants`](../packages/support/invariants) |
|
||||
| [`app-boot`](../packages/ui/app-boot) | `ui` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-connection`](../packages/client/connection) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-i18n`](../packages/client/i18n) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-runtime`](../packages/client/runtime) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-ui-primitives`](../packages/client/ui-primitives) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-ui-sidebar`](../packages/client/ui-sidebar) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-ui-slots`](../packages/client/ui-slots) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-ui-theme`](../packages/client/ui-theme) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-ui-trajectory`](../packages/client/ui-trajectory) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-web`](../packages/client/web) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`client-web-react`](../packages/client/web-react) | `client` | [`invariants`](../packages/support/invariants) |
|
||||
| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | [`invariants`](../packages/support/invariants) |
|
||||
| [`jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | [`invariants`](../packages/support/invariants) |
|
||||
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`invariants`](../packages/support/invariants) |
|
||||
| [`host-runtime`](../packages/host/runtime) | `host` | [`invariants`](../packages/support/invariants) |
|
||||
| [`host-webserver`](../packages/host/webserver) | `host` | [`invariants`](../packages/support/invariants) |
|
||||
| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
|
||||
| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants) |
|
||||
| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
|
||||
| [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants) |
|
||||
| [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) |
|
||||
| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) |
|
||||
| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) |
|
||||
| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
|
||||
| [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
|
||||
| [`web`](../packages/web/web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
|
||||
| [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
|
||||
| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
|
||||
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`agent`](../packages/core/agent) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`compact`](../packages/compact/compact) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
|
||||
| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |
|
||||
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |
|
||||
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) |
|
||||
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
|
||||
| [`session-title`](../packages/session-title/session-title) | `session-title` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`llm-replay`](../packages/support/llm-replay) | `support` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`lsp-local`](../packages/lsp/lsp-local) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`timeout`](../packages/util/timeout) |
|
||||
| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
|
||||
| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
|
||||
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session) |
|
||||
| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout) |
|
||||
| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) |
|
||||
| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) |
|
||||
| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`skill`](../packages/skill/skill) |
|
||||
| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/support/invariants), [`spill`](../packages/spill/spill) |
|
||||
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
|
||||
| [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-title`](../packages/session-title/session-title) |
|
||||
| [`session-title-llm`](../packages/session-title/session-title-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`timeout`](../packages/util/timeout) |
|
||||
| [`commands`](../packages/ui/commands) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope) |
|
||||
| [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
|
||||
| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
|
||||
| [`pty`](../packages/pty/pty) | `pty` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
|
||||
| [`tasks`](../packages/tasks/tasks) | `tasks` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
|
||||
| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/ui/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) |
|
||||
| [`goal-session`](../packages/goal/goal-session) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||
| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||
| [`session-title-all-messages-llm`](../packages/session-title/session-title-all-messages-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`session-title-llm`](../packages/session-title/session-title-llm) |
|
||||
| [`session-title-first-message-llm`](../packages/session-title/session-title-first-message-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`session-title-llm`](../packages/session-title/session-title-llm) |
|
||||
| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`pty-local`](../packages/pty/pty-local) | `pty` | [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
|
||||
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`tool-web`](../packages/web/tool-web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) |
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) |
|
||||
| [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`tool-cordis`](../packages/cordis/tool-cordis) | `cordis` | [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) |
|
||||
| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) |
|
||||
| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) |
|
||||
| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) |
|
||||
| [`tool-pty`](../packages/pty/tool-pty) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`pty`](../packages/pty/pty), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) |
|
||||
| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
| [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-title`](../packages/session-title/session-title), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
|
||||
| [`tui`](../packages/ui/tui) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`system-prompt`](../packages/core/system-prompt), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`paths`](../packages/util/paths), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tool-bash`](../packages/bash/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
|
||||
| [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
|
||||
| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/ui/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`command-goal`](../packages/goal/command-goal), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`cli-demo`](../packages/examples/cli-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| [`tui-demo`](../packages/examples/tui-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`command-goal`](../packages/goal/command-goal), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) |
|
||||
|
||||
@@ -79,7 +79,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}[T]
|
||||
```
|
||||
|
||||
Sources: [`packages/core/session/src/types.ts:255`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:262`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:292`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:324`](../packages/core/session/src/types.ts)
|
||||
Sources: [`packages/core/session/src/types.ts:276`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:289`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:319`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:351`](../packages/core/session/src/types.ts)
|
||||
|
||||
## Events
|
||||
|
||||
@@ -106,7 +106,7 @@ Sources: [`packages/core/session/src/types.ts:255`](../packages/core/session/src
|
||||
|
||||
Types: [CallId](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/ui/user-approval/src/index.ts:45`](../packages/ui/user-approval/src/index.ts)
|
||||
Source: [`packages/ui/user-approval/src/index.ts:44`](../packages/ui/user-approval/src/index.ts)
|
||||
|
||||
#### `approval/decided` — log-only
|
||||
|
||||
@@ -122,7 +122,7 @@ Source: [`packages/ui/user-approval/src/index.ts:45`](../packages/ui/user-approv
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/ui/user-approval/src/index.ts:56`](../packages/ui/user-approval/src/index.ts)
|
||||
Source: [`packages/ui/user-approval/src/index.ts:55`](../packages/ui/user-approval/src/index.ts)
|
||||
|
||||
#### `approval/policy` — log-only
|
||||
|
||||
@@ -138,7 +138,7 @@ Source: [`packages/ui/user-approval/src/index.ts:56`](../packages/ui/user-approv
|
||||
'approval/policy': { policy: ApprovalPolicy }
|
||||
```
|
||||
|
||||
Source: [`packages/ui/user-approval/src/index.ts:68`](../packages/ui/user-approval/src/index.ts)
|
||||
Source: [`packages/ui/user-approval/src/index.ts:67`](../packages/ui/user-approval/src/index.ts)
|
||||
|
||||
### `assistant/*`
|
||||
|
||||
@@ -151,7 +151,7 @@ Source: [`packages/ui/user-approval/src/index.ts:68`](../packages/ui/user-approv
|
||||
|
||||
Types: [StreamChunk](core-data-structures/llm-streaming.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:219`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:232`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `assistant/message` — surface
|
||||
|
||||
@@ -167,7 +167,7 @@ Source: [`packages/core/session/src/types.ts:219`](../packages/core/session/src/
|
||||
|
||||
Types: [ContentBlock](core-data-structures/core.md) · [TokenUsage](core-data-structures/llm-streaming.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:226`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:239`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `compact/*`
|
||||
|
||||
@@ -246,7 +246,7 @@ Source: [`packages/compact/compact/src/types.ts:22`](../packages/compact/compact
|
||||
|
||||
Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:213`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:226`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `hook/*`
|
||||
|
||||
@@ -294,6 +294,24 @@ Source: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-
|
||||
|
||||
Source: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts)
|
||||
|
||||
### `llm/*`
|
||||
|
||||
#### `llm/retry` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Durable, non-surface record of one transient retry scheduled after a closed failed step. */
|
||||
'llm/retry': {
|
||||
turn: number
|
||||
step: number
|
||||
retry: number
|
||||
maxRetries: number
|
||||
delayMs: number
|
||||
failure: LlmFailure
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/llm/llm-retry/src/index.ts:18`](../packages/llm/llm-retry/src/index.ts)
|
||||
|
||||
### `permission/*`
|
||||
|
||||
#### `permission/preset` — log-only
|
||||
@@ -310,6 +328,21 @@ Source: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-
|
||||
|
||||
Source: [`packages/ui/permission/src/index.ts:36`](../packages/ui/permission/src/index.ts)
|
||||
|
||||
### `plan/*`
|
||||
|
||||
#### `plan/mode` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Whether plan mode is in force from this point on: log-only, non-surface,
|
||||
* whole-value replace. The last `plan/mode` wins; a log with none folds to
|
||||
* inactive through {@link foldPlanMode}.
|
||||
*/
|
||||
'plan/mode': { active: boolean }
|
||||
```
|
||||
|
||||
Source: [`packages/plan/plan-mode/src/index.ts:40`](../packages/plan/plan-mode/src/index.ts)
|
||||
|
||||
### `prompt/*`
|
||||
|
||||
#### `prompt/blocked` — log-only
|
||||
@@ -317,14 +350,14 @@ Source: [`packages/ui/permission/src/index.ts:36`](../packages/ui/permission/src
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Durable record of a prompt veto and its reason. It is log-only: the blocked
|
||||
* prompt never enters the model-visible surface, including in a mixed batch.
|
||||
* prompt never enters the model-visible surface, and its turn runs zero steps.
|
||||
*/
|
||||
'prompt/blocked': { content: ContentBlock[]; source: MessageSource; reason: string }
|
||||
```
|
||||
|
||||
Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:201`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:214`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `request/*`
|
||||
|
||||
@@ -338,7 +371,7 @@ Source: [`packages/core/session/src/types.ts:201`](../packages/core/session/src/
|
||||
'request/header': { header: EpochHeader; reason: RequestHeaderReason }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:251`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:264`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `sandbox/*`
|
||||
|
||||
@@ -358,6 +391,33 @@ Source: [`packages/core/session/src/types.ts:251`](../packages/core/session/src/
|
||||
|
||||
Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:34`](../packages/sandbox/sandbox-policy/src/session-mode.ts)
|
||||
|
||||
### `session/*`
|
||||
|
||||
#### `session/title` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Latest-wins session title snapshot. Log-only: it never enters the model
|
||||
* surface or derived history.
|
||||
*/
|
||||
'session/title': SessionTitleEventData
|
||||
```
|
||||
|
||||
Types: [SessionTitleEventData](core-data-structures/session-title.md)
|
||||
|
||||
Source: [`packages/session-title/session-title/src/index.ts:95`](../packages/session-title/session-title/src/index.ts)
|
||||
|
||||
#### `session/title-llm-request` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Log-only pre-dispatch record of one session-title model request. */
|
||||
'session/title-llm-request': SessionTitleLlmRequestEventData
|
||||
```
|
||||
|
||||
Types: [SessionTitleLlmRequestEventData](core-data-structures/session-title.md)
|
||||
|
||||
Source: [`packages/session-title/session-title-llm/src/index.ts:44`](../packages/session-title/session-title-llm/src/index.ts)
|
||||
|
||||
### `steering/*`
|
||||
|
||||
#### `steering/message` — surface
|
||||
@@ -369,7 +429,7 @@ Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:34`](../packages/s
|
||||
|
||||
Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:244`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:257`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `step/*`
|
||||
|
||||
@@ -380,7 +440,7 @@ Source: [`packages/core/session/src/types.ts:244`](../packages/core/session/src/
|
||||
'step/end': { turn: number; step: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:194`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:207`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `step/start` — log-only
|
||||
|
||||
@@ -389,7 +449,7 @@ Source: [`packages/core/session/src/types.ts:194`](../packages/core/session/src/
|
||||
'step/start': { turn: number; step: number }
|
||||
```
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:192`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:205`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `todo/*`
|
||||
|
||||
@@ -402,7 +462,7 @@ Source: [`packages/core/session/src/types.ts:192`](../packages/core/session/src/
|
||||
|
||||
Types: [TodoItem](core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:246`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:259`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `tool/*`
|
||||
|
||||
@@ -419,7 +479,7 @@ Source: [`packages/core/session/src/types.ts:246`](../packages/core/session/src/
|
||||
|
||||
Types: [CallId](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:232`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:245`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `tool/code-dispatch` — log-only
|
||||
|
||||
@@ -463,7 +523,7 @@ Source: [`packages/core/tools/src/code-mode.ts:34`](../packages/core/tools/src/c
|
||||
|
||||
Types: [CallId](core-data-structures/core.md) · [ContentBlock](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:242`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:255`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `turn/*`
|
||||
|
||||
@@ -472,22 +532,23 @@ Source: [`packages/core/session/src/types.ts:242`](../packages/core/session/src/
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Closes turn `turn` with the {@link TurnEndReason} that ended it. The loop
|
||||
* fires the awaited `session/flush` checkpoint at every turn end, so the turn
|
||||
* boundary is also the durable-commit boundary.
|
||||
* awaits `session/flush` after an ordinary turn ends before claiming the next
|
||||
* queued item. Success commits the turn; rejection is reported live and does
|
||||
* not prevent later work.
|
||||
*/
|
||||
'turn/end': { turn: number; reason: TurnEndReason }
|
||||
```
|
||||
|
||||
Types: [TurnEndReason](core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:190`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:203`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `turn/start` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/**
|
||||
* Opens turn `turn`. `trigger` records what started it — a drained message
|
||||
* batch or an idle-time injection. The turn is the durability/replay
|
||||
* Opens turn `turn`. `trigger` records what started it — one claimed queued
|
||||
* message or an idle-time injection. The turn is the durability/replay
|
||||
* boundary: every event sits between a `turn/start` and its matching
|
||||
* `turn/end` (the turn-enclosure invariant).
|
||||
*/
|
||||
@@ -496,17 +557,17 @@ Source: [`packages/core/session/src/types.ts:190`](../packages/core/session/src/
|
||||
|
||||
Types: [TurnTrigger](core-data-structures/session.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:184`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:196`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `user/*`
|
||||
|
||||
#### `user/message` — surface
|
||||
|
||||
```ts persistence-catalog
|
||||
/** A user-visible prompt (queued message drained at turn start). */
|
||||
/** A user-visible prompt (the queued message claimed for this turn). */
|
||||
'user/message': { content: ContentBlock[]; source: MessageSource }
|
||||
```
|
||||
|
||||
Types: [ContentBlock](core-data-structures/core.md) · [MessageSource](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:196`](../packages/core/session/src/types.ts)
|
||||
Source: [`packages/core/session/src/types.ts:209`](../packages/core/session/src/types.ts)
|
||||
|
||||
@@ -24,7 +24,7 @@ The ACP server could not create or load a single session — the two RPCs an edi
|
||||
|
||||
## Root cause #1 — `export default apply` drops the plugin's `inject` (broke `session/new`)
|
||||
|
||||
`packages/ui/acp/src/index.ts` is a *namespace plugin*: it exports `name`, `inject`, `Config`, and `apply` as separate named exports — the same shape as every other plugin in the repo (`invariants`, `llm-deepseek`, `tool-bash`, `stdio-chat`, …). But it *also* ended with one extra line no other plugin had:
|
||||
`packages/ui/acp/src/index.ts` is a *namespace plugin*: it exports `name`, `inject`, `Config`, and `apply` as separate named exports — the same shape as every other plugin in the repo (`invariants`, `llm-deepseek`, `tool-bash`, `tui`, …). But it *also* ended with one extra line no other plugin had:
|
||||
|
||||
```ts ignore-check
|
||||
export const name = 'acp'
|
||||
|
||||
@@ -11,12 +11,14 @@ How this repo tests, tier by tier, and the rules that keep a green suite meaning
|
||||
|
||||
## The with-key policy: inference is cheap here
|
||||
|
||||
We are DeepSeek — do not ration real-API tests. A no-key test proves plumbing; only a with-key run proves the agent works against a real model. Write many: file-writing prompts, multi-turn conversations, tool use, cancellation mid-stream. Highest-value are **smoke tests** that boot the real example, send one real prompt, and check the world — they catch the "green unit tests, broken product" class that mocks structurally cannot ([postmortem 0001](postmortem/0001-acp-default-export-drops-inject.md)). The self-skip exists only so secretless CI and keyless contributors aren't blocked; it is not a cost signal. Every example ships a keyless smoke and — unless keyless-by-nature — a with-key smoke ([examples/AGENTS.md](../examples/AGENTS.md)).
|
||||
We are DeepSeek — do not ration real-API tests. A no-key test proves plumbing; only a with-key run proves the agent works against a real model. Write many: file-writing prompts, multi-turn conversations, tool use, cancellation mid-stream. Highest-value are **smoke tests** that boot the real example, send one real prompt, and check the world — they catch the "green unit tests, broken product" class that mocks structurally cannot ([postmortem 0001](postmortem/0001-acp-default-export-drops-inject.md)). The self-skip exists only so secretless CI and keyless contributors aren't blocked; it is not a cost signal. Every example ships both a keyless smoke and a with-key smoke ([examples/AGENTS.md](../examples/AGENTS.md)).
|
||||
|
||||
## Prefer the real implementation over a mock
|
||||
|
||||
Mock only the genuinely expensive or non-deterministic boundary (the LLM adapter, the network, the clock); keep everything downstream real. A hand-rolled stand-in proves the bridge moves bytes, not that the shipping tool behaves as asserted — the two drift while the test stays green. Example: bridge tool-call tests run the scripted mock MODEL but the real tool + real executor (`makeBridgeHarness({ withBash: true })` plugs `dsh-bash-local` + `dsh-tool-bash` and runs an actual `echo`).
|
||||
|
||||
Recovery tests separate pre/post-chunk failures by step and prove failed chunks derive no message or tool side effect. Cover exhaustion, cancellation, policy composition, persistence, status, wire counts, transport-closing idle timeouts, and shipping Loader composition.
|
||||
|
||||
## Verify the world, not the self-report
|
||||
|
||||
An e2e assertion re-runs the command or re-reads the file externally; a keyword probe on the agent's own output lets a cheating agent pass. Assert untouched files are byte-identical. e2e tests own their resources: create the harness in the test, dispose in `afterEach` (even on failure/retry/timeout); shared fixtures live in a plain `tests/harness.ts`, never another `*.e2e.ts` (importing a spec re-registers its `describe` and duplicates real API calls).
|
||||
@@ -35,4 +37,4 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword
|
||||
|
||||
## When a snapshot test is required
|
||||
|
||||
Any change affecting an editor-facing transcript, headless event stream, or end-to-end agent UX adds or updates a scenario in the owning snapshot suite, or states in the PR why none applies. ACP surfaces use `examples/<name>/tests/snapshots/`, a scenario table over the [`dsh-acp-snapshot`](../packages/support/acp-snapshot/README.md) suite factory (`examples/acp-agent` is primary); `examples/headless-agent` owns the `stream-json` snapshot and replay fixtures. Completed interactive-terminal journeys use JSONL-driven scenarios under `examples/tui-agent/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. New capability seams, lifecycle shapes, or transcript surfaces name their coverage at every tier at plan time and verify the harness can express it — a harness gap is scheduled work, not a mid-build surprise.
|
||||
Every non-trivial model- or human-visible change adds or updates a keyless scenario in the same PR through a runnable example's owning snapshot suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP surfaces use `examples/<name>/tests/snapshots/`, a scenario table over the [`dsh-acp-snapshot`](../packages/support/acp-snapshot/README.md) suite factory (`examples/acp-agent` is primary); `examples/headless-agent` owns the `stream-json` snapshot and replay fixtures. Completed interactive-terminal journeys use JSONL-driven scenarios under `examples/tui-agent/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. New capability seams, lifecycle shapes, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.
|
||||
|
||||
@@ -17,14 +17,18 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`, `ctx.userInteraction` | `tool/call`, `tool/result after a UI/provider answers the question` | - | ask_user_question pauses the tool call until the active UI provider returns a human answer. |
|
||||
| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`, `ctx.codeRuntime (execution time)`, `ctx.systemPrompt` | `tool/call`, `one tool/code-dispatch per bridged sub-call`, `tool/result` | - | Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry's only wire contribution; the other visible capabilities are declared in a generated TypeScript SDK section, and a program calls them through serialized bindings that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. |
|
||||
| `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.userInteraction (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-interaction seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. |
|
||||
| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`, `ctx.bash`, `ctx.tasks at call time for run_in_background` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.tasks` runtime and is collected/stopped through the `task_*` tools from `@deepseek-ai/dsh-tool-tasks`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled. |
|
||||
| `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect`, `cordis_mount`, `cordis_unmount` | `ctx.tools` | `tool/call`, `tool/result`, `live plugin-tree mutations (mount/unmount)` | - | Ships in examples/cordis-agent only (a deliberate opt-in — mounted code gets the real ctx, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins the model mounts may register ADDITIONAL model-visible tools at runtime; a full changed request header logs those tool-set changes. |
|
||||
| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after successful file operations`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin. |
|
||||
| `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.tools`, `ctx.bash`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are conditional bash-backed discovery tools: they register only when ctx.bash can find `rg`, then run fixed ripgrep commands through ctx.bash as ordinary foreground calls (never background tasks). Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. |
|
||||
| `@deepseek-ai/dsh-tool-pty` | `terminal_close`, `terminal_list`, `terminal_open`, `terminal_read`, `terminal_send`, `terminal_signal` | `ctx.tools`, `ctx.pty`, `ctx.systemPrompt`, `ctx.tasks at call time for run_in_background` | `tool/call`, `tool/result` | - | The six terminal tools are opt-in and complement one-shot bash/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.tasks`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. |
|
||||
| `@deepseek-ai/dsh-tool-goal` | `create_goal`, `get_goal`, `update_goal` | `ctx.tools`, `ctx.agents`, `ctx.goals`, `ctx.systemPrompt`, `a calling Agent in an authorized open turn` | `tool/call`, `context/message goal snapshot for mutations`, `tool/result` | - | create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds. |
|
||||
| `@deepseek-ai/dsh-tool-lsp` | `lsp` | `ctx.tools`, `ctx.lsp`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-local`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema. |
|
||||
| `@deepseek-ai/dsh-tool-ralph` | `ralph` | `ctx.tools`, `ctx.workflows`, `ctx.subagents`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents every fresh round)` | `tool/call`, `tool/result`, `workflow and child session events during execution` | - | A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap. |
|
||||
| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`, `ctx.skills` | `tool/call`, `tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml`. |
|
||||
| `@deepseek-ai/dsh-tool-tasks` | `task_kill`, `task_list`, `task_output` | `ctx.tools`, `ctx.tasks`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `context/message via agent.inject() for background completion notices` | - | The kind-agnostic background-task control surface: a background bash command and a background subagent are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' `ctx.tasks.start()`. |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/tui-agent/cordis.yml` and `examples/acp-agent/cordis.yml`. |
|
||||
| `@deepseek-ai/dsh-tool-tasks` | `task_kill`, `task_list`, `task_output` | `ctx.tools`, `ctx.tasks`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `context/message via agent.inject() for background completion notices` | - | The kind-agnostic background-task control surface: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' `ctx.tasks.start()`. |
|
||||
| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan. |
|
||||
| `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflows`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. |
|
||||
@@ -124,6 +128,31 @@ Source: [`packages/core/tools/src/code-mode.ts`](../packages/core/tools/src/code
|
||||
|
||||
Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry's only wire contribution; the other visible capabilities are declared in a generated TypeScript SDK section, and a program calls them through serialized bindings that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.
|
||||
|
||||
## `@deepseek-ai/dsh-plan-mode`
|
||||
|
||||
### `exit_plan_mode`
|
||||
|
||||
Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plan": {
|
||||
"type": "string",
|
||||
"description": "The complete plan, as markdown, starting with a # heading that names it."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"plan"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/plan/plan-mode/src/index.ts`](../packages/plan/plan-mode/src/index.ts)
|
||||
|
||||
exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-interaction seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-bash`
|
||||
|
||||
### `bash`
|
||||
@@ -557,6 +586,173 @@ Source: [`packages/pty/tool-pty/src/index.ts`](../packages/pty/tool-pty/src/inde
|
||||
|
||||
The six terminal tools are opt-in and complement one-shot bash/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.tasks`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-goal`
|
||||
|
||||
### `create_goal`
|
||||
|
||||
Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say "create a goal". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"objective": {
|
||||
"type": "string",
|
||||
"description": "The concrete completion objective inferred from the direct human request."
|
||||
},
|
||||
"max_goal_rounds": {
|
||||
"type": "number",
|
||||
"description": "Optional positive safe-integer limit on automatic continuation rounds."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"objective"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/goal/tool-goal/src/index.ts`](../packages/goal/tool-goal/src/index.ts)
|
||||
|
||||
### `get_goal`
|
||||
|
||||
Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/goal/tool-goal/src/index.ts`](../packages/goal/tool-goal/src/index.ts)
|
||||
|
||||
### `update_goal`
|
||||
|
||||
Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"goal_id": {
|
||||
"type": "string",
|
||||
"description": "Exact id returned by get_goal."
|
||||
},
|
||||
"revision": {
|
||||
"type": "number",
|
||||
"description": "Exact positive revision returned by get_goal."
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "edit | pause | resume | complete | blocked",
|
||||
"enum": [
|
||||
"edit",
|
||||
"pause",
|
||||
"resume",
|
||||
"complete",
|
||||
"blocked"
|
||||
]
|
||||
},
|
||||
"objective": {
|
||||
"type": "string",
|
||||
"description": "Replacement objective; valid only with action edit."
|
||||
},
|
||||
"max_goal_rounds": {
|
||||
"type": "number",
|
||||
"description": "Replacement cap; valid only with action edit."
|
||||
},
|
||||
"blocked_reason": {
|
||||
"type": "string",
|
||||
"description": "Concrete blocking condition; required only with action blocked."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"goal_id",
|
||||
"revision",
|
||||
"action"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/goal/tool-goal/src/index.ts`](../packages/goal/tool-goal/src/index.ts)
|
||||
|
||||
create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-lsp`
|
||||
|
||||
### `lsp`
|
||||
|
||||
Query a language server for precise code navigation. operation is one of goToDefinition, findReferences, goToImplementation, hover. line and character are one-based UTF-16 cursor coordinates. findReferences includes the declaration.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"description": "goToDefinition, findReferences, goToImplementation, or hover.",
|
||||
"enum": [
|
||||
"goToDefinition",
|
||||
"findReferences",
|
||||
"goToImplementation",
|
||||
"hover"
|
||||
]
|
||||
},
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "The source file to query, relative to the workspace or absolute."
|
||||
},
|
||||
"line": {
|
||||
"type": "number",
|
||||
"description": "One-based line of the cursor."
|
||||
},
|
||||
"character": {
|
||||
"type": "number",
|
||||
"description": "One-based UTF-16 column of the cursor."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"operation",
|
||||
"file_path",
|
||||
"line",
|
||||
"character"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/lsp/tool-lsp/src/index.ts`](../packages/lsp/tool-lsp/src/index.ts)
|
||||
|
||||
The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-local`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-ralph`
|
||||
|
||||
### `ralph`
|
||||
|
||||
Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"objective": {
|
||||
"type": "string",
|
||||
"description": "The immutable completion objective for every fresh Ralph round."
|
||||
},
|
||||
"maxRounds": {
|
||||
"type": "number",
|
||||
"description": "Optional positive safe-integer round cap, bounded by the deployment ceiling."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"objective"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/workflow/tool-ralph/src/index.ts`](../packages/workflow/tool-ralph/src/index.ts)
|
||||
|
||||
A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-skill`
|
||||
|
||||
### `skill`
|
||||
@@ -612,7 +808,7 @@ Delegate a self-contained task to a subagent (a separate agent that works in its
|
||||
|
||||
Source: [`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/tool-subagent/src/index.ts)
|
||||
|
||||
The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml`.
|
||||
The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/tui-agent/cordis.yml` and `examples/acp-agent/cordis.yml`.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-tasks`
|
||||
|
||||
@@ -683,7 +879,7 @@ Read a background task. Stream tasks return only output since the previous read;
|
||||
|
||||
Source: [`packages/tasks/tool-tasks/src/index.ts`](../packages/tasks/tool-tasks/src/index.ts)
|
||||
|
||||
The kind-agnostic background-task control surface: a background bash command and a background subagent are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' `ctx.tasks.start()`.
|
||||
The kind-agnostic background-task control surface: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' `ctx.tasks.start()`.
|
||||
|
||||
## `@deepseek-ai/dsh-tool-todo`
|
||||
|
||||
|
||||
6
docs/user/develop/basic/config.i18n.yaml
Normal file
6
docs/user/develop/basic/config.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
config.md: 26d2d48ebede74194fbf306aa97d214bdb99b722
|
||||
config.zh.md: 9ed389b16779f25c633d0c8772f8658197ba4322
|
||||
118
docs/user/develop/basic/config.md
Normal file
118
docs/user/develop/basic/config.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Plugin configuration
|
||||
|
||||
English | [中文](config.zh.md)
|
||||
|
||||
Accept configuration supplied through `cordis.yml`.
|
||||
|
||||
## Define the Config type
|
||||
|
||||
Export a `Config` type and a same-named Schemastery schema. Put defaults directly on the schema fields:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import Schema from 'schemastery'
|
||||
|
||||
export const name = 'my-plugin'
|
||||
|
||||
export interface Config {
|
||||
greeting: string
|
||||
maxRetries: number
|
||||
verbose?: boolean
|
||||
}
|
||||
|
||||
export const Config: Schema<Config> = Schema.object({
|
||||
greeting: Schema.string().default('Hello'),
|
||||
maxRetries: Schema.number().default(3),
|
||||
verbose: Schema.boolean().default(false),
|
||||
})
|
||||
|
||||
export function apply(ctx: Context, config: Config) {
|
||||
console.log(config.greeting) // User value or schema default.
|
||||
}
|
||||
```
|
||||
|
||||
Configure it in `cordis.yml`:
|
||||
|
||||
```yaml
|
||||
- name: './src/my-plugin.ts'
|
||||
config:
|
||||
greeting: 'Hi there'
|
||||
maxRetries: 5
|
||||
```
|
||||
|
||||
When loading the plugin, Cordis uses the exported schema to validate configuration and fill defaults. Do not export a plain object as `Config`; it does not implement the Standard Schema interface required by Cordis.
|
||||
|
||||
## Schema validation
|
||||
|
||||
Use Schemastery to express stricter validation:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import Schema from 'schemastery'
|
||||
|
||||
export const name = 'validated-plugin'
|
||||
|
||||
export interface Config {
|
||||
apiKey: string
|
||||
timeout: number
|
||||
mode: 'fast' | 'accurate'
|
||||
}
|
||||
|
||||
export const Config = Schema.object({
|
||||
apiKey: Schema.string().required(),
|
||||
timeout: Schema.number().default(30000),
|
||||
mode: Schema.union(['fast', 'accurate']).default('fast'),
|
||||
})
|
||||
|
||||
export function apply(ctx: Context, config: Config) {
|
||||
// config is validated and type-safe.
|
||||
}
|
||||
```
|
||||
|
||||
The schema runs while the plugin loads. Invalid configuration fails the load with an actionable error.
|
||||
|
||||
## Design principles
|
||||
|
||||
### Do not hardcode tunable values
|
||||
|
||||
Harness requires **anything that two deployments may want to set differently to be a configuration field**.
|
||||
|
||||
```ts
|
||||
// Wrong: hardcoded timeout.
|
||||
const TIMEOUT = 30000
|
||||
|
||||
// Correct: configurable.
|
||||
export interface Config {
|
||||
timeoutMs: number // Defaults to 30000.
|
||||
}
|
||||
```
|
||||
|
||||
The test is whether `cordis.yml` can change the value without a code edit.
|
||||
|
||||
### Fail loudly on invalid configuration
|
||||
|
||||
If configuration refers to an unregistered LLM provider route or another nonexistent resource, fail early instead of silently skipping it:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import type {} from '@deepseek-ai/dsh-llm'
|
||||
|
||||
export interface ModelConfig {
|
||||
provider: string
|
||||
}
|
||||
|
||||
export function apply(ctx: Context, config: ModelConfig) {
|
||||
if (!ctx.llm.listProviders().some(provider => provider.id === config.provider)) {
|
||||
throw new Error(`LLM provider "${config.provider}" is not registered`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Work with HMR
|
||||
|
||||
A configuration edit hot-replaces the plugin: the framework unloads the old instance and loads a new one. Because registrations are effects and clean themselves up, replacement does not retain the old instance's registrations.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Plugins and lifecycle](../framework/) — understand the full plugin lifecycle
|
||||
- [Services and dependencies](../framework/service.md) — provide a service to other plugins
|
||||
118
docs/user/develop/basic/config.zh.md
Normal file
118
docs/user/develop/basic/config.zh.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# 插件配置
|
||||
|
||||
[English](config.md) | 中文
|
||||
|
||||
让你的插件接受用户在 `cordis.yml` 中传入的配置。
|
||||
|
||||
## 定义 Config 类型
|
||||
|
||||
在插件中导出一个 `Config` 类型和同名的 Schemastery schema;默认值直接写在 schema 中:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import Schema from 'schemastery'
|
||||
|
||||
export const name = 'my-plugin'
|
||||
|
||||
export interface Config {
|
||||
greeting: string
|
||||
maxRetries: number
|
||||
verbose?: boolean
|
||||
}
|
||||
|
||||
export const Config: Schema<Config> = Schema.object({
|
||||
greeting: Schema.string().default('Hello'),
|
||||
maxRetries: Schema.number().default(3),
|
||||
verbose: Schema.boolean().default(false),
|
||||
})
|
||||
|
||||
export function apply(ctx: Context, config: Config) {
|
||||
console.log(config.greeting) // User value or schema default.
|
||||
}
|
||||
```
|
||||
|
||||
用户在 `cordis.yml` 中这样使用:
|
||||
|
||||
```yaml
|
||||
- name: './src/my-plugin.ts'
|
||||
config:
|
||||
greeting: 'Hi there'
|
||||
maxRetries: 5
|
||||
```
|
||||
|
||||
插件加载时,Cordis 会通过导出的 schema 校验配置,并填充未提供字段的默认值。不要导出普通对象作为 `Config`,因为它不满足 Cordis 要求的 Standard Schema 接口。
|
||||
|
||||
## Schema 校验
|
||||
|
||||
对于需要严格校验的场景,使用 Schemastery 定义 schema:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import Schema from 'schemastery'
|
||||
|
||||
export const name = 'validated-plugin'
|
||||
|
||||
export interface Config {
|
||||
apiKey: string
|
||||
timeout: number
|
||||
mode: 'fast' | 'accurate'
|
||||
}
|
||||
|
||||
export const Config = Schema.object({
|
||||
apiKey: Schema.string().required(),
|
||||
timeout: Schema.number().default(30000),
|
||||
mode: Schema.union(['fast', 'accurate']).default('fast'),
|
||||
})
|
||||
|
||||
export function apply(ctx: Context, config: Config) {
|
||||
// config is validated and type-safe.
|
||||
}
|
||||
```
|
||||
|
||||
Schema 在插件加载时执行校验。如果配置不合法,插件会加载失败并给出明确错误信息。
|
||||
|
||||
## 设计原则
|
||||
|
||||
### 无硬编码可调参数
|
||||
|
||||
Harness 的约定:**任何两个部署可能想要不同值的东西,都应该是配置字段**。
|
||||
|
||||
```ts
|
||||
// Wrong: hardcoded timeout.
|
||||
const TIMEOUT = 30000
|
||||
|
||||
// Correct: configurable.
|
||||
export interface Config {
|
||||
timeoutMs: number // Defaults to 30000.
|
||||
}
|
||||
```
|
||||
|
||||
检验标准:能否在 `cordis.yml` 中改变这个值,而不需要修改代码?
|
||||
|
||||
### 配置错误要响亮
|
||||
|
||||
如果配置引用了未注册的 LLM 提供方路由或其他不存在的资源,应该尽早报错,而不是静默跳过:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import type {} from '@deepseek-ai/dsh-llm'
|
||||
|
||||
export interface ModelConfig {
|
||||
provider: string
|
||||
}
|
||||
|
||||
export function apply(ctx: Context, config: ModelConfig) {
|
||||
if (!ctx.llm.listProviders().some(provider => provider.id === config.provider)) {
|
||||
throw new Error(`LLM provider "${config.provider}" is not registered`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 配合 HMR
|
||||
|
||||
配置变更会触发插件热替换:修改 `cordis.yml` 中某个插件的 `config`,框架会卸载旧实例、加载新实例。由于注册都是效果(自动清理),这个过程是安全的。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [插件与生命周期](../framework/) — 深入了解插件的完整生命周期
|
||||
- [服务与依赖](../framework/service.md) — 让你的插件对外提供服务
|
||||
6
docs/user/develop/basic/index.i18n.yaml
Normal file
6
docs/user/develop/basic/index.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
index.md: d7d657ff7b8cb9001dd5e9c3af658a7a3c45b5b7
|
||||
index.zh.md: 7a134f7aaed470b87ee8ca8978dd39593de2651b
|
||||
151
docs/user/develop/basic/index.md
Normal file
151
docs/user/develop/basic/index.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Your first plugin
|
||||
|
||||
English | [中文](index.zh.md)
|
||||
|
||||
This guide creates a minimal Harness plugin and loads it into an agent.
|
||||
|
||||
## What is a plugin?
|
||||
|
||||
In Harness, a plugin is a TypeScript module that exports an `apply` function. The framework calls `apply` when loading the plugin and passes a `ctx` context object through which the plugin registers capabilities:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export const name = 'my-plugin'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// Register capabilities here.
|
||||
}
|
||||
```
|
||||
|
||||
That is the complete shape.
|
||||
|
||||
## Create the plugin file
|
||||
|
||||
Create `src/my-plugin.ts` in your project:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export const name = 'hello-plugin'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// Required dependencies are ready before apply runs.
|
||||
console.log('[hello-plugin] plugin loaded!')
|
||||
}
|
||||
```
|
||||
|
||||
## Register it in cordis.yml
|
||||
|
||||
Add an entry to `cordis.yml`:
|
||||
|
||||
```yaml
|
||||
- id: hello
|
||||
name: './src/my-plugin.ts'
|
||||
```
|
||||
|
||||
After startup, the console prints `[hello-plugin] plugin loaded!`.
|
||||
|
||||
## Automatic cleanup
|
||||
|
||||
Anything registered through `ctx`—event listeners, tools, or timers—is cleaned up when the plugin unloads. You do not need to call removeListener or clearInterval manually.
|
||||
|
||||
For a resource that needs explicit cleanup, such as a network connection, use `ctx.effect()` to provide its disposer:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.effect(() => {
|
||||
const timer = setInterval(() => {
|
||||
console.log('heartbeat')
|
||||
}, 5000)
|
||||
|
||||
// The returned function runs when the plugin unloads.
|
||||
return () => clearInterval(timer)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Declare dependencies
|
||||
|
||||
If the plugin consumes another service such as `tools` or `llm`, declare it in `inject`:
|
||||
|
||||
```ts ignore-check
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export const name = 'my-tool-plugin'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// ctx.tools is ready here.
|
||||
ctx.tools.register(/* ... */)
|
||||
}
|
||||
```
|
||||
|
||||
The framework waits for every required service before loading the plugin.
|
||||
|
||||
## Three plugin forms
|
||||
|
||||
In addition to a function module, a plugin can use object or class form.
|
||||
|
||||
### Object form
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export default {
|
||||
name: 'my-plugin',
|
||||
inject: ['tools'],
|
||||
apply(ctx: Context) {
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Class form
|
||||
|
||||
```ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
export default class MyService extends Service {
|
||||
static inject = ['tools']
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'myService')
|
||||
// Perform synchronous initialization in the constructor.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Function form is sufficient in most cases. Use class form when the plugin provides a service to other plugins; see [services and dependencies](../framework/service.md).
|
||||
|
||||
## Complete example
|
||||
|
||||
A minimal tool plugin registers its definition on `ctx.tools`:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'greet-tool'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'greet',
|
||||
description: 'Greet the named person.',
|
||||
parameters: {
|
||||
name: { type: 'string', required: true },
|
||||
},
|
||||
async execute(args) {
|
||||
return [{ type: 'text', text: `Hello, ${args.name}!` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Build a tool](./tool.md) — learn the tool definition DSL
|
||||
- [Plugin configuration](./config.md) — accept user configuration
|
||||
151
docs/user/develop/basic/index.zh.md
Normal file
151
docs/user/develop/basic/index.zh.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# 第一个插件
|
||||
|
||||
[English](index.md) | 中文
|
||||
|
||||
本文带你编写一个最小的 Harness 插件并加载到 Agent 中。
|
||||
|
||||
## 插件是什么
|
||||
|
||||
在 Harness 中,插件是一个导出 `apply` 函数的 TypeScript 模块。框架在加载时调用 `apply`,传入一个 `ctx`(上下文对象),你通过 `ctx` 注册能力:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export const name = 'my-plugin'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// Register capabilities here.
|
||||
}
|
||||
```
|
||||
|
||||
就这么简单。
|
||||
|
||||
## 创建插件文件
|
||||
|
||||
在你的项目目录下创建 `src/my-plugin.ts`:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export const name = 'hello-plugin'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// Required dependencies are ready before apply runs.
|
||||
console.log('[hello-plugin] plugin loaded!')
|
||||
}
|
||||
```
|
||||
|
||||
## 注册到 cordis.yml
|
||||
|
||||
在你的 `cordis.yml` 中添加一条:
|
||||
|
||||
```yaml
|
||||
- id: hello
|
||||
name: './src/my-plugin.ts'
|
||||
```
|
||||
|
||||
启动后你会在控制台看到 `[hello-plugin] plugin loaded!`。
|
||||
|
||||
## 自动清理
|
||||
|
||||
通过 `ctx` 注册的任何东西——事件监听、tool、定时器——在插件卸载时都会被自动清理。你不需要手动 removeListener 或 clearInterval。
|
||||
|
||||
如果你有需要手动清理的资源(比如一个网络连接),用 `ctx.effect()` 告诉框架怎么清理:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.effect(() => {
|
||||
const timer = setInterval(() => {
|
||||
console.log('heartbeat')
|
||||
}, 5000)
|
||||
|
||||
// The returned function runs when the plugin unloads.
|
||||
return () => clearInterval(timer)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 声明依赖
|
||||
|
||||
如果你的插件需要使用其他服务(如 `tools`、`llm`),需要声明 `inject`:
|
||||
|
||||
```ts ignore-check
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export const name = 'my-tool-plugin'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// ctx.tools is ready here.
|
||||
ctx.tools.register(/* ... */)
|
||||
}
|
||||
```
|
||||
|
||||
框架会确保依赖的服务就绪后才加载你的插件。
|
||||
|
||||
## 插件的三种形态
|
||||
|
||||
除了函数形式,插件还支持对象形式和类形式:
|
||||
|
||||
### 对象形式
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
export default {
|
||||
name: 'my-plugin',
|
||||
inject: ['tools'],
|
||||
apply(ctx: Context) {
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 类形式
|
||||
|
||||
```ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
export default class MyService extends Service {
|
||||
static inject = ['tools']
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'myService')
|
||||
// Perform synchronous initialization in the constructor.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
大多数情况下,函数形式足够了。类形式用于需要对外提供服务的插件(见 [服务与依赖](../framework/service.md))。
|
||||
|
||||
## 完整示例
|
||||
|
||||
最小化的工具插件会在 `ctx.tools` 上注册其定义:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'greet-tool'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'greet',
|
||||
description: 'Greet the named person.',
|
||||
parameters: {
|
||||
name: { type: 'string', required: true },
|
||||
},
|
||||
async execute(args) {
|
||||
return [{ type: 'text', text: `Hello, ${args.name}!` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [开发一个 Tool](./tool.md) — 详细了解 tool 定义 DSL
|
||||
- [插件配置](./config.md) — 让插件接受用户配置
|
||||
6
docs/user/develop/basic/tool.i18n.yaml
Normal file
6
docs/user/develop/basic/tool.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
tool.md: 416733bcb584fa5303a8b3ba5e6e904302e7f992
|
||||
tool.zh.md: fce9a7d9b973853c8b4fb9ae2c034e749d8da999
|
||||
208
docs/user/develop/basic/tool.md
Normal file
208
docs/user/develop/basic/tool.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Build a tool
|
||||
|
||||
English | [中文](tool.zh.md)
|
||||
|
||||
A tool is a capability the model can call. This guide builds one with `defineTool`.
|
||||
|
||||
## Minimal example
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'my-tool'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'greet',
|
||||
description: 'Greet someone by name.',
|
||||
parameters: {
|
||||
name: { type: 'string', required: true, description: 'The name to greet' },
|
||||
},
|
||||
async execute(args) {
|
||||
// args is inferred as { name: string }.
|
||||
return [{ type: 'text', text: `Hello, ${args.name}!` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## Parameter definitions
|
||||
|
||||
`parameters` uses a compact format that the framework converts to the JSON Schema sent to the model.
|
||||
|
||||
### Primitive types
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
path: { type: 'string', required: true },
|
||||
limit: { type: 'number' },
|
||||
recursive: { type: 'boolean' },
|
||||
}
|
||||
// Inferred type: { path: string; limit?: number; recursive?: boolean }
|
||||
```
|
||||
|
||||
### Enums
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
mode: { type: 'string', required: true, enum: ['read', 'write', 'append'] },
|
||||
}
|
||||
// Inferred type: { mode: string } (enum values are validated at runtime)
|
||||
```
|
||||
|
||||
### Nested objects
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
timeout: { type: 'number' },
|
||||
retries: { type: 'number' },
|
||||
},
|
||||
},
|
||||
}
|
||||
// Inferred type: { options?: { timeout?: number; retries?: number } }
|
||||
```
|
||||
|
||||
### Arrays
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
}
|
||||
// Inferred type: { tags?: string[] }
|
||||
```
|
||||
|
||||
### Property fields
|
||||
|
||||
| Field | Type | Meaning |
|
||||
|------|------|------|
|
||||
| `type` | `'string' \| 'number' \| 'boolean' \| 'object' \| 'array'` | Value type |
|
||||
| `required` | `true` | Marks the property required and affects inference |
|
||||
| `description` | `string` | Description sent to the model |
|
||||
| `enum` | `string[]` | Allowed string values |
|
||||
| `properties` | `SchemaSpec` | Nested properties for an object |
|
||||
| `items` | `SchemaProp` | Element schema for an array |
|
||||
|
||||
## The execute function
|
||||
|
||||
`execute` receives validated, inferred `args` and an `exec` execution context:
|
||||
|
||||
```ts
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'example',
|
||||
description: 'Return an example result.',
|
||||
parameters: {},
|
||||
async execute(args, exec) {
|
||||
// args: inferred from parameters
|
||||
// exec: ToolExecution context
|
||||
|
||||
// Return a ContentBlock array.
|
||||
void args
|
||||
void exec
|
||||
return [{ type: 'text', text: 'result here' }]
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Return value
|
||||
|
||||
`execute` returns a `ContentBlock[]` that becomes the tool result visible to the model:
|
||||
|
||||
```ts ignore-check
|
||||
// Text result
|
||||
return [{ type: 'text', text: 'file content here...' }]
|
||||
|
||||
// Multiple blocks
|
||||
return [
|
||||
{ type: 'text', text: 'Found 3 matches:' },
|
||||
{ type: 'text', text: matchResults.join('\n') },
|
||||
]
|
||||
```
|
||||
|
||||
### Argument validation
|
||||
|
||||
Before calling `execute`, `defineTool` validates model-generated arguments. Invalid input raises `ToolArgsError`; the framework turns it into an `isError` result so the model can correct its call.
|
||||
|
||||
Do not repeat type validation inside `execute`.
|
||||
|
||||
## Presentation
|
||||
|
||||
A tool can define UI presentation methods for terminal and ACP clients:
|
||||
|
||||
```ts ignore-check
|
||||
defineTool({
|
||||
name: 'bash',
|
||||
// ...
|
||||
presentCall(args) {
|
||||
return {
|
||||
card: 'terminal',
|
||||
title: args.command,
|
||||
}
|
||||
},
|
||||
presentResult(args, result) {
|
||||
return {
|
||||
card: 'terminal',
|
||||
output: result.content.map(b => b.type === 'text' ? b.text : '').join(''),
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`presentCall` and `presentResult` are **pure functions**. Streaming UI and session replay may call them more than once.
|
||||
|
||||
## Registration and unloading
|
||||
|
||||
`ctx.tools.register()` returns a disposer, but a registration made through `ctx` is already tracked by the framework. Unloading the plugin removes the tool automatically, so the plugin does not call the disposer itself.
|
||||
|
||||
```ts ignore-check
|
||||
// This is sufficient:
|
||||
ctx.tools.register(defineTool({ /* ... */ }))
|
||||
|
||||
// No saved disposer or extra cleanup registration is needed.
|
||||
```
|
||||
|
||||
## Complete example
|
||||
|
||||
This tool counts files in a directory:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import { readdir } from 'node:fs/promises'
|
||||
|
||||
export const name = 'file-counter'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'count_files',
|
||||
description: 'Count files in a directory.',
|
||||
parameters: {
|
||||
path: { type: 'string', required: true, description: 'Directory path' },
|
||||
extension: { type: 'string', description: 'Filter by extension (e.g. ".ts")' },
|
||||
},
|
||||
async execute(args) {
|
||||
const entries = await readdir(args.path, { withFileTypes: true })
|
||||
let files = entries.filter(e => e.isFile())
|
||||
if (args.extension) {
|
||||
files = files.filter(f => f.name.endsWith(args.extension!))
|
||||
}
|
||||
return [{ type: 'text', text: `Found ${files.length} files.` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Plugin configuration](./config.md) — make the tool configurable
|
||||
- [Capability layering](../practice/) — understand the interface/implementation/consumer pattern
|
||||
208
docs/user/develop/basic/tool.zh.md
Normal file
208
docs/user/develop/basic/tool.zh.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 开发一个 Tool
|
||||
|
||||
[English](tool.md) | 中文
|
||||
|
||||
Tool 是模型可以调用的能力。本文介绍如何用 `defineTool` 编写一个 tool。
|
||||
|
||||
## 最小示例
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'my-tool'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'greet',
|
||||
description: 'Greet someone by name.',
|
||||
parameters: {
|
||||
name: { type: 'string', required: true, description: 'The name to greet' },
|
||||
},
|
||||
async execute(args) {
|
||||
// args is inferred as { name: string }.
|
||||
return [{ type: 'text', text: `Hello, ${args.name}!` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## 参数定义
|
||||
|
||||
`parameters` 用一种简洁的格式描述参数,框架会自动转换为模型需要的 JSON Schema。
|
||||
|
||||
### 基本类型
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
path: { type: 'string', required: true },
|
||||
limit: { type: 'number' },
|
||||
recursive: { type: 'boolean' },
|
||||
}
|
||||
// Inferred type: { path: string; limit?: number; recursive?: boolean }
|
||||
```
|
||||
|
||||
### 枚举
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
mode: { type: 'string', required: true, enum: ['read', 'write', 'append'] },
|
||||
}
|
||||
// Inferred type: { mode: string } (enum values are validated at runtime)
|
||||
```
|
||||
|
||||
### 嵌套对象
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
timeout: { type: 'number' },
|
||||
retries: { type: 'number' },
|
||||
},
|
||||
},
|
||||
}
|
||||
// Inferred type: { options?: { timeout?: number; retries?: number } }
|
||||
```
|
||||
|
||||
### 数组
|
||||
|
||||
```ts
|
||||
export const parameters = {
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
}
|
||||
// Inferred type: { tags?: string[] }
|
||||
```
|
||||
|
||||
### 每个属性的字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `type` | `'string' \| 'number' \| 'boolean' \| 'object' \| 'array'` | 值类型 |
|
||||
| `required` | `true` | 标记为必填(影响类型推导) |
|
||||
| `description` | `string` | 发送给模型的描述 |
|
||||
| `enum` | `string[]` | 允许的枚举值 |
|
||||
| `properties` | `SchemaSpec` | 嵌套属性(type 为 object 时) |
|
||||
| `items` | `SchemaProp` | 数组元素 schema(type 为 array 时) |
|
||||
|
||||
## execute 函数
|
||||
|
||||
`execute` 接收经过校验的 `args`(类型自动推导)和一个 `exec` 上下文对象:
|
||||
|
||||
```ts
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'example',
|
||||
description: 'Return an example result.',
|
||||
parameters: {},
|
||||
async execute(args, exec) {
|
||||
// args: inferred from parameters
|
||||
// exec: ToolExecution context
|
||||
|
||||
// Return a ContentBlock array.
|
||||
void args
|
||||
void exec
|
||||
return [{ type: 'text', text: 'result here' }]
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 返回值
|
||||
|
||||
`execute` 必须返回一个 `ContentBlock[]`,告诉模型 tool 的执行结果:
|
||||
|
||||
```ts ignore-check
|
||||
// Text result
|
||||
return [{ type: 'text', text: 'file content here...' }]
|
||||
|
||||
// Multiple blocks
|
||||
return [
|
||||
{ type: 'text', text: 'Found 3 matches:' },
|
||||
{ type: 'text', text: matchResults.join('\n') },
|
||||
]
|
||||
```
|
||||
|
||||
### 参数校验
|
||||
|
||||
`defineTool` 在调用 `execute` 之前会自动校验模型生成的参数。如果参数不合法,会抛出 `ToolArgsError`,框架将其转换为 `isError` 结果返回给模型,让模型自行修正。
|
||||
|
||||
你不需要在 `execute` 里手动校验参数类型。
|
||||
|
||||
## 展示层 (Presentation)
|
||||
|
||||
Tool 可以定义 UI 渲染方法,用于在终端或 ACP 客户端中展示 tool call 和 result:
|
||||
|
||||
```ts ignore-check
|
||||
defineTool({
|
||||
name: 'bash',
|
||||
// ...
|
||||
presentCall(args) {
|
||||
return {
|
||||
card: 'terminal',
|
||||
title: args.command,
|
||||
}
|
||||
},
|
||||
presentResult(args, result) {
|
||||
return {
|
||||
card: 'terminal',
|
||||
output: result.content.map(b => b.type === 'text' ? b.text : '').join(''),
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`presentCall` 和 `presentResult` 是**纯函数**,不能有副作用——UI 可能在流式传输中和会话回放中多次调用它们。
|
||||
|
||||
## 注册与卸载
|
||||
|
||||
`ctx.tools.register()` 返回值就是 disposer。但由于你在 `ctx` 上调用,框架已经自动追踪了这个注册——插件卸载时会自动移除 tool。你不需要手动调用 disposer。
|
||||
|
||||
```ts ignore-check
|
||||
// This is sufficient:
|
||||
ctx.tools.register(defineTool({ /* ... */ }))
|
||||
|
||||
// No saved disposer or extra cleanup registration is needed.
|
||||
```
|
||||
|
||||
## 完整实战示例
|
||||
|
||||
一个文件计数 tool:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import { readdir } from 'node:fs/promises'
|
||||
|
||||
export const name = 'file-counter'
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'count_files',
|
||||
description: 'Count files in a directory.',
|
||||
parameters: {
|
||||
path: { type: 'string', required: true, description: 'Directory path' },
|
||||
extension: { type: 'string', description: 'Filter by extension (e.g. ".ts")' },
|
||||
},
|
||||
async execute(args) {
|
||||
const entries = await readdir(args.path, { withFileTypes: true })
|
||||
let files = entries.filter(e => e.isFile())
|
||||
if (args.extension) {
|
||||
files = files.filter(f => f.name.endsWith(args.extension!))
|
||||
}
|
||||
return [{ type: 'text', text: `Found ${files.length} files.` }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [插件配置](./config.md) — 让你的 tool 可配置
|
||||
- [能力三件套](../practice/) — 了解 seam/impl/consumer 模式
|
||||
6
docs/user/develop/framework/events.i18n.yaml
Normal file
6
docs/user/develop/framework/events.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
events.md: 0c57681a55ea0200fe8f33293176fc94f09a4ce5
|
||||
events.zh.md: 3e14739d4a97ba014d545c9f226000507aaeacef
|
||||
143
docs/user/develop/framework/events.md
Normal file
143
docs/user/develop/framework/events.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Event system
|
||||
|
||||
English | [中文](events.zh.md)
|
||||
|
||||
Events are the core communication mechanism between Cordis plugins. Harness uses them extensively for loosely coupled extension points.
|
||||
|
||||
## Basic use
|
||||
|
||||
### Listen for an event
|
||||
|
||||
```ts ignore-check
|
||||
ctx.on('event-name', (payload) => {
|
||||
// Handle the event.
|
||||
})
|
||||
```
|
||||
|
||||
### Emit an event
|
||||
|
||||
```ts ignore-check
|
||||
ctx.emit('event-name', payload)
|
||||
```
|
||||
|
||||
## Event modes
|
||||
|
||||
Cordis provides several event modes for different interaction contracts.
|
||||
|
||||
### emit — broadcast
|
||||
|
||||
Every listener runs synchronously and return values are ignored:
|
||||
|
||||
```ts ignore-check
|
||||
// Emit
|
||||
ctx.emit('my-plugin/ready', { id: 'worker-1' })
|
||||
|
||||
// Listen
|
||||
ctx.on('my-plugin/ready', ({ id }) => {
|
||||
console.log(`${id} is ready`)
|
||||
})
|
||||
```
|
||||
|
||||
### bail — short circuit
|
||||
|
||||
Listeners run in order; the first non-`undefined` result becomes the final result:
|
||||
|
||||
```ts ignore-check
|
||||
// Dispatch
|
||||
const result = ctx.bail('some-check', input)
|
||||
|
||||
// Listen: a returned value stops later listeners.
|
||||
ctx.on('some-check', (input) => {
|
||||
if (shouldBlock(input)) return 'blocked'
|
||||
// Return undefined to continue to the next listener.
|
||||
})
|
||||
```
|
||||
|
||||
### serial — ordered execution
|
||||
|
||||
Listeners run in registration order and asynchronous results are awaited. The first listener to return a non-empty value stops further execution:
|
||||
|
||||
```ts ignore-check
|
||||
await ctx.serial('setup-phase', context)
|
||||
```
|
||||
|
||||
### waterfall — pipeline
|
||||
|
||||
Each listener may wrap the downstream result to form a processing chain. A listener **must call `next()` to delegate downstream**; omitting the call vetoes the pipeline:
|
||||
|
||||
```ts ignore-check
|
||||
// Dispatch
|
||||
const output = await ctx.waterfall('my-plugin/transform', input, async () => input)
|
||||
|
||||
// Listen: next() is mandatory.
|
||||
ctx.on('my-plugin/transform', async (_input, next) => {
|
||||
const downstream = await next()
|
||||
return downstream.trim()
|
||||
})
|
||||
```
|
||||
|
||||
::: warning
|
||||
A waterfall listener **must call `next()`**. Omitting it vetoes the pipeline by design, enabling interception and gateway behavior.
|
||||
:::
|
||||
|
||||
## Typed events
|
||||
|
||||
Harness uses TypeScript declaration merging for type-safe events:
|
||||
|
||||
```ts
|
||||
import 'cordis'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Events {
|
||||
'my-plugin/ready': (payload: { id: string }) => void
|
||||
'my-plugin/check': (input: string) => boolean | undefined
|
||||
'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string>
|
||||
}
|
||||
}
|
||||
|
||||
// ctx.on('my-plugin/ready', ...) and ctx.emit('my-plugin/ready', ...)
|
||||
// are now inferred correctly.
|
||||
```
|
||||
|
||||
## Cordis events and session records
|
||||
|
||||
Harness Cordis events use `namespace/action` names, including `agent/pre-step`, `agent/request`, `agent/step-result`, `tools/result`, and `session/event`. The generated [event catalog](../../../cordis-catalog/events.md) records complete signatures and modes.
|
||||
|
||||
`turn/*`, `step/*`, `tool/call`, `tool/result`, and `compact/*` are durable session-event types, not same-named Cordis events. To observe them, listen to `session/event` and inspect `event.type`.
|
||||
|
||||
## Event listeners are effects
|
||||
|
||||
A listener registered with `ctx.on()` is removed automatically when its plugin unloads:
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
// This listener is removed when the plugin disposes.
|
||||
ctx.on('tools/result', handler)
|
||||
}
|
||||
```
|
||||
|
||||
## Example: logging plugin
|
||||
|
||||
This plugin logs tool calls and results:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'tool-logger'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.on('tools/result', (exec, result) => {
|
||||
console.log(`[tool] ${exec.name}(${JSON.stringify(exec.arguments)})`)
|
||||
const text = result.content
|
||||
.map(block => block.type === 'text' ? block.text : '')
|
||||
.join('')
|
||||
console.log(`[tool result] ${text.slice(0, 100)}`)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Capability layering](../practice/) — understand events within capability interfaces
|
||||
- [LLM adapters](../practice/llm-adapter.md) — implement a complete LLM backend
|
||||
143
docs/user/develop/framework/events.zh.md
Normal file
143
docs/user/develop/framework/events.zh.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# 事件系统
|
||||
|
||||
[English](events.md) | 中文
|
||||
|
||||
事件是 Cordis 插件间通信的核心机制。Harness 大量使用事件来实现松耦合的扩展点。
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 监听事件
|
||||
|
||||
```ts ignore-check
|
||||
ctx.on('event-name', (payload) => {
|
||||
// Handle the event.
|
||||
})
|
||||
```
|
||||
|
||||
### 触发事件
|
||||
|
||||
```ts ignore-check
|
||||
ctx.emit('event-name', payload)
|
||||
```
|
||||
|
||||
## 事件模式
|
||||
|
||||
Cordis 提供多种事件触发模式,适用于不同场景:
|
||||
|
||||
### emit — 广播
|
||||
|
||||
所有监听器同步执行,不关心返回值:
|
||||
|
||||
```ts ignore-check
|
||||
// Emit
|
||||
ctx.emit('my-plugin/ready', { id: 'worker-1' })
|
||||
|
||||
// Listen
|
||||
ctx.on('my-plugin/ready', ({ id }) => {
|
||||
console.log(`${id} is ready`)
|
||||
})
|
||||
```
|
||||
|
||||
### bail — 短路
|
||||
|
||||
依次调用监听器,第一个返回非 `undefined` 值的结果作为最终值:
|
||||
|
||||
```ts ignore-check
|
||||
// Dispatch
|
||||
const result = ctx.bail('some-check', input)
|
||||
|
||||
// Listen: a returned value stops later listeners.
|
||||
ctx.on('some-check', (input) => {
|
||||
if (shouldBlock(input)) return 'blocked'
|
||||
// Return undefined to continue to the next listener.
|
||||
})
|
||||
```
|
||||
|
||||
### serial — 顺序执行
|
||||
|
||||
监听器按注册顺序依次执行,并等待异步结果;第一个返回非空值的监听器会终止后续执行:
|
||||
|
||||
```ts ignore-check
|
||||
await ctx.serial('setup-phase', context)
|
||||
```
|
||||
|
||||
### waterfall — 管道
|
||||
|
||||
每个监听器可以包装下游返回值,形成处理链。**必须调用 `next()` 传递给下游**,不调用即为否决:
|
||||
|
||||
```ts ignore-check
|
||||
// Dispatch
|
||||
const output = await ctx.waterfall('my-plugin/transform', input, async () => input)
|
||||
|
||||
// Listen: next() is mandatory.
|
||||
ctx.on('my-plugin/transform', async (_input, next) => {
|
||||
const downstream = await next()
|
||||
return downstream.trim()
|
||||
})
|
||||
```
|
||||
|
||||
::: warning
|
||||
Waterfall 监听器**必须调用 `next()`**。不调用 `next` 等于否决整个管道,这是故意为之的设计——用于实现拦截/网关逻辑。
|
||||
:::
|
||||
|
||||
## Typed Events
|
||||
|
||||
Harness 使用 TypeScript 声明合并来为事件提供类型安全:
|
||||
|
||||
```ts
|
||||
import 'cordis'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Events {
|
||||
'my-plugin/ready': (payload: { id: string }) => void
|
||||
'my-plugin/check': (input: string) => boolean | undefined
|
||||
'my-plugin/transform': (input: string, next: () => Promise<string>) => Promise<string>
|
||||
}
|
||||
}
|
||||
|
||||
// ctx.on('my-plugin/ready', ...) and ctx.emit('my-plugin/ready', ...)
|
||||
// are now inferred correctly.
|
||||
```
|
||||
|
||||
## Cordis 事件与会话记录
|
||||
|
||||
Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/pre-step`、`agent/request`、`agent/step-result`、`tools/result` 和 `session/event`。完整签名与触发模式见[Events 目录](../../../cordis-catalog/events.md)。
|
||||
|
||||
`turn/*`、`step/*`、`tool/call`、`tool/result` 和 `compact/*` 是持久化的会话事件类型,不是同名 Cordis 事件。需要观察它们时,监听 `session/event` 并检查 `event.type`。
|
||||
|
||||
## 事件也是效果
|
||||
|
||||
通过 `ctx.on()` 注册的监听器会在插件卸载时自动移除:
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
// This listener is removed when the plugin disposes.
|
||||
ctx.on('tools/result', handler)
|
||||
}
|
||||
```
|
||||
|
||||
## 实战示例:日志插件
|
||||
|
||||
一个记录所有 tool 调用的简单插件:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'tool-logger'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.on('tools/result', (exec, result) => {
|
||||
console.log(`[tool] ${exec.name}(${JSON.stringify(exec.arguments)})`)
|
||||
const text = result.content
|
||||
.map(block => block.type === 'text' ? block.text : '')
|
||||
.join('')
|
||||
console.log(`[tool result] ${text.slice(0, 100)}`)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [能力三件套](../practice/) — 事件在 capability seam 中的角色
|
||||
- [LLM 适配器](../practice/llm-adapter.md) — 实现一个完整的 LLM 后端
|
||||
6
docs/user/develop/framework/index.i18n.yaml
Normal file
6
docs/user/develop/framework/index.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
index.md: 79e925b54509da41535735527e283850384257ec
|
||||
index.zh.md: 62be8c706510704f7b07286f166f14fa81235a0a
|
||||
136
docs/user/develop/framework/index.md
Normal file
136
docs/user/develop/framework/index.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Plugins and lifecycle
|
||||
|
||||
English | [中文](index.zh.md)
|
||||
|
||||
This page describes the Cordis plugin model and lifecycle state machine.
|
||||
|
||||
## Fiber state machine
|
||||
|
||||
Every loaded plugin owns a **Fiber** scope with the following states:
|
||||
|
||||
```
|
||||
PENDING → LOADING → ACTIVE
|
||||
↘ FAILED
|
||||
ACTIVE → UNLOADING → DISPOSED
|
||||
```
|
||||
|
||||
| State | Meaning |
|
||||
|------|------|
|
||||
| PENDING | Declared, but required dependencies are not ready |
|
||||
| LOADING | Dependencies are ready and `apply` is running |
|
||||
| ACTIVE | The plugin is running |
|
||||
| FAILED | `apply` threw an error |
|
||||
| UNLOADING | The plugin is unloading and disposing resources |
|
||||
| DISPOSED | The plugin is fully unloaded |
|
||||
|
||||
## Dependency-driven loading
|
||||
|
||||
A plugin with `inject` waits for every required service before loading:
|
||||
|
||||
```ts ignore-check
|
||||
export const inject = ['tools', 'llm']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// ctx.tools and ctx.llm are ready here.
|
||||
}
|
||||
```
|
||||
|
||||
If a required service disappears, for example during provider replacement, the plugin unloads automatically (ACTIVE → DISPOSED) and loads again when the service returns.
|
||||
|
||||
## Automatic cleanup
|
||||
|
||||
Every registration made through `ctx` is undone when the plugin unloads:
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
// Event listener: removed automatically on unload.
|
||||
ctx.on('some-event', handler)
|
||||
|
||||
// Custom resource: the returned disposer runs on unload.
|
||||
ctx.effect(() => {
|
||||
const connection = createConnection()
|
||||
return () => connection.close()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The framework tracks and disposes all of these operations:
|
||||
- `ctx.on(event, handler)` — event listener
|
||||
- `ctx.tools.register(tool)` — tool registration
|
||||
- `ctx.llm.registerAdapter(names, adapter)` — LLM adapter registration
|
||||
- `ctx.effect(() => cleanup)` — custom resource
|
||||
|
||||
During unload, disposer invocation starts in reverse registration order, but multiple async disposers run concurrently and have no serial completion guarantee. Put order-dependent cleanup in one disposer returned from a single `ctx.effect()` and await its steps serially there.
|
||||
|
||||
## Nested contexts
|
||||
|
||||
`ctx.plugin()` creates a child Fiber that inherits the parent context but has an independent lifecycle:
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
// Register a child plugin.
|
||||
ctx.plugin(childPlugin)
|
||||
|
||||
// The child has its own Fiber and unloads with its parent.
|
||||
}
|
||||
```
|
||||
|
||||
## Dispose semantics
|
||||
|
||||
To stop a plugin instance early:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
declare const ctx: Context
|
||||
declare function myPlugin(ctx: Context): void
|
||||
|
||||
const fiber = ctx.plugin(myPlugin)
|
||||
|
||||
// Dispose it manually later.
|
||||
await fiber.dispose()
|
||||
```
|
||||
|
||||
`dispose` guarantees:
|
||||
1. All registrations owned by the plugin are removed.
|
||||
2. Child plugins are recursively unloaded.
|
||||
3. The returned promise resolves after all asynchronous cleanup finishes.
|
||||
|
||||
## Hot replacement (HMR)
|
||||
|
||||
With `@cordisjs/plugin-hmr` loaded from `cordis.yml`, editing a plugin source file triggers:
|
||||
|
||||
1. Unload the old plugin and clean up its registrations.
|
||||
2. Load the new code.
|
||||
3. Run the new `apply`.
|
||||
|
||||
Because plugin registrations clean themselves up, hot replacement does not retain registrations from the old instance.
|
||||
|
||||
## Example lifecycle
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
console.log('plugin loading')
|
||||
|
||||
ctx.effect(() => {
|
||||
console.log('effect registered')
|
||||
return () => console.log('effect cleaned up')
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Loading prints:
|
||||
```
|
||||
plugin loading
|
||||
effect registered
|
||||
```
|
||||
|
||||
Unloading prints:
|
||||
```
|
||||
effect cleaned up
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Services and dependencies](./service.md) — expose a capability to other plugins
|
||||
- [Event system](./events.md) — communicate between plugins
|
||||
136
docs/user/develop/framework/index.zh.md
Normal file
136
docs/user/develop/framework/index.zh.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# 插件与生命周期
|
||||
|
||||
[English](index.md) | 中文
|
||||
|
||||
深入了解 Cordis 插件模型和生命周期状态机。
|
||||
|
||||
## Fiber 状态机
|
||||
|
||||
每个被加载的插件对应一个 **Fiber**(作用域)。Fiber 有以下状态:
|
||||
|
||||
```
|
||||
PENDING → LOADING → ACTIVE
|
||||
↘ FAILED
|
||||
ACTIVE → UNLOADING → DISPOSED
|
||||
```
|
||||
|
||||
| 状态 | 含义 |
|
||||
|------|------|
|
||||
| PENDING | 已声明但依赖未就绪 |
|
||||
| LOADING | 依赖就绪,正在执行 `apply` |
|
||||
| ACTIVE | 插件运行中 |
|
||||
| FAILED | `apply` 抛出异常 |
|
||||
| UNLOADING | 正在卸载,清理中 |
|
||||
| DISPOSED | 已完全卸载 |
|
||||
|
||||
## 依赖驱动的加载
|
||||
|
||||
声明了 `inject` 的插件不会立即加载,而是等待依赖的服务就绪:
|
||||
|
||||
```ts ignore-check
|
||||
export const inject = ['tools', 'llm']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// ctx.tools and ctx.llm are ready here.
|
||||
}
|
||||
```
|
||||
|
||||
如果依赖的服务消失(比如提供者被热替换),插件会被自动卸载(ACTIVE → DISPOSED),待服务恢复后重新加载。
|
||||
|
||||
## 自动清理机制
|
||||
|
||||
通过 `ctx` 做的任何注册,在插件卸载时都会自动撤销:
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
// Event listener: removed automatically on unload.
|
||||
ctx.on('some-event', handler)
|
||||
|
||||
// Custom resource: the returned disposer runs on unload.
|
||||
ctx.effect(() => {
|
||||
const connection = createConnection()
|
||||
return () => connection.close()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
以下操作都会被自动追踪和清理:
|
||||
- `ctx.on(event, handler)` — 事件监听
|
||||
- `ctx.tools.register(tool)` — tool 注册
|
||||
- `ctx.llm.registerAdapter(names, adapter)` — LLM 适配器注册
|
||||
- `ctx.effect(() => cleanup)` — 自定义资源
|
||||
|
||||
插件卸载时,处置器按注册顺序的反向发起,但多个异步处置器会并发执行,不保证逐个完成。存在顺序依赖的清理步骤必须放进同一个 `ctx.effect()` 返回的处置器中,由该处置器负责串行等待。
|
||||
|
||||
## 嵌套上下文
|
||||
|
||||
`ctx.plugin()` 创建子 Fiber,它继承父上下文但有独立的生命周期:
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
// Register a child plugin.
|
||||
ctx.plugin(childPlugin)
|
||||
|
||||
// The child has its own Fiber and unloads with its parent.
|
||||
}
|
||||
```
|
||||
|
||||
## dispose 语义
|
||||
|
||||
当你需要提前终止一个插件实例:
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
declare const ctx: Context
|
||||
declare function myPlugin(ctx: Context): void
|
||||
|
||||
const fiber = ctx.plugin(myPlugin)
|
||||
|
||||
// Dispose it manually later.
|
||||
await fiber.dispose()
|
||||
```
|
||||
|
||||
`dispose` 保证:
|
||||
1. 该插件注册的所有东西被撤销
|
||||
2. 它的子插件也被递归卸载
|
||||
3. 所有异步清理完成后 Promise resolve
|
||||
|
||||
## 热替换 (HMR)
|
||||
|
||||
在开发环境中(`cordis.yml` 加载了 `@cordisjs/plugin-hmr`),修改插件源文件会自动触发:
|
||||
|
||||
1. 卸载旧插件(清理所有注册)
|
||||
2. 重新加载新代码
|
||||
3. 执行新的 `apply`
|
||||
|
||||
因为所有注册都会被自动清理,所以热替换天然安全——不会留下旧状态。
|
||||
|
||||
## 实战:理解生命周期
|
||||
|
||||
```ts ignore-check
|
||||
export function apply(ctx: Context) {
|
||||
console.log('plugin loading')
|
||||
|
||||
ctx.effect(() => {
|
||||
console.log('effect registered')
|
||||
return () => console.log('effect cleaned up')
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
加载时输出:
|
||||
```
|
||||
plugin loading
|
||||
effect registered
|
||||
```
|
||||
|
||||
卸载时输出:
|
||||
```
|
||||
effect cleaned up
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [服务与依赖](./service.md) — 让你的插件对外提供能力
|
||||
- [事件系统](./events.md) — 插件间通信的核心机制
|
||||
6
docs/user/develop/framework/service.i18n.yaml
Normal file
6
docs/user/develop/framework/service.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
service.md: 1bf28cb3c7dfdfbd6d0babfa3b1688ac65eea01e
|
||||
service.zh.md: 17785c056ab9a0a21974e6ed8bbe7f7de05fa00e
|
||||
148
docs/user/develop/framework/service.md
Normal file
148
docs/user/develop/framework/service.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Services and dependencies
|
||||
|
||||
English | [中文](service.zh.md)
|
||||
|
||||
A service is a capability one plugin exposes to other plugins. `inject` declares the services a plugin requires.
|
||||
|
||||
## What is a service?
|
||||
|
||||
In Harness, `tools`, `llm`, and `agents` are services. Each is a named capability mounted on `ctx`:
|
||||
|
||||
```ts ignore-check
|
||||
ctx.tools // ToolRegistry service
|
||||
ctx.llm // LLM service
|
||||
ctx.agents // Agent service
|
||||
```
|
||||
|
||||
Any plugin can provide a service for other plugins to consume.
|
||||
|
||||
## Consume a service
|
||||
|
||||
Declare `inject` to use an existing service:
|
||||
|
||||
```ts ignore-check
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// ctx.tools exists and is ready here.
|
||||
ctx.tools.register(/* ... */)
|
||||
}
|
||||
```
|
||||
|
||||
When `apply` runs, every service declared by `inject` is ready. If a service is not ready, the plugin waits instead of running.
|
||||
|
||||
## Provide a service
|
||||
|
||||
### Extend Service
|
||||
|
||||
```ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
export default class MetricsService extends Service {
|
||||
static inject = ['llm'] // A service may depend on other services.
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'metrics') // 'metrics' is the service name.
|
||||
}
|
||||
|
||||
// Public service method.
|
||||
record(event: string, value: number) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After loading this plugin, consumers access the service as `ctx.metrics`:
|
||||
|
||||
```ts ignore-check
|
||||
export const inject = ['metrics']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.metrics.record('tool_call', 1)
|
||||
}
|
||||
```
|
||||
|
||||
### Declare its type
|
||||
|
||||
Use TypeScript declaration merging to type `ctx.metrics`:
|
||||
|
||||
```ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
metrics: MetricsService
|
||||
}
|
||||
}
|
||||
|
||||
export default class MetricsService extends Service {
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'metrics')
|
||||
}
|
||||
|
||||
record(event: string, value: number) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Dependency behavior
|
||||
|
||||
### Required and optional dependencies
|
||||
|
||||
```ts ignore-check
|
||||
// Required: the plugin does not load while the service is absent.
|
||||
export const inject = ['tools']
|
||||
|
||||
// Optional: omit inject and query with ctx.get() at the use site.
|
||||
export function apply(ctx: Context) {
|
||||
const metrics = ctx.get('metrics')
|
||||
metrics?.record('plugin_loaded', 1)
|
||||
}
|
||||
```
|
||||
|
||||
### When a service disappears
|
||||
|
||||
If a required service disappears while the application is running, for example because its provider unloads:
|
||||
|
||||
1. Dependent plugins dispose automatically.
|
||||
2. They load again when the service returns.
|
||||
|
||||
This prevents a plugin from calling a service that no longer exists.
|
||||
|
||||
## Service isolation
|
||||
|
||||
`cordis.yml` can isolate services so separate plugin groups see separate instances of the same service:
|
||||
|
||||
```yaml
|
||||
- id: group-a
|
||||
name: '@cordisjs/plugin-group'
|
||||
group: true
|
||||
isolate:
|
||||
bash: true
|
||||
config:
|
||||
- name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
timeoutMs: 5000
|
||||
- name: './src/plugin-a.ts'
|
||||
|
||||
- id: group-b
|
||||
name: '@cordisjs/plugin-group'
|
||||
group: true
|
||||
isolate:
|
||||
bash: true
|
||||
config:
|
||||
- name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
timeoutMs: 60000
|
||||
- name: './src/plugin-b.ts'
|
||||
```
|
||||
|
||||
`plugin-a` and `plugin-b` each see the Bash instance in their own group, with no cross-group effect.
|
||||
|
||||
## Built-in Harness services
|
||||
|
||||
The repository generates the service names, public methods, and source locations in the [service catalog](../../../cordis-catalog/services.md). Use that catalog and the service's TypeScript interface while developing a plugin; do not maintain a second static list.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Event system](./events.md) — communicate between plugins without tight coupling
|
||||
- [Capability layering](../practice/) — use services as capability interfaces
|
||||
148
docs/user/develop/framework/service.zh.md
Normal file
148
docs/user/develop/framework/service.zh.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 服务与依赖
|
||||
|
||||
[English](service.md) | 中文
|
||||
|
||||
服务 (Service) 是插件对外暴露能力的方式。依赖 (inject) 是插件声明自己需要哪些服务。
|
||||
|
||||
## 什么是服务
|
||||
|
||||
在 Harness 中,`tools`、`llm`、`agents` 都是服务。服务是挂载在 `ctx` 上的命名能力:
|
||||
|
||||
```ts ignore-check
|
||||
ctx.tools // ToolRegistry service
|
||||
ctx.llm // LLM service
|
||||
ctx.agents // Agent service
|
||||
```
|
||||
|
||||
任何插件都可以提供一个新服务,供其他插件使用。
|
||||
|
||||
## 使用服务
|
||||
|
||||
声明 `inject` 来使用已有服务:
|
||||
|
||||
```ts ignore-check
|
||||
export const inject = ['tools']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
// ctx.tools exists and is ready here.
|
||||
ctx.tools.register(/* ... */)
|
||||
}
|
||||
```
|
||||
|
||||
框架保证:在 `apply` 执行时,`inject` 声明的服务已经全部就绪。如果服务还没准备好,你的插件会等着,不会执行。
|
||||
|
||||
## 提供服务
|
||||
|
||||
### 使用 Service 基类
|
||||
|
||||
```ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
export default class MetricsService extends Service {
|
||||
static inject = ['llm'] // A service may depend on other services.
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'metrics') // 'metrics' is the service name.
|
||||
}
|
||||
|
||||
// Public service method.
|
||||
record(event: string, value: number) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
加载这个插件后,其他插件就可以通过 `ctx.metrics` 访问它:
|
||||
|
||||
```ts ignore-check
|
||||
export const inject = ['metrics']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.metrics.record('tool_call', 1)
|
||||
}
|
||||
```
|
||||
|
||||
### 类型声明
|
||||
|
||||
使用 TypeScript 声明合并让 `ctx.metrics` 有正确类型:
|
||||
|
||||
```ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
metrics: MetricsService
|
||||
}
|
||||
}
|
||||
|
||||
export default class MetricsService extends Service {
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'metrics')
|
||||
}
|
||||
|
||||
record(event: string, value: number) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖的行为
|
||||
|
||||
### 必选依赖 vs 可选依赖
|
||||
|
||||
```ts ignore-check
|
||||
// Required: the plugin does not load while the service is absent.
|
||||
export const inject = ['tools']
|
||||
|
||||
// Optional: omit inject and query with ctx.get() at the use site.
|
||||
export function apply(ctx: Context) {
|
||||
const metrics = ctx.get('metrics')
|
||||
metrics?.record('plugin_loaded', 1)
|
||||
}
|
||||
```
|
||||
|
||||
### 服务消失时的行为
|
||||
|
||||
如果一个必选依赖的服务在运行时消失(比如提供者被卸载):
|
||||
|
||||
1. 依赖它的插件自动 dispose
|
||||
2. 当服务重新出现时,插件自动重新加载
|
||||
|
||||
这保证了不会出现"调用一个已不存在的服务"的情况。
|
||||
|
||||
## 服务隔离
|
||||
|
||||
`cordis.yml` 支持服务隔离——同一个服务可以有多个实例,不同插件组看到不同实例:
|
||||
|
||||
```yaml
|
||||
- id: group-a
|
||||
name: '@cordisjs/plugin-group'
|
||||
group: true
|
||||
isolate:
|
||||
bash: true
|
||||
config:
|
||||
- name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
timeoutMs: 5000
|
||||
- name: './src/plugin-a.ts'
|
||||
|
||||
- id: group-b
|
||||
name: '@cordisjs/plugin-group'
|
||||
group: true
|
||||
isolate:
|
||||
bash: true
|
||||
config:
|
||||
- name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
timeoutMs: 60000
|
||||
- name: './src/plugin-b.ts'
|
||||
```
|
||||
|
||||
`plugin-a` 和 `plugin-b` 各自看到自己组内的 bash 实例,互不影响。
|
||||
|
||||
## Harness 内置服务
|
||||
|
||||
服务名、公开方法和源码位置由仓库自动生成,见[服务目录](../../../cordis-catalog/services.md)。开发插件时应以该目录和服务接口的 TypeScript 类型为准,不要复制一份静态清单。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [事件系统](./events.md) — 插件间松耦合通信
|
||||
- [能力三件套](../practice/) — 服务在 seam 模式中的应用
|
||||
6
docs/user/develop/practice/index.i18n.yaml
Normal file
6
docs/user/develop/practice/index.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
index.md: 0261b49b071167f7c2a33f78bbc1959cc6f1879f
|
||||
index.zh.md: 5819344430fcbde31bf825e9815120983e44e3f6
|
||||
158
docs/user/develop/practice/index.md
Normal file
158
docs/user/develop/practice/index.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Three-layer capability design
|
||||
|
||||
English | [中文](index.zh.md)
|
||||
|
||||
When a capability is general enough to need replaceable implementations, such as Bash execution, Harness splits it into three packages: an **interface**, an **implementation**, and a **consumer**. Each layer can evolve or be replaced independently.
|
||||
|
||||
## Bash example
|
||||
|
||||
The Bash execution capability consists of:
|
||||
|
||||
- **Interface** (`dsh-bash`) — defines Bash request and result shapes
|
||||
- **Implementation** (`dsh-bash-local`) — executes commands on the local machine
|
||||
- **Consumer** (`dsh-tool-bash`) — exposes the capability as a model-callable tool
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
|
||||
│ dsh-bash │────▶│ dsh-bash-local │ │ dsh-tool-bash│
|
||||
│ (interface) │ │ (implementation) │ │(consumer/tool)│
|
||||
└─────────────┘ └──────────────────┘ └──────────────┘
|
||||
▲ │
|
||||
└────────────────────────────────────────────┘
|
||||
inject: ['bash']
|
||||
```
|
||||
|
||||
## Benefits of the split
|
||||
|
||||
### Replace implementations
|
||||
|
||||
One interface can have multiple implementations selected through `cordis.yml`:
|
||||
|
||||
```yaml
|
||||
# Local execution
|
||||
- name: '@deepseek-ai/dsh-bash-local'
|
||||
|
||||
# Or a future remote sandbox implementation
|
||||
# - name: '@deepseek-ai/dsh-bash-remote'
|
||||
# config:
|
||||
# endpoint: 'https://sandbox.example.com'
|
||||
```
|
||||
|
||||
The interface and tool remain unchanged while the implementation changes.
|
||||
|
||||
### Evolve independently
|
||||
|
||||
- The interface changes rarely after its contract stabilizes.
|
||||
- Implementations can improve performance and security independently.
|
||||
- Consumers can change how they present the capability to the model.
|
||||
|
||||
### Decouple dependencies
|
||||
|
||||
- The implementation depends on the interface.
|
||||
- The consumer depends on the interface.
|
||||
- The implementation and consumer **do not depend on each other**.
|
||||
|
||||
## Built-in three-layer capabilities
|
||||
|
||||
| Capability | Interface | Implementation | Consumer |
|
||||
|------|-------------|------|---------------|
|
||||
| Bash | `dsh-bash` | `dsh-bash-local` | `dsh-tool-bash` |
|
||||
| Filesystem | `dsh-fs` | `dsh-fs-local` + `dsh-fs-policy` | `dsh-tool-fs` |
|
||||
| Web | `dsh-web` | `dsh-web-fetch-local` / `dsh-web-search-*` | `dsh-tool-web` |
|
||||
| Subagent | `dsh-subagent` | `dsh-subagent-spawn` / `dsh-subagent-fork` | `dsh-tool-subagent` |
|
||||
| Compaction | `dsh-compact` | `dsh-compact-basic` | The implementation consumes agent-loop extension events |
|
||||
|
||||
## Develop a three-layer capability
|
||||
|
||||
### Step 1: define the interface
|
||||
|
||||
```ts ignore-check
|
||||
// packages/my-cap/my-cap/src/index.ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
myCap: MyCapService
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class MyCapService extends Service {
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'myCap')
|
||||
}
|
||||
|
||||
/** Execute the capability. */
|
||||
abstract execute(request: MyCapRequest): Promise<MyCapResult>
|
||||
}
|
||||
|
||||
export interface MyCapRequest {
|
||||
input: string
|
||||
}
|
||||
|
||||
export interface MyCapResult {
|
||||
output: string
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: write an implementation
|
||||
|
||||
```ts ignore-check
|
||||
// packages/my-cap/my-cap-local/src/index.ts
|
||||
import type { Context } from 'cordis'
|
||||
import { MyCapService, type MyCapRequest, type MyCapResult } from '@deepseek-ai/dsh-my-cap'
|
||||
|
||||
class MyCapLocal extends MyCapService {
|
||||
async execute(request: MyCapRequest): Promise<MyCapResult> {
|
||||
// Concrete implementation.
|
||||
return { output: request.input.toUpperCase() }
|
||||
}
|
||||
}
|
||||
|
||||
export const name = 'my-cap-local'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.plugin(MyCapLocal)
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: write a consumer
|
||||
|
||||
```ts ignore-check
|
||||
// packages/my-cap/tool-my-cap/src/index.ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'tool-my-cap'
|
||||
export const inject = ['tools', 'myCap']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'my_cap',
|
||||
description: 'Execute my capability.',
|
||||
parameters: {
|
||||
input: { type: 'string', required: true },
|
||||
},
|
||||
async execute(args) {
|
||||
const result = await ctx.myCap.execute({ input: args.input })
|
||||
return [{ type: 'text', text: result.output }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
### Compose them in cordis.yml
|
||||
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-my-cap-local'
|
||||
- name: '@deepseek-ai/dsh-tool-my-cap'
|
||||
```
|
||||
|
||||
## Design points
|
||||
|
||||
- **Do not split preemptively** — use three packages only when the capability needs replaceable implementations. A simple tool plugin does not.
|
||||
- **The interface owns Request/Result types** — implementations and consumers depend only on the interface package.
|
||||
- **Explicit > implicit** — resolve defaults in an explicit `resolve(request): Spec` step rather than hiding `?? default` expressions inside `run()`.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [LLM adapter](./llm-adapter.md) — implement an LLM backend, a common capability interface extension
|
||||
158
docs/user/develop/practice/index.zh.md
Normal file
158
docs/user/develop/practice/index.zh.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# 能力的三层拆分
|
||||
|
||||
[English](index.md) | 中文
|
||||
|
||||
当一个能力(插件)足够通用(比如"执行 bash 命令"),Harness 会把它拆成三个包:**接口**、**实现**、**消费者**。这样可以独立替换其中任何一层。
|
||||
|
||||
## 以 Bash 为例
|
||||
|
||||
考虑 "Bash 执行" 这个能力:
|
||||
|
||||
- **接口** (`dsh-bash`) — 定义"bash 执行"长什么样:输入是什么、输出是什么
|
||||
- **实现** (`dsh-bash-local`) — 真正在本地跑命令的代码
|
||||
- **消费者** (`dsh-tool-bash`) — 把这个能力包装成模型能调用的 tool
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
|
||||
│ dsh-bash │────▶│ dsh-bash-local │ │ dsh-tool-bash│
|
||||
│ (interface) │ │ (implementation) │ │(consumer/tool)│
|
||||
└─────────────┘ └──────────────────┘ └──────────────┘
|
||||
▲ │
|
||||
└────────────────────────────────────────────┘
|
||||
inject: ['bash']
|
||||
```
|
||||
|
||||
## 拆分的好处
|
||||
|
||||
### 具体实现可替换
|
||||
|
||||
同一个接口可以有多种实现。用户通过 `cordis.yml` 选择:
|
||||
|
||||
```yaml
|
||||
# Local execution
|
||||
- name: '@deepseek-ai/dsh-bash-local'
|
||||
|
||||
# Or a future remote sandbox implementation
|
||||
# - name: '@deepseek-ai/dsh-bash-remote'
|
||||
# config:
|
||||
# endpoint: 'https://sandbox.example.com'
|
||||
```
|
||||
|
||||
接口不变、tool 不变,只换实现。
|
||||
|
||||
### 独立演进
|
||||
|
||||
- 接口定义稳定后很少改动
|
||||
- 实现可以独立优化(性能、安全)
|
||||
- 消费者(tool)可以调整对模型的呈现方式
|
||||
|
||||
### 依赖解耦
|
||||
|
||||
- 实现 depend on 接口
|
||||
- 消费者 depend on 接口
|
||||
- 实现和消费者**互不依赖**
|
||||
|
||||
## Harness 中内置的三件套
|
||||
|
||||
| 能力 | 接口 (seam) | 实现 | 消费者 (tool) |
|
||||
|------|-------------|------|---------------|
|
||||
| Bash | `dsh-bash` | `dsh-bash-local` | `dsh-tool-bash` |
|
||||
| 文件系统 | `dsh-fs` | `dsh-fs-local` + `dsh-fs-policy` | `dsh-tool-fs` |
|
||||
| Web | `dsh-web` | `dsh-web-fetch-local` / `dsh-web-search-*` | `dsh-tool-web` |
|
||||
| 子代理 | `dsh-subagent` | `dsh-subagent-spawn` / `dsh-subagent-fork` | `dsh-tool-subagent` |
|
||||
| 压缩 | `dsh-compact` | `dsh-compact-basic` | 由实现插件消费 agent-loop 的扩展事件 |
|
||||
|
||||
## 开发你自己的三件套
|
||||
|
||||
### 第一步:定义接口
|
||||
|
||||
```ts ignore-check
|
||||
// packages/my-cap/my-cap/src/index.ts
|
||||
import { Service, type Context } from 'cordis'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
myCap: MyCapService
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class MyCapService extends Service {
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'myCap')
|
||||
}
|
||||
|
||||
/** Execute the capability. */
|
||||
abstract execute(request: MyCapRequest): Promise<MyCapResult>
|
||||
}
|
||||
|
||||
export interface MyCapRequest {
|
||||
input: string
|
||||
}
|
||||
|
||||
export interface MyCapResult {
|
||||
output: string
|
||||
}
|
||||
```
|
||||
|
||||
### 第二步:编写实现
|
||||
|
||||
```ts ignore-check
|
||||
// packages/my-cap/my-cap-local/src/index.ts
|
||||
import type { Context } from 'cordis'
|
||||
import { MyCapService, type MyCapRequest, type MyCapResult } from '@deepseek-ai/dsh-my-cap'
|
||||
|
||||
class MyCapLocal extends MyCapService {
|
||||
async execute(request: MyCapRequest): Promise<MyCapResult> {
|
||||
// Concrete implementation.
|
||||
return { output: request.input.toUpperCase() }
|
||||
}
|
||||
}
|
||||
|
||||
export const name = 'my-cap-local'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.plugin(MyCapLocal)
|
||||
}
|
||||
```
|
||||
|
||||
### 第三步:编写消费者 (tool)
|
||||
|
||||
```ts ignore-check
|
||||
// packages/my-cap/tool-my-cap/src/index.ts
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
export const name = 'tool-my-cap'
|
||||
export const inject = ['tools', 'myCap']
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'my_cap',
|
||||
description: 'Execute my capability.',
|
||||
parameters: {
|
||||
input: { type: 'string', required: true },
|
||||
},
|
||||
async execute(args) {
|
||||
const result = await ctx.myCap.execute({ input: args.input })
|
||||
return [{ type: 'text', text: result.output }]
|
||||
},
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
### 在 cordis.yml 中组合
|
||||
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-my-cap-local'
|
||||
- name: '@deepseek-ai/dsh-tool-my-cap'
|
||||
```
|
||||
|
||||
## 设计要点
|
||||
|
||||
- **不要预防性拆分** — 只有当你确实需要可替换实现时才拆三件套。一个简单的 tool 插件不需要拆分。
|
||||
- **接口定义 Request/Result 类型** — 实现和消费者只依赖接口包。
|
||||
- **Explicit > Implicit** — 实现中的默认值处理应该是显式的 `resolve(request): Spec` 步骤,不是隐藏在 `run()` 中的 `?? default`。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [LLM 适配器](./llm-adapter.md) — 实现一个 LLM 后端(最常见的 seam 扩展)
|
||||
6
docs/user/develop/practice/llm-adapter.i18n.yaml
Normal file
6
docs/user/develop/practice/llm-adapter.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
llm-adapter.md: 3e83289b8072ef231f83c0fa3cfe3260547b42fa
|
||||
llm-adapter.zh.md: 92fcf9b22f4bb356ada4c46f9a03ef0cc2d159da
|
||||
186
docs/user/develop/practice/llm-adapter.md
Normal file
186
docs/user/develop/practice/llm-adapter.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# LLM adapters
|
||||
|
||||
English | [中文](llm-adapter.zh.md)
|
||||
|
||||
This guide connects a new LLM provider to Harness.
|
||||
|
||||
## Overview
|
||||
|
||||
An LLM adapter extends `LlmAdapter` and implements `stream()`, translating Harness's provider-neutral request into a provider API call and translating the response back into Harness chunks.
|
||||
|
||||
## Minimal implementation
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import Schema from 'schemastery'
|
||||
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
class MyAdapter extends LlmAdapter {
|
||||
private apiKey: string
|
||||
|
||||
constructor(apiKey: string) {
|
||||
super()
|
||||
this.apiKey = apiKey
|
||||
}
|
||||
|
||||
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
// 1. Convert options.messages to the provider format.
|
||||
// 2. Call the streaming API.
|
||||
// 3. Convert the response into StreamChunk values.
|
||||
}
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
apiKey: string
|
||||
models: string[]
|
||||
}
|
||||
|
||||
export const Config: Schema<Config> = Schema.object({
|
||||
apiKey: Schema.string().required(),
|
||||
models: Schema.array(Schema.string()).required(),
|
||||
})
|
||||
|
||||
export const name = 'my-llm-adapter'
|
||||
export const inject = ['llm']
|
||||
|
||||
export function apply(ctx: Context, config: Config) {
|
||||
const adapter = new MyAdapter(config.apiKey)
|
||||
ctx.llm.registerAdapter(config.models, adapter)
|
||||
}
|
||||
```
|
||||
|
||||
## StreamChunk protocol
|
||||
|
||||
`stream()` yields chunks using this protocol:
|
||||
|
||||
```ts
|
||||
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
// 1. Start each content block with block-start.
|
||||
yield { type: 'block-start', index: 0, blockType: 'text' }
|
||||
|
||||
// 2. Stream text through text-delta.
|
||||
yield { type: 'text-delta', index: 0, text: 'Hello' }
|
||||
yield { type: 'text-delta', index: 0, text: ' world' }
|
||||
|
||||
// 3. End each content block with block-end and the complete block.
|
||||
yield {
|
||||
type: 'block-end',
|
||||
index: 0,
|
||||
block: { type: 'text', text: 'Hello world' },
|
||||
}
|
||||
|
||||
// 4. Tool-call block.
|
||||
yield { type: 'block-start', index: 1, blockType: 'tool-call' }
|
||||
yield {
|
||||
type: 'tool-call-delta',
|
||||
index: 1,
|
||||
id: CallId('call-123'),
|
||||
name: 'bash',
|
||||
argumentsDelta: '{"command":"ls"}',
|
||||
}
|
||||
yield {
|
||||
type: 'block-end',
|
||||
index: 1,
|
||||
block: {
|
||||
type: 'tool-call',
|
||||
id: CallId('call-123'),
|
||||
name: 'bash',
|
||||
arguments: '{"command":"ls"}',
|
||||
},
|
||||
}
|
||||
|
||||
// 5. Token usage.
|
||||
yield { type: 'usage', usage: { inputTokens: 100, outputTokens: 50 } }
|
||||
|
||||
// 6. Finish reason.
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
// Alternatively, { kind: 'tool-calls' } requests tool execution.
|
||||
}
|
||||
```
|
||||
|
||||
### Key rules
|
||||
|
||||
- Every `block-start` has a matching `block-end`.
|
||||
- `index` increases from 0 and identifies content-block order.
|
||||
- A `tool-call-delta` carries raw JSON text in `argumentsDelta`, either all at once or over multiple chunks.
|
||||
- `finish` is the final chunk.
|
||||
- Emit `usage` before `finish`.
|
||||
|
||||
## GenerateOptions
|
||||
|
||||
`stream()` receives the exported `GenerateOptions` type. It includes the model, conversation history, system prompt, tool schemas, generation parameters, stop sequences, and abort signal; treat the TypeScript type exported by `@deepseek-ai/dsh-llm` as authoritative. Map supported fields to the provider API. If the provider cannot honor a field, throw `LlmError` with a stable code instead of silently dropping it.
|
||||
|
||||
## Register an adapter
|
||||
|
||||
```ts ignore-check
|
||||
ctx.llm.registerAdapter(['model-name-1', 'model-name-2'], adapter)
|
||||
```
|
||||
|
||||
The first argument lists the model names handled by the adapter. If `cordis.yml` selects `model: model-name-1`, the service routes that request to this adapter.
|
||||
|
||||
## Use it from cordis.yml
|
||||
|
||||
```yaml
|
||||
- id: my-llm
|
||||
name: './src/my-llm-adapter.ts'
|
||||
config:
|
||||
apiKey: !!js process.env.MY_API_KEY
|
||||
models:
|
||||
- my-model-v1
|
||||
- my-model-v2
|
||||
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: my-llm
|
||||
model: my-model-v1 # References the model registered above.
|
||||
workspaceContext: false
|
||||
```
|
||||
|
||||
## Reference implementations
|
||||
|
||||
The repository contains complete implementations:
|
||||
|
||||
- `packages/llm/llm-deepseek/` — DeepSeek API adapter using the OpenAI-compatible format
|
||||
- `packages/llm/llm-pi-ai/` — Pi AI adapter using a different API format
|
||||
|
||||
Compare the two shipped adapters to see the same harness contract implemented over different provider SDKs.
|
||||
|
||||
## Error handling
|
||||
|
||||
Adapters throw transport and protocol failures as `LlmError` values with stable codes. The agent loop preserves the error and code for diagnostics and policy; it does not convert an ordinary `Error` automatically. Every provider HTTP request must also merge `attributionHeaders()` and forward `options.signal`.
|
||||
|
||||
```ts
|
||||
import {
|
||||
attributionHeaders,
|
||||
LlmAdapter,
|
||||
LlmError,
|
||||
type GenerateOptions,
|
||||
type StreamChunk,
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
|
||||
class HttpAdapter extends LlmAdapter {
|
||||
constructor(private readonly endpoint: string) {
|
||||
super()
|
||||
}
|
||||
|
||||
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
const response = await fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
...attributionHeaders(),
|
||||
},
|
||||
body: JSON.stringify({ model: options.model, messages: options.messages }),
|
||||
...options.signal ? { signal: options.signal } : {},
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new LlmError(`Provider API error: ${response.status}`, 'PROVIDER_HTTP_ERROR')
|
||||
}
|
||||
// A real adapter parses the response and emits the complete chunk sequence.
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
}
|
||||
}
|
||||
```
|
||||
186
docs/user/develop/practice/llm-adapter.zh.md
Normal file
186
docs/user/develop/practice/llm-adapter.zh.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# LLM 适配器
|
||||
|
||||
[English](llm-adapter.md) | 中文
|
||||
|
||||
本文介绍如何为 Harness 接入一个新的 LLM 提供方。
|
||||
|
||||
## 概述
|
||||
|
||||
LLM 适配器是一个继承 `LlmAdapter` 的类,实现 `stream()` 方法,将 Harness 的统一请求格式转换为具体 API 的调用。
|
||||
|
||||
## 最小实现
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import Schema from 'schemastery'
|
||||
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
class MyAdapter extends LlmAdapter {
|
||||
private apiKey: string
|
||||
|
||||
constructor(apiKey: string) {
|
||||
super()
|
||||
this.apiKey = apiKey
|
||||
}
|
||||
|
||||
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
// 1. Convert options.messages to the provider format.
|
||||
// 2. Call the streaming API.
|
||||
// 3. Convert the response into StreamChunk values.
|
||||
}
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
apiKey: string
|
||||
models: string[]
|
||||
}
|
||||
|
||||
export const Config: Schema<Config> = Schema.object({
|
||||
apiKey: Schema.string().required(),
|
||||
models: Schema.array(Schema.string()).required(),
|
||||
})
|
||||
|
||||
export const name = 'my-llm-adapter'
|
||||
export const inject = ['llm']
|
||||
|
||||
export function apply(ctx: Context, config: Config) {
|
||||
const adapter = new MyAdapter(config.apiKey)
|
||||
ctx.llm.registerAdapter(config.models, adapter)
|
||||
}
|
||||
```
|
||||
|
||||
## StreamChunk 协议
|
||||
|
||||
`stream()` 必须按以下协议 yield chunk:
|
||||
|
||||
```ts
|
||||
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
async function* exampleChunks(): AsyncIterable<StreamChunk> {
|
||||
// 1. Start each content block with block-start.
|
||||
yield { type: 'block-start', index: 0, blockType: 'text' }
|
||||
|
||||
// 2. Stream text through text-delta.
|
||||
yield { type: 'text-delta', index: 0, text: 'Hello' }
|
||||
yield { type: 'text-delta', index: 0, text: ' world' }
|
||||
|
||||
// 3. End each content block with block-end and the complete block.
|
||||
yield {
|
||||
type: 'block-end',
|
||||
index: 0,
|
||||
block: { type: 'text', text: 'Hello world' },
|
||||
}
|
||||
|
||||
// 4. Tool-call block.
|
||||
yield { type: 'block-start', index: 1, blockType: 'tool-call' }
|
||||
yield {
|
||||
type: 'tool-call-delta',
|
||||
index: 1,
|
||||
id: CallId('call-123'),
|
||||
name: 'bash',
|
||||
argumentsDelta: '{"command":"ls"}',
|
||||
}
|
||||
yield {
|
||||
type: 'block-end',
|
||||
index: 1,
|
||||
block: {
|
||||
type: 'tool-call',
|
||||
id: CallId('call-123'),
|
||||
name: 'bash',
|
||||
arguments: '{"command":"ls"}',
|
||||
},
|
||||
}
|
||||
|
||||
// 5. Token usage.
|
||||
yield { type: 'usage', usage: { inputTokens: 100, outputTokens: 50 } }
|
||||
|
||||
// 6. Finish reason.
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
// Alternatively, { kind: 'tool-calls' } requests tool execution.
|
||||
}
|
||||
```
|
||||
|
||||
### 关键规则
|
||||
|
||||
- 每个 `block-start` 必须有对应的 `block-end`
|
||||
- `index` 从 0 递增,标识内容块顺序
|
||||
- `tool-call-delta` 的 `argumentsDelta` 是 JSON 字符串的增量(可以一次 yield 全部,也可以分多次)
|
||||
- `finish` 必须是最后一个 chunk
|
||||
- `usage` 在 `finish` 之前 yield
|
||||
|
||||
## GenerateOptions
|
||||
|
||||
`stream()` 接收仓库导出的 `GenerateOptions`。它包含模型名、对话历史、系统提示词、tool schema、生成参数、停止序列和中止信号;完整字段以 `@deepseek-ai/dsh-llm` 导出的 TypeScript 类型为准。适配器必须将支持的字段映射到具体 API;无法支持的字段应抛出带稳定 code 的 `LlmError`,不能静默丢弃。
|
||||
|
||||
## 注册适配器
|
||||
|
||||
```ts ignore-check
|
||||
ctx.llm.registerAdapter(['model-name-1', 'model-name-2'], adapter)
|
||||
```
|
||||
|
||||
第一个参数是该适配器支持的模型名列表。当用户在 `cordis.yml` 中配置 `model: model-name-1` 时,框架会路由到这个适配器。
|
||||
|
||||
## 在 cordis.yml 中使用
|
||||
|
||||
```yaml
|
||||
- id: my-llm
|
||||
name: './src/my-llm-adapter.ts'
|
||||
config:
|
||||
apiKey: !!js process.env.MY_API_KEY
|
||||
models:
|
||||
- my-model-v1
|
||||
- my-model-v2
|
||||
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: my-llm
|
||||
model: my-model-v1 # References the model registered above.
|
||||
workspaceContext: false
|
||||
```
|
||||
|
||||
## 实战参考
|
||||
|
||||
仓库中有两个完整实现可供参考:
|
||||
|
||||
- `packages/llm/llm-deepseek/` — DeepSeek API 适配器(OpenAI 兼容格式)
|
||||
- `packages/llm/llm-pi-ai/` — Pi AI 适配器(不同的 API 格式)
|
||||
|
||||
对比这两个已交付的适配器,可以看到同一套 harness 契约如何在不同提供方 SDK 之上实现。
|
||||
|
||||
## 错误处理
|
||||
|
||||
适配器应将传输和协议故障作为带稳定 code 的 `LlmError` 抛出;agent loop 会保留该错误及其 code,供诊断和策略使用。不要依赖普通 `Error` 被自动转换。每个提供方 HTTP 请求还必须合并 `attributionHeaders()`,并传递 `options.signal`。
|
||||
|
||||
```ts
|
||||
import {
|
||||
attributionHeaders,
|
||||
LlmAdapter,
|
||||
LlmError,
|
||||
type GenerateOptions,
|
||||
type StreamChunk,
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
|
||||
class HttpAdapter extends LlmAdapter {
|
||||
constructor(private readonly endpoint: string) {
|
||||
super()
|
||||
}
|
||||
|
||||
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
const response = await fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
...attributionHeaders(),
|
||||
},
|
||||
body: JSON.stringify({ model: options.model, messages: options.messages }),
|
||||
...options.signal ? { signal: options.signal } : {},
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new LlmError(`Provider API error: ${response.status}`, 'PROVIDER_HTTP_ERROR')
|
||||
}
|
||||
// A real adapter parses the response and emits the complete chunk sequence.
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
}
|
||||
}
|
||||
```
|
||||
6
docs/user/guide/config.i18n.yaml
Normal file
6
docs/user/guide/config.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
config.md: 8958729d04224215ca420c3103d253a8a5783405
|
||||
config.zh.md: 530f2b335453d5064acdac28a60d7df51cd915f0
|
||||
64
docs/user/guide/config.md
Normal file
64
docs/user/guide/config.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Configuration
|
||||
|
||||
English | [中文](config.zh.md)
|
||||
|
||||
Harness uses `cordis.yml` to describe which plugins an agent loads and the configuration passed to each one. The file composes capabilities; the generated configuration catalog records the fields and defaults each package actually supports, avoiding a second hand-maintained reference.
|
||||
|
||||
## Start from a real configuration
|
||||
|
||||
The repository examples are runnable configurations and the most reliable starting points for a new project:
|
||||
|
||||
- [tui-agent](../../../examples/tui-agent/cordis.yml) combines the DeepSeek model, Bash, filesystem, compaction, subagents, workflows, and the interactive TUI.
|
||||
- [headless-agent](../../../examples/headless-agent/cordis.yml) exposes the coding composition as a one-shot task.
|
||||
- [acp-agent](../../../examples/acp-agent/cordis.yml) connects to editor clients over ACP.
|
||||
|
||||
A minimal configuration is a list of plugin entries:
|
||||
|
||||
```yaml
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
models:
|
||||
- deepseek-v4-flash
|
||||
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
workspaceContext: false
|
||||
```
|
||||
|
||||
## Plugin entries
|
||||
|
||||
`name` identifies an npm package or a local module relative to `cordis.yml`; `id` gives the plugin instance a stable identity; and `config` supplies plugin-specific configuration. Set `disabled: true` to skip an entry temporarily.
|
||||
|
||||
```yaml
|
||||
- id: local-tool
|
||||
name: './src/my-tool.ts'
|
||||
disabled: false
|
||||
config:
|
||||
toolName: my_tool
|
||||
```
|
||||
|
||||
Plugins load in file order. Place plugins that depend on services after the applications or capability plugins that provide them. Missing models, tools, and plugins fail as early as possible instead of being silently ignored.
|
||||
|
||||
## JavaScript values and environment variables
|
||||
|
||||
The Cordis loader evaluates runtime expressions tagged with `!!js`. Keep API keys and other secrets in the gitignored `.env` file at the repository root, never in committed configuration.
|
||||
|
||||
```yaml
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
cwd: !!js process.cwd()
|
||||
```
|
||||
|
||||
The tag is `!!js`, not `!js`.
|
||||
|
||||
## Exact configuration reference
|
||||
|
||||
The generated [plugin configuration catalog](../../config-catalog.md) lists every current field, type, and default. For composition concepts, continue to the [architecture](../../architecture.md) and [capability interfaces](../../capability-seams.md). To create a configuration, copy the closest entry from the [examples overview](../../../examples/README.md) and adapt it.
|
||||
64
docs/user/guide/config.zh.md
Normal file
64
docs/user/guide/config.zh.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# 配置文件
|
||||
|
||||
[English](config.md) | 中文
|
||||
|
||||
Harness 使用 `cordis.yml` 描述 Agent 加载哪些插件以及每个插件的参数。配置文件负责组合能力;每个包真正支持的字段和默认值由源码生成的配置目录负责记录,避免两份手写表格逐渐不一致。
|
||||
|
||||
## 从真实配置开始
|
||||
|
||||
仓库中的示例就是可以运行的配置,也是新项目最可靠的起点:
|
||||
|
||||
- [tui-agent](../../../examples/tui-agent/cordis.yml) 组合 DeepSeek 模型、Bash、文件系统、压缩、子代理、工作流和交互式 TUI。
|
||||
- [headless-agent](../../../examples/headless-agent/cordis.yml) 以单次任务形式暴露 coding 组装。
|
||||
- [acp-agent](../../../examples/acp-agent/cordis.yml) 通过 ACP 接入编辑器客户端。
|
||||
|
||||
最小配置由一组插件条目组成:
|
||||
|
||||
```yaml
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
models:
|
||||
- deepseek-v4-flash
|
||||
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
workspaceContext: false
|
||||
```
|
||||
|
||||
## 插件条目
|
||||
|
||||
`name` 指定 npm 包或相对于 `cordis.yml` 的本地模块,`id` 为插件实例提供稳定标识,`config` 传入插件自己的配置。需要临时跳过某个条目时可设置 `disabled: true`。
|
||||
|
||||
```yaml
|
||||
- id: local-tool
|
||||
name: './src/my-tool.ts'
|
||||
disabled: false
|
||||
config:
|
||||
toolName: my_tool
|
||||
```
|
||||
|
||||
插件按文件中的顺序加载。依赖其他服务的插件应该排在提供这些服务的应用或能力插件之后;引用不存在的模型、工具或插件会尽早报错,而不是被静默忽略。
|
||||
|
||||
## JavaScript 值和环境变量
|
||||
|
||||
Cordis loader 使用 `!!js` 标签读取运行时表达式。API key 等凭据应放在仓库根目录、已被 Git 忽略的 `.env` 中,不能提交到配置文件。
|
||||
|
||||
```yaml
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
cwd: !!js process.cwd()
|
||||
```
|
||||
|
||||
标签是 `!!js`,不是 `!js`。
|
||||
|
||||
## 精确配置参考
|
||||
|
||||
每个插件当前支持的字段、类型和默认值见自动生成的[插件配置目录](../../config-catalog.md)。理解插件如何组合可继续阅读[架构说明](../../architecture.md)和[能力接口](../../capability-seams.md);要创建自己的配置,优先复制并修改[示例目录说明](../../../examples/README.md)中最接近的例子。
|
||||
6
docs/user/guide/index.i18n.yaml
Normal file
6
docs/user/guide/index.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
index.md: b698b8aeee6cebff374e20ca0f76ddc9e75213c0
|
||||
index.zh.md: 337d246baa12ccf6d7a9656d1ea3b06002554c13
|
||||
51
docs/user/guide/index.md
Normal file
51
docs/user/guide/index.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Introduction
|
||||
|
||||
English | [中文](index.zh.md)
|
||||
|
||||
DeepSeek Harness is a **plugin-based agent development framework** built on the [Cordis](https://github.com/cordiverse/cordis) microkernel. Its central idea is simple: **everything is a plugin**.
|
||||
|
||||
## What it is
|
||||
|
||||
Harness implements every capability an AI agent needs—including LLM calls, tool execution, session management, and subtask delegation—as a composable plugin. A `cordis.yml` file declares which plugins to load and how to configure them, assembling a complete agent.
|
||||
|
||||
```yaml
|
||||
# Select the LLM backend
|
||||
- name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
|
||||
# Select the interactive application
|
||||
- name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
workspaceContext: false
|
||||
```
|
||||
|
||||
## Who it is for
|
||||
|
||||
### Application users
|
||||
|
||||
To run an existing agent application, such as a coding assistant or conversational agent:
|
||||
|
||||
1. Copy an example template.
|
||||
2. Add an API key.
|
||||
3. Run it.
|
||||
|
||||
No code is required. See the [quick start](./quickstart.md).
|
||||
|
||||
### Plugin developers
|
||||
|
||||
To add a custom tool, a new LLM adapter, or another execution backend, write a plugin. Harness provides explicit extension interfaces and a type-safe development experience. See [development](../develop/basic/).
|
||||
|
||||
## Core features
|
||||
|
||||
- **Configuration only** — `cordis.yml` selects the capability set; changing a model or adding a tool is a configuration edit.
|
||||
- **Hot replacement (HMR)** — edit plugin code during development without restarting the process.
|
||||
|
||||
## Technology
|
||||
|
||||
- **Runtime**: Node.js ^22.19 or >= 24
|
||||
- **Language**: TypeScript (ESM)
|
||||
- **Framework**: Cordis
|
||||
- **Package manager**: pnpm workspaces (the repository pins pnpm 11)
|
||||
51
docs/user/guide/index.zh.md
Normal file
51
docs/user/guide/index.zh.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 介绍
|
||||
|
||||
[English](index.md) | 中文
|
||||
|
||||
DeepSeek Harness 是一个**插件化的 Agent 开发框架**,基于 [Cordis](https://github.com/cordiverse/cordis) 微内核构建。它的核心理念是:**一切皆插件**。
|
||||
|
||||
## 它是什么
|
||||
|
||||
Harness 将一个 AI Agent(智能体) 所需要的所有能力——LLM 调用、工具执行、会话管理、子任务分配——全部构建为可组合的插件。你通过一个 `cordis.yml` 配置文件来声明加载哪些插件、使用什么参数,就能组装出一个完整的 Agent。
|
||||
|
||||
```yaml
|
||||
# Select the LLM backend
|
||||
- name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
|
||||
# Select the interactive application
|
||||
- name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
workspaceContext: false
|
||||
```
|
||||
|
||||
## 适合谁
|
||||
|
||||
### 应用使用者
|
||||
|
||||
如果你只是想用一个现成的 Agent 应用(如编程助手、对话代理),你需要的全部操作就是:
|
||||
|
||||
1. 复制一个 example 模板
|
||||
2. 填写 API key
|
||||
3. 运行
|
||||
|
||||
不需要写任何代码。详见 [快速开始](./quickstart.md)。
|
||||
|
||||
### 插件开发者
|
||||
|
||||
如果你想为 Agent 添加新能力——一个自定义 tool、一个新的 LLM 适配器、一个新的执行后端——你需要编写一个插件。Harness 提供了清晰的扩展接口和类型安全的开发体验。详见 [开发](../develop/basic/)。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- **只需要配置** — `cordis.yml` 决定能力集合,换模型、加工具只需改一行
|
||||
- **随时替换 (HMR)** — 开发时修改插件代码,无需重启进程
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **运行时**: Node.js ^22.19 或 >= 24
|
||||
- **语言**: TypeScript (ESM)
|
||||
- **框架**: Cordis
|
||||
- **包管理**: pnpm workspaces(仓库固定使用 pnpm 11)
|
||||
6
docs/user/guide/quickstart.i18n.yaml
Normal file
6
docs/user/guide/quickstart.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
quickstart.md: 25ce51ee3d010d2eb800071b9697fc62857dace1
|
||||
quickstart.zh.md: e2e023670a999566e273d8893c42103dc273e7b1
|
||||
60
docs/user/guide/quickstart.md
Normal file
60
docs/user/guide/quickstart.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Quick start
|
||||
|
||||
English | [中文](quickstart.zh.md)
|
||||
|
||||
This guide gets an agent running in five minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) ^22.19 or >= 24
|
||||
- [pnpm](https://pnpm.io/) 11 through Corepack
|
||||
- A [DeepSeek Platform](https://platform.deepseek.com/) API key
|
||||
|
||||
```sh
|
||||
node -v
|
||||
corepack enable
|
||||
pnpm -v
|
||||
```
|
||||
|
||||
## Step 1: install and configure the API key
|
||||
|
||||
```sh
|
||||
git clone https://github.com/deepseek-harness/deepseek-harness.git
|
||||
cd deepseek-harness
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Create the gitignored repository-root `.env`:
|
||||
|
||||
```sh
|
||||
DEEPSEEK_API_KEY=sk-your-key-here
|
||||
```
|
||||
|
||||
## Step 2: run one Headless task
|
||||
|
||||
Run a non-interactive task and print its final answer:
|
||||
|
||||
```sh
|
||||
pnpm run demo:headless "summarize the architecture of this workspace"
|
||||
```
|
||||
|
||||
Headless runs one complete model/tool turn, persists the session, prints the result, and exits. Use `--output-format stream-json` when you need the canonical event stream.
|
||||
|
||||
## Step 3: use the TUI
|
||||
|
||||
Start the interactive coding agent:
|
||||
|
||||
```sh
|
||||
pnpm run demo:tui
|
||||
```
|
||||
|
||||
The full-screen agent can read and write files, run commands, delegate subtasks, and track a plan. Try: `Create hello.js in the current directory, print "Hello from Harness!", and run it`.
|
||||
|
||||
## What happened
|
||||
|
||||
headless-agent uses the `@deepseek-ai/dsh-cli-demo` app; tui-agent uses the interactive `@deepseek-ai/dsh-tui-demo` app. Both load the same providerless agent spine, while their `cordis.yml` files select the DeepSeek model and capability plugins appropriate to each surface.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Configuration](./config.md) — understand the `cordis.yml` format
|
||||
- [Develop a plugin](../develop/basic/) — build your own tool or backend
|
||||
60
docs/user/guide/quickstart.zh.md
Normal file
60
docs/user/guide/quickstart.zh.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 快速开始
|
||||
|
||||
[English](quickstart.md) | 中文
|
||||
|
||||
本指南带你在 5 分钟内跑起一个 Agent。
|
||||
|
||||
## 环境准备
|
||||
|
||||
- [Node.js](https://nodejs.org/) ^22.19 或 >= 24
|
||||
- 通过 Corepack 使用 [pnpm](https://pnpm.io/) 11
|
||||
- [DeepSeek Platform](https://platform.deepseek.com/) API key
|
||||
|
||||
```sh
|
||||
node -v
|
||||
corepack enable
|
||||
pnpm -v
|
||||
```
|
||||
|
||||
## 第一步:安装并配置 API key
|
||||
|
||||
```sh
|
||||
git clone https://github.com/deepseek-harness/deepseek-harness.git
|
||||
cd deepseek-harness
|
||||
pnpm install
|
||||
```
|
||||
|
||||
在仓库根目录创建已被 Git 忽略的 `.env`:
|
||||
|
||||
```sh
|
||||
DEEPSEEK_API_KEY=sk-your-key-here
|
||||
```
|
||||
|
||||
## 第二步:运行一个 Headless 任务
|
||||
|
||||
运行一个非交互式任务并打印最终回答:
|
||||
|
||||
```sh
|
||||
pnpm run demo:headless "summarize the architecture of this workspace"
|
||||
```
|
||||
|
||||
Headless 运行一个完整的模型/工具轮次,持久化会话,打印结果后退出。需要规范事件流时可使用 `--output-format stream-json`。
|
||||
|
||||
## 第三步:使用 TUI
|
||||
|
||||
启动交互式 coding agent:
|
||||
|
||||
```sh
|
||||
pnpm run demo:tui
|
||||
```
|
||||
|
||||
这个全屏 Agent 可以读写文件、运行命令、分配子任务和跟踪计划。可以尝试:`Create hello.js in the current directory, print "Hello from Harness!", and run it`。
|
||||
|
||||
## 回头看
|
||||
|
||||
headless-agent 使用 `@deepseek-ai/dsh-cli-demo` app,tui-agent 使用交互式 `@deepseek-ai/dsh-tui-demo` app。二者加载同一个 providerless agent spine,并通过各自的 `cordis.yml` 为对应 surface 选择 DeepSeek 模型和能力插件。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [配置文件](./config.md) — 了解 `cordis.yml` 的格式
|
||||
- [开发插件](../develop/basic/) — 编写自己的 tool 或后端
|
||||
6
docs/user/index.i18n.yaml
Normal file
6
docs/user/index.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write
|
||||
index.md: e9a1f03785c7472c47550ec59ea0165d28d3d9a6
|
||||
index.zh.md: 907f1452c9ff50d619989c18dcf2727addb2573d
|
||||
25
docs/user/index.md
Normal file
25
docs/user/index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
layout: home
|
||||
hero:
|
||||
name: DeepSeek Harness
|
||||
text: Plugin-based agent development framework
|
||||
tagline: Built on the Cordis microkernel; everything is a plugin
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Quick start
|
||||
link: /en/guide/quickstart
|
||||
- theme: alt
|
||||
text: Develop plugins
|
||||
link: /en/develop/basic/
|
||||
features:
|
||||
- title: Plugin architecture
|
||||
details: Built on the Cordis plugin system. Every capability is registered by a plugin, takes effect when loaded, and is reverted when unloaded.
|
||||
- title: Configuration as composition
|
||||
details: One cordis.yml determines the agent's complete capability set. Change a model or add a tool by editing configuration.
|
||||
- title: Ready to use
|
||||
details: Includes LLM calls, file access, Bash execution, subagent delegation, and the rest of the core toolchain. Copy a template to get started.
|
||||
---
|
||||
|
||||
# DeepSeek Harness
|
||||
|
||||
English | [中文](index.zh.md)
|
||||
25
docs/user/index.zh.md
Normal file
25
docs/user/index.zh.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
layout: home
|
||||
hero:
|
||||
name: DeepSeek Harness
|
||||
text: 插件化 Agent 开发框架
|
||||
tagline: 基于 Cordis 微内核,一切皆插件
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 快速开始
|
||||
link: /guide/quickstart
|
||||
- theme: alt
|
||||
text: 开发插件
|
||||
link: /develop/basic/
|
||||
features:
|
||||
- title: 插件化架构
|
||||
details: 基于 Cordis 插件系统,所有能力通过插件注册,加载即生效、卸载即还原。
|
||||
- title: 配置即组合
|
||||
details: 一个 cordis.yml 决定整个 Agent 的能力组合——换模型、加工具,只需改一行配置。
|
||||
- title: 开箱即用
|
||||
details: 内置 LLM 调用、文件读写、Bash 执行、子代理委派等完整工具链,复制模板即可运行。
|
||||
---
|
||||
|
||||
# DeepSeek Harness
|
||||
|
||||
[English](index.md) | 中文
|
||||
107
docs/web-styling.md
Normal file
107
docs/web-styling.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Web GUI 样式规范
|
||||
|
||||
> **【token 体系已换代——§1 表格仅历史参考】** 本文的 `--bg-*`/`--text-*`/`--accent` token 族与其宿主包 `packages/client/web-ui` 已随插件化重构退役。现行 token 唯一来源=`packages/client/ui-theme/src/styles/` 的 `--dsw-*` 体系(static 色阶+alias 语义层,暗色=`body[data-ds-dark-theme]` 覆写);组件对账基准=`missions/tasks/20260721-1520-web-plugin-rfc/style-spec.md`。**仍然有效**:工程约束(CSS Modules + clsx、无组件库、无 tailwind、组件禁 hardcode 色值)、字号成对写行高、间距 4 倍数、代码字体栈末位不放 monospace——这些已收编进 architecture.md §15。
|
||||
|
||||
> 状态:原「活文档」(随 `packages/client/web-ui` 演进)。视觉基线源自对 deepseekchat 前端仓的实测调研。框架决策与工程约束由 [web-styling-system RFC](../.agents/notes/implemented/process/2026-07-19-web-styling-system.md) 拍板,本文不重复论证。
|
||||
|
||||
## 1. 设计 token 表(权威定义)
|
||||
|
||||
所有 token 住 `packages/client/web-ui/src/style/global.css`:`:root` 亮色实值,`[data-theme='dark']` 块覆盖同名变量(未补全前列为占位)。组件 CSS 只引 token,不出现字面量色值。
|
||||
|
||||
### 1.1 颜色(两层:注释里是 base 色板出处,变量名即语义别名)
|
||||
|
||||
| token | 亮色实值 | 暗色(占位) | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--bg-base` | `#ffffff` | `#151517` | 页面底 |
|
||||
| `--bg-layer` | `#ffffff` | `#232324` | 浮层/面板 |
|
||||
| `--bg-sidebar` | `#f9fafb` | `#1b1b1c` | 侧边栏底 |
|
||||
| `--text-primary` | `#0f1115` | `#f9fafb` | 正文 |
|
||||
| `--text-secondary` | `#61666b` | `#cfd3d6` | 次要文字 |
|
||||
| `--text-tertiary` | `#81858c` | `#adb2b8` | 辅助/说明 |
|
||||
| `--border-l1` | `rgba(0,0,0,.04)` | `rgba(255,255,255,.06)` | 弱分隔(侧边栏右缘) |
|
||||
| `--border-l2` | `rgba(0,0,0,.1)` | `rgba(255,255,255,.12)` | 常规边框 |
|
||||
| `--hover-bg` | `rgba(38,49,72,.06)` | `rgba(255,255,255,.08)` | hover 态底 |
|
||||
| `--active-bg` | `rgba(38,49,72,.1)` | `rgba(255,255,255,.14)` | 按压/激活态底 |
|
||||
| `--accent` | `#3964fe` | `#5686fe` | 品牌蓝(deepseek-500;暗提亮一档) |
|
||||
| `--accent-soft` | `#edf3fe` | `#28313f` | 淡品牌底(强调块) |
|
||||
| `--accent-item` | `#e4edfd` | `#35363a` | 侧边栏选中条目底 |
|
||||
| `--bubble-bg` | `#edf3fe` | `#2c2c2e` | 用户消息气泡底 |
|
||||
| `--ok` / `--error` / `--warn` | `#22c55e` / `#ec1313` / `#f59e0b` | 同值 | 语义状态色 |
|
||||
| `--text-on-solid` | `#ffffff` | 同值 | 实色底(accent/error 徽标等)上的文字 |
|
||||
| `--ok-soft` / `--error-soft` | `#e6faed` / `#fee2e2` | `#233c2c` / `#570c0c` | 语义状态软底(徽章);green-100/red-100,暗为 900 档 |
|
||||
| `--color-frame-mux` / `--color-frame-host` | `#8250df` / `#0969da` | 同值 | RPC 调试面板方向色(自有,非基线) |
|
||||
| `--frame-mux-soft` / `--frame-host-soft` | `rgba(130,80,223,.1)` / `rgba(9,105,218,.1)` | 同色 `.24` | 方向色软底(徽章) |
|
||||
| `--scroll-color` / `--scroll-color-hover` | `rgba(0,0,0,.08)` / `.15` | `rgba(255,255,255,.15)` / `.24` | 滚动条(`.scrollable` 专用) |
|
||||
|
||||
### 1.2 非颜色
|
||||
|
||||
| token | 值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `--font-ui` | `Inter, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif` | 正文栈 |
|
||||
| `--font-mono` | `Menlo, Monaco, Consolas, 'JetBrains Mono', 'Courier New', sans-serif` | 代码栈;**末位不放 monospace**(防 Windows 中文回退宋体) |
|
||||
| `--fw-strong` | `600` | 粗体统一权重 |
|
||||
| `--ease` | `cubic-bezier(.4,0,.2,1)` | 唯一缓动曲线 |
|
||||
| `--dur` / `--dur-fast` / `--dur-slow` | `.2s` / `.1s` / `.3s` | 过渡三档 |
|
||||
| `--radius-s` / `--radius-m` / `--radius-l` / `--radius-bubble` / `--radius-xl` | `8px` / `12px` / `16px` / `22px` / `24px` | 圆角语义档:小控件 / 列表条目与面板内块 / 浮层 / 气泡 / 输入卡片(基线 inputWrapper 同值);胶囊直接写 `999px` |
|
||||
| `--shadow-panel` | `0 0 1px rgba(0,0,0,.2), 0 0 4px rgba(0,0,0,.02), 0 12px 32px rgba(0,0,0,.08)` | 浮层阴影(基线 lv3) |
|
||||
| `--shadow-float` | `0 0 1px rgba(0,0,0,.24), 0 4px 12px rgba(0,0,0,.06), 0 16px 48px rgba(0,0,0,.16)` | 强浮动面板(lv3 加强档,如 RPC 调试浮层) |
|
||||
| `--shadow-card` | `0 4px 10px rgba(0,0,0,.02), 0 2px 4px rgba(0,0,0,.04)`;暗色 `none` | 输入卡片微阴影(基线:亮色同底靠边框+微影区分,暗色靠提亮底、阴影关闭) |
|
||||
|
||||
字号与间距**不 token 化**(基线仓同款决策):字号在组件里写 px 且**成对写行高**,常用对 16/24(气泡)、14/22(UI 默认)、12/18(辅助);间距用 4 的倍数。
|
||||
|
||||
## 2. 视觉基线(源自 deepseekchat)
|
||||
|
||||
- 侧边栏:宽 `260px + 1px` 右边框(`--border-l1`);底色 `--bg-sidebar`。
|
||||
- 侧边栏条目:高 `40px`、圆角 `--radius-m`、字号 14px;hover 底 `--hover-bg` 或 sidebar 专属灰、**选中底 `--accent-item` 且不改文字色**。
|
||||
- 侧边栏分组标题:12px / weight 500 / `--text-tertiary` / sticky 顶部(底色同侧边栏遮滚动内容)。
|
||||
- 会话列:`max-width: 840px` 居中,<1024px 降 712px。
|
||||
- 消息流:**仅用户侧有气泡**——`--bubble-bg` 底、圆角 `--radius-bubble`、padding `10px 16px`、字号 16px/24px、`max-width: calc(100% - 88px)`;**助手侧纯文档流无底色**。
|
||||
- 消息操作条:默认 `opacity: 0`,父块 hover/focus-within 淡入(`--dur` + `--ease`)。
|
||||
- 输入卡片:与会话列同宽(840px,<1024px 降 712px)居中悬浮(距底留白带);圆角 `--radius-xl`、边框 `--border-l2`、底 `--bg-base`、阴影 `--shadow-card`;内部上下两段=textarea(16px/24px,min 2 行 max 14 行=336px,镜像 div 自增高)+ 操作行(右下嵌 34px 主圆钮);focus 无边框/阴影变化(基线同款)。
|
||||
- 输入主按钮(拍板 2026-07-20 三连,视觉参照 Codex App):32px 实心正圆图标钮(内联 SVG)——空闲=`--accent` 底白↑箭头「发送」,运行中原地变 `--accent-soft` 底 accent ■「停止」(同色系不告警、不用红)。**运行中锁输入**(拍板 3,取代早先 hover 菜单方案):textarea disabled(灰、草稿内容保留可见)、无任何排队/插话菜单,停止是唯一动作;turn 结束解禁并 refocus。键盘 Enter=发送、Ctrl/Meta+Enter=换行(运行中键盘路径随锁失效)。
|
||||
- 滚动条:近隐形、hover 加深、`scrollbar-gutter: stable` 不占布局(统一走 `.scrollable`,见 §3-9)。
|
||||
- RPC 四象限方向符(官方视觉词汇,空间隐喻:上=去 server、下=来自 server;单线=unary、双线=SSE):
|
||||
|
||||
| 符号 | 象限 | 徽章配色 |
|
||||
| --- | --- | --- |
|
||||
| `↑` | client-request(unary 出站) | `--accent` / `--accent-soft` |
|
||||
| `↓` | server-response(unary 回包) | ok `--ok`/`--ok-soft`,error `--error`/`--error-soft` |
|
||||
| `⇟` | server-request(SSE 帧推送) | mux `--color-frame-mux`/`--frame-mux-soft`,host `--color-frame-host`/`--frame-host-soft` |
|
||||
| `⇞` | client-response(SSE 侧回应) | `--accent`/`--accent-soft` 降透明度 |
|
||||
|
||||
## 3. 样式编码规范(review 对照打勾)
|
||||
|
||||
1. 颜色/圆角/动效/字体栈只引 §1 token;组件 CSS 出现字面量色值即打回(渐变遮罩等特效除外,须注释说明)。
|
||||
2. 组件 CSS 禁止出现 `[data-theme]` 选择器;暗色差异只在 global.css token 表做。确需按主题换非 token 值(渐变端点等),组件定义局部 CSS 变量、主题块只覆写变量(变量桥)。
|
||||
3. 类名 camelCase;状态类用单形容词(`.active` `.show`),由 clsx 挂载:`clsx(styles.x, cond && styles.active, className)`。
|
||||
4. 对外组件必须透传 `className` 并合入根元素。
|
||||
5. 禁用 `composes`;复用靠 token 与组件抽取。
|
||||
6. `:global` 仅用于穿透第三方/跨包类名;禁止用它定义新全局类。
|
||||
7. 交互过渡一律 `var(--dur*) var(--ease)`,只过渡 opacity / transform / 背景色 / 阴影;纯 hover 展示型元素包 `@media (hover: hover)`。
|
||||
8. hover/active 底色优先用透明度制 token(叠任意海拔底色都成立),不新造实色灰。
|
||||
9. 滚动容器统一挂 global.css 的 `.scrollable` 工具类;组件内禁写 `::-webkit-scrollbar`。
|
||||
10. 媒体查询写在组件 css 尾部、贴着被覆盖规则;断点当前仅 1024px 一档(会话列降档),加第二档需先记入本文档。
|
||||
11. 动态样式 JS 侧只写 CSS 变量(`style={{'--x': v}}`),规则留在 CSS;禁止在 TSX 里拼接样式对象做主题/状态分支。
|
||||
12. 文字灰阶只用 `--text-primary/secondary/tertiary` 三级,不新造灰色。
|
||||
|
||||
## 4. 文件组织
|
||||
|
||||
- `src/style/global.css` 固定分区顺序:① token 表(`:root` + `[data-theme='dark']`)② 全局基础(box-sizing、body、button reset)③ 全局工具类(`.scrollable` 等,总数保持个位数)。
|
||||
- `*.module.css` 与组件同目录同名;一个组件一个 module 文件。
|
||||
- 类型声明用现有 `css-modules.d.ts` 通配;组件数超 20 再评估引入 tcm 生成精确 `.css.d.ts`。
|
||||
- PostCSS 特性白名单:当前**零插件**(平铺 CSS + 原生嵌套按需);引入 nested/custom-media 需先记入本文档。
|
||||
|
||||
## 5. 演进规则与偏离记录
|
||||
|
||||
- **加新 token**:先进 §1 表(含暗色占位列)再在组件使用;review 见到未入表的 `--` 新变量即打回(组件局部变量桥除外)。
|
||||
- **偏离基线**:与 §2 任一常数不一致的实现,须在下方偏离表记一行(日期/项/理由)。
|
||||
- **暗色表补全验收**:`[data-theme='dark']` 覆盖 §1 全部占位列后,用 RPC 面板 + 侧边栏 + 会话流三个界面人工/截图核对一遍,无组件级主题选择器即达标。
|
||||
|
||||
| 日期 | 偏离项 | 理由 |
|
||||
| --- | --- | --- |
|
||||
| (空) | | |
|
||||
|
||||
## 6. 相关文档
|
||||
|
||||
- [web-styling-system RFC](../.agents/notes/implemented/process/2026-07-19-web-styling-system.md)(框架五条与工程约束的裁决记录)
|
||||
- 客户端消费架构与分层协议:[Web 客户端架构 RFC](../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md)、[GUI 分层与 RPC 协议 RFC](../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)
|
||||
Reference in New Issue
Block a user