Merge remote-tracking branch 'origin/master' into worktree/preset-plane-fallout-p1
`minimal` was rewritten upstream into a fixed-prompt PTY surface that now carries its own compaction group, so this branch's token-meter move applies to it too: the row leaves the group and `tokenMeter` leaves the realm, exactly as in the other three presets. Master's header prose is kept whole — the note this branch had added there described a composition that no longer exists. `docs/event-producer-consumer.*` is generated; regenerated after the merge.
This commit is contained in:
@@ -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 examples/jsonrpc-agent/README.md
|
||||
README.md: bcc1027d2edb30ab374dfa2ed13ad8e6360d923b
|
||||
README.zh.md: ce255e4dd70bf8c5c6edc51afbe03bb4c66560a0
|
||||
README.md: 5f60a64a4888c64e4fd68835f78e4a334ffed263
|
||||
README.zh.md: 8d2f9807ff259000b5a6823357f8c41b43bfa434
|
||||
@@ -21,16 +21,16 @@ The surrounding runtime also loads JSONL session persistence and automatic conte
|
||||
| `DEEPSEEK_BASE_URL` | Host endpoint used by `dsh-llm-deepseek` |
|
||||
| `DSH_CWD` | Agent workspace for bash and filesystem tools |
|
||||
| `DSH_MAX_TOKENS_AS_SUCCESS` | `true` (default) accepts token-limited results; `false` reports them as errors |
|
||||
| `DSH_SESSION_ROOT` | JSONL trajectory directory |
|
||||
| `DSH_SESSION_ROOT` | JSONL session 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.
|
||||
|
||||
## Persistent tools variant
|
||||
## Minimal variant
|
||||
|
||||
[`persistent-tools.cordis.yml`](persistent-tools.cordis.yml) is a minimal runnable variant whose model-facing surface is exactly:
|
||||
[`minimal.cordis.yml`](minimal.cordis.yml) is the complete standalone counterpart of the Web `minimal` preset. It fixes the system prompt and compaction policy, and its model-facing surface is exactly:
|
||||
|
||||
- owner-scoped persistent `bash`
|
||||
- `str_replace_editor` with `view`, `create`, `str_replace`, and `insert`
|
||||
|
||||
It composes the local PTY, filesystem intent policy, and session sandbox policy.
|
||||
It composes the local PTY, filesystem intent policy, session sandbox policy, and JSONL persistence needed by the bundled runtime. [`minimal.py`](minimal.py) runs it through the Python SDK; the [Python SDK tutorial](../../docs/user/guide/python-sdk.md) uses this configuration to cover setup, session management, and the security boundary.
|
||||
@@ -21,16 +21,16 @@
|
||||
| `DEEPSEEK_BASE_URL` | `dsh-llm-deepseek` 使用的宿主端点 |
|
||||
| `DSH_CWD` | bash 和文件系统工具使用的 agent workspace |
|
||||
| `DSH_MAX_TOKENS_AS_SUCCESS` | `true`(默认)接受受 token 上限限制的结果;`false` 将其报告为错误 |
|
||||
| `DSH_SESSION_ROOT` | JSONL 轨迹目录 |
|
||||
| `DSH_SESSION_ROOT` | JSONL 会话目录 |
|
||||
| `DSH_SYSTEM_PROMPT` | 由部署提供的编码人格 |
|
||||
|
||||
通过 Python SDK 的 `cordis` 选项或 `DSH_CORDIS_CONFIG` 传入配置路径。内置可执行文件已携带此文件中指定的每个插件;目标机器无需 Node.js。
|
||||
|
||||
## 持久化工具变体
|
||||
## 极简变体
|
||||
|
||||
[`persistent-tools.cordis.yml`](persistent-tools.cordis.yml) 是一个最小可运行变体,面向模型的能力严格只有:
|
||||
[`minimal.cordis.yml`](minimal.cordis.yml) 是 Web `minimal` preset 的完整独立版本。它固定系统提示词与压缩策略,面向模型的能力严格只有:
|
||||
|
||||
- 所有者作用域内持久化的 `bash`
|
||||
- 提供 `view`、`create`、`str_replace` 与 `insert` 的 `str_replace_editor`
|
||||
|
||||
它组合了本地 PTY、文件系统意图策略与会话沙箱策略。
|
||||
它组合了内置运行时所需的本地 PTY、文件系统意图策略、会话沙箱策略与 JSONL 持久化。[`minimal.py`](minimal.py) 通过 Python SDK 运行该配置;[Python SDK 教程](../../docs/user/guide/python-sdk.md)以此配置介绍设置方式、会话管理与安全边界。
|
||||
@@ -0,0 +1,91 @@
|
||||
# Complete unattended minimal-agent composition for the Python SDK. The model
|
||||
# sees one fixed system prompt and only the owner-scoped persistent Bash and
|
||||
# string-replace editor tools.
|
||||
|
||||
- id: jsonrpc
|
||||
name: '@deepseek-ai/dsh-jsonrpc'
|
||||
config:
|
||||
maxTokensAsSuccess: false
|
||||
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
|
||||
- id: sandbox
|
||||
name: '@deepseek-ai/dsh-sandbox-local'
|
||||
|
||||
- id: sandbox-policy
|
||||
name: '@deepseek-ai/dsh-sandbox-policy'
|
||||
config:
|
||||
mode: danger-full-access
|
||||
workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
- id: subprocess
|
||||
name: '@deepseek-ai/dsh-subprocess-local'
|
||||
|
||||
- id: pty
|
||||
name: '@deepseek-ai/dsh-pty'
|
||||
|
||||
- id: pty-local
|
||||
name: '@deepseek-ai/dsh-pty-local'
|
||||
config:
|
||||
timeoutMs: 300000
|
||||
|
||||
# The sandbox-aware filesystem backend applies the same per-session policy as
|
||||
# Bash. danger-full-access permits unrestricted workspace behavior while
|
||||
# keeping one policy boundary for both tools.
|
||||
- id: fs-sandbox
|
||||
name: '@deepseek-ai/dsh-fs-sandbox'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
- id: fs-policy
|
||||
name: '@deepseek-ai/dsh-fs-policy'
|
||||
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
includeHarnessIdentity: false
|
||||
persona: You are a helpful software engineer assistant.
|
||||
workspaceContext: false
|
||||
skills:
|
||||
enabled: false
|
||||
toolBash: false
|
||||
toolTasks: false
|
||||
|
||||
- id: persistent-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash-persistent'
|
||||
config:
|
||||
timeoutMs: 300000
|
||||
description: |-
|
||||
Run commands in a bash shell
|
||||
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
|
||||
* You don't have access to the internet via this tool.
|
||||
* You do have access to a mirror of common linux and python packages via apt and pip.
|
||||
* State is persistent across command calls and discussions with the user.
|
||||
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
|
||||
* Please avoid commands that may produce a very large amount of output.
|
||||
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.
|
||||
|
||||
- id: str-replace-editor
|
||||
name: '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
config:
|
||||
maxOutputChars: 16000
|
||||
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
|
||||
compression: none
|
||||
|
||||
- id: token-meter
|
||||
name: '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
- id: compact-basic
|
||||
name: '@deepseek-ai/dsh-compact-basic'
|
||||
config:
|
||||
thresholdRatio: 0.8
|
||||
retainTokens: 20480
|
||||
summarizationProvider: ''
|
||||
summarizationModel: ''
|
||||
maxTokens: 8192
|
||||
compactionRetries: 1
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run one minimal-agent turn through the bundled Python SDK runtime."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
|
||||
|
||||
CONFIG = Path(__file__).with_name("minimal.cordis.yml")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Parse one task and print the agent's final response."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("prompt", help="Task for the minimal agent")
|
||||
parser.add_argument("--workspace", type=Path, default=Path.cwd())
|
||||
parser.add_argument("--session-root", type=Path, default=Path(".dsh-sessions"))
|
||||
parser.add_argument("--session-id")
|
||||
parser.add_argument("--provider", default="deepseek-official")
|
||||
parser.add_argument("--model", default="deepseek-v4-flash")
|
||||
parser.add_argument("--max-tokens", type=int)
|
||||
args = parser.parse_args()
|
||||
|
||||
workspace = args.workspace.resolve()
|
||||
session_root = args.session_root.resolve()
|
||||
with DeepSeekHarness(
|
||||
provider=args.provider,
|
||||
model=args.model,
|
||||
max_tokens=args.max_tokens,
|
||||
cwd=str(workspace),
|
||||
session_root=str(session_root),
|
||||
cordis=str(CONFIG.resolve()),
|
||||
) as harness:
|
||||
result = harness.run(args.prompt, session_id=args.session_id)
|
||||
print(result.final_response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+4
-6
@@ -1,12 +1,10 @@
|
||||
# Keyless replay keeps the persistent-tool composition intact and replaces
|
||||
# only its live DeepSeek adapter with the fixture-backed provider. The catalog
|
||||
# below claims the same `deepseek-official` route the agent asks for: an
|
||||
# unowned route makes the SDK server mount the real adapter, which then demands
|
||||
# a key this keyless lane has no way to supply.
|
||||
# Keyless replay keeps the complete minimal composition intact and replaces
|
||||
# only its live DeepSeek adapter with the fixture-backed provider. The replay
|
||||
# catalog claims the same route initialized by the SDK.
|
||||
- id: base
|
||||
name: '@deepseek-ai/cordis-plugin-include'
|
||||
config:
|
||||
path: ./persistent-tools.cordis.yml
|
||||
path: ./minimal.cordis.yml
|
||||
patches:
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
@@ -1,59 +0,0 @@
|
||||
# Minimal unattended composition for the persistent Bash and string-replace
|
||||
# editor. It is runnable through the JSON-RPC example runtime and intentionally
|
||||
# keeps the model-facing surface to exactly these two tools.
|
||||
|
||||
- id: jsonrpc
|
||||
name: '@deepseek-ai/dsh-jsonrpc'
|
||||
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
|
||||
- id: sandbox
|
||||
name: '@deepseek-ai/dsh-sandbox-local'
|
||||
|
||||
- id: sandbox-policy
|
||||
name: '@deepseek-ai/dsh-sandbox-policy'
|
||||
config:
|
||||
mode: danger-full-access
|
||||
workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
- id: subprocess
|
||||
name: '@deepseek-ai/dsh-subprocess-local'
|
||||
|
||||
- id: pty
|
||||
name: '@deepseek-ai/dsh-pty'
|
||||
|
||||
- id: pty-local
|
||||
name: '@deepseek-ai/dsh-pty-local'
|
||||
|
||||
- id: fs-sandbox
|
||||
name: '@deepseek-ai/dsh-fs-sandbox'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
- id: fs-policy
|
||||
name: '@deepseek-ai/dsh-fs-policy'
|
||||
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
includeHarnessIdentity: false
|
||||
persona: 'You are a helpful software engineer assistant.'
|
||||
workspaceContext: false
|
||||
skills:
|
||||
enabled: false
|
||||
toolBash: false
|
||||
toolTasks: false
|
||||
|
||||
- id: persistent-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash-persistent'
|
||||
|
||||
- id: str-replace-editor
|
||||
name: '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
|
||||
compression: none
|
||||
@@ -33,11 +33,21 @@ const testsDir = dirOf(import.meta.url)
|
||||
const snapshotsDir = join(testsDir, 'snapshots')
|
||||
const liveConfig = join(testsDir, '..', 'cordis.yml')
|
||||
const replayConfig = join(testsDir, '..', 'cordis.snapshot.yml')
|
||||
const persistentToolsLiveConfig = join(testsDir, '..', 'persistent-tools.cordis.yml')
|
||||
const persistentToolsReplayConfig = join(testsDir, '..', 'persistent-tools.snapshot.cordis.yml')
|
||||
const minimalLiveConfig = join(testsDir, '..', 'minimal.cordis.yml')
|
||||
const minimalReplayConfig = join(testsDir, '..', 'minimal.snapshot.cordis.yml')
|
||||
const runtimeBin = fileURLToPath(new URL('../../../packages/examples/jsonrpc-demo/src/bin.ts', import.meta.url))
|
||||
const repoTsconfig = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
|
||||
const MINIMAL_SYSTEM_PROMPT = 'You are a helpful software engineer assistant.'
|
||||
const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell
|
||||
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
|
||||
* You don't have access to the internet via this tool.
|
||||
* You do have access to a mirror of common linux and python packages via apt and pip.
|
||||
* State is persistent across command calls and discussions with the user.
|
||||
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
|
||||
* Please avoid commands that may produce a very large amount of output.
|
||||
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.`
|
||||
|
||||
const mode = process.env.DSH_SNAPSHOT ?? 'replay'
|
||||
const recording = mode === 'record'
|
||||
const refreshing = mode === 'refresh'
|
||||
@@ -61,6 +71,10 @@ interface SdkScenario {
|
||||
expectedFiles?: Readonly<Record<string, string>>
|
||||
/** Assembled model-facing tool names and required argument keys. */
|
||||
expectedTools?: Readonly<Record<string, readonly string[]>>
|
||||
/** Exact assembled system prompt for the root request. */
|
||||
expectedSystem?: string
|
||||
/** Exact model-facing descriptions for selected tools. */
|
||||
expectedToolDescriptions?: Readonly<Record<string, string>>
|
||||
/** Stable policy-context clauses the real assembled request must include or omit. */
|
||||
policyContext?: { includes: readonly string[]; excludes: readonly string[] }
|
||||
}
|
||||
@@ -89,9 +103,11 @@ const SCENARIOS: SdkScenario[] = [
|
||||
prompt: 'Prove that bash state persists. Then create {{cwd}}/note.txt with a tab-indented line, view it, replace that literal tab-indented line, and make the persistent shell exit with code 9.',
|
||||
sessionId: 'persistent-tools-snapshot',
|
||||
children: 0,
|
||||
configs: { live: persistentToolsLiveConfig, replay: persistentToolsReplayConfig },
|
||||
configs: { live: minimalLiveConfig, replay: minimalReplayConfig },
|
||||
expectedFiles: { 'note.txt': 'target:\n\tnew\n' },
|
||||
expectedTools: { bash: ['command'], str_replace_editor: ['command', 'path'] },
|
||||
expectedSystem: MINIMAL_SYSTEM_PROMPT,
|
||||
expectedToolDescriptions: { bash: MINIMAL_BASH_DESCRIPTION },
|
||||
policyContext: {
|
||||
includes: ['Current DSH file policy: danger-full-access.', 'file modifications by available operations'],
|
||||
excludes: ['write and edit tools', 'terminal sessions', 'one-shot bash commands'],
|
||||
@@ -125,16 +141,33 @@ async function persistedLogs(sessionsRoot: string): Promise<PersistedLog[]> {
|
||||
|
||||
interface LoggedRequestHeader {
|
||||
type?: string
|
||||
data?: { header?: { system?: unknown; tools?: Array<{ name: string; parameters: { required?: string[] } }> } }
|
||||
data?: { header?: { system?: unknown; tools?: LoggedTool[] } }
|
||||
}
|
||||
|
||||
function assembledToolRequirements(log: PersistedLog): Record<string, string[]> {
|
||||
interface LoggedTool {
|
||||
readonly name: string
|
||||
readonly description?: unknown
|
||||
readonly parameters: { readonly required?: string[] }
|
||||
}
|
||||
|
||||
function assembledTools(log: PersistedLog): LoggedTool[] {
|
||||
const event = log.content.trimEnd().split('\n')
|
||||
.map(line => JSON.parse(line) as LoggedRequestHeader)
|
||||
.find(candidate => candidate.type === 'request/header')
|
||||
const tools = event?.data?.header?.tools
|
||||
if (tools === undefined) throw new Error('session log has no request/header tools')
|
||||
return Object.fromEntries(tools.map(tool => [tool.name, tool.parameters.required ?? []]))
|
||||
return tools
|
||||
}
|
||||
|
||||
function assembledToolRequirements(log: PersistedLog): Record<string, string[]> {
|
||||
return Object.fromEntries(assembledTools(log).map(tool => [tool.name, tool.parameters.required ?? []]))
|
||||
}
|
||||
|
||||
function assembledToolDescriptions(log: PersistedLog): Record<string, string> {
|
||||
return Object.fromEntries(assembledTools(log).map((tool) => {
|
||||
if (typeof tool.description !== 'string') throw new Error(`tool ${tool.name} has no description`)
|
||||
return [tool.name, tool.description]
|
||||
}))
|
||||
}
|
||||
|
||||
function assembledSystem(log: PersistedLog): string {
|
||||
@@ -400,6 +433,16 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => {
|
||||
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
|
||||
expect(assembledToolRequirements(parent)).toEqual(scenario.expectedTools)
|
||||
}
|
||||
if (scenario.expectedSystem !== undefined) {
|
||||
const parent = ordered[0]
|
||||
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
|
||||
expect(assembledSystem(parent)).toBe(scenario.expectedSystem)
|
||||
}
|
||||
if (scenario.expectedToolDescriptions !== undefined) {
|
||||
const parent = ordered[0]
|
||||
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
|
||||
expect(assembledToolDescriptions(parent)).toMatchObject(scenario.expectedToolDescriptions)
|
||||
}
|
||||
if (scenario.policyContext !== undefined) {
|
||||
const parent = ordered[0]
|
||||
if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`)
|
||||
|
||||
Reference in New Issue
Block a user