Merge remote-tracking branch 'origin/master' into codex/project-instruction-files

# Conflicts:
#	docs/event-producer-consumer.md
#	docs/rfc/README.md
#	packages/core/agent-core/src/index.ts
#	packages/ui/acp-agent/src/index.ts
This commit is contained in:
Yichen Jiang
2026-07-06 10:14:47 +08:00
207 changed files with 2754 additions and 1290 deletions

View File

@@ -2,7 +2,9 @@
The model-facing bash tools — `bash`, `bash_output`, `bash_kill` — registered over the `ctx.bash` executor seam (`@deepseek-ai/dsh-bash`). Pure schema + text shaping; every process concern lives behind the seam, so sandboxed or remote executor implementations swap in without changing what the model sees.
Requires a loaded executor implementation (e.g. `@deepseek-ai/dsh-bash-local`); the plugin stays pending until `ctx.bash` exists (`inject: ['tools', 'bash']`).
Requires a loaded executor implementation (e.g. `@deepseek-ai/dsh-bash-local`); the plugin stays pending until `ctx.bash` exists (`inject: ['tools', 'bash', 'systemPrompt']`).
The plugin also contributes the `tool:bash` prompt section (order 105) — the cross-call habit the per-tool descriptions cannot carry: check the `[exit code: N]` marker on every result and investigate failures before moving on.
## Tools

View File

@@ -25,6 +25,7 @@
"@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-bash": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-system-prompt": "^0.0.1",
"@deepseek-ai/dsh-tools": "^0.0.1",
"cordis": "^4.0.0-rc.6"
},

View File

@@ -43,11 +43,12 @@ import { isAbsolute, resolve as resolvePath } from 'node:path'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type { GenericCallView, TerminalCallView, ToolResult, ToolResultView } from '@deepseek-ai/dsh-tools'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-system-prompt'
import { BashTaskId, OwnerToken } from '@deepseek-ai/dsh-bash'
import type { BashRunResult, BashTask, CollectedOutput } from '@deepseek-ai/dsh-bash'
export const name = 'tool-bash'
export const inject = ['tools', 'bash']
export const inject = ['tools', 'bash', 'systemPrompt']
/**
* Validate the constraints the SchemaSpec can't express. `defineTool` now
@@ -285,6 +286,15 @@ function statusLine(task: BashTask): string {
}
export function apply(ctx: Context): void {
// The bash tools' cross-call HABIT, which the per-tool descriptions cannot
// carry (they describe one call each): the exit-code marker is only useful
// if the model actually checks it every time.
ctx.systemPrompt.section({
name: 'tool:bash',
order: 105,
text: 'Check the [exit code: N] marker on every bash result; investigate failures before moving on.',
})
/**
* The caller's owner TOKEN — the owning agent's `session.header.id`, or
* `undefined` for a non-agent caller. Read `session.header.id` (NOT

View File

@@ -261,6 +261,14 @@ describe('bash tool', () => {
})
})
it('contributes the exit-code habit as its prompt section (guidance the descriptions cannot carry)', async () => {
const ctx = await setup()
const assembly = await ctx.systemPrompt.assemble()
const section = assembly.sections.find(s => s.name === 'tool:bash')
expect(section?.order).toBe(105)
expect(section?.text).toContain('[exit code: N]')
})
it('unregisters everything when the plugin fiber is disposed (HMR safety)', async () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
@@ -268,8 +276,11 @@ describe('bash tool', () => {
await ctx.plugin(LocalBashExecutor, {})
const fiber = await ctx.plugin(ToolBash)
expect(ctx.tools.schemas()).toHaveLength(3)
expect((await ctx.systemPrompt.assemble()).sections.map(s => s.name)).toEqual(['harness:identity', 'deployment:persona', 'tool:bash'])
await fiber.dispose()
expect(ctx.tools.schemas()).toHaveLength(0)
// Only the system-prompt plugin's own built-in sections remain.
expect((await ctx.systemPrompt.assemble()).sections.map(s => s.name)).toEqual(['harness:identity', 'deployment:persona'])
})
it('tools depend on the executor: no registration without ctx.bash', async () => {