revert: remove pwsh changes from desktop PR

This commit is contained in:
07akioni committed 2026-09-07 13:08:11 +08:00
1 parent 694d250f7a
commit 0125f9019e
9 files changed
+59 -29

No files matched your search

+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: ef3a549df0a06d0c2e46a25c077b758e15cb74c3
config-catalog.zh.md: 6c1a190d4cfc1a7a352d3b5d11333dc2543a632d
config-catalog.md: 51bca4bda1f2b1b69fc8d6cbb04d545b9fd78b20
config-catalog.zh.md: 4572fcd8a9a6e5bbbbccdfb3eb7e64e9465e81c0
+1 -1
View File
@@ -2830,7 +2830,7 @@ export interface Config {
}
```
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:472`](../packages/shell/tool-pwsh-persistent/src/index.ts)
<a id="deepseek-aidsh-tool-ralph"></a>
+1 -1
View File
@@ -2832,7 +2832,7 @@ export interface Config {
}
```
来源:[`packages/shell/tool-pwsh-persistent/src/index.ts:456`](../packages/shell/tool-pwsh-persistent/src/index.ts)
来源:[`packages/shell/tool-pwsh-persistent/src/index.ts:472`](../packages/shell/tool-pwsh-persistent/src/index.ts)
<a id="deepseek-aidsh-tool-ralph"></a>
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/shell/tool-pwsh-persistent/README.md
README.md: ed5e0c340a0a9a0af53d4c9d80e2516e1a2568a8
README.zh.md: 28c700e7e5f693df48b433737d49c3f95e4d7305
README.md: a23bd1ca677d00edd83d9f07f8520d12ce2b8e6c
README.zh.md: 1eb2ae252c2baeca7dc6cb5dfdaca18e10abd93b
@@ -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)).
- **Prompt-function readiness.** The pwsh terminal backend owns prompt bootstrap and publishes the session only after its controlled `prompt` function is ready. The tool uses that existing prompt instead of submitting a second definition. Its BEL-terminated OSC marker and printable `dsh> ` tail provide the fast readiness path; the silence tier still settles a completed command when the host cannot accept that prompt evidence. A model redefinition of `prompt` also degrades readiness to the silence tier.
- **Prompt-function readiness.** The tool installs its own `prompt` function that prints a BEL-terminated OSC marker plus a printable prompt; the OSC marker carries the last exit code and the printable prompt settles every command, so a model redefinition of `prompt` degrades readiness to the silence tier.
- **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.
@@ -81,12 +81,12 @@ This section explains the design decisions behind the tool and points at the cod
| File | Role |
|---|---|
| [`src/index.ts`](src/index.ts) | Plugin entry: shell registry, command wrapping, scrollback polling, extraction and rendering |
| [`src/index.ts`](src/index.ts) | Plugin entry: shell registry, prompt setup, command wrapping, scrollback polling, extraction and rendering |
| — | No runtime invariant companion is published; the adapter's private owner-to-shell cache has no observable event or data relation. Lifecycle tests prove its cleanup without adding a public API solely for an invariant. |
### Command flow
A first command spawns the shell through `ctx.terminals.spawn` and receives it only after the selected terminal backend has completed prompt bootstrap. 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 or a completed prompt appears, extracts the span, strips the echoed wrapper and prompts, 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`, installs the `prompt` override, and waits for 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 or a completed prompt appears, extracts the span, strips the echoed wrapper and prompts, and renders it with any status marker. A timeout aborts the deadline, captures the partial output, and resets the shell.
</details>
@@ -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))。
- **prompt 函数就绪。** pwsh terminal 后端负责 prompt 引导,并且只在其受控 `prompt` 函数就绪后发布会话。工具直接使用现有 prompt,不再提交第二次定义。BEL 结尾的 OSC 标记与可打印的 `dsh> ` 尾部提供快速就绪路径;当宿主无法接受该 prompt 证据时,静默层级仍会结算已完成的命令。模型重定义 `prompt` 也会把就绪降级到静默层级。
- **prompt 函数就绪。** 工具安装自己的 `prompt` 函数,打印 BEL 结尾的 OSC 标记加可打印提示词;OSC 标记携带最后的退出码,可打印提示词让每条命令都能结算,因此模型重定义 `prompt` 会把就绪降级到静默层级。
- **PSReadLine 回显靠锚定剥离。** PowerShell 会把提交的输入渲染回流中;标记锚定提取与包装源码剥离移除回显,而跨终端宽度换行的包装可能在部分输出结果中留下部分回显。
- **重置,而非修复。** 任何不确定状态——显式 `exit`、超时、发送失败、中止——都会关闭 shell 并让下一次调用从全新状态开始。
@@ -81,12 +81,12 @@ kind: "package-reference"
| 文件 | 职责 |
|---|---|
| [`src/index.ts`](src/index.ts) | 插件入口:shell 注册表、命令包装、scrollback 轮询、提取与渲染 |
| [`src/index.ts`](src/index.ts) | 插件入口:shell 注册表、prompt 设置、命令包装、scrollback 轮询、提取与渲染 |
| — | 不发布运行时不变式伴生入口;shell 复用可通过工具执行观察。 |
### 命令流程
首条命令通过 `ctx.terminals.spawn` 生成 shell,并且只在所选 terminal 后端完成 prompt 引导后取得会话。随后每条命令都包装成一行物理文本——`Write-Output` 起始标记、用反引号转义进双引号字符串的命令体、`Write-Output` 结束标记加退出状态——因此 PSReadLine 对换行包装的回显无法伪造完成。工具以 1,000 行一页轮询 scrollback,直到出现结束标记或完成的提示词,提取区间、剥离回显的包装与提示词,并连同任何状态标记一起渲染。超时会中止截止时间、捕获部分输出并重置 shell。
首条命令通过 `ctx.terminals.spawn` 生成 shell,安装 `prompt` 覆盖,并等待就绪。随后每条命令都包装成一行物理文本——`Write-Output` 起始标记、用反引号转义进双引号字符串的命令体、`Write-Output` 结束标记加退出状态——因此 PSReadLine 对换行包装的回显无法伪造完成。工具以 1,000 行一页轮询 scrollback,直到出现结束标记或完成的提示词,提取区间、剥离回显的包装与提示词,并连同任何状态标记一起渲染。超时会中止截止时间、捕获部分输出并重置 shell。
</details>
@@ -17,7 +17,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
const TRUNCATED_MESSAGE = '<response clipped><NOTE>To save on context only part of this file has been shown to you. You should retry this tool after you have searched inside the file with Select-String in order to find the line numbers of what you are looking for.</NOTE>'
const LOST_PREFIX_MESSAGE = '<response clipped><NOTE>The beginning of this command output was dropped by the terminal scrollback limit. The following text is the earliest retained output.</NOTE>\n'
const SHELL_RESET_MESSAGE = 'The persistent pwsh shell was reset; the next pwsh call starts from the workspace with a fresh current directory and environment.'
const SHELL_PROMPT = 'dsh> '
const SHELL_PROMPT = '__DSH_PERSISTENT_PWSH_PROMPT__ '
const TIMEOUT_CODE = 'PERSISTENT_PWSH_TIMEOUT'
// One page is enough to find a just-emitted completion marker; the full
// scrollback is assembled only when a command settles or needs partial output.
@@ -252,6 +252,15 @@ async function respondToSessionExit(
].filter(part => part.length > 0).join('\n')
}
/**
* The pwsh prompt function that overrides the backend bootstrap value with
* this tool's own prompt. `[char]27`/`[char]7` build the OSC bytes at runtime
* because raw ESC characters in submitted input are unreliable under
* PSReadLine.
*/
const PWSH_PROMPT_SETUP =
"function prompt { [Console]::Write([char]27 + ']133;D;' + [int]$LASTEXITCODE + [char]7); '" + SHELL_PROMPT + "' }"
function persistentShells(ctx: Context, config: ResolvedConfig): PersistentShells {
const pending = new WeakMap<Agent, Promise<TerminalSessionId>>()
const live = new Map<Agent, TerminalSessionId>()
@@ -298,8 +307,15 @@ function persistentShells(ctx: Context, config: ResolvedConfig): PersistentShell
live.delete(owner)
}, 'tool-pwsh-persistent owner cache cleanup')
}
// The selected terminal backend owns bootstrap and publishes only a
// ready session.
const setup = ctx.terminals.startSend(owner, spawned.sessionId, {
text: PWSH_PROMPT_SETUP,
submit: true,
signal: combinedSignal,
})
const result = await setup.done
if (result.sessionStatus.kind === 'exited' || result.waitReason === 'timeout') {
throw new Error('persistent pwsh shell did not accept initialization')
}
return spawned.sessionId
} catch (error: unknown) {
await reset(owner, 'persistent pwsh initialization failed')
@@ -150,10 +150,7 @@ describe.skipIf(!hasPwsh)('persistent pwsh through a real cordis.yml Loader comp
})
expect(context.tools.schemas().map(schema => schema.name)).toEqual(['pwsh'])
expect(text(await execute(
'state',
'$env:KEEP = "loader"; New-Item -ItemType Directory -Force -Path nested | Out-Null; Set-Location nested',
))).toBe('')
await execute('state', '$env:KEEP = "loader"; New-Item -ItemType Directory -Force -Path nested | Out-Null; Set-Location nested')
const observed = text(await execute('observe', 'Write-Output "cwd=$PWD keep=$env:KEEP"'))
expect(observed).toContain(`cwd=${join(root, 'nested')} keep=loader`)
expect(observed).not.toContain('DSH_PERSISTENT_PWSH')
@@ -91,6 +91,8 @@ type StubMode =
| 'torn-status'
| 'finish-torn-status'
| 'end-only'
| 'init-exit'
| 'init-timeout'
| 'spawn-error'
| 'send-error'
| 'prompt-after-idle'
@@ -105,14 +107,13 @@ const START_PATTERN = /__DSH_PERSISTENT_PWSH_START_[^_]+(?:-[^_]+)*__/
const END_PATTERN = /__DSH_PERSISTENT_PWSH_END_[^:]+:/
class StubTerminalSession implements TerminalBackendSession {
readonly motd = 'dsh> '
readonly motd = '__DSH_PERSISTENT_PWSH_PROMPT__ '
readonly pid = 123
statusValue: TerminalSessionStatus = { kind: 'running' }
scrollback = this.motd
closed: string[] = []
mode: StubMode
sends = 0
requests: TerminalSendRequest[] = []
pendingText = ''
historyTruncated = false
throwOnSend = false
@@ -123,7 +124,16 @@ class StubTerminalSession implements TerminalBackendSession {
startSend(request: TerminalSendRequest): TerminalSendOperation {
this.sends += 1
this.requests.push(request)
if (request.text.startsWith('function prompt')) {
if (this.mode === 'init-exit') {
this.statusValue = { kind: 'exited', exitCode: 1, signal: null }
return this.operation(Promise.resolve(this.result('', 'session_exit')))
}
if (this.mode === 'init-timeout') {
return this.operation(Promise.resolve(this.result('', 'timeout')))
}
return this.operation(Promise.resolve(this.result(this.motd, 'stdin_read')))
}
if (this.mode === 'send-error') throw new Error('stub send failed')
if (this.throwOnSend) throw new Error('PTY session has exited')
if (this.mode === 'wait-for-abort' || this.mode === 'end-on-abort') {
@@ -334,9 +344,7 @@ describe('tool-pwsh-persistent', () => {
expect(text(await call(ctx, owner, 'Write-Output one'))).toBe('hello from stub')
expect(text(await call(ctx, owner, 'Write-Output two'))).toBe('hello from stub')
expect(stub.sessions).toHaveLength(1)
expect(stub.sessions[0]?.sends).toBe(2)
expect(stub.sessions[0]?.requests[0]?.text).toContain('__DSH_PERSISTENT_PWSH_START_')
expect(stub.sessions[0]?.requests[0]?.text).not.toContain('function prompt')
expect(stub.sessions[0]?.sends).toBe(3)
const ownerWithoutCwd = agent(ctx, undefined)
expect(text(await call(ctx, ownerWithoutCwd, 'pwd'))).toBe('hello from stub')
@@ -358,7 +366,7 @@ describe('tool-pwsh-persistent', () => {
expect(result).not.toContain('Invoke-Expression')
})
it('preserves command output that equals the controlled shell prompt', async () => {
it('preserves command output that equals the private shell prompt', async () => {
const { ctx, owner, stub } = await setup({ backendType: 'stub' })
await call(ctx, owner, 'warm up')
const session = stub.sessions[0]!
@@ -400,13 +408,13 @@ describe('tool-pwsh-persistent', () => {
session.mode = 'prompt-only'
const promptFallback = text(await call(ctx, owner, 'bad {'))
expect(promptFallback).toContain('pwsh: synt')
expect(promptFallback).not.toContain(session.motd)
expect(promptFallback).not.toContain('DSH_PERSISTENT_PWSH_PROMPT')
session.mode = 'prompt-crlf'
session.scrollback = ''
const crlfPromptFallback = text(await call(ctx, owner, 'bad {'))
expect(crlfPromptFallback).toContain('pwsh: synt')
expect(crlfPromptFallback).not.toContain(session.motd)
expect(crlfPromptFallback).not.toContain('DSH_PERSISTENT_PWSH_PROMPT')
session.mode = 'end-only'
session.scrollback = ''
@@ -501,7 +509,7 @@ describe('tool-pwsh-persistent', () => {
const result = text(await call(ctx, owner, 'bad {'))
expect(result).toContain('partial syntax output')
expect(result).toContain('pwsh: syntax error')
expect(result).not.toContain(session.motd)
expect(result).not.toContain('DSH_PERSISTENT_PWSH_PROMPT')
expect(result).not.toContain('DSH_PERSISTENT_PWSH_START')
})
@@ -546,6 +554,15 @@ describe('tool-pwsh-persistent', () => {
},
)
it.each(['init-exit', 'init-timeout'] as const)(
'fails initialization and closes the unusable shell for %s',
async (mode) => {
const { ctx, owner, stub } = await setup({ backendType: 'stub' }, mode)
expect((await call(ctx, owner, 'pwd')).isError).toBe(true)
expect(stub.sessions[0]?.closed).toContain('persistent pwsh initialization failed')
},
)
it('clears a failed spawn without trying to close an unpublished shell', async () => {
const { ctx, owner, stub } = await setup({ backendType: 'stub' }, 'spawn-error')
expect((await call(ctx, owner, 'pwd')).isError).toBe(true)