diff --git a/docs/config-catalog.md b/docs/config-catalog.md index aeb6975be9..cc659f5e8c 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -3714,7 +3714,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-pwsh-persistent` - `inject`: `tools` · `terminals` -- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:456`](../packages/shell/tool-pwsh-persistent/src/index.ts) +- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:457`](../packages/shell/tool-pwsh-persistent/src/index.ts) ```ts config-catalog /** Configuration for the persistent pwsh tool. */ diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 13218e1684..e89f722b88 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -3716,7 +3716,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-pwsh-persistent` - `inject`: `tools` · `terminals` -- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:456`](../packages/shell/tool-pwsh-persistent/src/index.ts) +- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:457`](../packages/shell/tool-pwsh-persistent/src/index.ts) ```ts config-catalog /** Configuration for the persistent pwsh tool. */ diff --git a/packages/shell/tool-pwsh-persistent/README.i18n.yaml b/packages/shell/tool-pwsh-persistent/README.i18n.yaml index bc6ff68375..178cdcd384 100644 --- a/packages/shell/tool-pwsh-persistent/README.i18n.yaml +++ b/packages/shell/tool-pwsh-persistent/README.i18n.yaml @@ -33,14 +33,14 @@ en: 8d94a7381bca3fa9 zh: f4bd2866fff6fa8d /deepseek-ai-dsh-tool-pwsh-persistent/understand-the-implementation/design-philosophy: - en: b8544c7025ef7f9e - zh: 573b0ec492dde975 + en: b9b75c3251f29f02 + zh: 9114aad4726f6d0c /deepseek-ai-dsh-tool-pwsh-persistent/understand-the-implementation/source-map: en: bb2d17f1385fb11d zh: 8e103837238ccda2 /deepseek-ai-dsh-tool-pwsh-persistent/understand-the-implementation/command-flow: - en: 52f69b3256d2d2c2 - zh: 34ce45e101760793 + en: 7440916d998ed5bd + zh: 66f70f803c519802 /deepseek-ai-dsh-tool-pwsh-persistent/further-exploration: en: e836ecbf7370bc76 zh: 84fed68318df81ff diff --git a/packages/shell/tool-pwsh-persistent/README.md b/packages/shell/tool-pwsh-persistent/README.md index cd78f224f9..871db34210 100644 --- a/packages/shell/tool-pwsh-persistent/README.md +++ b/packages/shell/tool-pwsh-persistent/README.md @@ -73,7 +73,7 @@ This section explains the design decisions behind the tool and points at the cod ### Design philosophy - **A deliberate twin of `dsh-tool-bash-persistent`.** The session registry, polling loop, and reset contract mirror the persistent bash tool by design ([pwsh persistent PTY Agent Note](../../../.agents/notes/archived/architecture/2026-08-11-pwsh-persistent-pty.md)). -- **Backend-owned prompt readiness.** The shell's `prompt` function belongs to the backend: it prints a BEL-terminated OSC marker plus the controlled printable prompt, and that exact prompt text settles every command through the backend's fast path. The tool neither installs nor matches a prompt of its own, so only a model redefinition of `prompt` degrades readiness to the silence tier. +- **Backend-owned prompt readiness.** The shell's `prompt` function belongs to the backend: it prints a BEL-terminated OSC marker plus the controlled printable prompt. The backend accepts recognized prompt text through its fast path and otherwise uses its own readiness checks or silence tier. The tool neither installs nor matches a prompt of its own. - **PSReadLine echo stripped by anchoring.** PowerShell renders submitted input back into the stream; the marker-anchored extraction and a wrapper-source strip remove the echo, and a wrapper that wraps across the terminal width may leave a partial echo in partial-output results. - **Reset, never repair.** Any uncertain state — an explicit `exit`, a timeout, a send failure, an abort — closes the shell and starts the next call fresh. @@ -86,7 +86,7 @@ This section explains the design decisions behind the tool and points at the cod ### Command flow -A first command spawns the shell through `ctx.terminals.spawn`, whose pwsh startup installs the controlled `prompt` function and returns only once the shell reached readiness. Each command is wrapped into one physical line — `Write-Output` of the start marker, the body escaped with backtick escapes into a double-quoted string, and `Write-Output` of the end marker plus the exit status — so PSReadLine's echo of a wrapped line cannot fabricate completion. The tool polls the scrollback in 1,000-line pages until the end marker appears or the send settles as `stdin_read`, extracts the span, strips the echoed wrapper, and renders it with any status marker. A timeout aborts the deadline, captures the partial output, and resets the shell. +A first command spawns the shell through `ctx.terminals.spawn`, whose pwsh startup installs the controlled `prompt` function and returns only once the shell reached readiness. Each command is wrapped into one physical line — `Write-Output` of the start marker, the body escaped with backtick escapes into a double-quoted string, and `Write-Output` of the end marker plus the exit status — so PSReadLine's echo of a wrapped line cannot fabricate completion. The parser accepts ASCII-space padding after the status digits but still requires LF or CRLF. Spaces in command output remain unchanged. The tool polls the scrollback in 1,000-line pages until a complete end-marker status line is available or the send settles as `stdin_read`, extracts the span, strips the echoed wrapper, and renders it with any status marker. A timeout aborts the deadline, captures the partial output, and resets the shell. diff --git a/packages/shell/tool-pwsh-persistent/README.zh.md b/packages/shell/tool-pwsh-persistent/README.zh.md index 53b0b4cdd6..caea272185 100644 --- a/packages/shell/tool-pwsh-persistent/README.zh.md +++ b/packages/shell/tool-pwsh-persistent/README.zh.md @@ -73,7 +73,7 @@ kind: "package-reference" ### 设计理念 - **`dsh-tool-bash-persistent` 的刻意孪生。** 会话注册表、轮询循环与重置约定按设计镜像持久 bash 工具([pwsh 持久 PTY Agent Note](../../../.agents/notes/archived/architecture/2026-08-11-pwsh-persistent-pty.md))。 -- **后端拥有提示符就绪。** shell 的 `prompt` 函数属于后端:它打印 BEL 结尾的 OSC 标记加受控的可打印提示符,该确切提示符文本让每条命令都经由后端快路径结算。工具既不安装也不匹配自己的提示符,因此只有模型重定义 `prompt` 才会把就绪降级到静默层级。 +- **后端拥有提示符就绪。** shell 的 `prompt` 函数属于后端:它打印 BEL 结尾的 OSC 标记加受控的可打印提示符。后端通过快路径接受已识别的提示符文本,否则使用其自身的就绪检查或静默层级。工具既不安装也不匹配自己的提示符。 - **PSReadLine 回显靠锚定剥离。** PowerShell 会把提交的输入渲染回流中;标记锚定提取与包装源码剥离移除回显,而跨终端宽度换行的包装可能在部分输出结果中留下部分回显。 - **重置,而非修复。** 任何不确定状态——显式 `exit`、超时、发送失败、中止——都会关闭 shell 并让下一次调用从全新状态开始。 @@ -86,7 +86,7 @@ kind: "package-reference" ### 命令流程 -首条命令通过 `ctx.terminals.spawn` 生成 shell,其 pwsh 启动流程安装受控的 `prompt` 函数,并只在该 shell 达到就绪后返回。随后每条命令都包装成一行物理文本——`Write-Output` 起始标记、用反引号转义进双引号字符串的命令体、`Write-Output` 结束标记加退出状态——因此 PSReadLine 对换行包装的回显无法伪造完成。工具以 1,000 行一页轮询 scrollback,直到出现结束标记或该 send 以 `stdin_read` 结算,提取区间、剥离回显的包装,并连同任何状态标记一起渲染。超时会中止截止时间、捕获部分输出并重置 shell。 +首条命令通过 `ctx.terminals.spawn` 生成 shell,其 pwsh 启动流程安装受控的 `prompt` 函数,并只在该 shell 达到就绪后返回。随后每条命令都包装成一行物理文本——`Write-Output` 起始标记、用反引号转义进双引号字符串的命令体、`Write-Output` 结束标记加退出状态——因此 PSReadLine 对换行包装的回显无法伪造完成。解析器允许状态码数字后的 ASCII 空格,但仍要求以 LF 或 CRLF 结束。命令输出中的空格保持不变。工具以 1,000 行一页轮询 scrollback,直到出现完整的结束标记状态行或该 send 以 `stdin_read` 结算,提取区间、剥离回显的包装,并连同任何状态标记一起渲染。超时会中止截止时间、捕获部分输出并重置 shell。 diff --git a/packages/shell/tool-pwsh-persistent/src/index.ts b/packages/shell/tool-pwsh-persistent/src/index.ts index 93a3e9331f..84c6833a73 100644 --- a/packages/shell/tool-pwsh-persistent/src/index.ts +++ b/packages/shell/tool-pwsh-persistent/src/index.ts @@ -109,7 +109,8 @@ function commandOutput( ): CapturedOutput | undefined { const text = snapshot.text const end = text.lastIndexOf(marker.end) - const status = /^(\d+)\r?\n/.exec(text.slice(end + marker.end.length))?.[1] + // Terminal padding belongs to the status line, not to captured command output. + const status = /^(\d+) *\r?\n/.exec(text.slice(end + marker.end.length))?.[1] if (status === undefined) return undefined const startMarker = text.lastIndexOf(marker.start, end) const start = startMarker < 0 ? 0 : startMarker + marker.start.length diff --git a/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts b/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts index 701402b7fc..45429d4f4e 100644 --- a/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts +++ b/packages/shell/tool-pwsh-persistent/tests/tools.spec.ts @@ -92,6 +92,8 @@ type StubMode = | 'nonzero' | 'torn-status' | 'finish-torn-status' + | 'status-tail' + | 'continue-status-tail' | 'end-only' | 'spawn-error' | 'send-error' @@ -118,6 +120,7 @@ class StubTerminalSession implements TerminalBackendSession { historyTruncated = false throwOnSend = false largeOutput = 'x'.repeat(100) + statusChunks: string[] = [] constructor(mode: StubMode) { this.mode = mode @@ -190,6 +193,15 @@ class StubTerminalSession implements TerminalBackendSession { const incremental = `${start ?? ''}\nincrement\n${this.motd}` return this.operation(Promise.resolve(this.result(this.motd, 'stdin_read')), incremental) } + if (this.mode === 'status-tail' || this.mode === 'continue-status-tail') { + const prefix = this.mode === 'status-tail' ? `${start ?? ''}\nkept \ndsh> \n${end ?? ''}` : '' + const fragment = this.statusChunks.shift() + if (fragment === undefined) throw new Error('missing scripted completion fragment') + const output = prefix + fragment + this.mode = this.statusChunks.length > 0 ? 'continue-status-tail' : 'send-error' + this.scrollback += output + return this.operation(Promise.resolve(this.result(output, 'inferred_idle'))) + } if (this.mode === 'torn-status') { const output = `${start ?? ''}\nhello from stub\n${end ?? ''}` this.scrollback += output @@ -366,6 +378,53 @@ describe('tool-pwsh-persistent', () => { expect(text(await call(ctx, owner, 'complete prompt collision'))).toBe(session.motd) }) + it.each([ + ['0 \r\n', 0], + ['7 \r\n', 7], + ['0 \n', 0], + ['7 \n', 7], + ] as const)('accepts ASCII-padded completion status %j without changing output', async (tail, exitCode) => { + const { ctx, owner, stub } = await setup({ backendType: 'stub' }) + await call(ctx, owner, 'warm up') + const session = stub.sessions[0]! + session.mode = 'status-tail' + session.statusChunks = [tail] + + const result = await call(ctx, owner, 'padded completion') + expect(result.isError).not.toBe(true) + expect(text(result)).toBe('kept \ndsh> ' + (exitCode === 0 ? '' : `\n[exit code: ${exitCode}]`)) + expect(session.sends).toBe(2) + }) + + it.each([0, 1, 2, 3, 4])('waits for the complete padded status newline at split %i', async (split) => { + const { ctx, owner, stub } = await setup({ backendType: 'stub' }) + await call(ctx, owner, 'warm up') + const session = stub.sessions[0]! + session.mode = 'status-tail' + const tail = '7 \r\n' + session.statusChunks = [tail.slice(0, split), tail.slice(split)] + const send = vi.spyOn(session, 'startSend') + + expect(text(await call(ctx, owner, 'split completion'))).toBe('kept \ndsh> \n[exit code: 7]') + expect(send).toHaveBeenCalledTimes(2) + expect(send.mock.calls[1]?.[0]).toMatchObject({ text: '', submit: false }) + }) + + it.each(['0\t\n', '0\u00a0\n', '0\v\n', '0 ', '0 \r', '0junk\n', ' 0\n', '-1\n', '+1\n', "' + $__s)\n"])( + 'does not accept unsupported completion status %j', async (tail) => { + const { ctx, owner, stub } = await setup({ backendType: 'stub' }) + await call(ctx, owner, 'warm up') + const session = stub.sessions[0]! + session.mode = 'status-tail' + session.statusChunks = [tail] + + const result = await call(ctx, owner, 'invalid completion') + expect(result.isError).toBe(true) + expect(text(result)).toContain('stub send failed') + expect(session.sends).toBe(3) + }, + ) + it('reports the exit path when the shell exits between send settlement and the next poll', async () => { const { ctx, owner, stub } = await setup({ backendType: 'stub' }) await call(ctx, owner, 'warm up') diff --git a/snapshots/session/persistent-pwsh-padded-completion/cordis.snapshot.yml b/snapshots/session/persistent-pwsh-padded-completion/cordis.snapshot.yml new file mode 100644 index 0000000000..014c8c7aaa --- /dev/null +++ b/snapshots/session/persistent-pwsh-padded-completion/cordis.snapshot.yml @@ -0,0 +1,92 @@ +# Keyless replay for padded persistent PowerShell completion frames. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek-api-key' + disabled: true + +- insert: + - id: llm-replay + name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official + name: DeepSeek + models: + - id: deepseek-v4-pro + +- insert: + - id: terminal + name: '@deepseek-ai/dsh-terminal' + +- id: sandbox-policy + name: '@deepseek-ai/dsh-sandbox-policy' + config: + mode: danger-full-access + workspaceRoot: !!js process.cwd() + +- id: subprocess + name: '@deepseek-ai/dsh-subprocess-local' + +- insert: + - id: padded-pwsh-backend + name: './pwsh-snapshot-backend.mjs' + +- id: agent-default-model + name: '@deepseek-ai/dsh-agent-default-model' + config: + provider: deepseek-official + model: deepseek-v4-pro + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js dshHomePath('sessions') + compression: none + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + personaPrefix: You are a concise snapshot agent working in {{cwd}}. + +- id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + disabled: true + +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: true + +- insert: + - id: tool-pwsh-persistent + name: '@deepseek-ai/dsh-tool-pwsh-persistent' diff --git a/snapshots/session/persistent-pwsh-padded-completion/cordis.yml b/snapshots/session/persistent-pwsh-padded-completion/cordis.yml new file mode 100644 index 0000000000..2ab9c9d4b0 --- /dev/null +++ b/snapshots/session/persistent-pwsh-padded-completion/cordis.yml @@ -0,0 +1,84 @@ +# Minimal live counterpart for the persistent-pwsh-padded-completion snapshot composition. +- id: llm-deepseek + name: '@deepseek-ai/dsh-llm-deepseek-api-key' + config: + models: + - id: deepseek-v4-pro + +- id: sandbox-policy + name: '@deepseek-ai/dsh-sandbox-policy' + config: + mode: danger-full-access + workspaceRoot: !!js process.cwd() + +- id: subprocess + name: '@deepseek-ai/dsh-subprocess-local' + +- insert: + - id: terminal + name: '@deepseek-ai/dsh-terminal' + +- insert: + - id: padded-pwsh-backend + name: './pwsh-snapshot-backend.mjs' + +- id: agent-default-model + name: '@deepseek-ai/dsh-agent-default-model' + config: + provider: deepseek-official + model: deepseek-v4-pro + +- id: session-persistence-jsonl + name: '@deepseek-ai/dsh-session-persistence-jsonl' + config: + root: !!js dshHomePath('sessions') + compression: !!js 'process.env.DSH_SNAPSHOT === undefined ? ''zstd'' : ''none''' + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + disabled: true + +- id: system-prompt + name: '@deepseek-ai/dsh-system-prompt' + config: + personaPrefix: You are a concise snapshot agent working in {{cwd}}. + +- id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + disabled: true + +- id: goal + name: '@deepseek-ai/dsh-goal' + disabled: true + +- id: goal-round-driver + name: '@deepseek-ai/dsh-goal-round-driver' + disabled: true + +- id: command-goal + name: '@deepseek-ai/dsh-command-goal' + disabled: true + +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + disabled: true + +- id: skill + name: '@deepseek-ai/dsh-skill' + disabled: true + +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + disabled: true + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + disabled: true + +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: true + +- insert: + - id: tool-pwsh-persistent + name: '@deepseek-ai/dsh-tool-pwsh-persistent' diff --git a/snapshots/session/persistent-pwsh-padded-completion/pwsh-snapshot-backend.mjs b/snapshots/session/persistent-pwsh-padded-completion/pwsh-snapshot-backend.mjs new file mode 100644 index 0000000000..7e059029d3 --- /dev/null +++ b/snapshots/session/persistent-pwsh-padded-completion/pwsh-snapshot-backend.mjs @@ -0,0 +1,72 @@ +/** Scripted terminal frames exercise the real persistent PowerShell tool and Session log. */ + +const responses = [ + { token: 'PWSH_OK', code: 0 }, + { token: 'PWSH_FAIL', code: 7 }, +] + +class SnapshotSession { + motd = 'dsh> ' + statusValue = { kind: 'running' } + scrollback = 'dsh> ' + next = 0 + + startSend(request) { + const response = responses[this.next++] + if (!response || !request.submit || !request.text.includes(`[Console]::Out.Write(' ${response.token} ')`)) { + throw new Error('unexpected padded-completion snapshot command') + } + const start = /__DSH_PERSISTENT_PWSH_START_[a-f0-9-]+__/.exec(request.text)?.[0] + const end = /__DSH_PERSISTENT_PWSH_END_[a-f0-9-]+:/.exec(request.text)?.[0] + if (!start || !end) throw new Error('padded-completion snapshot requires the actual command markers') + const viewport = `${request.text}\r\n${start}\r\n ${response.token} ${end}${response.code} \r\n${this.motd}` + this.scrollback += viewport + const result = { viewport, waitReason: 'stdin_read', sessionStatus: this.statusValue, truncated: false } + let consumed = false + return { + done: Promise.resolve(result), + readOutput: () => { + if (consumed) return { delta: '', truncated: false } + consumed = true + return { delta: viewport, truncated: false } + }, + cancel: () => false, + } + } + + read(request) { + const lines = this.scrollback.split('\n') + const offset = request.offset ?? 0 + const count = request.count ?? 500 + const end = lines.length - offset + const start = Math.max(0, end - count) + const text = lines.slice(start, end).join('\n') + return { text, totalLines: lines.length, lineBegin: offset, lineEnd: offset + text.split('\n').length, truncated: false } + } + + signal() { + return Promise.resolve({ delivered: true, targetPgid: 1 }) + } + + status() { + return this.statusValue + } + + close() { + this.statusValue = { kind: 'exited', exitCode: 0, signal: null } + return Promise.resolve() + } +} + +/** Scenario-local fixture identity. */ +export const name = 'pwsh-padded-completion-snapshot' +/** Terminal service owns the backend registration and sessions. */ +export const inject = ['terminals'] + +/** Register deterministic completion frames under the real terminal service. @param ctx - Effect-scoped plugin context. */ +export function apply(ctx) { + ctx.terminals.registerBackend({ + type: 'shell', + spawn: () => Promise.resolve(new SnapshotSession()), + }) +} diff --git a/snapshots/session/persistent-pwsh-padded-completion/session.v4.jsonl b/snapshots/session/persistent-pwsh-padded-completion/session.v4.jsonl new file mode 100644 index 0000000000..c3376af2af --- /dev/null +++ b/snapshots/session/persistent-pwsh-padded-completion/session.v4.jsonl @@ -0,0 +1,27 @@ +{"type":"session","version":4,"id":"{{session:1}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"permission/preset","data":{"preset":"danger-full-access"}} +{"type":"sandbox/mode","data":{"mode":"danger-full-access"}} +{"type":"approval/policy","data":{"policy":"never"}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Run both pwsh commands in order: [Console]::Out.Write(' PWSH_OK '), then [Console]::Out.Write(' PWSH_FAIL '); $global:LASTEXITCODE = 7. Reply DONE and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"turn/start","data":{"turn":1}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":1,"step":1}} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Run both pwsh commands in order: [Console]::Out.Write(' PWSH_OK '), then [Console]::Out.Write(' PWSH_FAIL '); $global:LASTEXITCODE = 7. Reply DONE and stop."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-pro"},"tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-pro"}} +{"type":"session/title","data":{"title":"Run both pwsh commands in","messageSeqs":[8],"source":{"kind":"fallback"}}} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"pwsh-padded-1","name":"pwsh","arguments":"{\"command\":\"[Console]::Out.Write(' PWSH_OK ')\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"pwsh-padded-1","name":"pwsh","args":["{\"command\":\"[Console]::Out.Write(' PWSH_OK ')\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"pwsh-padded-1","name":"pwsh","arguments":"{\"command\":\"[Console]::Out.Write(' PWSH_OK ')\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":1,"callId":"pwsh-padded-1","name":"pwsh","arguments":"{\"command\":\"[Console]::Out.Write(' PWSH_OK ')\"}"}} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"pwsh-padded-1"},"toolCallId":"pwsh-padded-1","content":[{"type":"text","text":" PWSH_OK "}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":1}} +{"type":"step/start","data":{"turn":1,"step":2}} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"pwsh-padded-2","name":"pwsh","arguments":"{\"command\":\"[Console]::Out.Write(' PWSH_FAIL '); $global:LASTEXITCODE = 7\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"pwsh-padded-2","name":"pwsh","args":["{\"command\":\"[Console]::Out.Write(' PWSH_FAIL '); $global:LASTEXITCODE = 7\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"pwsh-padded-2","name":"pwsh","arguments":"{\"command\":\"[Console]::Out.Write(' PWSH_FAIL '); $global:LASTEXITCODE = 7\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":2,"callId":"pwsh-padded-2","name":"pwsh","arguments":"{\"command\":\"[Console]::Out.Write(' PWSH_FAIL '); $global:LASTEXITCODE = 7\"}"}} +{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"pwsh-padded-2"},"toolCallId":"pwsh-padded-2","content":[{"type":"text","text":" PWSH_FAIL \n[exit code: 7]"}],"isError":false,"id":"{{message:7}}"}},"sourceEventSeqs":[19],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":2}} +{"type":"step/start","data":{"turn":1,"step":3}} +{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:8}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":3}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/snapshots/session/persistent-pwsh-padded-completion/snapshot.yml b/snapshots/session/persistent-pwsh-padded-completion/snapshot.yml new file mode 100644 index 0000000000..f59c8e521f --- /dev/null +++ b/snapshots/session/persistent-pwsh-padded-completion/snapshot.yml @@ -0,0 +1,10 @@ +version: 1 +scenario: persistent-pwsh-padded-completion +profile: headless +composition: persistent-pwsh-padded-completion +recording: authored +header: + class: persistent-pwsh-padded-completion + pin: true + systemPromptSource: persistent-pwsh-tool-turn + toolSchemasSource: persistent-pwsh-tool-turn