Merge remote-tracking branch 'origin/master' into codex/trim-ai-prose

# Conflicts:
#	docs/config-catalog.md
#	docs/development.i18n.yaml
#	docs/development.zh.md
#	docs/rfc/implemented/feature/2026-06-14-acp-agent-client-protocol.md
#	docs/rfc/implemented/feature/2026-07-06-sandbox.md
#	examples/AGENTS.md
#	examples/README.md
#	examples/acp-agent/README.md
#	examples/acp-agent/cordis.yml
#	examples/acp-agent/tests/acp.e2e.ts
#	examples/acp-agent/tests/escalation.e2e.ts
#	examples/sandbox-acp-agent/README.md
#	examples/sandbox-acp-agent/cordis.snapshot.yml
#	examples/sandbox-acp-agent/cordis.yml
#	examples/sandbox-acp-agent/tests/acp.snapshot.ts
#	packages/ui/acp-agent/src/bin.ts
#	packages/ui/acp/README.md
#	packages/ui/jsonrpc-agent/README.md
#	packages/ui/jsonrpc-agent/src/bin.ts
#	scripts/verify-translation-pairing.ts
This commit is contained in:
Tianyi Cui
2026-07-14 12:34:14 +08:00
180 changed files with 3874 additions and 1935 deletions
+2 -2
View File
@@ -29,11 +29,11 @@ Because the package wires no logger entry, an ACP leaf has **nothing to get wron
| `toolOrder` | — | explicit model-facing tool order (a name list with one `'<unlisted-tools>'` rest entry; absent — lexicographic; an unregistered name fails each turn at prompt assembly), routed to `dsh-system-prompt` |
| `persistenceRoot` | `./.sessions` | the JSONL backend's root directory |
The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the real model, `llm-replay` for keyless snapshot replay) and a bash executor (`bash-local`).
The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the real model, `llm-replay` for keyless snapshot replay) and a bash executor.
## The bin
`dsh-acp-agent [path-to-cordis.yml]` (default `./cordis.yml`):
`dsh-acp-agent [--config path-to-cordis.yml]` (short form `-c`; default `./cordis.yml`):
- loads a gitignored `.env` from the cwd — **skipped** in snapshot REPLAY so a stray key can never trigger a live call;
- honors `DSH_SNAPSHOT=replay` by booting the sibling `cordis.snapshot.yml` (the keyless replay tree, `llm-replay` in place of `llm-deepseek`);
+14 -6
View File
@@ -1,13 +1,16 @@
#!/usr/bin/env node
/**
* Boot an ACP stdio server from `cordis.yml`; usage is `dsh-acp-agent [config]`, defaulting to the
* cwd file. Shared env loading, Loader guards, snapshot config selection, and settled-tree boot live
* in dsh-app-boot. Replay skips `.env` and selects sibling `cordis.snapshot.yml` so a stray key
* cannot trigger a model call. EOF disposes and flushes snapshot runs; editors normally own process
* lifetime. Stdout is reserved for JSON-RPC—write diagnostics only to stderr.
* Boot an ACP stdio server from `cordis.yml`; usage is
* `dsh-acp-agent [--config path]`, defaulting to `./cordis.yml`. Shared env
* loading, Loader guards, snapshot config selection, and settled-tree boot live
* in dsh-app-boot. Replay skips `.env` and selects sibling
* `cordis.snapshot.yml` so a stray key cannot trigger a model call. EOF disposes
* and flushes snapshot runs; editors normally own process lifetime. Stdout is
* reserved for JSON-RPC, so diagnostics go only to stderr.
* @module @deepseek-ai/dsh-acp-agent/bin
*/
import { parseArgs } from 'node:util'
import { boot, installFailLoud, loadEnv, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
const NAME = 'dsh-acp-agent'
@@ -18,7 +21,12 @@ const NAME = 'dsh-acp-agent'
installFailLoud(NAME)
const snapshotMode = process.env['DSH_SNAPSHOT']
if (snapshotMode !== 'replay') loadEnv(NAME)
const ctx = await boot(NAME, resolveConfigPath(process.argv[2] ?? './cordis.yml', snapshotMode))
const { values } = parseArgs({
args: process.argv.slice(2),
options: { config: { type: 'string', short: 'c' } },
strict: true,
})
const ctx = await boot(NAME, resolveConfigPath(values.config ?? './cordis.yml', snapshotMode))
if (snapshotMode !== undefined) {
process.stdin.on('end', () => {
void ctx.fiber.dispose().then(() => { process.exit(0) })
+3 -3
View File
@@ -1,7 +1,7 @@
/**
* The ACP server app: the default agent spine ({@link @deepseek-ai/dsh-agent-core}) plus the
* coupled front-door cluster an ACP server needs — JSONL session persistence and the {@link
* @deepseek-ai/dsh-acp} bridge, and deliberately NOTHING that writes to stdout.
* The ACP server app: the default agent spine ({@link @deepseek-ai/dsh-agent-core}),
* JSONL session persistence, and the {@link @deepseek-ai/dsh-acp} bridge. 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
+2 -2
View File
@@ -103,7 +103,7 @@ afterEach(async () => {
describe.skipIf(!existsSync(acpBin))('dsh-acp-agent BUILT bin (node lib/bin.js, no tsx)', () => {
it('boots the published bin and answers an initialize JSON-RPC frame on stdout', async () => {
consumer = await makeConsumer()
child = spawn(process.execPath, ['--expose-internals', acpBin, './cordis.yml'], {
child = spawn(process.execPath, ['--expose-internals', acpBin, '--config', './cordis.yml'], {
cwd: consumer,
// Dummy key: initialize never reaches the model, so it is never used.
env: {
@@ -166,7 +166,7 @@ describe.skipIf(!existsSync(acpBin))('dsh-acp-agent BUILT bin (node lib/bin.js,
/** Spawn the built acp bin against `configArg` and resolve with its exit code + stderr. */
function runBinExpectingExit(configArg: string, cwd: string = tmpdir()): Promise<{ code: number; stderr: string }> {
return new Promise((resolve, reject) => {
const proc = spawn(process.execPath, ['--expose-internals', acpBin, configArg], {
const proc = spawn(process.execPath, ['--expose-internals', acpBin, '--config', configArg], {
cwd,
env: {
...process.env,
+1 -1
View File
@@ -71,7 +71,7 @@ async function boot(): Promise<Spawned & { cwd: string }> {
await writeFile(configPath, CORDIS_YML)
const child = spawn(
process.execPath,
['--import', tsxLoader, binScript, configPath],
['--import', tsxLoader, binScript, '--config', configPath],
{
cwd,
env: {