Merge remote-tracking branch 'origin/master' into worktree/pr468-retarget-latest-master
# Conflicts: # docs/architecture.i18n.yaml # docs/config-catalog.md # docs/event-producer-consumer.md # docs/module-graph.md # packages/examples/acp-demo/README.md # packages/examples/acp-demo/src/index.ts # packages/examples/acp-demo/tests/built-bin.e2e.ts # packages/examples/tui-demo/package.json # packages/examples/tui-demo/src/index.ts # packages/examples/tui-demo/tests/tui-agent.spec.ts
This commit is contained in:
@@ -16,13 +16,14 @@ stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it
|
||||
| `@deepseek-ai/dsh-user-interaction` | the human question/answer seam used by clients that can complete ACP elicitation requests |
|
||||
| `@deepseek-ai/dsh-session-persistence-jsonl` | durable JSONL session log (the bridge advertises `loadSession`) |
|
||||
| `@deepseek-ai/dsh-session-query` + `@deepseek-ai/dsh-session-reference` | exact current-surface reads and bounded `dsh-session:` snapshots |
|
||||
| `@deepseek-ai/dsh-session-checkpoint-policy` | semantic durability barriers before model requests and top-level tool effects, plus completed-step checkpoints |
|
||||
| `@deepseek-ai/dsh-acp` | the bridge that owns stdout for JSON-RPC and provides ACP-backed user answers when a leaf explicitly exposes a user-question tool |
|
||||
| ~~`@deepseek-ai/dsh-tool-ask-user`~~ | **omitted by default** — ACP elicitation support is still client-dependent, so leaves must opt in deliberately |
|
||||
| ~~`@deepseek-ai/dsh-user-approval`~~ | **omitted by default** — permission policy is deployment-specific; sandbox/approval leaves opt in and the ACP bridge then supplies the answerer |
|
||||
| ~~console logger~~ | **omitted** — it writes to stdout and would corrupt the protocol frames ([the stdout-purity footgun](../../ui/acp/README.md)) |
|
||||
| ~~`hmr`~~ | **omitted** — the editor owns the subprocess |
|
||||
|
||||
Because the package wires no logger entry, an ACP leaf has **nothing to get wrong by default**: it only picks backends. A leaf author can still add `@cordisjs/plugin-logger-console` as a sibling entry, so the rule remains: never add a stdout logger to an ACP leaf; use a stderr exporter instead.
|
||||
The app owns this cluster through one ordered Cordis effect. Teardown drains the ACP bridge before removing the checkpoint policy or persistence backend, so a graceful disconnect persists the real closing `step/end` and `turn/end` events rather than leaving crash recovery to synthesize them. Because the package wires no logger entry, an ACP leaf has **nothing to get wrong by default**: it only picks backends. A leaf author can still add `@cordisjs/plugin-logger-console` as a sibling entry, so the rule remains: never add a stdout logger to an ACP leaf; use a stderr exporter instead.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -44,6 +45,7 @@ Because the package wires no logger entry, an ACP leaf has **nothing to get wron
|
||||
| `llmRetry` | owner defaults | bounded transient model-request retry policy routed through `dsh-agent-spine-demo` |
|
||||
| `persistenceRoot` | `./.sessions` | the JSONL backend's root directory |
|
||||
| `persistenceCompression` | `'zstd'` | JSONL artifact encoding (`'zstd'` or raw `'none'`) |
|
||||
| `sessionReferences` | service defaults | cross-session candidate and snapshot limits routed to `dsh-session-reference` |
|
||||
|
||||
The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the real model, `llm-replay` for keyless snapshot replay), a bash executor, and optionally a `ctx.fs` provider. Workspace context becomes a no-op without `ctx.fs`; the shipped [`examples/acp-agent/cordis.yml`](../../../examples/acp-agent/cordis.yml) selects `dsh-sandbox-policy`, `dsh-fs-sandbox`, `dsh-fs-policy`, and `dsh-tool-fs` so baseline instructions and model-facing `read`/`write`/`edit` share one provider, sandbox mode, workspace root, and observed-version policy.
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "^0.0.1",
|
||||
"@deepseek-ai/dsh-app-boot": "^0.0.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-checkpoint-policy": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-query": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-reference": "^0.0.1",
|
||||
@@ -62,6 +63,7 @@
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "workspace:^",
|
||||
"@deepseek-ai/dsh-app-boot": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-checkpoint-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-query": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-reference": "workspace:^",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/**
|
||||
* The ACP server app: the default agent spine ({@link @deepseek-ai/dsh-agent-spine-demo}),
|
||||
* human-command registry, JSONL session persistence, and the
|
||||
* {@link @deepseek-ai/dsh-acp} bridge. It writes nothing to stdout.
|
||||
* {@link @deepseek-ai/dsh-acp} bridge. The app owns those plugins through one
|
||||
* ordered lifecycle so ACP sessions quiesce before persistence detaches. It
|
||||
* writes nothing to stdout.
|
||||
* It pre-creates no agents and leaves adapters, executors, and optional tools to
|
||||
* the leaf, which must likewise avoid stdout loggers. Named exports are
|
||||
* required so Loader retains this plugin's `Config` schema (see
|
||||
@@ -21,6 +23,7 @@ import SessionPersistenceJsonl, {
|
||||
JsonlCompressionSchema,
|
||||
type JsonlCompression,
|
||||
} from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import * as sessionCheckpointPolicy from '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
import SessionQueryService from '@deepseek-ai/dsh-session-query'
|
||||
import SessionReferenceService, { type Config as SessionReferenceConfig } from '@deepseek-ai/dsh-session-reference'
|
||||
@@ -106,23 +109,24 @@ export const Config: z<Config> = z.object({
|
||||
* NO agents (its `agents` list defaults to `[]`) and carries the deployment
|
||||
* `persona`; the JSONL backend persists under `persistenceRoot`; the ACP
|
||||
* bridge owns stdout for JSON-RPC and creates one agent per `session/new`
|
||||
* from the provider/model pair. No logger, no `hmr` — stdout stays pure.
|
||||
* from the provider/model pair. The composite effect unloads in reverse order,
|
||||
* keeping checkpoint and persistence listeners attached until ACP agents have
|
||||
* flushed their closing events. No logger, no `hmr` — stdout stays pure.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
const goals = config.goals ?? {}
|
||||
ctx.plugin(CommandService)
|
||||
if (goals !== false) ctx.plugin(commandGoal)
|
||||
ctx.plugin(agentCore, { ...agentCore.pickSpineConfig(config), goals })
|
||||
// This front door owns the same persistence/reference cluster as the TUI;
|
||||
// extracting these few calls would introduce a shared app-composition facade.
|
||||
/* jscpd:ignore-start */
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT,
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
})
|
||||
ctx.plugin(SessionQueryService)
|
||||
ctx.plugin(SessionReferenceService, config.sessionReferences ?? {})
|
||||
/* jscpd:ignore-end */
|
||||
ctx.plugin(acp, { provider: config.provider, model: config.model })
|
||||
ctx.effect(function* () {
|
||||
yield ctx.plugin(CommandService).dispose
|
||||
if (goals !== false) yield ctx.plugin(commandGoal).dispose
|
||||
yield ctx.plugin(agentCore, { ...agentCore.pickSpineConfig(config), goals }).dispose
|
||||
yield ctx.plugin(UserInteractionService).dispose
|
||||
yield ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT,
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
}).dispose
|
||||
yield ctx.plugin(sessionCheckpointPolicy).dispose
|
||||
yield ctx.plugin(SessionQueryService).dispose
|
||||
yield ctx.plugin(SessionReferenceService, config.sessionReferences ?? {}).dispose
|
||||
yield ctx.plugin(acp, { provider: config.provider, model: config.model }).dispose
|
||||
}, 'acp-demo.composition')
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ const dshPackages = [
|
||||
'core/tools', 'core/agent-loop', 'llm/llm', 'bash/bash',
|
||||
'bash/bash-local', 'bash/tool-bash', 'context/workspace-context', 'support/invariants', 'ui/app-boot',
|
||||
'session-persistence/session-persistence',
|
||||
'session-persistence/session-persistence-jsonl', 'session-query/session-query',
|
||||
'context/session-reference', 'ui/acp', 'examples/acp-demo', 'util/paths',
|
||||
'session-persistence/session-checkpoint-policy', 'session-persistence/session-persistence-jsonl',
|
||||
'session-query/session-query', 'context/session-reference', 'ui/acp', 'examples/acp-demo', 'util/paths',
|
||||
]
|
||||
const vendorPackages = [
|
||||
'cordis', 'loader', 'include', 'timer', 'hmr', 'logger-console',
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
{
|
||||
"path": "../../ui/tool-ask-user"
|
||||
},
|
||||
{
|
||||
"path": "../../session-persistence/session-checkpoint-policy"
|
||||
},
|
||||
{
|
||||
"path": "../../session-persistence/session-persistence-jsonl"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user