Merge remote-tracking branch 'origin/master' into codex/agent-session-jsonl-location

# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/bash.md
#	docs/module-graph.md
#	docs/rfc/INDEX.md
#	docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md
#	docs/rfc/implemented/feature/2026-06-30-hook-bridges.md
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md
#	examples/acp-agent/tests/snapshots/skill-load/session.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	examples/sandbox-acp-agent/tests/snapshots/mode-switching/session.jsonl
#	packages/bash/bash-local/README.md
#	packages/bash/bash-local/src/run.ts
#	packages/bash/bash-local/tests/run.spec.ts
#	packages/bash/bash/README.md
#	packages/bash/tool-bash/README.md
#	packages/bash/tool-bash/src/index.ts
#	packages/bash/tool-bash/tests/tools.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-core/src/index.ts
#	packages/session-persistence/session-persistence-jsonl/src/index.ts
#	packages/session-persistence/session-persistence-sqlite/src/index.ts
#	packages/session-persistence/session-persistence/README.md
#	packages/session-persistence/session-persistence/src/index.ts
#	packages/ui/acp-agent/README.md
#	packages/ui/stdio-agent/README.md
This commit is contained in:
Yichen Jiang
2026-07-14 18:04:05 +08:00
672 changed files with 10233 additions and 14251 deletions

View File

@@ -28,8 +28,7 @@ async function backend(path = ':memory:'): Promise<{ ctx: Context; dispose: () =
return { ctx, dispose: () => fiber.dispose() }
}
// The payoff: the SAME backend-agnostic contract the JSONL backend runs, now
// proving the SQLite backend satisfies identical semantics.
// Run the same backend-agnostic contract as JSONL to pin identical semantics.
runPersistenceContract('sqlite', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
@@ -40,11 +39,8 @@ runPersistenceContract('sqlite', async () => {
}
})
// Run the shared coordinator orchestration suite against the real SQLite backend.
// A FILE-backed db (not :memory:) is the shared storage scope so two mounted
// instances see the same rows (HMR/reload). `corruptTail` INSERTs a row past the
// committed seq whose `data` is invalid JSON — a never-committed torn tail that
// drives the coordinator's commitRepair-with-tornMarker branch over real db rows.
// A file-backed database lets two mounts share rows across reload. `corruptTail` inserts invalid
// JSON past the committed seq, exercising coordinator repair against real database rows.
runCoordinatorContract('sqlite', async (): Promise<CoordinatorFixture> => {
const dir = await mkdtemp(join(tmpdir(), 'dsh-sqlite-coord-'))
const path = join(dir, 'sessions.db')
@@ -66,9 +62,9 @@ runCoordinatorContract('sqlite', async (): Promise<CoordinatorFixture> => {
})
describe('scanRows', () => {
// scanRows works off EventRows (data is a JSON string column); build them from
// SessionEvents so the unit tests read in terms of the event vocabulary. Surface
// fields are serialized to their nullable columns so a round trip is faithful.
// scanRows works off EventRows (data is a JSON string column); build them from SessionEvents
// so the unit tests read in terms of the event vocabulary. Surface metadata is serialized to
// its nullable columns so the conversion remains faithful.
const rows = (events: SessionEvent[]): EventRow[] =>
events.map((e) => {
const se = e as SessionEvent<SurfaceEventType>
@@ -262,11 +258,9 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => {
})
it('rejects a sibling v3 database (the merge-collided version) rather than opening it against missing columns', async () => {
// Two unmerged branches each shipped a DISTINCT layout under user_version 3
// (one added only `seed_length`, the other only the surface columns). The
// merged build is v4; an on-disk v3 is ambiguous and is missing at least one
// of this build's columns, so it MUST be rejected, not opened. Stamp a v3
// database and confirm the version check refuses it.
// Two unmerged branches each shipped a DISTINCT layout under user_version 3 (one added only
// `seed_length`, the other only the surface columns). The merged v4 cannot interpret that
// ambiguous, incomplete layout and must reject it.
const path = await freshDbPath()
openDatabase(path, 'wal').close() // creates + stamps user_version = SCHEMA_VERSION (4)
const db = openDatabase(path, 'wal')
@@ -283,11 +277,9 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => {
await b1.ctx.sessionPersistence.append(m.id, oneTurnLog()) // committed: seqs 0..5
await b1.dispose()
// Hand-insert a torn tail row (seq 6, no closing turn/end) whose `data` is
// invalid JSON. The contract: only a parse error in the COMMITTED region is
// unloadable; a torn tail must be discarded. scanRows finds the last
// turn/end on the seq+type columns (never parsing tail `data`), so the
// unparsable row after it bounds the preserved prefix and is deleted by load.
// A torn row after the last committed turn has invalid JSON. `scanRows` locates the boundary
// from seq/type columns without parsing the tail, preserves the committed prefix, and load
// deletes the row; invalid JSON inside the committed region would remain fatal.
const db = openDatabase(path, 'wal')
db.prepare('INSERT INTO events (session_id, seq, type, time, data) VALUES (?, 6, ?, 7, ?)')
.run(m.id, 'turn/start', '{not valid json')