Merge latest master into codex/llm-error-recovery-rfc
# Conflicts: # docs/architecture.md # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/event-producer-consumer.md # docs/module-graph.md # docs/persistence-catalog.md # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/src/loop.ts # packages/core/agent/src/types.ts # packages/examples/agent-spine-demo/README.md # packages/examples/stdio-demo/README.md # packages/examples/stdio-demo/src/index.ts # packages/llm/llm-deepseek/README.md # packages/llm/llm-deepseek/src/adapter.ts # packages/llm/llm-deepseek/tests/adapter.spec.ts # packages/llm/llm/README.md # packages/llm/llm/src/error.ts # packages/llm/llm/tests/service.spec.ts # packages/sandbox/sandbox-policy/tsconfig.json # packages/ui/stdio/README.md # packages/ui/stdio/src/index.ts # packages/ui/stdio/tests/stdio.spec.ts # packages/ui/tui/src/index.ts # pnpm-lock.yaml # website/zh-CN/api/harness/events.md # website/zh-CN/api/harness/llm.md
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# @deepseek-ai/dsh-cli-demo
|
||||
|
||||
Headless one-shot app and bin for running one agent task without a readline or editor client. It composes [`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md), JSONL persistence, and exactly one fresh top-level agent. The bin submits the task, waits for its durable turn ending, renders the selected output, disposes to quiescence, and exits.
|
||||
Headless one-shot app and bin for running one agent task without an interactive UI or editor client. It composes [`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md), JSONL persistence, and exactly one fresh top-level agent. The bin submits the task, waits for its durable turn ending, renders the selected output, disposes to quiescence, and exits.
|
||||
|
||||
The package mounts no console logger, readline UI, user-interaction service, or `ask_user_question` tool. Stdout is reserved for the selected output format; diagnostics use stderr.
|
||||
The package mounts no console logger, interactive UI, user-interaction service, or `ask_user_question` tool. Stdout is reserved for the selected output format; diagnostics use stderr.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -20,6 +20,7 @@ The package mounts no console logger, readline UI, user-interaction service, or
|
||||
| `toolTasks` | owner defaults | generic `task_output` wait bounds |
|
||||
| `llmRetry` | owner defaults | bounded transient model-request retry policy |
|
||||
| `persistenceRoot` | `./.sessions` | JSONL session root |
|
||||
| `persistenceCompression` | `'zstd'` | JSONL artifact encoding (`'zstd'` or raw `'none'`) |
|
||||
| `workspaceContext` | required | workspace-instruction byte budget, or `false` to disable loading |
|
||||
|
||||
## CLI contract
|
||||
@@ -33,7 +34,7 @@ dsh-cli-demo [--config path] [--output-format text|json|stream-json] <task>
|
||||
The root headless-agent example supplies its leaf:
|
||||
|
||||
```sh
|
||||
pnpm run demo:headless -- "inspect the failing test and fix it"
|
||||
pnpm run demo:headless "inspect the failing test and fix it"
|
||||
```
|
||||
|
||||
Loader configs with bare package specifiers require `node --expose-internals` or the Loader's optional native fallback. The root command supplies the Node flag.
|
||||
|
||||
@@ -11,7 +11,10 @@ import z from 'schemastery'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
|
||||
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import SessionPersistenceJsonl, {
|
||||
JsonlCompressionSchema,
|
||||
type JsonlCompression,
|
||||
} from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
|
||||
|
||||
const DEFAULT_PERSISTENCE_ROOT = './.sessions'
|
||||
@@ -36,6 +39,8 @@ export interface Config {
|
||||
dshHome?: string
|
||||
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
|
||||
persistenceRoot?: string
|
||||
/** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
|
||||
persistenceCompression?: JsonlCompression
|
||||
/** Skill registry, local-provider, and model-facing consumer config. */
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-spine-demo. */
|
||||
@@ -56,6 +61,7 @@ export const Config: z<Config> = z.object({
|
||||
model: z.string().required(),
|
||||
maxParallelToolCalls: z.number().step(1).min(1),
|
||||
persistenceRoot: z.string().default(DEFAULT_PERSISTENCE_ROOT),
|
||||
persistenceCompression: JsonlCompressionSchema,
|
||||
persona: z.string(),
|
||||
dshHome: z.string(),
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
@@ -81,5 +87,8 @@ export function apply(ctx: Context, config: Config): void {
|
||||
...agentCore.pickSpineConfig(config),
|
||||
agents: [{ id: SessionId('main'), provider: config.provider, model: config.model, cwd: process.cwd() }],
|
||||
})
|
||||
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT })
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT,
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ import { existsSync } from 'node:fs'
|
||||
import { mkdtemp, mkdir, readFile, readdir, rm, symlink, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { zstdDecompress } from 'node:zlib'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url))
|
||||
const cliBin = join(repoRoot, 'packages/examples/cli-demo/lib/bin.js')
|
||||
const decompress = promisify(zstdDecompress)
|
||||
const dshPackages = [
|
||||
'examples/agent-spine-demo', 'examples/cli-demo', 'core/agent', 'core/session',
|
||||
'core/system-prompt', 'core/tools', 'core/agent-loop', 'llm/llm', 'bash/bash',
|
||||
@@ -140,8 +143,13 @@ describe.skipIf(!existsSync(cliBin))('dsh-cli-demo BUILT bin', () => {
|
||||
const lines = stream.stdout.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
|
||||
expect(lines[0]).toMatchObject({ type: 'session_event', event: { type: 'turn/start' } })
|
||||
expect(lines.at(-1)).toMatchObject({ type: 'result', success: true, result: 'BUILT: stream task' })
|
||||
const files = await readdir(join(consumer, '.sessions'), { recursive: true })
|
||||
expect(files.filter(file => file.endsWith('.jsonl'))).toHaveLength(3)
|
||||
const sessionsRoot = join(consumer, '.sessions')
|
||||
const files = await readdir(sessionsRoot, { recursive: true })
|
||||
const logs = files.filter(file => file.endsWith('.jsonl.zstd'))
|
||||
expect(logs).toHaveLength(3)
|
||||
const compressed = await readFile(join(sessionsRoot, logs[0]!))
|
||||
expect(compressed.subarray(0, 4).toString('hex')).toBe('28b52ffd')
|
||||
expect(JSON.parse((await decompress(compressed)).toString())).toMatchObject({ type: 'session' })
|
||||
}, 30_000)
|
||||
|
||||
it('keeps stdout empty for invalid argv and missing config', async () => {
|
||||
|
||||
@@ -51,12 +51,14 @@ describe('dsh-cli-demo app composition', () => {
|
||||
persona: 'Headless.',
|
||||
tools: { mode: 'native' },
|
||||
persistenceRoot: root,
|
||||
persistenceCompression: 'none',
|
||||
skills: await skillConfig(),
|
||||
workspaceContext: false,
|
||||
})
|
||||
const [agent] = ctx.get('agents')?.roots() ?? []
|
||||
expect(ctx.get('agentLoop')).toBeDefined()
|
||||
expect(ctx.get('sessionPersistence')).toBeDefined()
|
||||
expect((ctx.get('sessionPersistence') as unknown as { config: { compression?: string } }).config.compression).toBe('none')
|
||||
expect(agent?.session.header.cwd).toBe(process.cwd())
|
||||
expect(ctx.get('userInteraction')).toBeUndefined()
|
||||
expect(ctx.get('tools')?.get('ask_user_question')).toBeUndefined()
|
||||
|
||||
@@ -313,7 +313,7 @@ describe('runOneShot and executeCli', () => {
|
||||
expect(output).toEqual({ code: 0, stdout: 'final answer\n', stderr: '' })
|
||||
expect(agent.status).toBe('disposed')
|
||||
const files = await readdir(persistenceRoot, { recursive: true })
|
||||
expect(files.some(file => file.endsWith('.jsonl'))).toBe(true)
|
||||
expect(files.some(file => file.endsWith('.jsonl.zstd'))).toBe(true)
|
||||
})
|
||||
|
||||
it('sums usage across tool steps and selects the last text-bearing assistant message', async () => {
|
||||
|
||||
Reference in New Issue
Block a user