feat(bash): shell tools reject a mismatched executor dialect at load
The seam gains ShellDialect ('bash' | 'powershell' - concrete shells, not
families: zsh or fish would be their own values, never 'bash'); bash-local
declares bash (bash-sandbox inherits), pwsh-local declares powershell, and
both tools throw at load when the mounted executor speaks another dialect -
previously tool-pwsh over bash-local handed PowerShell text to bash -c and
the deployment error surfaced as ordinary nonzero exits. Pinned by mismatch
tests on both tools; the parity note records the contract (both languages).
Also from the review round: the tool-bash README's managed-environment
section becomes a summary linking the owning dsh-bash-env contract (the
duplicated prose carried a stale owner in its example import), the
pwshOnly JSDoc drops the stale 'on PATH' phrasing, and the task-tools
contract comment in the two pwsh compositions is indented into its block.
This commit is contained in:
@@ -5,7 +5,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { BashExecutor } from '@deepseek-ai/dsh-bash'
|
||||
import type { BashExecRequest, BashExecSpec, BashProcess, BashProcessRead, BashRunResult } from '@deepseek-ai/dsh-bash'
|
||||
import type { BashExecRequest, BashExecSpec, BashProcess, BashProcessRead, BashRunResult, ShellDialect } from '@deepseek-ai/dsh-bash'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { TOOL_ABORTED, TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
@@ -101,6 +101,8 @@ async function callUntilText(
|
||||
}
|
||||
|
||||
class RecordingSandboxExecutor extends BashExecutor {
|
||||
readonly dialect = 'bash' as const
|
||||
|
||||
readonly modes: Array<string | undefined> = []
|
||||
|
||||
override get sandboxMode() {
|
||||
@@ -154,6 +156,8 @@ class RecordingSandboxExecutor extends BashExecutor {
|
||||
|
||||
/** Test executor that records whether the background start boundary was crossed. */
|
||||
class CountingStartExecutor extends BashExecutor {
|
||||
readonly dialect: ShellDialect = 'bash'
|
||||
|
||||
starts = 0
|
||||
|
||||
resolve(request: BashExecRequest): BashExecSpec {
|
||||
@@ -361,6 +365,19 @@ describe('bash tool', () => {
|
||||
expect(text(result)).toContain('tool execution arguments must be losslessly JSON-serializable')
|
||||
})
|
||||
|
||||
it('rejects an executor speaking another shell dialect at load', async () => {
|
||||
class PowershellDialectExecutor extends CountingStartExecutor {
|
||||
override readonly dialect: ShellDialect = 'powershell'
|
||||
}
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(BashEnvPlugin)
|
||||
await ctx.plugin(PowershellDialectExecutor)
|
||||
await expect(ctx.plugin(ToolBash)).rejects.toThrow("the mounted executor speaks 'powershell', not bash")
|
||||
})
|
||||
|
||||
it('registers the bash schema with run_in_background exposed by default', async () => {
|
||||
const ctx = await setup()
|
||||
const schemas = ctx.tools.schemas()
|
||||
@@ -1067,6 +1084,8 @@ describe('the model-facing bash tool builds its request from named args only (no
|
||||
* hands back an already-settled fake handle so the task registration completes.
|
||||
*/
|
||||
class RecordingBashExecutor extends BashExecutor {
|
||||
readonly dialect = 'bash' as const
|
||||
|
||||
readonly requests: BashExecRequest[] = []
|
||||
resolve(request: BashExecRequest): BashExecSpec {
|
||||
this.requests.push(request)
|
||||
|
||||
Reference in New Issue
Block a user