Merge remote-tracking branch 'origin/rfc/loop' into codex/goal-domain

This commit is contained in:
Tianyi Cui
2026-07-19 22:49:25 +08:00
82 changed files with 3637 additions and 163 deletions

View File

@@ -33,6 +33,10 @@ The full-screen terminal sibling of `repl-agent`: it reuses the same coding back
Run with: `pnpm run demo:tui` (needs `DEEPSEEK_API_KEY`). See [tui-agent/README.md](tui-agent/README.md) for controls and composition.
## jsonrpc-agent
An unattended coding agent driven through the Python SDK: JSON-RPC stdio, foreground-only `bash`, `read` / `write` / `edit`, one foreground `subagent`, `todo_write`, JSONL persistence, and compaction. It excludes terminal UI, stdout logging, approvals, skills, and background task controls. See [jsonrpc-agent/README.md](jsonrpc-agent/README.md).
## cordis-agent
The **self-referential** demo: the coding spine plus [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis), whose three tools (`cordis_inspect` / `cordis_mount` / `cordis_unmount`) let the agent inspect the live cordis runtime it runs inside, mount model-written plugins into it (an event listener, a brand-new tool for itself, or a service another mount injects), and dispose them again — all dynamic mounts grouped under one `cordis-dynamic` fiber subtree. The `ctx.fs`/`ctx.web` services ride along provider-only, as the capabilities those plugins build on.

View File

@@ -0,0 +1,24 @@
# jsonrpc-agent
The unattended coding-agent composition for the Python SDK's bundled JSON-RPC runtime. It intentionally loads no terminal UI, console logger, approval surface, or user-interaction tool because stdout belongs to the SDK protocol and turns are driven by the SDK.
The model-facing tools are:
- `bash`, foreground only
- `read`, `write`, and `edit`
- `subagent`, using one foreground in-process spawn provider
- `todo_write`
The surrounding runtime also loads JSONL session persistence and automatic context compaction. `maxTokensAsSuccess` keeps a token-limited model turn as an accepted evaluation result while preserving its `max-tokens` reason.
## Runtime environment
| Variable | Purpose |
|---|---|
| `DEEPSEEK_API_KEY` | Credential passed to the OpenAI-compatible host endpoint |
| `DEEPSEEK_BASE_URL` | Host endpoint used by `dsh-llm-deepseek` |
| `DSH_CWD` | Agent workspace for bash and filesystem tools |
| `DSH_SESSION_ROOT` | JSONL trajectory directory |
| `DSH_SYSTEM_PROMPT` | Deployment-provided coding persona |
Pass the config path through the Python SDK's `cordis` option or `DSH_CORDIS_CONFIG`. The bundled executable already carries every plugin named by this file; the target machine does not need Node.js.

View File

@@ -0,0 +1,74 @@
# Unattended coding-agent deployment for the bundled dsh-jsonrpc-agent runtime.
# stdout is reserved for JSON-RPC; do not add a console logger or terminal UI.
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
config:
maxTokensAsSuccess: true
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
timeoutMs: 60000
- id: agent-spine
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a coding agent.'
workspaceContext: false
skills:
enabled: false
toolBash:
enableRunInBackground: false
toolTasks: false
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
- id: subagent
name: '@deepseek-ai/dsh-subagent'
- id: subagent-spawn
name: '@deepseek-ai/dsh-subagent-spawn'
config:
providerName: spawn
- id: tool-subagent
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: spawn
toolName: subagent
enableRunInBackground: false
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
- id: fs-local
name: '@deepseek-ai/dsh-fs-local'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
- id: fs-policy
name: '@deepseek-ai/dsh-fs-policy'
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
- id: compact-basic
name: '@deepseek-ai/dsh-compact-basic'
config:
contextWindow: 128000
thresholdRatio: 0.8
retainTokens: 20480
summarizationModel: ''
maxTokens: 8192
compactionRetries: 1

View File

@@ -0,0 +1,7 @@
{
"name": "jsonrpc-agent-example",
"private": true,
"version": "0.0.1",
"type": "module",
"description": "Unattended JSON-RPC coding-agent composition"
}

View File

