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 TerminalSessionService from '@deepseek-ai/dsh-terminal' import * as TerminalLocal from '@deepseek-ai/dsh-terminal-bash' 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 LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime 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-terminal'", "- 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-terminal-bash'", ' 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([ ['@deepseek-ai/dsh-agent', AgentRegistry], ['@deepseek-ai/dsh-system-prompt', SystemPrompt], ['@deepseek-ai/dsh-tools', ToolRuntime], ['@deepseek-ai/dsh-terminal', TerminalSessionService], ['@deepseek-ai/dsh-test-sandbox', PassthroughSandbox], ['@deepseek-ai/dsh-sandbox-policy', SandboxPolicyService], ['@deepseek-ai/dsh-subprocess-local', LocalSubprocessRuntime], ['@deepseek-ai/dsh-terminal-bash', TerminalLocal], ['@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 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('') 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) })