mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
162 lines
6.4 KiB
TypeScript
162 lines
6.4 KiB
TypeScript
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { Context } from '@deepseek-ai/cordis'
|
|
import Loader from '@deepseek-ai/cordis-plugin-loader'
|
|
import Include from '@deepseek-ai/cordis-plugin-include'
|
|
import { CallId } from '@deepseek-ai/dsh-llm'
|
|
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
|
import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent'
|
|
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
import PtyService from '@deepseek-ai/dsh-pty'
|
|
import * as PtyLocal from '@deepseek-ai/dsh-pty-local'
|
|
import SandboxProvider from '@deepseek-ai/dsh-sandbox'
|
|
import type { ConfinedArgv, SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
|
|
import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy'
|
|
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
|
|
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
|
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
|
import * as ToolBashPersistent from '@deepseek-ai/dsh-tool-bash-persistent'
|
|
|
|
let root: string | undefined
|
|
let context: Context | undefined
|
|
|
|
afterEach(async () => {
|
|
await context?.fiber.dispose()
|
|
context = undefined
|
|
if (root !== undefined) await rm(root, { recursive: true, force: true })
|
|
root = undefined
|
|
})
|
|
|
|
class PassthroughSandbox extends SandboxProvider {
|
|
confine(argv: readonly string[], _policy: SandboxPolicy): ConfinedArgv {
|
|
return { argv: [...argv], enforcement: 'full', denialSignatures: [], runnerFailureRules: [] }
|
|
}
|
|
}
|
|
|
|
function agent(ctx: Context, cwd: string): Agent {
|
|
const id = SessionId('persistent-bash-loader-agent')
|
|
const scope = ctx.plugin(() => {})
|
|
const session = Session.create(id, [], { version: 0, id, createdAt: 0, cwd })
|
|
const value: Agent = {
|
|
id,
|
|
options: {},
|
|
session,
|
|
inbox: new Inbox(session, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
|
|
status: 'idle',
|
|
ctx: scope.ctx,
|
|
send: () => {},
|
|
followup: () => {},
|
|
steer: () => ({ outcome: Promise.resolve({ status: 'rejected' as const }) }),
|
|
inject: () => {},
|
|
cancel() {},
|
|
runMaintenance: task => task(new AbortController().signal),
|
|
whenIdle: () => Promise.resolve(),
|
|
}
|
|
ctx.agents.register(value)
|
|
return value
|
|
}
|
|
|
|
function text(result: { content: { type: string; text?: string }[] }): string {
|
|
return result.content.filter(block => block.type === 'text').map(block => block.text).join('')
|
|
}
|
|
|
|
const suite = process.platform === 'linux' || process.platform === 'darwin' ? describe : describe.skip
|
|
|
|
suite('persistent Bash through a real cordis.yml Loader composition', () => {
|
|
it('preserves cwd and environment across calls', async () => {
|
|
root = await mkdtemp(join(tmpdir(), 'dsh-persistent-bash-loader-'))
|
|
const configPath = join(root, 'cordis.yml')
|
|
await writeFile(configPath, [
|
|
"- name: '@deepseek-ai/dsh-agent'",
|
|
"- name: '@deepseek-ai/dsh-system-prompt'",
|
|
"- name: '@deepseek-ai/dsh-tools'",
|
|
"- name: '@deepseek-ai/dsh-pty'",
|
|
"- name: '@deepseek-ai/dsh-test-sandbox'",
|
|
"- name: '@deepseek-ai/dsh-sandbox-policy'",
|
|
' config:',
|
|
' mode: danger-full-access',
|
|
` workspaceRoot: ${JSON.stringify(root)}`,
|
|
"- name: '@deepseek-ai/dsh-subprocess-local'",
|
|
"- name: '@deepseek-ai/dsh-pty-local'",
|
|
' config:',
|
|
' pollIntervalMs: 10',
|
|
' exactProbeAfterMs: 20',
|
|
' idleSilenceMs: 100',
|
|
' handoffGraceMs: 100',
|
|
' scrollbackLines: 20000',
|
|
' timeoutMs: 2000',
|
|
' disposeGraceMs: 500',
|
|
"- name: '@deepseek-ai/dsh-tool-bash-persistent'",
|
|
' config:',
|
|
' timeoutMs: 5000',
|
|
'',
|
|
].join('\n'))
|
|
|
|
context = new Context()
|
|
context.baseUrl = pathToFileURL(root).href + '/'
|
|
await context.plugin(Loader)
|
|
context.loader.builtins.include = Include
|
|
const modules = new Map<string, unknown>([
|
|
['@deepseek-ai/dsh-agent', AgentRegistry],
|
|
['@deepseek-ai/dsh-system-prompt', SystemPrompt],
|
|
['@deepseek-ai/dsh-tools', ToolRegistry],
|
|
['@deepseek-ai/dsh-pty', PtyService],
|
|
['@deepseek-ai/dsh-test-sandbox', PassthroughSandbox],
|
|
['@deepseek-ai/dsh-sandbox-policy', SandboxPolicyService],
|
|
['@deepseek-ai/dsh-subprocess-local', LocalSubprocessService],
|
|
['@deepseek-ai/dsh-pty-local', PtyLocal],
|
|
['@deepseek-ai/dsh-tool-bash-persistent', ToolBashPersistent],
|
|
])
|
|
context.loader.internal = {
|
|
version: 'v2',
|
|
async import(specifier: string) {
|
|
if (!modules.has(specifier)) throw new Error(`unexpected Loader import: ${specifier}`)
|
|
return modules.get(specifier)
|
|
},
|
|
} as unknown as NonNullable<typeof context.loader.internal>
|
|
await context.loader.create({ name: 'cordis:include', config: { path: pathToFileURL(configPath).href } })
|
|
await context.loader.await()
|
|
|
|
const owner = agent(context, root)
|
|
const signal = new AbortController().signal
|
|
const execute = (id: string, command: string) => context!.tools.execute({
|
|
signal,
|
|
callId: CallId(id),
|
|
name: 'bash',
|
|
arguments: { command },
|
|
agent: owner,
|
|
})
|
|
|
|
expect(context.tools.schemas().map(schema => schema.name)).toEqual(['bash'])
|
|
await execute('state', 'export KEEP=loader; mkdir -p nested; cd nested')
|
|
const observed = text(await execute('observe', 'printf "cwd=%s keep=%s\\n" "$PWD" "$KEEP"'))
|
|
expect(observed).toContain(`cwd=${join(root, 'nested')} keep=loader`)
|
|
expect(observed).not.toContain('DSH_PERSISTENT_BASH')
|
|
|
|
const multiline = text(await execute(
|
|
'multiline',
|
|
'value="line one"\nprintf "%s:%s\\n" "$value" "it\'s fine"',
|
|
))
|
|
expect(multiline).toBe("line one:it's fine")
|
|
expect(multiline).not.toContain('DSH_PERSISTENT_BASH')
|
|
|
|
const heredoc = text(await execute(
|
|
'heredoc',
|
|
"cat <<'EOF'\nalpha\nbeta\nEOF",
|
|
))
|
|
expect(heredoc).toBe('alpha\nbeta')
|
|
|
|
const large = text(await execute('large-output', 'seq 1 12050'))
|
|
expect(large.startsWith('1\n2\n3\n')).toBe(true)
|
|
expect(large).toContain('<response clipped>')
|
|
expect(large).not.toContain('beginning of this command output was dropped')
|
|
|
|
const exited = text(await execute('exit', 'exit'))
|
|
expect(exited).toContain('next bash call starts from the workspace')
|
|
expect(text(await execute('after-exit', 'printf "%s\\n" "$PWD"'))).toBe(root)
|
|
}, 20_000)
|
|
})
|