Merge origin/master into token-meter-service

This commit is contained in:
Tianyi Cui
2026-07-17 22:22:55 +08:00
431 changed files with 28451 additions and 3065 deletions

View File

@@ -14,6 +14,8 @@
model: deepseek-v4-flash
resumeSessionId: !!js process.env.RESUME_SESSION_ID
persistenceRoot: './.sessions'
workspaceContext:
maxBytes: 65536
tools:
mode: code
welcome: 'code-mode agent ready. Give it a multi-tool task.'

View File

@@ -49,6 +49,14 @@ flowchart LR
cfg --> plugin_coding_fs_policy
plugin_coding_tool_fs["tool-fs<br/>@deepseek-ai/dsh-tool-fs"]
cfg --> plugin_coding_tool_fs
plugin_coding_tool_fs_search["tool-fs-search<br/>@deepseek-ai/dsh-tool-fs-search"]
cfg --> plugin_coding_tool_fs_search
plugin_coding_timeout_policy["timeout-policy<br/>@deepseek-ai/dsh-timeout-policy"]
cfg --> plugin_coding_timeout_policy
plugin_coding_spill_local["spill-local<br/>@deepseek-ai/dsh-spill-local"]
cfg --> plugin_coding_spill_local
plugin_coding_spill_policy["spill-policy<br/>@deepseek-ai/dsh-spill-policy"]
cfg --> plugin_coding_spill_policy
```
| Plugin id | Package / module |
@@ -70,6 +78,10 @@ flowchart LR
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
| `tool-fs-search` | `@deepseek-ai/dsh-tool-fs-search` |
| `timeout-policy` | `@deepseek-ai/dsh-timeout-policy` |
| `spill-local` | `@deepseek-ai/dsh-spill-local` |
| `spill-policy` | `@deepseek-ai/dsh-spill-policy` |
Source config: [`examples/coding-agent/cordis.yml`](cordis.yml).

View File

@@ -1,6 +1,6 @@
# REPL agent with swappable DeepSeek and local-bash backends. `dsh-stdio-demo`
# supplies the agent spine, generic task controls, logging, JSONL persistence,
# readline UI, and `main` agent.
# supplies the agent spine, workspace instructions, generic task controls,
# logging, JSONL persistence, readline UI, and `main` agent.
# HMR remains a leaf because it requires Loader internals; `demo:repl` passes
# `--expose-internals`. The app bin loads the gitignored root `.env`; this file
# reads `DEEPSEEK_API_KEY` and optional `DEEPSEEK_BASE_URL` through `!!js`.
@@ -37,6 +37,8 @@
# under ./.sessions); unset starts a fresh session each run.
resumeSessionId: !!js process.env.RESUME_SESSION_ID
persistenceRoot: './.sessions'
workspaceContext:
maxBytes: 65536
welcome: 'agent REPL ready. Give it a coding task.'
# Keep the persona to identity and behavior; tool plugins own tool guidance.
# The loop resolves {{model}} from this agent's configuration.
@@ -109,3 +111,29 @@
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
# Bash-backed discovery tools (glob/grep): fixed ripgrep commands through the
# local bash executor above — not ctx.fs. Capped results save the complete
# formatted list through the spill backend below (ctx.spillStore, optional).
- id: tool-fs-search
name: '@deepseek-ai/dsh-tool-fs-search'
# The tool-call timeout enforcer: arms each declared ToolDefinition.timeoutMs
# (the search tools above declare 30s) as a deadline on exec.signal. Without
# it a declared budget is advisory and only the bash executor's own timeout
# backstop applies.
- id: timeout-policy
name: '@deepseek-ai/dsh-timeout-policy'
# Tool-output spill stack: a local backend that saves oversized tool text under
# a private session-scoped dir, and the tools/post-execute policy that replaces
# an over-budget plain-text result with a preview + the spill locator/retrieval
# hint. A leaf pair after the app (needs ctx.tools). The policy is a no-op until
# a tool returns more than maxInlineBytes of plain text.
- id: spill-local
name: '@deepseek-ai/dsh-spill-local'
- id: spill-policy
name: '@deepseek-ai/dsh-spill-policy'
config:
maxInlineBytes: 50000

View File

@@ -1,10 +1,10 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import LlmService from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
@@ -14,6 +14,9 @@ import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { WorkerCodeRuntime } from '@deepseek-ai/dsh-code-runtime-worker'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import * as WorkspaceContext from '@deepseek-ai/dsh-workspace-context'
/**
* With-key Code Mode proof: a real model receives only `run_code`, composes two
@@ -23,6 +26,7 @@ import { WorkerCodeRuntime } from '@deepseek-ai/dsh-code-runtime-worker'
const PERSONA = 'You are coding-agent. You work by writing TypeScript programs for run_code: '
+ 'batch related tool work into one program and print or return ONLY the findings that matter.'
const WORKSPACE_PROBE = 'dragonfruit-8675309'
let ctx: Context | undefined
let workdir: string | undefined
@@ -52,6 +56,22 @@ async function codeModeHarness(cwd: string): Promise<Context> {
return harness
}
async function workspaceCodeModeHarness(): Promise<Context> {
const harness = new Context()
await harness.plugin(LlmService)
await harness.plugin(SessionStore)
await harness.plugin(SystemPrompt, { persona: PERSONA })
await harness.plugin(ToolRegistry, { mode: 'code' })
await harness.plugin(AgentRegistry)
await harness.plugin(LocalFileSystem, { cwd: '/' })
await harness.plugin(ToolFs)
await harness.plugin(WorkspaceContext, { maxBytes: 65536 })
await harness.plugin(AgentLoop, { agents: [] })
await harness.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
await harness.plugin(WorkerCodeRuntime, {})
return harness
}
function waitForIdle(harness: Context, agent: ReactLoopAgent): Promise<void> {
return new Promise((resolve) => {
const dispose = harness.on('agent/status', (subject, status) => {
@@ -107,4 +127,43 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('Code Mode: real model writes a p
expect(finalText).toContain('alpha-7')
expect(finalText).toContain('beta-9')
}, 180_000)
it('delivers nested workspace instructions discovered by an fs sub-call after the outer result', async () => {
workdir = await mkdtemp(join(tmpdir(), 'dsh-code-mode-workspace-e2e-'))
await mkdir(join(workdir, '.git'), { recursive: true })
await mkdir(join(workdir, 'pkg/deep'), { recursive: true })
await writeFile(join(workdir, 'pkg/AGENTS.md'), `If asked for the Code Mode workspace handshake, reply with exactly ${WORKSPACE_PROBE} and nothing else.\n`)
await writeFile(join(workdir, 'pkg/deep/task.txt'), 'Touch this file to discover the nested instructions.\n')
ctx = await workspaceCodeModeHarness()
const handle = await ctx.agents.create({
agentId: AgentId('e2e-code-mode-workspace'),
sessionId: SessionId('e2e-code-mode-workspace-session'),
meta: { cwd: workdir },
agentOptions: { model: 'deepseek-v4-flash' },
})
handle.agent.send([{
type: 'text',
text: 'Use one run_code program to call tools.read on pkg/deep/task.txt. After it finishes, answer: Code Mode workspace handshake?',
}])
await waitForIdle(ctx, handle.agent as ReactLoopAgent)
const events: SessionEvent[] = [...handle.agent.session.events]
const dispatch = events.find(event => event.type === 'tool/code-dispatch' && event.data.name === 'read')
const outerResult = events.find(event => event.type === 'tool/result')
const workspaceContext = events.find(event => event.type === 'context/message'
&& typeof event.data.meta === 'object'
&& event.data.meta !== null
&& !Array.isArray(event.data.meta)
&& event.data.meta.kind === 'workspace-instructions')
expect(dispatch).toBeDefined()
expect(outerResult).toBeDefined()
expect(workspaceContext).toBeDefined()
expect(workspaceContext!.seq).toBeGreaterThan(outerResult!.seq)
const finalMessage = events.findLast(event => event.type === 'assistant/message')
const answer = finalMessage?.type === 'assistant/message'
? finalMessage.data.content.filter(block => block.type === 'text').map(block => block.text).join('')
: ''
expect(answer).toContain(WORKSPACE_PROBE)
}, 180_000)
})

View File

@@ -1,11 +1,7 @@
import { Context } from 'cordis'
import LlmService from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
@@ -54,11 +50,9 @@ export interface CodingHarnessOptions {
export async function codingHarness(workdir: string, options: CodingHarnessOptions = {}): Promise<Context> {
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt, { persona: options.persona ?? '' })
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
await mountAgentLoopTestDependencies(ctx, {
systemPrompt: { persona: options.persona ?? '' },
})
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 })