Merge remote-tracking branch 'origin/master' into codex/enforce-tool-cancellation

# Conflicts:
#	docs/cookbook/adding-a-tool.i18n.yaml
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	packages/bash/tool-bash/src/index.ts
#	packages/core/agent-loop/README.md
#	packages/core/tools/README.md
#	packages/core/tools/tests/scoped.spec.ts
#	packages/fs/tool-fs-search/tests/tools.spec.ts
#	website/zh-CN/api/harness/events.md
#	website/zh-CN/api/harness/tools.md
This commit is contained in:
Tianyi Cui
2026-07-20 23:00:21 +08:00
736 changed files with 22158 additions and 13229 deletions

View File

@@ -1,6 +1,6 @@
# cordis-agent
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 Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md).
The self-referential harness demo: the DeepSeek V4 coding spine on the full-screen TUI 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 Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md).
## Run it

View File

@@ -20,11 +20,11 @@ flowchart LR
cfg --> plugin_cordis_web
plugin_cordis_web_fetch_local["web-fetch-local<br/>@deepseek-ai/dsh-web-fetch-local"]
cfg --> plugin_cordis_web_fetch_local
plugin_cordis_stdio_agent["stdio-agent<br/>@deepseek-ai/dsh-stdio-demo"]
cfg --> plugin_cordis_stdio_agent
plugin_cordis_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
plugin_cordis_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
plugin_cordis_stdio_agent --> frontdoor_stdio["dsh-tui (TTY) / dsh-stdio (pipes)<br/>pre-created main agent"]
plugin_cordis_tui_agent["tui-agent<br/>@deepseek-ai/dsh-tui-demo"]
cfg --> plugin_cordis_tui_agent
plugin_cordis_tui_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
plugin_cordis_tui_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
plugin_cordis_tui_agent --> frontdoor_tui["@deepseek-ai/dsh-tui<br/>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"]
@@ -41,7 +41,7 @@ flowchart LR
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
| `web` | `@deepseek-ai/dsh-web` |
| `web-fetch-local` | `@deepseek-ai/dsh-web-fetch-local` |
| `stdio-agent` | `@deepseek-ai/dsh-stdio-demo` |
| `tui-agent` | `@deepseek-ai/dsh-tui-demo` |
| `tool-cordis` | `@deepseek-ai/dsh-tool-cordis` |
Source config: [`examples/cordis-agent/cordis.yml`](cordis.yml).

View File

@@ -1,4 +1,4 @@
# Self-referential stdio demo: the coding spine plus tools to inspect the live
# Self-referential TUI demo: the coding spine plus tools to inspect the live
# service/plugin/tool/mount/API/event state, mount a model-written plugin under
# `cordis-dynamic`, and quiescently unmount it. The app bin loads the gitignored
# root `.env` before reading the required DeepSeek key and optional base URL.
@@ -45,8 +45,8 @@
name: '@deepseek-ai/dsh-web-fetch-local'
# The app bundle pre-creates the self-referential demo's `main` agent.
- id: stdio-agent
name: '@deepseek-ai/dsh-stdio-demo'
- id: tui-agent
name: '@deepseek-ai/dsh-tui-demo'
config:
provider: deepseek
model: deepseek-v4-flash

View File

@@ -1,27 +1,23 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
import { LOADER_SMOKE_TEST_TIMEOUT_MS } from '@deepseek-ai/dsh-loader-smoke'
import { runTuiPtySmoke } from '../../tui-agent/tests/pty-harness.ts'
/**
* Keyless Loader-path smoke for examples/cordis-agent: boot the real tree,
* including tool-cordis resolved by package name, then close stdin without a
* prompt and assert the banner. The dummy key never reaches a model call.
*/
const binScript = fileURLToPath(new URL('../../../packages/examples/stdio-demo/src/bin.ts', import.meta.url))
const binScript = fileURLToPath(new URL('../../../packages/examples/tui-demo/src/bin.ts', import.meta.url))
const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
describe('cordis-agent keyless smoke (real cordis.yml via the Loader)', () => {
it('boots the full plugin tree incl. tool-cordis, prints its banner, and exits cleanly on EOF', async () => {
const { stdout } = await runLoaderSmoke({
describe('cordis-agent keyless smoke (real Loader tree in a PTY)', () => {
it('boots the full tool-cordis tree and exits cleanly through the TUI', async () => {
const output = await runTuiPtySmoke({
label: 'cordis-agent',
tempDirPrefix: 'cordis-smoke-',
tempDirPrefix: 'cordis-agent-smoke-',
binScript,
configPath,
tsconfigPath,
env: { DEEPSEEK_API_KEY: 'keyless-smoke-no-call' },
actions: [{ waitFor: 'cordis-agent ready.', send: '/exit\r' }],
})
expect(stdout).toContain('cordis-agent ready.')
expect(output).toContain('cordis-agent ready.')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})