| import assert from 'node:assert/strict'; |
| import { describe, test } from 'node:test'; |
| import { |
| buildLocalForegroundBashTool, |
| buildManagedBashTool, |
| shapeTerminalResult, |
| type ShellRunLauncher, |
| } from '../shell-tools.js'; |
| import type { ShellPlan } from '../shell-detect.js'; |
| |
| const pwshPlan: ShellPlan = { |
| kind: 'pwsh', |
| displayName: 'PowerShell 7 (pwsh)', |
| exe: 'C:\\pf\\pwsh.exe', |
| }; |
| |
| describe('Bash tool description declares the executing shell', () => { |
| test('foreground and background variants declare command activity', () => { |
| assert.equal(buildLocalForegroundBashTool().activityKind, 'command'); |
| assert.equal(buildManagedBashTool(fakeShellRuns()).activityKind, 'command'); |
| }); |
| |
| test('foreground tool tells the model commands run under PowerShell 7', () => { |
| const tool = buildLocalForegroundBashTool({ shell: pwshPlan }); |
| assert.match(tool.description, /PowerShell 7 \(pwsh\)/); |
| assert.match(tool.description, /PowerShell syntax/); |
| assert.match(tool.description, /git ls-files/); |
| assert.match(tool.description, /node_modules/); |
| assert.match(tool.description, /Subject to permission policy\.$/); |
| }); |
| |
| test('foreground tool description is unchanged on POSIX', () => { |
| const tool = buildLocalForegroundBashTool({ shell: { kind: 'posix', displayName: '/bin/sh' } }); |
| assert.equal( |
| tool.description, |
| 'Run a shell command in the session cwd. Subject to permission policy.', |
| ); |
| }); |
| |
| test('background tool tells the model commands run under PowerShell 7', () => { |
| const tool = buildManagedBashTool(fakeShellRuns(), { shell: pwshPlan }); |
| assert.match(tool.description, /PowerShell 7 \(pwsh\)/); |
| assert.match(tool.description, /git ls-files/); |
| assert.match(tool.description, /run_in_background=true/); |
| }); |
| }); |
| |
| describe('Bash tool shell is threaded through to execution, not just the description', () => { |
| test('foreground tool executes with the same shell it declares', async () => { |
| // /bin/echo stands in for pwsh.exe: if the tool's shell reaches the |
| // spawn, stdout echoes the PowerShell flags back. A shell that only |
| // reached the description would run via the default POSIX shell and |
| // print a bare 'wired-marker'. |
| const tool = buildLocalForegroundBashTool({ |
| shell: { kind: 'pwsh', displayName: 'PowerShell 7 (pwsh)', exe: '/bin/echo' }, |
| }); |
| const result = (await tool.impl({ command: 'echo wired-marker' }, fakeToolContext())) as { |
| output: { stdout: string }; |
| }; |
| assert.ok( |
| result.output.stdout.startsWith( |
| '-NoLogo -NoProfile -NonInteractive -Command echo wired-marker\n', |
| ), |
| `expected declared shell to execute, got: ${result.output.stdout}`, |
| ); |
| }); |
| |
| test('background tool forwards its shell to the shell-run controller', async () => { |
| const captured: unknown[] = []; |
| const controller: ShellRunLauncher = { |
| runForegroundBash: () => Promise.reject(new Error('not used')), |
| runBackgroundBash: (input: unknown) => { |
| captured.push(input); |
| return Promise.resolve({ |
| kind: 'shell_run', |
| ref: 'maka://runtime/background-tasks/sr_test', |
| mode: 'pipes', |
| status: 'running', |
| cwd: '.', |
| cmd: '', |
| startedAt: 1, |
| updatedAt: 1, |
| revision: 1, |
| }); |
| }, |
| }; |
| const tool = buildManagedBashTool(controller, { shell: pwshPlan }); |
| await tool.impl({ command: 'echo hi', run_in_background: true }, fakeToolContext()); |
| assert.deepEqual((captured[0] as { shell?: unknown }).shell, pwshPlan); |
| }); |
| |
| test('managed completion callback remains exactly-once when the launcher settles it', async () => { |
| let completionCount = 0; |
| const controller: ShellRunLauncher = { |
| async runForegroundBash(input) { |
| input.onCompletion?.({ successful: true }); |
| return { |
| kind: 'terminal', |
| cwd: input.cwd, |
| cmd: input.command, |
| status: 'completed', |
| exitCode: 0, |
| output: { |
| mode: 'pipes', |
| stdout: '', |
| stderr: '', |
| stdoutTruncated: false, |
| stderrTruncated: false, |
| redacted: false, |
| }, |
| }; |
| }, |
| runBackgroundBash: () => Promise.reject(new Error('not used')), |
| }; |
| const tool = buildManagedBashTool(controller, { |
| transformCommand: ({ ctx }) => ({ |
| cwd: ctx.cwd, |
| onCompletion: () => { |
| completionCount += 1; |
| }, |
| }), |
| }); |
| |
| await tool.impl({ command: 'true' }, fakeToolContext()); |
| assert.equal(completionCount, 1); |
| }); |
| }); |
| |
| describe('Bash provider-facing result projection', () => { |
| test('managed Bash removes only the duplicated foreground command', async () => { |
| const tool = buildManagedBashTool(fakeShellRuns()); |
| const terminal = { |
| kind: 'terminal', |
| cwd: '/workspace', |
| cmd: 'printf foreground', |
| status: 'completed', |
| exitCode: 0, |
| output: { |
| mode: 'pipes', |
| stdout: 'foreground', |
| stderr: '', |
| stdoutTruncated: false, |
| stderrTruncated: false, |
| redacted: false, |
| }, |
| }; |
| const background = { |
| kind: 'shell_run', |
| ref: 'maka://runtime/background-tasks/sr_test', |
| mode: 'pipes', |
| status: 'running', |
| cwd: '/workspace', |
| cmd: 'printf background', |
| startedAt: 1, |
| updatedAt: 1, |
| revision: 1, |
| }; |
| |
| assert.deepEqual( |
| await tool.toModelOutput?.({ toolCallId: 'foreground', input: {}, output: terminal }), |
| { |
| type: 'json', |
| value: { |
| kind: 'terminal', |
| cwd: '/workspace', |
| status: 'completed', |
| exitCode: 0, |
| output: terminal.output, |
| }, |
| }, |
| ); |
| assert.deepEqual( |
| await tool.toModelOutput?.({ toolCallId: 'background', input: {}, output: background }), |
| { type: 'json', value: background }, |
| ); |
| assert.equal(terminal.cmd, 'printf foreground'); |
| }); |
| }); |
| |
| describe('shapeTerminalResult sandbox denial projection', () => { |
| test('surfaces sandboxDenial when a sandboxed command fails with a denial message', () => { |
| const result = shapeTerminalResult({ |
| cwd: '/ws', |
| command: 'rm -rf /', |
| result: { |
| exitCode: 1, |
| stdout: '', |
| stderr: 'Operation not permitted', |
| sandboxed: true, |
| sandboxType: 'macos-seatbelt', |
| }, |
| }); |
| assert.deepEqual(result.sandboxDenial, { |
| likely: true, |
| backend: 'macos-seatbelt', |
| }); |
| }); |
| |
| test('omits sandboxDenial when sandboxed is false', () => { |
| const result = shapeTerminalResult({ |
| cwd: '/ws', |
| command: 'ls /no-such-dir', |
| result: { |
| exitCode: 1, |
| stdout: '', |
| stderr: 'Operation not permitted', |
| sandboxed: false, |
| }, |
| }); |
| assert.equal(result.sandboxDenial, undefined); |
| }); |
| |
| test('omits sandboxDenial when sandboxed field is absent (BoundedShellResult shape)', () => { |
| const result = shapeTerminalResult({ |
| cwd: '/ws', |
| command: 'ls', |
| result: { |
| exitCode: 1, |
| stdout: '', |
| stderr: 'Operation not permitted', |
| stdoutTruncated: false, |
| stderrTruncated: false, |
| timedOut: false, |
| aborted: false, |
| }, |
| }); |
| assert.equal(result.sandboxDenial, undefined); |
| }); |
| }); |
| |
| function fakeToolContext() { |
| return { |
| sessionId: 'session-1', |
| turnId: 'turn-1', |
| toolCallId: 'tool-1', |
| cwd: process.cwd(), |
| abortSignal: new AbortController().signal, |
| emitOutput: () => {}, |
| }; |
| } |
| |
| function fakeShellRuns() { |
| return { |
| runForegroundBash: () => Promise.reject(new Error('not used')), |
| runBackgroundBash: () => Promise.reject(new Error('not used')), |
| }; |
| } |