Merge master into worktree-windows-runtime

This commit is contained in:
Tianyi Cui
2026-07-21 23:39:14 +08:00
936 changed files with 36271 additions and 10361 deletions

View File

@@ -11,11 +11,16 @@
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
@@ -26,6 +31,7 @@
},
"peerDependencies": {
"@deepseek-ai/dsh-bash": "^0.0.1",
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-retention": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
@@ -38,6 +44,7 @@
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-bash": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-retention": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",

View File

@@ -0,0 +1,30 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-tool-fs-search`.
* @module @deepseek-ai/dsh-tool-fs-search/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-tool-fs-search'
/** Cordis companion plugin name. */
export const name = 'tool-fs-search-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: this model-facing adapter has no independent lifecycle stream; execution
* relations are owned by the capability seam it calls.
*/
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */

View File

@@ -162,7 +162,7 @@ export async function runRipgrep(
command,
stdoutMaxBytes: rawOutputMaxBytes,
...cwd !== undefined ? { workdir: cwd } : {},
...exec.signal ? { signal: exec.signal } : {},
signal: exec.signal,
})
let result: BashRunResult
try {

View File

@@ -16,10 +16,12 @@ import { join } from 'node:path'
import { Context } from 'cordis'
import { CallId } from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import ToolRegistry, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
import * as ToolFsSearch from '@deepseek-ai/dsh-tool-fs-search'
const testToolSignal = new AbortController().signal
const hasRg = spawnSync('rg', ['--version'], { encoding: 'utf8' }).status === 0
let dir: string
@@ -28,6 +30,7 @@ let ctx: Context
let callCounter = 0
function call(name: string, args: unknown, agentObj?: object) {
return ctx.tools.execute({
signal: testToolSignal,
callId: CallId(`it-${++callCounter}`),
name,
arguments: args,
@@ -165,8 +168,8 @@ describe.skipIf(!hasRg)('search tools over the real bash executor + real rg', ()
})
})
describe('bash-start infrastructure failures stay in the SEARCH_* taxonomy', () => {
it('a pre-aborted exec.signal (real executor rejects before spawn) is SEARCH_ABORTED', async () => {
describe('pre-dispatch cancellation and bash-start failures', () => {
it('a pre-aborted registry call is ABORTED_BEFORE_DISPATCH', async () => {
const controller = new AbortController()
controller.abort()
const result = await ctx.tools.execute({
@@ -176,7 +179,7 @@ describe.skipIf(!hasRg)('search tools over the real bash executor + real rg', ()
signal: controller.signal,
})
expect(result.isError).toBe(true)
expect(result.error).toMatchObject({ name: 'SearchError', code: 'SEARCH_ABORTED' })
expect(result.error).toMatchObject({ name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH })
})
it('an unusable session cwd (spawn failure) is SEARCH_FAILED', async () => {

View File

@@ -15,7 +15,7 @@ import { Context } from 'cordis'
import { join } from 'node:path'
import { CallId } from '@deepseek-ai/dsh-llm'
import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import ToolRegistry, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
import { BashExecutor } from '@deepseek-ai/dsh-bash'
import type { BashExecRequest, BashExecSpec, BashProcess, BashRunResult } from '@deepseek-ai/dsh-bash'
import { SpillLocator, SpillStore } from '@deepseek-ai/dsh-spill'
@@ -32,6 +32,7 @@ import {
toWorkdirRelative,
} from '@deepseek-ai/dsh-tool-fs-search'
const testToolSignal = new AbortController().signal
const RG_PROBE_COMMAND = 'command -v rg >/dev/null 2>&1'
/** A successful run result over the given stdout; overrides script the failure shapes. */
@@ -60,6 +61,7 @@ class FakeBash extends BashExecutor {
requests: BashExecRequest[] = []
specs: BashExecSpec[] = []
startCalls = 0
forwardSignal = true
probeResult: BashRunResult = runResult('')
probeError?: Error
handler: (spec: BashExecSpec) => BashRunResult = () => runResult('')
@@ -72,7 +74,7 @@ class FakeBash extends BashExecutor {
workdir: request.workdir ?? '/work',
timeoutMs: request.timeoutMs ?? 60_000,
stdoutMaxBytes: request.stdoutMaxBytes ?? 64_000,
signal: request.signal,
...this.forwardSignal ? { signal: request.signal } : {},
sandboxMode: request.sandboxMode,
}
}
@@ -148,6 +150,7 @@ const agent = (cwd?: string) => ({ session: { header: { id: 'session-1', ...cwd
let callCounter = 0
function call(ctx: Context, name: string, args: unknown, options: { agent?: object; signal?: AbortSignal } = {}) {
return ctx.tools.execute({
signal: testToolSignal,
callId: CallId(`call-${++callCounter}`),
name,
arguments: args,
@@ -303,16 +306,13 @@ describe('workdir derivation and signal forwarding', () => {
expect(bash.requests[1]).not.toHaveProperty('workdir')
})
it('forwards exec.signal into the bash spec (the abort reaches the backend)', async () => {
it('forwards exec.signal into the bash spec', async () => {
const { ctx, bash } = await setup()
const controller = new AbortController()
controller.abort()
bash.handler = spec => runResult('', { aborted: spec.signal?.aborted === true })
bash.handler = () => runResult('')
const result = await call(ctx, 'grep', { pattern: 'x' }, { signal: controller.signal })
expect(bash.specs[0]?.signal).toBe(controller.signal)
expect(result.isError).toBe(true)
expect(result.error).toMatchObject({ name: 'SearchError', code: 'SEARCH_ABORTED' })
expect(text(result)).toContain('aborted')
expect(result.isError).toBe(false)
})
it('reports the bash executor timeout as SEARCH_ABORTED with the budget', async () => {
@@ -324,20 +324,46 @@ describe('workdir derivation and signal forwarding', () => {
expect(text(result)).toContain('timed out after 1234ms')
})
it('translates a run() rejection under a pre-aborted signal into SEARCH_ABORTED', async () => {
// The seam contract: run() REJECTS for a pre-aborted signal (it never
// spawns). The plain rejection must not escape the SEARCH_* taxonomy.
it('skips a pre-aborted registry call before run()', async () => {
const { ctx, bash } = await setup()
const controller = new AbortController()
controller.abort()
bash.handler = () => { throw new Error('aborted before spawn') }
const result = await call(ctx, 'grep', { pattern: 'x' }, { signal: controller.signal })
expect(result.isError).toBe(true)
expect(result.error).toMatchObject({ name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH })
expect(bash.specs).toHaveLength(0)
})
it('translates a run() rejection after the forwarded signal aborts', async () => {
const { ctx, bash } = await setup()
const controller = new AbortController()
bash.handler = () => {
controller.abort('cancel search')
throw new Error('executor stopped on abort')
}
const result = await call(ctx, 'grep', { pattern: 'x' }, { signal: controller.signal })
expect(result.isError).toBe(true)
expect(result.error).toMatchObject({ name: 'SearchError', code: 'SEARCH_ABORTED' })
expect(text(result)).toContain('aborted before completion')
})
it('translates an aborted executor result after dispatch starts', async () => {
const { ctx, bash } = await setup()
bash.handler = () => runResult('', { aborted: true, exitCode: null })
const result = await call(ctx, 'glob', { pattern: '*' })
expect(result.isError).toBe(true)
expect(result.error).toMatchObject({ name: 'SearchError', code: 'SEARCH_ABORTED' })
expect(text(result)).toContain('aborted before completion')
})
it('translates a run() rejection without an abort (unusable workdir) into SEARCH_FAILED', async () => {
const { ctx, bash } = await setup()
bash.forwardSignal = false
bash.handler = () => { throw new Error('spawn bash ENOENT') }
const result = await call(ctx, 'glob', { pattern: '*' })
expect(result.isError).toBe(true)

View File

@@ -6,15 +6,38 @@
},
"include": ["src"],
"references": [
{ "path": "../../../vendor/cosmokit" },
{ "path": "../../../vendor/cordis" },
{ "path": "../../../vendor/schemastery" },
{ "path": "../../util/retention" },
{ "path": "../../llm/llm" },
{ "path": "../../core/session" },
{ "path": "../../core/tools" },
{ "path": "../../core/system-prompt" },
{ "path": "../../bash/bash" },
{ "path": "../../spill/spill" }
{
"path": "../../../vendor/cosmokit"
},
{
"path": "../../../vendor/cordis"
},
{
"path": "../../../vendor/schemastery"
},
{
"path": "../../util/retention"
},
{
"path": "../../llm/llm"
},
{
"path": "../../core/session"
},
{
"path": "../../core/tools"
},
{
"path": "../../core/system-prompt"
},
{
"path": "../../bash/bash"
},
{
"path": "../../spill/spill"
},
{
"path": "../../support/invariants"
}
]
}