From 96975f3840ba0dc64327a306e4044e9694858767 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Fri, 17 Jul 2026 10:15:19 +0800 Subject: [PATCH 1/6] fix(session-persistence): create SQLite databases owner-only --- docs/config-catalog.md | 6 +-- .../session-persistence-sqlite/README.md | 2 +- .../session-persistence-sqlite/src/index.ts | 17 +++++-- .../tests/sqlite.spec.ts | 50 ++++++++++++++++++- 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 91fe7c6497..9474a4877b 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -603,8 +603,8 @@ Requires: `sessions` export interface Config { /** * Filesystem path to the SQLite database file. The special value `:memory:` - * opens an in-process database (tests); a file path is created (with parent - * dirs) on construction. + * opens an in-process database (tests). Missing directories and the database + * are created with owner-only permissions; existing path modes are preserved. */ path: string /** @@ -627,7 +627,7 @@ export interface Config { export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ``` -Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:38`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) +Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:48`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) ## `@deepseek-ai/dsh-session-query` diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index 1411e177be..1dd480bc38 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -8,7 +8,7 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` i Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)` — `data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed. -The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. +The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). On filesystems with POSIX modes, missing directories are created as `0700` and a missing database is exclusively created as `0600` before SQLite opens it, causing new WAL sidecars to inherit owner-only access. Existing directories, database files, and sidecars keep their modes; ordinary filesystem access errors still fail initialization. `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. ## Contract semantics over rows diff --git a/packages/session-persistence/session-persistence-sqlite/src/index.ts b/packages/session-persistence/session-persistence-sqlite/src/index.ts index 7d23292e9d..9f0b7cb7c4 100644 --- a/packages/session-persistence/session-persistence-sqlite/src/index.ts +++ b/packages/session-persistence/session-persistence-sqlite/src/index.ts @@ -8,7 +8,7 @@ import { Context } from 'cordis' import z from 'schemastery' import { DatabaseSync } from 'node:sqlite' -import { mkdir } from 'node:fs/promises' +import { mkdir, open } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { SessionPersistence, PersistenceCoordinator, @@ -34,12 +34,22 @@ function surfaceBindings(event: SessionEvent): [string | null, string | null] { ] } +/** Create a missing database owner-only while preserving an existing file's mode. */ +async function createDatabaseFile(path: string): Promise { + try { + const handle = await open(path, 'wx', 0o600) + await handle.close() + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error + } +} + /** Plugin configuration. */ export interface Config { /** * Filesystem path to the SQLite database file. The special value `:memory:` - * opens an in-process database (tests); a file path is created (with parent - * dirs) on construction. + * opens an in-process database (tests). Missing directories and the database + * are created with owner-only permissions; existing path modes are preserved. */ path: string /** @@ -87,6 +97,7 @@ export class SessionPersistenceSqlite extends SessionPersistence implements Pers if (path !== ':memory:') { const abs = resolve(path) await mkdir(dirname(abs), { recursive: true, mode: 0o700 }) + await createDatabaseFile(abs) this.db = openDatabase(abs, journalMode) } else { this.db = openDatabase(path, journalMode) diff --git a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts index bae79b0a53..3606afce67 100644 --- a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts +++ b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts @@ -1,9 +1,9 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from 'cordis' import { existsSync } from 'node:fs' -import { mkdtemp, rm } from 'node:fs/promises' +import { chmod, mkdtemp, mkdir, rm, stat, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' -import { join } from 'node:path' +import { dirname, join } from 'node:path' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import type { Session, SessionEvent, SurfaceEvent, SurfaceEventType } from '@deepseek-ai/dsh-session' import SessionPersistenceSqlite, { SCHEMA_VERSION } from '@deepseek-ai/dsh-session-persistence-sqlite' @@ -348,6 +348,52 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => { }) describe('SessionPersistenceSqlite: edge cases', () => { + it('creates a new database and WAL sidecars owner-only without changing an existing directory mode', async () => { + if (process.platform === 'win32') return + const path = await freshDbPath() + const dir = dirname(path) + await chmod(dir, 0o755) + + const b = await backend(path) + await b.ctx.sessionPersistence.list() + + expect((await stat(dir)).mode & 0o777).toBe(0o755) + expect((await stat(path)).mode & 0o777).toBe(0o600) + expect((await stat(`${path}-wal`)).mode & 0o777).toBe(0o600) + expect((await stat(`${path}-shm`)).mode & 0o777).toBe(0o600) + await b.dispose() + }) + + it('preserves the mode of an existing database file', async () => { + if (process.platform === 'win32') return + const path = await freshDbPath() + await writeFile(path, '', { mode: 0o644 }) + await chmod(path, 0o644) + + const ctx = new Context() + await ctx.plugin(SessionStore) + const fiber = await ctx.plugin(SessionPersistenceSqlite, { path, journalMode: 'delete' }) + await ctx.sessionPersistence.list() + + expect((await stat(path)).mode & 0o777).toBe(0o644) + await fiber.dispose() + }) + + it('surfaces database pre-creation errors other than an existing file', async () => { + if (process.platform === 'win32') return + const path = await freshDbPath() + const blocked = join(dirname(path), 'blocked') + await mkdir(blocked, { mode: 0o500 }) + const b = await backend(join(blocked, 'sessions.db')) + + try { + await expect(b.ctx.sessionPersistence.list()).rejects.toMatchObject({ code: 'EACCES' }) + await b.dispose() + } finally { + await chmod(blocked, 0o700) + } + }) + it('append rolls back and rethrows when an event INSERT fails inside the transaction', async () => { const path = await freshDbPath() const m = meta('rollback-insert') From ac893f97bbbc3727beeab4034d689464dc4f2018 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Fri, 17 Jul 2026 10:47:32 +0800 Subject: [PATCH 2/6] fix(session-persistence): address permission review --- docs/config-catalog.md | 4 +++- .../session-persistence-sqlite/README.md | 2 +- .../session-persistence-sqlite/src/index.ts | 8 +++++++- .../tests/sqlite.spec.ts | 17 +++++------------ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 9474a4877b..49607eb494 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -605,6 +605,8 @@ export interface Config { * Filesystem path to the SQLite database file. The special value `:memory:` * opens an in-process database (tests). Missing directories and the database * are created with owner-only permissions; existing path modes are preserved. + * Parent directories writable by another principal are outside the backend's + * database-integrity boundary. */ path: string /** @@ -627,7 +629,7 @@ export interface Config { export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ``` -Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:48`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) +Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:52`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) ## `@deepseek-ai/dsh-session-query` diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index 1dd480bc38..b5f85bd1a8 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -8,7 +8,7 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` i Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)` — `data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed. -The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). On filesystems with POSIX modes, missing directories are created as `0700` and a missing database is exclusively created as `0600` before SQLite opens it, causing new WAL sidecars to inherit owner-only access. Existing directories, database files, and sidecars keep their modes; ordinary filesystem access errors still fail initialization. `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. +The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). On filesystems with POSIX modes, missing directories are created as `0700` and a missing database is exclusively created as `0600` before SQLite opens it, causing new WAL sidecars to inherit owner-only access. Existing directories, database files, and sidecars keep their modes; ordinary filesystem access errors still fail initialization. This default prevents incidental exposure through the process umask; it does not protect database integrity when another principal can modify entries in an existing parent directory. `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. ## Contract semantics over rows diff --git a/packages/session-persistence/session-persistence-sqlite/src/index.ts b/packages/session-persistence/session-persistence-sqlite/src/index.ts index 9f0b7cb7c4..45c05f750b 100644 --- a/packages/session-persistence/session-persistence-sqlite/src/index.ts +++ b/packages/session-persistence/session-persistence-sqlite/src/index.ts @@ -34,7 +34,11 @@ function surfaceBindings(event: SessionEvent): [string | null, string | null] { ] } -/** Create a missing database owner-only while preserving an existing file's mode. */ +/** + * Create a missing database owner-only while preserving an existing file's + * mode. `DatabaseSync` cannot adopt this handle, so a parent directory writable + * by another principal is outside the backend's database-integrity boundary. + */ async function createDatabaseFile(path: string): Promise { try { const handle = await open(path, 'wx', 0o600) @@ -50,6 +54,8 @@ export interface Config { * Filesystem path to the SQLite database file. The special value `:memory:` * opens an in-process database (tests). Missing directories and the database * are created with owner-only permissions; existing path modes are preserved. + * Parent directories writable by another principal are outside the backend's + * database-integrity boundary. */ path: string /** diff --git a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts index 3606afce67..50d761a39d 100644 --- a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts +++ b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { Context } from 'cordis' import { existsSync } from 'node:fs' -import { chmod, mkdtemp, mkdir, rm, stat, writeFile } from 'node:fs/promises' +import { chmod, mkdtemp, rm, stat, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' @@ -379,19 +379,12 @@ describe('SessionPersistenceSqlite: edge cases', () => { await fiber.dispose() }) - it('surfaces database pre-creation errors other than an existing file', async () => { - if (process.platform === 'win32') return + it('surfaces database pre-creation errors independently of process privileges', async () => { const path = await freshDbPath() - const blocked = join(dirname(path), 'blocked') - await mkdir(blocked, { mode: 0o500 }) - const b = await backend(join(blocked, 'sessions.db')) + const b = await backend(`${path}\0`) - try { - await expect(b.ctx.sessionPersistence.list()).rejects.toMatchObject({ code: 'EACCES' }) - await b.dispose() - } finally { - await chmod(blocked, 0o700) - } + await expect(b.ctx.sessionPersistence.list()).rejects.toMatchObject({ code: 'ERR_INVALID_ARG_VALUE' }) + await b.dispose() }) it('append rolls back and rethrows when an event INSERT fails inside the transaction', async () => { From 85fc58943e34a1d207cde9e2343ba80b9b5afb72 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:37:41 +0800 Subject: [PATCH 3/6] docs(session-persistence): tighten SQLite permission prose --- docs/config-catalog.md | 11 ++++++----- .../session-persistence-sqlite/README.md | 4 +++- .../session-persistence-sqlite/src/index.ts | 16 +++++++++------- .../tests/sqlite.spec.ts | 4 ++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/config-catalog.md b/docs/config-catalog.md index a442b5a197..6069263b8e 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -656,10 +656,11 @@ Requires: `sessions` export interface Config { /** * Filesystem path to the SQLite database file. The special value `:memory:` - * opens an in-process database (tests). Missing directories and the database - * are created with owner-only permissions; existing path modes are preserved. - * Parent directories writable by another principal are outside the backend's - * database-integrity boundary. + * opens an in-process database (tests). On filesystems with POSIX modes, + * missing directories and databases are created owner-only; existing path + * modes are preserved. Filesystem setup errors other than an existing database + * fail initialization. The backend does not protect integrity when another + * principal can replace the database entry in its parent directory. */ path: string /** @@ -682,7 +683,7 @@ export interface Config { export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ``` -Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:53`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) +Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:54`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) ## `@deepseek-ai/dsh-session-query` diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index 17713dda8f..602bd4e33f 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -10,7 +10,9 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` i Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)` — `data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed. -The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). On filesystems with POSIX modes, missing directories are created as `0700` and a missing database is exclusively created as `0600` before SQLite opens it, causing new WAL sidecars to inherit owner-only access. Existing directories, database files, and sidecars keep their modes; ordinary filesystem access errors still fail initialization. This default prevents incidental exposure through the process umask; it does not protect database integrity when another principal can modify entries in an existing parent directory. `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. +The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. + +On filesystems with POSIX modes, the backend creates missing directories as `0700` and exclusively creates a missing database as `0600` before SQLite opens it. New WAL sidecars receive the database's owner-only mode. Existing directories, database files, and sidecars keep their modes; filesystem setup errors other than an existing database fail initialization. These defaults prevent incidental exposure through the process umask, but do not protect database integrity when another principal can replace the database entry in its parent directory. ## Contract semantics over rows diff --git a/packages/session-persistence/session-persistence-sqlite/src/index.ts b/packages/session-persistence/session-persistence-sqlite/src/index.ts index 4203f38a87..3515309f9a 100644 --- a/packages/session-persistence/session-persistence-sqlite/src/index.ts +++ b/packages/session-persistence/session-persistence-sqlite/src/index.ts @@ -36,9 +36,10 @@ function surfaceBindings(event: SessionEvent): [string | null, string | null] { } /** - * Create a missing database owner-only while preserving an existing file's - * mode. `DatabaseSync` cannot adopt this handle, so a parent directory writable - * by another principal is outside the backend's database-integrity boundary. + * Exclusively create a missing database file with owner-only permissions. + * Existing files retain their modes, and errors other than `EEXIST` propagate. + * `DatabaseSync` reopens by path, so this does not protect integrity when + * another principal can replace the database entry in its parent directory. */ async function createDatabaseFile(path: string): Promise { try { @@ -53,10 +54,11 @@ async function createDatabaseFile(path: string): Promise { export interface Config { /** * Filesystem path to the SQLite database file. The special value `:memory:` - * opens an in-process database (tests). Missing directories and the database - * are created with owner-only permissions; existing path modes are preserved. - * Parent directories writable by another principal are outside the backend's - * database-integrity boundary. + * opens an in-process database (tests). On filesystems with POSIX modes, + * missing directories and databases are created owner-only; existing path + * modes are preserved. Filesystem setup errors other than an existing database + * fail initialization. The backend does not protect integrity when another + * principal can replace the database entry in its parent directory. */ path: string /** diff --git a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts index a07bcb7913..4a6b141e04 100644 --- a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts +++ b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts @@ -390,7 +390,7 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => { }) describe('SessionPersistenceSqlite: edge cases', () => { - it('creates a new database and WAL sidecars owner-only without changing an existing directory mode', async () => { + it('creates a new database and WAL sidecars with owner-only modes without changing its parent mode', async () => { if (process.platform === 'win32') return const path = await freshDbPath() const dir = dirname(path) @@ -421,7 +421,7 @@ describe('SessionPersistenceSqlite: edge cases', () => { await fiber.dispose() }) - it('surfaces database pre-creation errors independently of process privileges', async () => { + it('surfaces an invalid database path during pre-creation', async () => { const path = await freshDbPath() const b = await backend(`${path}\0`) From 176973cb7b618a82eec4d24f7497352ea5da4acc Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:51:22 +0800 Subject: [PATCH 4/6] docs(session-persistence): clarify SQLite permission limits --- docs/config-catalog.md | 7 ++++--- .../session-persistence-sqlite/README.md | 2 +- .../session-persistence-sqlite/src/index.ts | 10 ++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 6069263b8e..05fa3eab67 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -659,8 +659,9 @@ export interface Config { * opens an in-process database (tests). On filesystems with POSIX modes, * missing directories and databases are created owner-only; existing path * modes are preserved. Filesystem setup errors other than an existing database - * fail initialization. The backend does not protect integrity when another - * principal can replace the database entry in its parent directory. + * fail initialization. The backend does not protect confidentiality or + * integrity when another principal can replace the database entry in its + * parent directory. */ path: string /** @@ -683,7 +684,7 @@ export interface Config { export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ``` -Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:54`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) +Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:55`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) ## `@deepseek-ai/dsh-session-query` diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index 602bd4e33f..e9066d3748 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -12,7 +12,7 @@ Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. -On filesystems with POSIX modes, the backend creates missing directories as `0700` and exclusively creates a missing database as `0600` before SQLite opens it. New WAL sidecars receive the database's owner-only mode. Existing directories, database files, and sidecars keep their modes; filesystem setup errors other than an existing database fail initialization. These defaults prevent incidental exposure through the process umask, but do not protect database integrity when another principal can replace the database entry in its parent directory. +On filesystems with POSIX modes, the backend requests mode `0700` for missing directories and exclusively creates a missing database with mode `0600` before SQLite opens it; the process umask may further restrict both. New WAL sidecars receive the database's resulting owner-only mode. Existing directories, database files, and sidecars keep their modes; filesystem setup errors other than an existing database fail initialization. These defaults prevent incidental exposure through a permissive process umask, but do not protect database confidentiality or integrity when another principal can replace the database entry in its parent directory. ## Contract semantics over rows diff --git a/packages/session-persistence/session-persistence-sqlite/src/index.ts b/packages/session-persistence/session-persistence-sqlite/src/index.ts index 3515309f9a..4661b41309 100644 --- a/packages/session-persistence/session-persistence-sqlite/src/index.ts +++ b/packages/session-persistence/session-persistence-sqlite/src/index.ts @@ -38,8 +38,9 @@ function surfaceBindings(event: SessionEvent): [string | null, string | null] { /** * Exclusively create a missing database file with owner-only permissions. * Existing files retain their modes, and errors other than `EEXIST` propagate. - * `DatabaseSync` reopens by path, so this does not protect integrity when - * another principal can replace the database entry in its parent directory. + * `DatabaseSync` reopens by path, so this does not protect confidentiality or + * integrity when another principal can replace the database entry in its parent + * directory. */ async function createDatabaseFile(path: string): Promise { try { @@ -57,8 +58,9 @@ export interface Config { * opens an in-process database (tests). On filesystems with POSIX modes, * missing directories and databases are created owner-only; existing path * modes are preserved. Filesystem setup errors other than an existing database - * fail initialization. The backend does not protect integrity when another - * principal can replace the database entry in its parent directory. + * fail initialization. The backend does not protect confidentiality or + * integrity when another principal can replace the database entry in its + * parent directory. */ path: string /** From ddd3370477fbf420364c9369da4c88c10c0c9786 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:12:16 +0800 Subject: [PATCH 5/6] test(session-persistence): pin rollback journal permissions --- .../session-persistence-sqlite/README.md | 2 +- .../tests/sqlite.spec.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index e9066d3748..11fef96dad 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -12,7 +12,7 @@ Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, The repository's Node range supports unflagged `node:sqlite`. The database enables foreign keys and uses the configured journal mode (`wal` by default; use a rollback mode where WAL shared-memory files are unsuitable). `PRAGMA user_version` stores the table-layout version; databases with any other version are rejected because this unreleased format has no migrations. -On filesystems with POSIX modes, the backend requests mode `0700` for missing directories and exclusively creates a missing database with mode `0600` before SQLite opens it; the process umask may further restrict both. New WAL sidecars receive the database's resulting owner-only mode. Existing directories, database files, and sidecars keep their modes; filesystem setup errors other than an existing database fail initialization. These defaults prevent incidental exposure through a permissive process umask, but do not protect database confidentiality or integrity when another principal can replace the database entry in its parent directory. +On filesystems with POSIX modes, the backend requests mode `0700` for missing directories and exclusively creates a missing database with mode `0600` before SQLite opens it; the process umask may further restrict both. New WAL, shared-memory, and persistent rollback-journal sidecars receive the database's resulting owner-only mode. Existing directories, database files, and sidecars keep their modes; filesystem setup errors other than an existing database fail initialization. These defaults prevent incidental exposure through a permissive process umask, but do not protect database confidentiality or integrity when another principal can replace the database entry in its parent directory. ## Contract semantics over rows diff --git a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts index 4a6b141e04..f26edfa54d 100644 --- a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts +++ b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts @@ -406,6 +406,22 @@ describe('SessionPersistenceSqlite: edge cases', () => { await b.dispose() }) + it('creates a persistent rollback journal with owner-only mode', async () => { + if (process.platform === 'win32') return + const path = await freshDbPath() + const ctx = new Context() + await ctx.plugin(SessionStore) + const fiber = await ctx.plugin(SessionPersistenceSqlite, { path, journalMode: 'persist' }) + const m = meta('persist-permissions') + + await ctx.sessionPersistence.create(m) + await ctx.sessionPersistence.append(m.id, oneTurnLog()) + + expect((await stat(path)).mode & 0o777).toBe(0o600) + expect((await stat(`${path}-journal`)).mode & 0o777).toBe(0o600) + await fiber.dispose() + }) + it('preserves the mode of an existing database file', async () => { if (process.platform === 'win32') return const path = await freshDbPath() From 2faeabb05a2ed90f4853d04162bbc3dc57c86164 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:13:14 +0800 Subject: [PATCH 6/6] refactor(examples): rename coding-agent leaf to repl-agent Name the runnable leaf for the line-oriented front door it owns, matching the existing tui-agent and acp-agent organization. Move the complete config, Code Mode overlay, tests, metadata, and generated composition graph together, then update every loader path and repository reference. Keep the shared model identity independent of its terminal front door by phrasing the persona as a coding-agent role rather than retaining the retired leaf name. Regenerate graph and tool catalogs and re-record each affected bilingual pair so derived documentation cannot point at the removed path. --- docs/cookbook/extension-cookbook.i18n.yaml | 4 +- docs/cookbook/extension-cookbook.md | 2 +- docs/cookbook/extension-cookbook.zh.md | 2 +- docs/development.i18n.yaml | 4 +- docs/development.md | 4 +- docs/development.zh.md | 4 +- docs/graph-atlas.md | 2 +- ...t-variables-and-tool-guidance-ownership.md | 6 +- .../2026-07-08-tool-output-spill-files.md | 2 +- .../2026-06-18-compaction-capability-seam.md | 2 +- ...6-07-09-bash-backed-grep-glob-discovery.md | 2 +- ...cated-full-screen-tui-front-door.i18n.yaml | 4 +- ...17-dedicated-full-screen-tui-front-door.md | 4 +- ...dedicated-full-screen-tui-front-door.zh.md | 4 +- .../2026-07-03-documentation-graph-atlas.md | 2 +- .../2026-07-04-fold-stdio-ui-helper.md | 2 +- ...-18-tui-terminal-state-snapshots.i18n.yaml | 4 +- ...2026-07-18-tui-terminal-state-snapshots.md | 2 +- ...6-07-18-tui-terminal-state-snapshots.zh.md | 2 +- docs/tool-catalog.md | 4 +- examples/README.md | 12 +-- examples/coding-agent/composition.md | 88 ------------------- examples/cordis-agent/README.md | 2 +- examples/echo-agent/README.md | 2 +- .../{coding-agent => repl-agent}/README.md | 4 +- .../code-mode.cordis.yml | 2 +- examples/repl-agent/composition.md | 88 +++++++++++++++++++ .../{coding-agent => repl-agent}/cordis.yml | 2 +- .../{coding-agent => repl-agent}/package.json | 2 +- .../tests/code-mode-keyless-smoke.e2e.ts | 0 .../tests/code-mode.e2e.ts | 2 +- .../tests/coding-task.e2e.ts | 0 .../tests/compaction.e2e.ts | 0 .../tests/full-loop.e2e.ts | 0 .../tests/harness.ts | 2 +- .../tests/keyless-smoke.e2e.ts | 8 +- .../tests/resume.e2e.ts | 0 .../tests/todo-write.e2e.ts | 0 examples/tui-agent/README.md | 4 +- examples/tui-agent/code-mode.cordis.yml | 6 +- examples/tui-agent/composition.md | 2 +- examples/tui-agent/cordis.yml | 6 +- package.json | 2 +- packages/examples/stdio-demo/src/bin.ts | 2 +- packages/examples/stdio-demo/src/index.ts | 2 +- packages/ui/tui/src/index.ts | 2 +- scripts/demo-code-mode.mjs | 2 +- scripts/gen-doc-graphs.ts | 20 ++--- scripts/gen-tool-catalog.ts | 2 +- website/zh-CN/guide/config.md | 4 +- website/zh-CN/guide/quickstart.md | 4 +- 51 files changed, 167 insertions(+), 167 deletions(-) delete mode 100644 examples/coding-agent/composition.md rename examples/{coding-agent => repl-agent}/README.md (95%) rename examples/{coding-agent => repl-agent}/code-mode.cordis.yml (93%) create mode 100644 examples/repl-agent/composition.md rename examples/{coding-agent => repl-agent}/cordis.yml (98%) rename examples/{coding-agent => repl-agent}/package.json (81%) rename examples/{coding-agent => repl-agent}/tests/code-mode-keyless-smoke.e2e.ts (100%) rename examples/{coding-agent => repl-agent}/tests/code-mode.e2e.ts (98%) rename examples/{coding-agent => repl-agent}/tests/coding-task.e2e.ts (100%) rename examples/{coding-agent => repl-agent}/tests/compaction.e2e.ts (100%) rename examples/{coding-agent => repl-agent}/tests/full-loop.e2e.ts (100%) rename examples/{coding-agent => repl-agent}/tests/harness.ts (98%) rename examples/{coding-agent => repl-agent}/tests/keyless-smoke.e2e.ts (81%) rename examples/{coding-agent => repl-agent}/tests/resume.e2e.ts (100%) rename examples/{coding-agent => repl-agent}/tests/todo-write.e2e.ts (100%) diff --git a/docs/cookbook/extension-cookbook.i18n.yaml b/docs/cookbook/extension-cookbook.i18n.yaml index 241ab178a4..3829d4eca7 100644 --- a/docs/cookbook/extension-cookbook.i18n.yaml +++ b/docs/cookbook/extension-cookbook.i18n.yaml @@ -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: 1a1f0b200801a54f067faf9866b72ccadfdc62d3 -extension-cookbook.zh.md: 891a808b4ed87b1fd92d0b68c580895956f9ab8c +extension-cookbook.md: d271ceee208e276d97188a6ebbe91e1e90a4219f +extension-cookbook.zh.md: bdd2c5f0861aa55c6f0104764a34784e5e834d10 diff --git a/docs/cookbook/extension-cookbook.md b/docs/cookbook/extension-cookbook.md index 1a1f0b2008..d271ceee20 100644 --- a/docs/cookbook/extension-cookbook.md +++ b/docs/cookbook/extension-cookbook.md @@ -87,7 +87,7 @@ export function apply(ctx: Context) { ## Runnable wirings -Five runnable leaves load their plugin trees from `cordis.yml`: [`examples/echo-agent`](../../examples/echo-agent) (mock model + echo tool, `pnpm run demo:echo`), [`examples/coding-agent`](../../examples/coding-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/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 ACP leaf loads [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and both app packages share the spine through [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo). +Five 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/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 ACP leaf loads [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and both app packages share the spine through [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo). ## The feature → mechanism map diff --git a/docs/cookbook/extension-cookbook.zh.md b/docs/cookbook/extension-cookbook.zh.md index 891a808b4e..bdd2c5f086 100644 --- a/docs/cookbook/extension-cookbook.zh.md +++ b/docs/cookbook/extension-cookbook.zh.md @@ -87,7 +87,7 @@ export function apply(ctx: Context) { ## 可运行的组装示例 -五个可运行叶子从 `cordis.yml` 加载各自的插件树:[`examples/echo-agent`](../../examples/echo-agent)(mock 模型 + echo 工具,`pnpm run demo:echo`)、[`examples/coding-agent`](../../examples/coding-agent)(DeepSeek V4 + coding 工具,通过面向行的 readline REPL 交互,`pnpm run demo:repl`)、[`examples/tui-agent`](../../examples/tui-agent)(通过全屏 pi-tui 复用相同的 coding 组装,`pnpm run demo:tui`)、[`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),ACP 叶子加载 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),两个 app 包通过 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 共享主干。 +五个可运行叶子从 `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/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),ACP 叶子加载 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),两个 app 包通过 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 共享主干。 ## 功能→机制映射 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index e5b3feba33..692113eb2a 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -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: 37811f7215001fc371ac4943fe109dd5512ea8b0 -development.zh.md: 5036e3e75516fcaf063675fc9ab4e63c1fca851a +development.md: 452f4e82beaeacb23aa6bc3e7a60c0f2b1e2c95e +development.zh.md: 2285482726c43aa02d31c7d2094e2058de1d3863 diff --git a/docs/development.md b/docs/development.md index 37811f7215..452f4e82be 100644 --- a/docs/development.md +++ b/docs/development.md @@ -109,13 +109,13 @@ The echo demo does not need API credentials: pnpm run demo:echo ``` -The coding-agent REPL uses the line-oriented readline front door and needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`: +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 coding-agent composition through the pi-tui front door and needs the same credentials: +The full-screen TUI reuses the repl-agent composition through the pi-tui front door and needs the same credentials: ```sh pnpm run demo:tui diff --git a/docs/development.zh.md b/docs/development.zh.md index 5036e3e755..2285482726 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -109,13 +109,13 @@ echo 演示不需要 API 凭证: pnpm run demo:echo ``` -coding-agent REPL 使用面向行的 readline 前端,并需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`: +repl-agent 示例使用面向行的 readline 前端,并需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`: ```sh pnpm run demo:repl ``` -全屏 TUI 通过 pi-tui 前端复用 coding-agent 组装,并需要相同的凭证: +全屏 TUI 通过 pi-tui 前端复用 repl-agent 组装,并需要相同的凭证: ```sh pnpm run demo:tui diff --git a/docs/graph-atlas.md b/docs/graph-atlas.md index 5220cfa81f..987d8ad7a5 100644 --- a/docs/graph-atlas.md +++ b/docs/graph-atlas.md @@ -13,7 +13,7 @@ The process decision behind this index is recorded in [the documentation graph R | [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` | -| [coding-agent app composition](../examples/coding-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` | | [cordis-agent app composition](../examples/cordis-agent/composition.md) | `hybrid generated` | | [acp-agent app composition](../examples/acp-agent/composition.md) | `hybrid generated` | diff --git a/docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md b/docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md index 854807c109..c154b2080b 100644 --- a/docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md +++ b/docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md @@ -8,9 +8,9 @@ The assembled system prompt had four defects, all of one family: facts the harne **The model could not know its own name.** `AgentOptions.model` drives every request, but no prompt text carried it — and nothing COULD carry it: sections in `dsh-system-prompt` were context-global while the model name is per-agent, and `assemble()` took no per-agent input at all. -**Tool guidance was hand-written prose in leaf YAML.** The bash/subagent/todo_write usage guidance lived in the `systemPrompt` strings of `examples/coding-agent/cordis.yml` and `examples/acp-agent/cordis.yml` — two drifting copies (the ACP one was already abridged) — while `dsh-tool-fs` and `dsh-tool-web` owned their guidance as `ctx.systemPrompt.section()` contributions. Loading or dropping a tool plugin meant editing every deployment's persona by hand; both YAMLs carried a `FIXME(config-comments)` apologizing for a symptom of the split, and the stdio welcome banner hand-enumerated the tool set too. +**Tool guidance was hand-written prose in leaf YAML.** The bash/subagent/todo_write usage guidance lived in the `systemPrompt` strings of `examples/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml` — two drifting copies (the ACP one was already abridged) — while `dsh-tool-fs` and `dsh-tool-web` owned their guidance as `ctx.systemPrompt.section()` contributions. Loading or dropping a tool plugin meant editing every deployment's persona by hand; both YAMLs carried a `FIXME(config-comments)` apologizing for a symptom of the split, and the stdio welcome banner hand-enumerated the tool set too. -**The persona rendered after tool guidance.** The loop string-joined `agent.options.systemPrompt` AFTER the assembled sections, so the model read "Use the read tool…" before "You are coding-agent" — backwards relative to the identity-first convention (Claude Code, Codex) and a second composition path besides the section pipeline. +**The persona rendered after tool guidance.** The loop string-joined `agent.options.systemPrompt` AFTER the assembled sections, so the model read "Use the read tool…" before "You are a coding agent" — backwards relative to the identity-first convention (Claude Code, Codex) and a second composition path besides the section pipeline. **The fork tool's description was false.** `dsh-tool-subagent` hardcoded one description written for spawn semantics — "a separate agent that works in its own context … it does not see this conversation" — and the `subagent_fork` instance (whose child inherits the parent's completed turns) got the same words; the YAML prose corrected the lie out-of-band. Minor kin: `PromptSection.name` was documented "(diagnostics / dedup)" but duplicates were silently accepted. @@ -56,7 +56,7 @@ Per-tool semantics and selection guidance live in tool descriptions. Prompt sect ## Shipped invariants -- The coding-agent prompt renders identity, persona with the interpolated model, then fs/bash/web guidance through one assembly path. +- The repl-agent prompt renders identity, persona with the interpolated model, then fs/bash/web guidance through one assembly path. - Fork and fresh subagent descriptions reflect whether the provider inherits completed conversation turns; the tool appears, disappears, and is reworded with provider lifecycle changes. - Unknown, valueless, malformed, or unbalanced variable references name the section and throw; duplicate section, variable, and tool registrations also throw. - Snapshot replay is prompt-independent: it keys recorded chunk streams by turn and step without comparing the outgoing request. diff --git a/docs/rfc/implemented/architecture/2026-07-08-tool-output-spill-files.md b/docs/rfc/implemented/architecture/2026-07-08-tool-output-spill-files.md index 3a60c5c223..bbca065d87 100644 --- a/docs/rfc/implemented/architecture/2026-07-08-tool-output-spill-files.md +++ b/docs/rfc/implemented/architecture/2026-07-08-tool-output-spill-files.md @@ -162,7 +162,7 @@ Those cases can consume `ctx.spillStore` directly in later work. They are not pa - `dsh-spill-local` unit tests cover `saveText`, `encodeSegment` sanitization (separators/tilde/whole-segment dots/empty), the session-hash directory, owner-only permissions, distinct paths per save, the configured/private root, and a storage-failure rejection. - `dsh-spill-policy` unit tests drive real tools through `ctx.tools.execute`: disabled-mode no-op, oversized-text replacement, small/non-text passthrough, `read` skip, best-effort fallback (save failure / no backend / no owner), and downstream-composition (bounding a replaced result, preserving `additionalContexts`). - `dsh-tool-web` integration drives `web_fetch` through `ctx.tools.execute` with the real `spill-local` backend + policy, proving the model-facing text changes only by the deliberate spill notice while the spill file holds the full formatted result. -- The `coding-agent` example loads `spill-local` + `spill-policy`, so its keyless Loader smoke exercises the real load path (the namespace-plugin export shape + `inject`). +- The `repl-agent` example loads `spill-local` + `spill-policy`, so its keyless Loader smoke exercises the real load path (the namespace-plugin export shape + `inject`). ## Consequences diff --git a/docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md b/docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md index 1986ad7624..f32355326f 100644 --- a/docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md +++ b/docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md @@ -115,7 +115,7 @@ Two failure paths, both documented: - **`SessionEventMap`** gains `compact/start` / `compact/summary` / `compact/end` by declaration merging (merge-extensible); `SurfaceEventType` is **not** touched. These are session events, not cordis `Events`, so the event-taxonomy gate needs no entry. - **`dsh-compact`** owns `toolPairingBalancedBefore(session, seq)` and `toolPairingBalancedAfter(session, seq)`, the cached surface-edge checks that `compactRegion` and `compactIfNeeded` use to avoid splitting a tool-call/result pair. The cache validates current membership by seq and answers both edges from one per-cut balance sequence; stale or missing seqs and orphan results reject. `dsh-session` continues to own the surface `replace` operation, ordered event sequences, and rewrite generation. - **`dsh-invariants`** drops its `surface replace: start must be <= end` assertion: a head-anchored compaction lands a high-seq replacement entry at an older range's *position*, so `start > end` numerically is normal and valid (the range is positional, validated by the surface's `indexOf` checks that remain). The turn-enclosure invariant is reused unchanged. -- **Wiring**: `examples/coding-agent/cordis.yml` loads zero-config `dsh-token-meter` before `dsh-compact-basic`; the service-wide window and compact defaults make the pair usable without repeated numeric policy. +- **Wiring**: `examples/repl-agent/cordis.yml` loads zero-config `dsh-token-meter` before `dsh-compact-basic`; the service-wide window and compact defaults make the pair usable without repeated numeric policy. ## Testing diff --git a/docs/rfc/implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md b/docs/rfc/implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md index bc8a452c91..35f65c38f2 100644 --- a/docs/rfc/implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md +++ b/docs/rfc/implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md @@ -153,7 +153,7 @@ If the complete logical result fits under the inline cap, no formatted spill art - The tools execute through `ctx.bash.resolve(request)` → `ctx.bash.run(spec)`, forward `exec.signal`, never call `ctx.bash.start()`, and never expose a bash task id. The bash request workdir comes from `exec.agent?.session.header.cwd` when available; the resolved `spec.workdir` drives execution and relative-path display. - The tools request `stdoutMaxBytes: rawOutputMaxBytes` from the bash seam, parse only untruncated stdout within that cap, and treat over-cap or still-truncated raw output as a clear search failure; raw `rg` output is never exposed to the model. - Oversized complete formatted results are saved through `ctx.spillStore.saveText()` when available while inline results stay bounded; spill failure, a missing backend, or a missing owner preserves the inline result and reports the unsaved remainder — never an `isError`. -- The package README, the generated config catalog, and exported JSDoc document the Config fields and `SEARCH_*` codes; the coding-agent example ships the tools (the acp-agent tree waits on the snapshot re-record above); the fs group README records the co-located bash/filesystem deployment requirement. +- The package README, the generated config catalog, and exported JSDoc document the Config fields and `SEARCH_*` codes; the repl-agent example ships the tools (the acp-agent tree waits on the snapshot re-record above); the fs group README records the co-located bash/filesystem deployment requirement. ## Risks diff --git a/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml b/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml index d57bf423f5..8618fd884f 100644 --- a/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml +++ b/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml @@ -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 -2026-07-17-dedicated-full-screen-tui-front-door.md: 5e66b85fa23ef394b88fd16880cf218ac5cc2202 -2026-07-17-dedicated-full-screen-tui-front-door.zh.md: 4ccfde2d633115a41b24227ee78f7e4010bd70ee +2026-07-17-dedicated-full-screen-tui-front-door.md: c834594b3af1e1f348aec2cf67324d5123a1ed2d +2026-07-17-dedicated-full-screen-tui-front-door.zh.md: 8cb8eb6d2812e6ecf9d98d20c64da24c2943363c diff --git a/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.md b/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.md index 5e66b85fa2..c834594b3a 100644 --- a/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.md +++ b/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.md @@ -14,7 +14,7 @@ The interactive channel must remain a Cordis plugin over the same agent, session DeepSeek Harness ships [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) as a dedicated Cordis plugin. It owns terminal input and presentation only; agent lifecycle, session persistence, tool execution, and the model-facing question tool remain separate composition entries. The plugin requires both stdin and stdout to be TTYs and fails instead of silently changing to line-oriented behavior. -The app layer selects a concrete terminal front door before mounting it. `@deepseek-ai/dsh-stdio-demo` can resolve `auto` from the two process streams, while the `coding-agent` and `tui-agent` leaves explicitly select readline and TUI respectively. The TUI leaf reuses the coding agent's backend and tool composition through an asserted include patch, so the three runnable agent leaves remain symmetric without duplicating deployment choices. +The app layer selects a concrete terminal front door before mounting it. `@deepseek-ai/dsh-stdio-demo` can resolve `auto` from the two process streams, while the `repl-agent` and `tui-agent` leaves explicitly select readline and TUI respectively. The TUI leaf reuses the repl-agent backend and tool composition through an asserted include patch, so the three runnable agent leaves remain symmetric without duplicating deployment choices. The selected front door receives the exact generated or resumed `SessionId` used by the pre-created agent. It mounts before the agent composition, waits for the matching root agent, and enters full-screen mode only after that agent exists. A matching `agent-loop/config-start-failed` event is therefore reported before screen takeover and exits with status 1. @@ -38,7 +38,7 @@ The implemented [TUI terminal-state snapshot RFC](../testing/2026-07-18-tui-term - **Keep readline and full-screen modes inside `@deepseek-ai/dsh-stdio`** — rejected because line-oriented output and differential TTY rendering have different dependencies, input rules, logging ownership, and teardown obligations. Separate packages keep the pipe-safe contract small and explicit. - **Let the TUI plugin silently downgrade when either stream is not a TTY** — rejected because a fallback hides deployment mistakes and changes interaction semantics. The app bundle may select a front door with `auto`; an explicitly mounted TUI fails loud. -- **Keep TUI wiring and tests under the readline `coding-agent` leaf** — rejected because one leaf would represent two distinct front doors and break symmetry with `acp-agent`. A dedicated `tui-agent` leaf owns TUI overlays and tests while reusing the coding agent's backend composition. +- **Keep TUI wiring and tests under the readline `repl-agent` leaf** — rejected because one leaf would represent two distinct front doors and break symmetry with `acp-agent`. A dedicated `tui-agent` leaf owns TUI overlays and tests while reusing the repl-agent backend composition. ## Consequences diff --git a/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md b/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md index 4ccfde2d63..8cb8eb6d28 100644 --- a/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md +++ b/docs/rfc/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md @@ -14,7 +14,7 @@ Status: implemented DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现;agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY;条件不满足时会失败,不会静默切换为逐行输出。 -应用组合层在挂载前选择具体的终端入口。`@deepseek-ai/dsh-stdio-demo` 可以根据两个进程流通过 `auto` 作出选择,`coding-agent` 和 `tui-agent` 叶节点则分别明确选择 readline 与 TUI。TUI 叶节点通过带断言的 include patch 复用 coding agent 的后端和工具组合,使三个可运行的 agent 叶节点保持对称,同时避免重复部署选项。 +应用组合层在挂载前选择具体的终端入口。`@deepseek-ai/dsh-stdio-demo` 可以根据两个进程流通过 `auto` 作出选择,`repl-agent` 和 `tui-agent` 叶节点则分别明确选择 readline 与 TUI。TUI 叶节点通过带断言的 include patch 复用 repl-agent 的后端和工具组合,使三个可运行的 agent 叶节点保持对称,同时避免重复部署选项。 所选入口接收预创建 agent 使用的同一个新建或恢复 `SessionId`。入口先于 agent 组合挂载,等待相符的根 agent 出现,然后才进入全屏模式。因此,相符的 `agent-loop/config-start-failed` 事件会在接管屏幕前报告,并以状态码 1 退出。 @@ -38,7 +38,7 @@ agent 空闲时,编辑器输入调用 `agent.send()`;轮次运行中则调 - **把 readline 与全屏模式都保留在 `@deepseek-ai/dsh-stdio` 中**:不予采纳,因为逐行输出和差分 TTY 渲染具有不同的依赖、输入规则、日志所有权和资源清理义务。拆分为独立包可以让管道安全契约保持精简、明确。 - **当任一进程流不是 TTY 时,让 TUI 插件静默降级**:不予采纳,因为回退会掩盖部署错误并改变交互语义。应用包可以通过 `auto` 选择入口;明确挂载的 TUI 会快速失败。 -- **把 TUI 接线与测试保留在 readline `coding-agent` 叶节点下**:不予采纳,因为一个叶节点会代表两个不同入口,也会破坏它与 `acp-agent` 的对称性。独立的 `tui-agent` 叶节点负责 TUI 浮层和测试,同时复用 coding agent 的后端组合。 +- **把 TUI 接线与测试保留在 readline `repl-agent` 叶节点下**:不予采纳,因为一个叶节点会代表两个不同入口,也会破坏它与 `acp-agent` 的对称性。独立的 `tui-agent` 叶节点负责 TUI 浮层和测试,同时复用 repl-agent 的后端组合。 ## 后果 diff --git a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md index a17973ea2e..4e3148ead6 100644 --- a/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md +++ b/docs/rfc/implemented/process/2026-07-03-documentation-graph-atlas.md @@ -34,7 +34,7 @@ The first index links ten relationship surfaces. Package topology and tool-packa | [tool schema catalog and package map](../../../tool-catalog.md) | generated | boot-harvested tool schemas plus tool-package service/effect metadata | | [capability seams and core services](../../../capability-seams.md) | hybrid generated | Cordis service declarations plus a role manifest in `gen-doc-graphs.ts` | | [echo-agent app composition](../../../../examples/echo-agent/composition.md) | hybrid generated | `examples/echo-agent/cordis.yml` plugin list plus curated app/bundle expansion | -| [coding-agent app composition](../../../../examples/coding-agent/composition.md) | hybrid generated | `examples/coding-agent/cordis.yml` plugin list plus curated app/bundle expansion | +| [repl-agent app composition](../../../../examples/repl-agent/composition.md) | hybrid generated | `examples/repl-agent/cordis.yml` plugin list plus curated app/bundle expansion | | [acp-agent app composition](../../../../examples/acp-agent/composition.md) | hybrid generated | `examples/acp-agent/cordis.yml` plugin list plus curated app/bundle expansion | | [event producer/consumer matrix](../../../event-producer-consumer.md) | hybrid generated | Cordis event declarations, AST-scanned `ctx.on/emit/parallel/serial/waterfall` sites, and explicit dynamic dispatch overrides | | [agent turn and step lifecycle](../../../agent-lifecycle.md) | curated | architecture.md loop lifecycle, Cordis catalog links, and session event semantics | diff --git a/docs/rfc/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md b/docs/rfc/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md index d3775754b9..43f35ee770 100644 --- a/docs/rfc/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md +++ b/docs/rfc/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md @@ -10,7 +10,7 @@ The boundary bought package metadata, workspace and tsconfig references, module- ## Decision -The helper lives in `@deepseek-ai/dsh-stdio` as the terminal-channel plugin (`packages/ui/stdio/src/index.ts`): `createStdioChat`, its `StdioRuntime` test seam, and its unit tests (`packages/ui/stdio/tests/stdio.spec.ts`, `readline.spec.ts`) moved with it, so EOF handling, rendering, disposal, and piped-vs-TTY behavior stay unit-covered under the per-file coverage gate without hijacking process globals. The module keeps the named `name`/`inject`/`Config`/`apply` export shape — the contract the app's `ctx.plugin(uiStdio, …)` mount consumes — and the keyless Loader-path smokes in `examples/echo-agent` and `examples/coding-agent` keep proving the composed tree boots through the real Loader (the stdio package's plugin-shape unit suite pins the explicit `unwrapExports` assertion, since a bundle without `inject` would boot past a stray default rather than crash). +The helper lives in `@deepseek-ai/dsh-stdio` as the terminal-channel plugin (`packages/ui/stdio/src/index.ts`): `createStdioChat`, its `StdioRuntime` test seam, and its unit tests (`packages/ui/stdio/tests/stdio.spec.ts`, `readline.spec.ts`) moved with it, so EOF handling, rendering, disposal, and piped-vs-TTY behavior stay unit-covered under the per-file coverage gate without hijacking process globals. The module keeps the named `name`/`inject`/`Config`/`apply` export shape — the contract the app's `ctx.plugin(uiStdio, …)` mount consumes — and the keyless Loader-path smokes in `examples/echo-agent` and `examples/repl-agent` keep proving the composed tree boots through the real Loader (the stdio package's plugin-shape unit suite pins the explicit `unwrapExports` assertion, since a bundle without `inject` would boot past a stray default rather than crash). The `packages/support/ui-stdio` package is gone: manifest, tsconfig references, module-graph rows, and README rows deleted; the doc comments that named the package (the example e2e module docs, `packages/README.md`, the support and todo READMEs, [the ui group README](../../../../packages/ui/README.md)) describe the in-package module. diff --git a/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.i18n.yaml b/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.i18n.yaml index b68f7e7e78..f111c337dc 100644 --- a/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.i18n.yaml +++ b/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.i18n.yaml @@ -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 -2026-07-18-tui-terminal-state-snapshots.md: 280c2b4faec3bd5e14a24a5df7bc901ad31718cd -2026-07-18-tui-terminal-state-snapshots.zh.md: d1c609c6ce511bba1f498b72444af662b4444578 +2026-07-18-tui-terminal-state-snapshots.md: a1363a521372c11dab8b239cad87df5ff5ca8f22 +2026-07-18-tui-terminal-state-snapshots.zh.md: aa365ba1f2ba17e5bc5409dbdc3736cbc29fe26a diff --git a/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md b/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md index 280c2b4fae..a1363a5213 100644 --- a/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md +++ b/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md @@ -21,7 +21,7 @@ TUI coverage has four complementary layers: 3. `examples/tui-agent/tests/tui.snapshot.ts` replays committed JSONL session logs through the production agent loop and real tools, then compares the resulting semantic terminal state. 4. `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` boots the real Loader composition in a PTY, drives a scripted conversation through streaming and `ask_user_question`, and verifies startup, input, exit, failure reporting, and terminal restoration. -The runnable TUI has its own `examples/tui-agent` leaf beside the readline `coding-agent` and `acp-agent` leaves. It reuses the coding agent's backend and tool composition through an asserted include patch while fixing the shared terminal app to `ui.mode: tui`; TUI snapshots and PTY tests live with that leaf. +The runnable TUI has its own `examples/tui-agent` leaf beside the readline `repl-agent` and `acp-agent` leaves. It reuses the repl-agent backend and tool composition through an asserted include patch while fixing the shared terminal app to `ui.mode: tui`; TUI snapshots and PTY tests live with that leaf. ### Recorded-session replay diff --git a/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.zh.md b/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.zh.md index d1c609c6ce..aa365ba1f2 100644 --- a/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.zh.md +++ b/docs/rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.zh.md @@ -21,7 +21,7 @@ TUI 覆盖分为四个互补层次: 3. `examples/tui-agent/tests/tui.snapshot.ts` 通过生产 agent loop 和真实工具回放已提交的 JSONL 会话日志,再比较生成的语义终端状态。 4. `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` 在 PTY 中启动真实 Loader 组合,驱动一段经过流式输出和 `ask_user_question` 的脚本化会话,并验证启动、输入、退出、失败报告和终端恢复。 -可运行 TUI 在 `examples/tui-agent` 中拥有独立叶节点,与 readline `coding-agent` 和 `acp-agent` 叶节点并列。它通过带断言的 include patch 复用 coding agent 的后端与工具组合,只把共享终端应用固定为 `ui.mode: tui`;TUI 快照和 PTY 测试也归属这个叶节点。 +可运行 TUI 在 `examples/tui-agent` 中拥有独立叶节点,与 readline `repl-agent` 和 `acp-agent` 叶节点并列。它通过带断言的 include patch 复用 repl-agent 的后端与工具组合,只把共享终端应用固定为 `ui.mode: tui`;TUI 快照和 PTY 测试也归属这个叶节点。 ### 已录制会话回放 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index f6a2e0625f..3dacb56902 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -22,7 +22,7 @@ This table connects model-visible tool names to the plugin package and service s | `@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 bash-backed discovery tools: they 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-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/coding-agent/cordis.yml` and `examples/acp-agent/cordis.yml`. | +| `@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-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` | - | - | @@ -444,7 +444,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/coding-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/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml`. ## `@deepseek-ai/dsh-tool-tasks` diff --git a/examples/README.md b/examples/README.md index 03b798e164..17aa65bb8e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,21 +9,21 @@ A mock model + echo tool on the stdio chat app — the all-mock skeleton. The le - A thin leaf `cordis.yml` loading the `@deepseek-ai/dsh-stdio-demo` app - Registering a mock `LlmAdapter` (streaming scripted responses) - Registering a tool via `ctx.tools.register()` -- "Swap the backend, keep the app" — the only difference from `coding-agent` is the adapter +- "Swap the backend, keep the app" — the only difference from `repl-agent` is the adapter Run with: `pnpm run demo:echo`. When prompted, type "echo " to trigger a tool call round-trip. -## coding-agent +## repl-agent -A coding-agent REPL: DeepSeek V4 + the `read`/`write`/`edit` filesystem tools + the bash tool suite, `subagent` delegation, and the `todo_write` task tracker on the `@deepseek-ai/dsh-stdio-demo` app's readline front door. +A coding agent with DeepSeek V4, the `read`/`write`/`edit` filesystem tools, the bash tool suite, `subagent` delegation, and the `todo_write` task tracker on the `@deepseek-ai/dsh-stdio-demo` app's readline front door. -Run with: `pnpm run demo:repl` (needs `DEEPSEEK_API_KEY` in the environment or a gitignored repo-root `.env`). See [coding-agent/README.md](coding-agent/README.md) for details. +Run with: `pnpm run demo:repl` (needs `DEEPSEEK_API_KEY` in the environment or a gitignored repo-root `.env`). See [repl-agent/README.md](repl-agent/README.md) for details. -Run the Code Mode overlay with `pnpm run demo:code-mode`, or pass `acp` for the ACP example. See the [Code Mode example](coding-agent/README.md#code-mode) for its composition and a sample task. +Run the Code Mode overlay with `pnpm run demo:code-mode`, or pass `acp` for the ACP example. See the [Code Mode example](repl-agent/README.md#code-mode) for its composition and a sample task. ## tui-agent -The full-screen terminal sibling of `coding-agent`: it reuses the same coding backends and tools while forcing the shared terminal app to `dsh-tui`. It is the home of TUI PTY and snapshot scenarios. +The full-screen terminal sibling of `repl-agent`: it reuses the same coding backends and tools while forcing the shared terminal app to `dsh-tui`. It is the home of TUI PTY and snapshot scenarios. Run with: `pnpm run demo:tui` (needs `DEEPSEEK_API_KEY`). See [tui-agent/README.md](tui-agent/README.md) for controls and composition. diff --git a/examples/coding-agent/composition.md b/examples/coding-agent/composition.md deleted file mode 100644 index 2956291dc8..0000000000 --- a/examples/coding-agent/composition.md +++ /dev/null @@ -1,88 +0,0 @@ - - -# Coding Agent App Composition - -The coding-agent demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package. - -```mermaid -flowchart LR - cfg["examples/coding-agent
cordis.yml"] - plugin_coding_hmr["hmr
@cordisjs/plugin-hmr"] - cfg --> plugin_coding_hmr - plugin_coding_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] - cfg --> plugin_coding_llm_deepseek - plugin_coding_bash["bash
@deepseek-ai/dsh-bash-local"] - cfg --> plugin_coding_bash - plugin_coding_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-demo"] - cfg --> plugin_coding_stdio_agent - plugin_coding_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"] - plugin_coding_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"] - plugin_coding_stdio_agent --> frontdoor_stdio["@deepseek-ai/dsh-stdio
pre-created main agent"] - bundle_agent_core --> spine_llm["ctx.llm"] - bundle_agent_core --> spine_sessions["ctx.sessions"] - bundle_agent_core --> spine_tools["ctx.tools + tool-bash"] - bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"] - plugin_coding_token_meter["token-meter
@deepseek-ai/dsh-token-meter"] - cfg --> plugin_coding_token_meter - plugin_coding_compact_basic["compact-basic
@deepseek-ai/dsh-compact-basic"] - cfg --> plugin_coding_compact_basic - plugin_coding_subagent["subagent
@deepseek-ai/dsh-subagent"] - cfg --> plugin_coding_subagent - plugin_coding_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] - cfg --> plugin_coding_subagent_spawn - plugin_coding_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] - cfg --> plugin_coding_subagent_fork - plugin_coding_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] - cfg --> plugin_coding_tool_subagent - plugin_coding_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] - cfg --> plugin_coding_tool_subagent_fork - plugin_coding_workflow_workerthread["workflow-workerthread
@deepseek-ai/dsh-workflow-workerthread"] - cfg --> plugin_coding_workflow_workerthread - plugin_coding_tool_workflow["tool-workflow
@deepseek-ai/dsh-tool-workflow"] - cfg --> plugin_coding_tool_workflow - plugin_coding_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] - cfg --> plugin_coding_tool_todo - plugin_coding_fs_local["fs-local
@deepseek-ai/dsh-fs-local"] - cfg --> plugin_coding_fs_local - plugin_coding_fs_policy["fs-policy
@deepseek-ai/dsh-fs-policy"] - cfg --> plugin_coding_fs_policy - plugin_coding_tool_fs["tool-fs
@deepseek-ai/dsh-tool-fs"] - cfg --> plugin_coding_tool_fs - plugin_coding_tool_fs_search["tool-fs-search
@deepseek-ai/dsh-tool-fs-search"] - cfg --> plugin_coding_tool_fs_search - plugin_coding_timeout_policy["timeout-policy
@deepseek-ai/dsh-timeout-policy"] - cfg --> plugin_coding_timeout_policy - plugin_coding_spill_local["spill-local
@deepseek-ai/dsh-spill-local"] - cfg --> plugin_coding_spill_local - plugin_coding_spill_policy["spill-policy
@deepseek-ai/dsh-spill-policy"] - cfg --> plugin_coding_spill_policy -``` - -| Plugin id | Package / module | -| --- | --- | -| `hmr` | `@cordisjs/plugin-hmr` | -| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` | -| `bash` | `@deepseek-ai/dsh-bash-local` | -| `stdio-agent` | `@deepseek-ai/dsh-stdio-demo` | -| `token-meter` | `@deepseek-ai/dsh-token-meter` | -| `compact-basic` | `@deepseek-ai/dsh-compact-basic` | -| `subagent` | `@deepseek-ai/dsh-subagent` | -| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` | -| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` | -| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` | -| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` | -| `workflow-workerthread` | `@deepseek-ai/dsh-workflow-workerthread` | -| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` | -| `tool-todo` | `@deepseek-ai/dsh-tool-todo` | -| `fs-local` | `@deepseek-ai/dsh-fs-local` | -| `fs-policy` | `@deepseek-ai/dsh-fs-policy` | -| `tool-fs` | `@deepseek-ai/dsh-tool-fs` | -| `tool-fs-search` | `@deepseek-ai/dsh-tool-fs-search` | -| `timeout-policy` | `@deepseek-ai/dsh-timeout-policy` | -| `spill-local` | `@deepseek-ai/dsh-spill-local` | -| `spill-policy` | `@deepseek-ai/dsh-spill-policy` | - -Source config: [`examples/coding-agent/cordis.yml`](cordis.yml). - -Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. diff --git a/examples/cordis-agent/README.md b/examples/cordis-agent/README.md index 1ddf8b1d3b..5900d6a0f0 100644 --- a/examples/cordis-agent/README.md +++ b/examples/cordis-agent/README.md @@ -1,6 +1,6 @@ # cordis-agent -The self-referential harness demo: the coding-agent spine (DeepSeek V4 + local bash on the stdio chat app) plus [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md), which hands the model three tools over the **live cordis runtime it is running inside** — inspect it, mount new plugins into it, and dispose them again. The `ctx.fs` and `ctx.web` services are mounted (provider-only, no model-facing file/web tools) so the plugins the agent writes have real capabilities to build on; Node built-ins are trapped in the sandbox and redirect to those services. The design (sandbox semantics, mount lifecycle, cross-mount composition, caveats) lives in [the toolset RFC](../../docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). +The self-referential harness demo: the coding spine (DeepSeek V4 + local bash on the stdio chat app) plus [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md), which hands the model three tools over the **live cordis runtime it is running inside** — inspect it, mount new plugins into it, and dispose them again. The `ctx.fs` and `ctx.web` services are mounted (provider-only, no model-facing file/web tools) so the plugins the agent writes have real capabilities to build on; Node built-ins are trapped in the sandbox and redirect to those services. The design (sandbox semantics, mount lifecycle, cross-mount composition, caveats) lives in [the toolset RFC](../../docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). ## Run it diff --git a/examples/echo-agent/README.md b/examples/echo-agent/README.md index 183268f5da..f397cc633f 100644 --- a/examples/echo-agent/README.md +++ b/examples/echo-agent/README.md @@ -9,7 +9,7 @@ This example is just a leaf `cordis.yml`: it loads the [`@deepseek-ai/dsh-stdio- - `mock-llm.ts` — a mock `LlmAdapter` that streams scripted responses and calls the `echo` tool when the user types "echo ". Registered with `ctx.llm.registerAdapter(['mock-echo'], …)`. - `echo-tool.ts` — a tool registered via `ctx.tools.register(defineTool(…))` with typed `execute` args; echoes text back uppercased. -Swapping `mock-llm` for the real `llm-deepseek` adapter is all that separates this from `coding-agent` — the same app, a different backend. +Swapping `mock-llm` for the real `llm-deepseek` adapter is all that separates this from `repl-agent` — the same app, a different backend. ## Plugin files diff --git a/examples/coding-agent/README.md b/examples/repl-agent/README.md similarity index 95% rename from examples/coding-agent/README.md rename to examples/repl-agent/README.md index 40e8b7087f..2e559d129a 100644 --- a/examples/coding-agent/README.md +++ b/examples/repl-agent/README.md @@ -1,6 +1,6 @@ -# coding-agent +# repl-agent -The coding-agent REPL wiring: DeepSeek V4 + the `read`/`write`/`edit` filesystem tools + the bash tool suite + subagent delegation + `todo_write` + readline chat + JSONL persistence, loaded from `cordis.yml`. The sibling [`tui-agent`](../tui-agent/README.md) fixes the same agent composition to the full-screen terminal front door. +The repl-agent wiring: DeepSeek V4 + the `read`/`write`/`edit` filesystem tools + the bash tool suite + subagent delegation + `todo_write` + readline chat + JSONL persistence, loaded from `cordis.yml`. The sibling [`tui-agent`](../tui-agent/README.md) fixes the same agent composition to the full-screen terminal front door. ## Run it diff --git a/examples/coding-agent/code-mode.cordis.yml b/examples/repl-agent/code-mode.cordis.yml similarity index 93% rename from examples/coding-agent/code-mode.cordis.yml rename to examples/repl-agent/code-mode.cordis.yml index b1f62829bc..8802b510ba 100644 --- a/examples/coding-agent/code-mode.cordis.yml +++ b/examples/repl-agent/code-mode.cordis.yml @@ -23,7 +23,7 @@ ui: mode: readline persona: | - You are coding-agent, a coding assistant powered by the {{model}} model. + You are a coding agent powered by the {{model}} model. You work by writing TypeScript programs for run_code: batch related tool work into one program, loop and branch where it helps, and print diff --git a/examples/repl-agent/composition.md b/examples/repl-agent/composition.md new file mode 100644 index 0000000000..af3f810585 --- /dev/null +++ b/examples/repl-agent/composition.md @@ -0,0 +1,88 @@ + + +# REPL Agent App Composition + +The REPL agent demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package. + +```mermaid +flowchart LR + cfg["examples/repl-agent
cordis.yml"] + plugin_repl_hmr["hmr
@cordisjs/plugin-hmr"] + cfg --> plugin_repl_hmr + plugin_repl_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"] + cfg --> plugin_repl_llm_deepseek + plugin_repl_bash["bash
@deepseek-ai/dsh-bash-local"] + cfg --> plugin_repl_bash + plugin_repl_stdio_agent["stdio-agent
@deepseek-ai/dsh-stdio-demo"] + cfg --> plugin_repl_stdio_agent + plugin_repl_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"] + plugin_repl_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"] + plugin_repl_stdio_agent --> frontdoor_stdio["@deepseek-ai/dsh-stdio
pre-created main agent"] + bundle_agent_core --> spine_llm["ctx.llm"] + bundle_agent_core --> spine_sessions["ctx.sessions"] + bundle_agent_core --> spine_tools["ctx.tools + tool-bash"] + bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"] + plugin_repl_token_meter["token-meter
@deepseek-ai/dsh-token-meter"] + cfg --> plugin_repl_token_meter + plugin_repl_compact_basic["compact-basic
@deepseek-ai/dsh-compact-basic"] + cfg --> plugin_repl_compact_basic + plugin_repl_subagent["subagent
@deepseek-ai/dsh-subagent"] + cfg --> plugin_repl_subagent + plugin_repl_subagent_spawn["subagent-spawn
@deepseek-ai/dsh-subagent-spawn"] + cfg --> plugin_repl_subagent_spawn + plugin_repl_subagent_fork["subagent-fork
@deepseek-ai/dsh-subagent-fork"] + cfg --> plugin_repl_subagent_fork + plugin_repl_tool_subagent["tool-subagent
@deepseek-ai/dsh-tool-subagent"] + cfg --> plugin_repl_tool_subagent + plugin_repl_tool_subagent_fork["tool-subagent-fork
@deepseek-ai/dsh-tool-subagent"] + cfg --> plugin_repl_tool_subagent_fork + plugin_repl_workflow_workerthread["workflow-workerthread
@deepseek-ai/dsh-workflow-workerthread"] + cfg --> plugin_repl_workflow_workerthread + plugin_repl_tool_workflow["tool-workflow
@deepseek-ai/dsh-tool-workflow"] + cfg --> plugin_repl_tool_workflow + plugin_repl_tool_todo["tool-todo
@deepseek-ai/dsh-tool-todo"] + cfg --> plugin_repl_tool_todo + plugin_repl_fs_local["fs-local
@deepseek-ai/dsh-fs-local"] + cfg --> plugin_repl_fs_local + plugin_repl_fs_policy["fs-policy
@deepseek-ai/dsh-fs-policy"] + cfg --> plugin_repl_fs_policy + plugin_repl_tool_fs["tool-fs
@deepseek-ai/dsh-tool-fs"] + cfg --> plugin_repl_tool_fs + plugin_repl_tool_fs_search["tool-fs-search
@deepseek-ai/dsh-tool-fs-search"] + cfg --> plugin_repl_tool_fs_search + plugin_repl_timeout_policy["timeout-policy
@deepseek-ai/dsh-timeout-policy"] + cfg --> plugin_repl_timeout_policy + plugin_repl_spill_local["spill-local
@deepseek-ai/dsh-spill-local"] + cfg --> plugin_repl_spill_local + plugin_repl_spill_policy["spill-policy
@deepseek-ai/dsh-spill-policy"] + cfg --> plugin_repl_spill_policy +``` + +| Plugin id | Package / module | +| --- | --- | +| `hmr` | `@cordisjs/plugin-hmr` | +| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` | +| `bash` | `@deepseek-ai/dsh-bash-local` | +| `stdio-agent` | `@deepseek-ai/dsh-stdio-demo` | +| `token-meter` | `@deepseek-ai/dsh-token-meter` | +| `compact-basic` | `@deepseek-ai/dsh-compact-basic` | +| `subagent` | `@deepseek-ai/dsh-subagent` | +| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` | +| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` | +| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` | +| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` | +| `workflow-workerthread` | `@deepseek-ai/dsh-workflow-workerthread` | +| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` | +| `tool-todo` | `@deepseek-ai/dsh-tool-todo` | +| `fs-local` | `@deepseek-ai/dsh-fs-local` | +| `fs-policy` | `@deepseek-ai/dsh-fs-policy` | +| `tool-fs` | `@deepseek-ai/dsh-tool-fs` | +| `tool-fs-search` | `@deepseek-ai/dsh-tool-fs-search` | +| `timeout-policy` | `@deepseek-ai/dsh-timeout-policy` | +| `spill-local` | `@deepseek-ai/dsh-spill-local` | +| `spill-policy` | `@deepseek-ai/dsh-spill-policy` | + +Source config: [`examples/repl-agent/cordis.yml`](cordis.yml). + +Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. diff --git a/examples/coding-agent/cordis.yml b/examples/repl-agent/cordis.yml similarity index 98% rename from examples/coding-agent/cordis.yml rename to examples/repl-agent/cordis.yml index 497e2a80c8..00a6b4b9a6 100644 --- a/examples/coding-agent/cordis.yml +++ b/examples/repl-agent/cordis.yml @@ -42,7 +42,7 @@ # Keep the persona to identity and behavior; tool plugins own tool guidance. # The loop resolves {{model}} from this agent's configuration. persona: | - You are coding-agent, a coding assistant powered by the {{model}} model. + You are a coding agent powered by the {{model}} model. Verify your work by running the code or tests. Keep answers brief and factual. diff --git a/examples/coding-agent/package.json b/examples/repl-agent/package.json similarity index 81% rename from examples/coding-agent/package.json rename to examples/repl-agent/package.json index b3594ff597..34c7db6918 100644 --- a/examples/coding-agent/package.json +++ b/examples/repl-agent/package.json @@ -1,5 +1,5 @@ { - "name": "coding-agent-example", + "name": "repl-agent-example", "private": true, "version": "0.0.1", "type": "module", diff --git a/examples/coding-agent/tests/code-mode-keyless-smoke.e2e.ts b/examples/repl-agent/tests/code-mode-keyless-smoke.e2e.ts similarity index 100% rename from examples/coding-agent/tests/code-mode-keyless-smoke.e2e.ts rename to examples/repl-agent/tests/code-mode-keyless-smoke.e2e.ts diff --git a/examples/coding-agent/tests/code-mode.e2e.ts b/examples/repl-agent/tests/code-mode.e2e.ts similarity index 98% rename from examples/coding-agent/tests/code-mode.e2e.ts rename to examples/repl-agent/tests/code-mode.e2e.ts index aab4da376f..c7f5d48562 100644 --- a/examples/coding-agent/tests/code-mode.e2e.ts +++ b/examples/repl-agent/tests/code-mode.e2e.ts @@ -25,7 +25,7 @@ import * as WorkspaceContext from '@deepseek-ai/dsh-workspace-context' * each `tool/code-dispatch`. The keyless Loader smoke is in the sibling test. */ -const PERSONA = 'You are coding-agent. You work by writing TypeScript programs for run_code: ' +const PERSONA = 'You are a coding agent. You work by writing TypeScript programs for run_code: ' + 'batch related tool work into one program and print or return ONLY the findings that matter.' const WORKSPACE_PROBE = 'dragonfruit-8675309' diff --git a/examples/coding-agent/tests/coding-task.e2e.ts b/examples/repl-agent/tests/coding-task.e2e.ts similarity index 100% rename from examples/coding-agent/tests/coding-task.e2e.ts rename to examples/repl-agent/tests/coding-task.e2e.ts diff --git a/examples/coding-agent/tests/compaction.e2e.ts b/examples/repl-agent/tests/compaction.e2e.ts similarity index 100% rename from examples/coding-agent/tests/compaction.e2e.ts rename to examples/repl-agent/tests/compaction.e2e.ts diff --git a/examples/coding-agent/tests/full-loop.e2e.ts b/examples/repl-agent/tests/full-loop.e2e.ts similarity index 100% rename from examples/coding-agent/tests/full-loop.e2e.ts rename to examples/repl-agent/tests/full-loop.e2e.ts diff --git a/examples/coding-agent/tests/harness.ts b/examples/repl-agent/tests/harness.ts similarity index 98% rename from examples/coding-agent/tests/harness.ts rename to examples/repl-agent/tests/harness.ts index 290ffcdf0b..eeba57fc61 100644 --- a/examples/coding-agent/tests/harness.ts +++ b/examples/repl-agent/tests/harness.ts @@ -14,7 +14,7 @@ import { BasicCompactService } from '@deepseek-ai/dsh-compact-basic' import type { BasicCompactConfig } from '@deepseek-ai/dsh-compact-basic' /** - * Shared harness for the coding-agent e2e suites: the full plugin stack + * Shared harness for the repl-agent e2e suites: the full plugin stack * with the real DeepSeek adapter and the real bash + todo_write tools. Lives * outside the *.e2e.ts pattern so importing it never re-registers another * file's tests. diff --git a/examples/coding-agent/tests/keyless-smoke.e2e.ts b/examples/repl-agent/tests/keyless-smoke.e2e.ts similarity index 81% rename from examples/coding-agent/tests/keyless-smoke.e2e.ts rename to examples/repl-agent/tests/keyless-smoke.e2e.ts index 831d0daf5c..62eb43f55a 100644 --- a/examples/coding-agent/tests/keyless-smoke.e2e.ts +++ b/examples/repl-agent/tests/keyless-smoke.e2e.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest' import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' /** - * Keyless Loader-path smoke for examples/coding-agent: boot the real example + * Keyless Loader-path smoke for examples/repl-agent: boot the real example * through the stdio-agent bin and its `cordis.yml`, then close stdin without a * prompt and assert the banner. The dummy key satisfies adapter construction; * immediate EOF guarantees there is no model call. @@ -13,11 +13,11 @@ const binScript = fileURLToPath(new URL('../../../packages/examples/stdio-demo/s const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)) -describe('coding-agent keyless smoke (real cordis.yml via the Loader)', () => { +describe('repl-agent keyless smoke (real cordis.yml via the Loader)', () => { it('boots the full plugin tree, prints its banner, and exits cleanly on EOF', async () => { const { stdout } = await runLoaderSmoke({ - label: 'coding-agent', - tempDirPrefix: 'coding-smoke-', + label: 'repl-agent', + tempDirPrefix: 'repl-smoke-', binScript, configPath, tsconfigPath, diff --git a/examples/coding-agent/tests/resume.e2e.ts b/examples/repl-agent/tests/resume.e2e.ts similarity index 100% rename from examples/coding-agent/tests/resume.e2e.ts rename to examples/repl-agent/tests/resume.e2e.ts diff --git a/examples/coding-agent/tests/todo-write.e2e.ts b/examples/repl-agent/tests/todo-write.e2e.ts similarity index 100% rename from examples/coding-agent/tests/todo-write.e2e.ts rename to examples/repl-agent/tests/todo-write.e2e.ts diff --git a/examples/tui-agent/README.md b/examples/tui-agent/README.md index ee5b610d53..b4074881fb 100644 --- a/examples/tui-agent/README.md +++ b/examples/tui-agent/README.md @@ -1,6 +1,6 @@ # tui-agent -The full-screen terminal counterpart to the [`coding-agent`](../coding-agent/README.md) readline REPL and [`acp-agent`](../acp-agent/README.md) server. It reuses the coding agent's backends and tool composition, then fixes the shared terminal app to the `dsh-tui` front door. +The full-screen terminal counterpart to the [`repl-agent`](../repl-agent/README.md) readline REPL and [`acp-agent`](../acp-agent/README.md) server. It reuses the coding agent's backends and tool composition, then fixes the shared terminal app to the `dsh-tui` front door. ## Run it @@ -16,7 +16,7 @@ Run `pnpm run demo:code-mode tui` for the sibling Code Mode overlay. ## Composition -[`cordis.yml`](cordis.yml) includes the readline coding-agent leaf so the LLM, bash, filesystem, compaction, subagent, workflow, todo, timeout, and spill choices have one owner. Its asserted patch replaces only the terminal app config and forces `ui.mode: tui`; [`code-mode.cordis.yml`](code-mode.cordis.yml) applies the same front-door patch to the coding agent's Code Mode overlay. +[`cordis.yml`](cordis.yml) includes the readline repl-agent leaf so the LLM, bash, filesystem, compaction, subagent, workflow, todo, timeout, and spill choices have one owner. Its asserted patch replaces only the terminal app config and forces `ui.mode: tui`; [`code-mode.cordis.yml`](code-mode.cordis.yml) applies the same front-door patch to the repl-agent Code Mode overlay. ## Snapshot tests diff --git a/examples/tui-agent/code-mode.cordis.yml b/examples/tui-agent/code-mode.cordis.yml index 7bb7932ba0..75d2cea38a 100644 --- a/examples/tui-agent/code-mode.cordis.yml +++ b/examples/tui-agent/code-mode.cordis.yml @@ -1,9 +1,9 @@ -# Code Mode keeps the TUI front door while reusing the coding-agent overlay's +# Code Mode keeps the TUI front door while reusing the repl-agent overlay's # worker runtime and one-tool registry composition. - id: base name: '@cordisjs/plugin-include' config: - path: ../coding-agent/code-mode.cordis.yml + path: ../repl-agent/code-mode.cordis.yml patches: - id: stdio-agent name: '@deepseek-ai/dsh-stdio-demo' @@ -23,7 +23,7 @@ showReasoning: true maxToolOutputLines: 12 persona: | - You are coding-agent, a coding assistant powered by the {{model}} model. + You are a coding agent powered by the {{model}} model. You work by writing TypeScript programs for run_code: batch related tool work into one program, loop and branch where it helps, and print diff --git a/examples/tui-agent/composition.md b/examples/tui-agent/composition.md index 511f76d41e..94515c32c4 100644 --- a/examples/tui-agent/composition.md +++ b/examples/tui-agent/composition.md @@ -3,7 +3,7 @@ # TUI Agent App Composition -The TUI agent reuses the coding-agent backend and tool composition while fixing the shared terminal app to the full-screen dsh-tui front door. +The TUI agent reuses the repl-agent backend and tool composition while fixing the shared terminal app to the full-screen dsh-tui front door. ```mermaid flowchart LR diff --git a/examples/tui-agent/cordis.yml b/examples/tui-agent/cordis.yml index 0f329c38e2..515274b2a8 100644 --- a/examples/tui-agent/cordis.yml +++ b/examples/tui-agent/cordis.yml @@ -1,10 +1,10 @@ -# Full-screen TUI front door over the same coding-agent composition used by the +# Full-screen TUI front door over the same repl-agent composition used by the # readline REPL. The include keeps backends and optional tools aligned; the # patch owns only the terminal-specific app config. - id: base name: '@cordisjs/plugin-include' config: - path: ../coding-agent/cordis.yml + path: ../repl-agent/cordis.yml patches: - id: stdio-agent name: '@deepseek-ai/dsh-stdio-demo' @@ -22,7 +22,7 @@ showReasoning: true maxToolOutputLines: 12 persona: | - You are coding-agent, a coding assistant powered by the {{model}} model. + You are a coding agent powered by the {{model}} model. Verify your work by running the code or tests. Keep answers brief and factual. diff --git a/package.json b/package.json index ca85fd32d2..19f6d15005 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-cordis-api && pnpm run verify-export-jsdoc && pnpm run verify-tool-catalog && pnpm run verify-config-catalog && pnpm run verify-persistence-catalog && pnpm run verify-doc-graphs && pnpm run verify-scoped-events && pnpm run verify-website-api && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-package-readme-model-experience && pnpm run verify-mermaid && pnpm run verify-rfc-classification && pnpm run verify-rfc-format && pnpm run verify-type-equiv && pnpm run verify-translation-prompt && pnpm run verify-translation-pairing && pnpm run verify-doc-budgets && pnpm run verify-package-readme-limitations && pnpm run verify-website-yaml", "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-runtime-closure", "demo:echo": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/echo-agent/cordis.yml", - "demo:repl": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/coding-agent/cordis.yml", + "demo:repl": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/repl-agent/cordis.yml", "demo:tui": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/tui-agent/cordis.yml", "demo:code-mode": "node scripts/demo-code-mode.mjs", "demo:cordis": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/cordis-agent/cordis.yml", diff --git a/packages/examples/stdio-demo/src/bin.ts b/packages/examples/stdio-demo/src/bin.ts index 278bb5faca..3d8a0c2a33 100644 --- a/packages/examples/stdio-demo/src/bin.ts +++ b/packages/examples/stdio-demo/src/bin.ts @@ -2,7 +2,7 @@ /** * Boot a stdio app from a leaf `cordis.yml`; usage is `dsh-stdio-demo [config]`, defaulting to the * cwd file. Shared `.env` loading, fail-loud Loader guards, and settled-tree boot live in - * dsh-app-boot. The echo and coding-agent demos invoke this bin with their own leaf configs. + * dsh-app-boot. The echo-agent and repl-agent demos invoke this bin with their own leaf configs. * @module @deepseek-ai/dsh-stdio-demo/bin */ diff --git a/packages/examples/stdio-demo/src/index.ts b/packages/examples/stdio-demo/src/index.ts index 69a0be1171..531c4a3d5e 100644 --- a/packages/examples/stdio-demo/src/index.ts +++ b/packages/examples/stdio-demo/src/index.ts @@ -174,7 +174,7 @@ export function composeTerminalApp(ctx: Context, config: Config, isTTY: boolean) /** Compose the configured terminal front door with the agent app. */ /* v8 ignore start -- production stream capability wiring; composeTerminalApp is unit-covered, - and the coding-agent PTY smoke covers the interactive process path */ + and the repl-agent PTY smoke covers the interactive process path */ export function apply(ctx: Context, config: Config): void { composeTerminalApp(ctx, config, process.stdin.isTTY && process.stdout.isTTY) } diff --git a/packages/ui/tui/src/index.ts b/packages/ui/tui/src/index.ts index 0906b7bc9f..1c3fc1315c 100644 --- a/packages/ui/tui/src/index.ts +++ b/packages/ui/tui/src/index.ts @@ -1342,7 +1342,7 @@ export function mountTui(ctx: Context, config: Config, runtime: TuiRuntime): voi /** Cordis entry point using the process terminal; explicit TUI composition requires a TTY pair. */ /* v8 ignore start -- production process wiring; fake-terminal tests cover mountTui/createTuiChat, - and the coding-agent PTY smoke covers the real entry */ + and the repl-agent PTY smoke covers the real entry */ export function apply(ctx: Context, config: Config): void { if (!process.stdin.isTTY || !process.stdout.isTTY) { throw new Error('ui-tui: both stdin and stdout must be TTYs; use @deepseek-ai/dsh-stdio for pipes') diff --git a/scripts/demo-code-mode.mjs b/scripts/demo-code-mode.mjs index 7a5e054cc0..43bff2d4ba 100644 --- a/scripts/demo-code-mode.mjs +++ b/scripts/demo-code-mode.mjs @@ -9,7 +9,7 @@ import { spawn } from 'node:child_process' // the overlay config (the stdio bin keeps --expose-internals for the cordis // Loader's HMR path). const UIS = new Map([ - ['repl', ['--expose-internals', '--import', 'tsx', 'packages/examples/stdio-demo/src/bin.ts', 'examples/coding-agent/code-mode.cordis.yml']], + ['repl', ['--expose-internals', '--import', 'tsx', 'packages/examples/stdio-demo/src/bin.ts', 'examples/repl-agent/code-mode.cordis.yml']], ['tui', ['--expose-internals', '--import', 'tsx', 'packages/examples/stdio-demo/src/bin.ts', 'examples/tui-agent/code-mode.cordis.yml']], ['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/code-mode.cordis.yml']], ]) diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 7164e65a5c..7c698c0cb9 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -427,12 +427,12 @@ const APP_EXAMPLES = [ summary: 'The echo demo swaps in a local mock LLM and teaching echo tool, then loads the stdio app package for the shared spine and terminal front door.', }, { - id: 'coding', - rel: 'examples/coding-agent/composition.md', - title: 'Coding Agent App Composition', - label: 'examples/coding-agent', - config: 'examples/coding-agent/cordis.yml', - summary: 'The coding-agent demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package.', + id: 'repl', + rel: 'examples/repl-agent/composition.md', + title: 'REPL Agent App Composition', + label: 'examples/repl-agent', + config: 'examples/repl-agent/cordis.yml', + summary: 'The REPL agent demo adds the real DeepSeek adapter, filesystem tools, todo_write, compaction, and both subagent transports on top of the stdio app package.', }, { id: 'tui', @@ -440,7 +440,7 @@ const APP_EXAMPLES = [ title: 'TUI Agent App Composition', label: 'examples/tui-agent', config: 'examples/tui-agent/cordis.yml', - summary: 'The TUI agent reuses the coding-agent backend and tool composition while fixing the shared terminal app to the full-screen dsh-tui front door.', + summary: 'The TUI agent reuses the repl-agent backend and tool composition while fixing the shared terminal app to the full-screen dsh-tui front door.', }, { id: 'cordis', @@ -470,7 +470,7 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string if (pluginName === '@deepseek-ai/dsh-stdio-demo') { const frontDoor = exampleId === 'tui' ? '@deepseek-ai/dsh-tui
pre-created main agent' - : exampleId === 'coding' + : exampleId === 'repl' ? '@deepseek-ai/dsh-stdio
pre-created main agent' : 'dsh-tui (TTY) / dsh-stdio (pipes)
pre-created main agent' lines.push(` ${appNode} --> ${nodeId('frontdoor', 'stdio')}["${frontDoor}"]`) @@ -990,7 +990,7 @@ function renderIndex(docs: GraphDoc[]): string { const labels: Record = { 'docs/capability-seams.md': 'capability seams and core services', 'examples/echo-agent/composition.md': 'echo-agent app composition', - 'examples/coding-agent/composition.md': 'coding-agent app composition', + 'examples/repl-agent/composition.md': 'repl-agent app composition', 'examples/tui-agent/composition.md': 'tui-agent app composition', 'examples/cordis-agent/composition.md': 'cordis-agent app composition', 'examples/acp-agent/composition.md': 'acp-agent app composition', @@ -1002,7 +1002,7 @@ function renderIndex(docs: GraphDoc[]): string { const modes: Record = { 'docs/capability-seams.md': 'hybrid generated', 'examples/echo-agent/composition.md': 'hybrid generated', - 'examples/coding-agent/composition.md': 'hybrid generated', + 'examples/repl-agent/composition.md': 'hybrid generated', 'examples/tui-agent/composition.md': 'hybrid generated', 'examples/cordis-agent/composition.md': 'hybrid generated', 'examples/acp-agent/composition.md': 'hybrid generated', diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index ca69768227..8606d30895 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -206,7 +206,7 @@ const TOOL_PACKAGES: ToolPackage[] = [ await ctx.plugin(ToolSubagent, { provider: 'mock' }) }, note: - '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/coding-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/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml`.', }, { pkg: '@deepseek-ai/dsh-tool-tasks', diff --git a/website/zh-CN/guide/config.md b/website/zh-CN/guide/config.md index 07943288b8..35fb42185b 100644 --- a/website/zh-CN/guide/config.md +++ b/website/zh-CN/guide/config.md @@ -39,7 +39,7 @@ Harness 使用 `cordis.yml` 描述一个 Agent 加载哪些插件、以什么参 persistenceRoot: './.sessions' ``` -### coding-agent 的配置 +### repl-agent 的配置 真实场景——接入 DeepSeek API,带完整工具链: @@ -81,7 +81,7 @@ Harness 使用 `cordis.yml` 描述一个 Agent 加载哪些插件、以什么参 persistenceRoot: './.sessions' welcome: 'agent REPL ready. Give it a coding task.' persona: | - You are coding-agent, a coding assistant powered by the {{model}} model. + You are a coding agent powered by the {{model}} model. Verify your work by running the code or tests. Keep answers brief and factual. # Token 计量:统一定义模型能看到的 token 上限 diff --git a/website/zh-CN/guide/quickstart.md b/website/zh-CN/guide/quickstart.md index 76ff1fd3fe..27725fc6b9 100644 --- a/website/zh-CN/guide/quickstart.md +++ b/website/zh-CN/guide/quickstart.md @@ -69,7 +69,7 @@ echo-agent ready. Type a message ("echo " triggers the tool). DEEPSEEK_API_KEY=sk-your-key-here ``` -### 启动 coding-agent +### 启动 repl-agent ```sh pnpm run demo:repl @@ -90,7 +90,7 @@ agent REPL ready. Give it a coding task. ## 回头看 -echo-agent 和 coding-agent 用的是同一个应用框架(`@deepseek-ai/dsh-stdio-demo`),区别只在 `cordis.yml`——换了哪些插件、填了什么配置。你以后定制自己的 Agent 也是同样的方式。 +echo-agent 和 repl-agent 用的是同一个应用框架(`@deepseek-ai/dsh-stdio-demo`),区别只在 `cordis.yml`——换了哪些插件、填了什么配置。你以后定制自己的 Agent 也是同样的方式。 ## 下一步