@@ -0,0 +1,146 @@
import { spawn } from 'node:child_process'
import { createServer } from 'node:http'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
const binScript = fileURLToPath(new URL('../../../packages/examples/jsonrpc-demo/src/bin.ts', import.meta.url))
const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
const repoRoot = fileURLToPath(new URL('../../..', import.meta.url))
function waitForLine(
lines: string[],
predicate: (value: Record<string, unknown>) => boolean,
stderr: () => string,
): Promise<Record<string, unknown>> {
return new Promise((resolve, reject) => {
const deadline = Date.now() + 30_000
const poll = (): void => {
while (lines.length > 0) {
const line = lines.shift()!
if (!line.trim()) continue
try {
const value = JSON.parse(line) as Record<string, unknown>
if (predicate(value)) {
resolve(value)
return
}
} catch {
reject(new Error(`non-JSON stdout from JSON-RPC agent runtime: ${line}`))
return
}
}
if (Date.now() >= deadline) {
reject(new Error(`timed out waiting for JSON-RPC response; stderr=${stderr()}`))
return
}
setTimeout(poll, 10)
}
poll()
})
}
describe('jsonrpc-agent keyless smoke', () => {
it('boots the real Cordis tree and serves initialize/shutdown over clean stdout', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-agent-smoke-'))
const modelRequests: Record<string, unknown>[] = []
const modelServer = createServer((request, response) => {
let body = ''
request.setEncoding('utf8')
request.on('data', (chunk: string) => { body += chunk })
request.on('end', () => {
modelRequests.push(JSON.parse(body) as Record<string, unknown>)
response.writeHead(200, { 'content-type': 'text/event-stream' })
response.write('data: {"choices":[{"delta":{"role":"assistant","content":null}}]}\n\n')
response.write('data: {"choices":[{"delta":{"content":"done"}}]}\n\n')
response.write('data: {"choices":[{"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":3,"completion_tokens":1}}\n\n')
response.end('data: [DONE]\n\n')
})
})
await new Promise<void>(resolve => modelServer.listen(0, '127.0.0.1', resolve))
const address = modelServer.address()
if (address === null || typeof address === 'string') throw new Error('model server did not bind a TCP port')
const child = spawn(process.execPath, [
'--expose-internals',
'--import',
'tsx',
binScript,
configPath,
], {
cwd: repoRoot,
env: {
...process.env,
DEEPSEEK_API_KEY: 'keyless-smoke-no-call',
DEEPSEEK_BASE_URL: `http://127.0.0.1:${address.port}`,
DSH_CWD: root,
DSH_SESSION_ROOT: join(root, '.sessions'),
},
stdio: ['pipe', 'pipe', 'pipe'],
})
const lines: string[] = []
let stdoutBuffer = ''
let stderr = ''
child.stdout.setEncoding('utf8')
child.stdout.on('data', (chunk: string) => {
stdoutBuffer += chunk
const parts = stdoutBuffer.split('\n')
stdoutBuffer = parts.pop() ?? ''
lines.push(...parts)
})
child.stderr.setEncoding('utf8')
child.stderr.on('data', (chunk: string) => { stderr += chunk })
try {
child.stdin.write(`${JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: { cwd: root, provider: 'deepseek', model: 'deepseek-v4-pro' },
})}\n`)
const initialized = await waitForLine(lines, value => value.id === 1, () => stderr)
expect(initialized).toMatchObject({
jsonrpc: '2.0',
id: 1,
result: { serverInfo: { name: 'deepseek-harness-sdk-runtime' } },
})
child.stdin.write(`${JSON.stringify({
jsonrpc: '2.0',
id: 2,
method: 'session/prompt',
params: { sessionId: 'main', contentBlocks: [{ type: 'text', text: 'inspect tools' }] },
})}\n`)
const prompt = await waitForLine(lines, value => value.id === 2, () => stderr)
expect(prompt).toMatchObject({ jsonrpc: '2.0', id: 2, result: { accepted: true } })
const tools = modelRequests[0]?.tools as { function?: { name?: string } }[]
expect(tools.map(tool => tool.function?.name).sort()).toEqual([
'bash',
'edit',
'read',
'subagent',
'todo_write',
'write',
])
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'shutdown' })}\n`)
const shutdown = await waitForLine(lines, value => value.id === 3, () => stderr)
expect(shutdown).toMatchObject({ jsonrpc: '2.0', id: 3, result: {} })
if (child.exitCode === null) {
await new Promise<void>((resolve, reject) => {
child.once('exit', (code) => {
if (code === 0) resolve()
else reject(new Error(`runtime exited ${code}; stderr=${stderr}`))
})
})
} else {
expect(child.exitCode, stderr).toBe(0)
}
} finally {
if (child.exitCode === null) child.kill('SIGKILL')
await new Promise<void>(resolve => modelServer.close(() => { resolve() }))
await rm(root, { recursive: true, force: true })
}
}, 40_000)
})

View File

@@ -8,6 +8,7 @@
"@cordisjs/plugin-hmr": "workspace:*",
"@cordisjs/plugin-include": "workspace:*",
"@deepseek-ai/dsh-acp-demo": "workspace:*",
"@deepseek-ai/dsh-agent-spine-demo": "workspace:*",
"@deepseek-ai/dsh-bash-local": "workspace:*",
"@deepseek-ai/dsh-bash-sandbox": "workspace:*",
"@deepseek-ai/dsh-cli-demo": "workspace:*",
@@ -18,12 +19,14 @@
"@deepseek-ai/dsh-goal": "workspace:*",
"@deepseek-ai/dsh-hooks-claude": "workspace:*",
"@deepseek-ai/dsh-hooks-codex": "workspace:*",
"@deepseek-ai/dsh-jsonrpc": "workspace:*",
"@deepseek-ai/dsh-llm": "workspace:*",
"@deepseek-ai/dsh-llm-deepseek": "workspace:*",
"@deepseek-ai/dsh-llm-replay": "workspace:*",
"@deepseek-ai/dsh-permission": "workspace:*",
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:*",
"@deepseek-ai/dsh-sandbox-local": "workspace:*",
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:*",
"@deepseek-ai/dsh-spill-local": "workspace:*",
"@deepseek-ai/dsh-spill-policy": "workspace:*",
"@deepseek-ai/dsh-stdio-demo": "workspace:*",