chore: remove unrelated PTY test repair from agent refactor

This commit is contained in:
_Kerman
2026-09-08 20:19:09 +08:00
parent 5045631a9a
commit d5e6b4e2b2
4 changed files with 17 additions and 20 deletions
@@ -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 .agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md
2026-07-16-persistent-pty-sessions.md: 834f0e003350b97293d9d93ae0d8dd5259cfe1c1
2026-07-16-persistent-pty-sessions.zh.md: fea4d0d025de71f5e753e5ed8e50065746c99a97
2026-07-16-persistent-pty-sessions.md: d62587ced78be75de121fe907ca258d07a2830a1
2026-07-16-persistent-pty-sessions.zh.md: a26c3ed852b3a18842585e19ecb2a6994f8d9117
@@ -158,8 +158,6 @@ The package ships concise tool guidance explaining persistent state, owner isola
## Verification
The disowned-descendant fixture's parent shell ignores `SIGTERM` before fork and publishes the child PID, so neither identity nor signal disposition depends on child startup. A file barrier also holds the descendant before its workload while natural shell exit and explicit close must still reach quiescence.
- Per-file coverage pins owner fencing, concurrent reservations, cancellation during pre-write inspection, unpublished-spawn cancellation and awaited teardown, sandbox-mode change rejection, retriable lifecycle cleanup, readiness tiers, rejection of pre-write stdin waits and delayed earlier prompts, the configured handoff grace holding the idle fallback past one poll and its rejection below `pollIntervalMs`, sanitizer carry state, complete UTF-8 bounds, task integration, schemas, and exact render intents.
- Subprocess process fixtures cover non-leader and non-main-thread stdin waits, thread-local fd tables, the `/dev/tty` alias, supported kernel ABIs under user-mode emulation, rejection of fd 0 backed by a pipe, zombie quiescence, unreadable process state, unsupported architectures, and other false-positive rejection; macOS inspector logic is injected into the same unit suite.
- Real `node-pty` and PTY-consumer tests jointly exercise shell state, controlling-terminal input through `/dev/tty`, the exact attribution when process syscalls are readable, its bounded idle fallback when host policy denies them, shared sandbox policy, environment scrubbing, raw-mode foreground `SIGINT`, a TERM-ignoring descendant, and immediate post-disposal quiescence. The Linux native smoke keeps the PTY PID, session leader, controlling terminal, foreground `inputWaiting`, and readiness while a reparented `setsid` descendant remains owned by the scope; fallback suites retain identity-fenced observational cleanup coverage.
@@ -158,8 +158,6 @@ plugins:
## 验证
脱离作业表的后代进程由父 shell 在 fork 前设置忽略 `SIGTERM`,并由父 shell 发布其 PID,因此身份与信号处置都不依赖子进程启动。文件屏障还会将后代进程阻挡在工作负载之前,此时 shell 自然退出与显式关闭仍必须使其完全停稳。
- 逐文件覆盖测试锁定了 owner 隔离、并发预留、写入前检查期间的取消、未发布 spawn 的取消与等待式 teardown、沙箱模式变更拒绝、可重试的生命周期清理、就绪层级、对写入前 stdin 等待与延迟到达的先前 prompt 的拒绝、配置化交接宽限把 idle fallback 顶过一次轮询以及低于 `pollIntervalMs` 时的拒绝、sanitizer carry state、完整 UTF-8 结果上限、task 集成、schema 和精确 render intent。
- 子进程 fixture(测试前置数据)覆盖非 leader 与非主线程的 stdin 等待、线程本地 fd 表、`/dev/tty` 别名、用户态模拟下受支持的内核 ABI、拒绝把指向管道的 fd 0 当作终端输入、僵尸进程完全停稳、不可读进程状态、不支持的架构和其他误报拒绝;同一单元测试套件通过注入覆盖 macOS 检查器逻辑。
- 真实 `node-pty` 与 PTY 消费方测试共同覆盖 shell 状态、通过 `/dev/tty` 读取控制终端输入、进程 syscall 可读时的精确归因、宿主策略拒绝读取时的有界 idle fallback、共享沙箱策略、环境清洗、raw mode 前台 `SIGINT`、忽略 `SIGTERM` 的后代进程,以及 dispose 返回后立即完全停稳。Linux native 冒烟测试会在一个 reparent 的 `setsid` 后代仍由 scope 拥有时,保持 PTY PID、session leader、控制终端、前台 `inputWaiting` 与 readinessfallback 测试套件继续覆盖带身份围栏的观察式清理。
@@ -1,4 +1,4 @@
import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -238,24 +238,27 @@ describe.skipIf(process.platform === 'win32')('terminal-bash real shell', () =>
expect(() => process.kill(pid, 0)).toThrow()
}, 10_000)
it.each([false, true])('quiesces a disowned same-session descendant after the shell exits naturally (hold descendant: %s)', async (holdDescendant) => {
it('quiesces a disowned same-session descendant after the shell exits naturally', async () => {
const { ctx, root, agent } = await harness('danger-full-access')
const created = await ctx.terminals.spawn(agent, { type: 'shell' })
const releaseFile = join(root, 'release-descendant')
// Keep the descendant before its workload without delaying the parent's PID publication.
const barrier = holdDescendant ? 'while [ ! -e "$1" ]; do sleep 0.01; done; ' : ''
const pidFile = join(root, 'disowned.pid')
let pid: number | undefined
try {
// Ignore TERM before forking so even an unscheduled child inherits it.
const background = ctx.terminals.startSend(agent, created.sessionId, {
text: `trap "" TERM; sh -c '${barrier}exec sleep 60' dsh "${releaseFile}" & disown; trap - TERM; echo CHILD=$!`,
text: `sh -c 'trap "" TERM; printf "%s" "$$" > "$1"; sleep 60' dsh "${pidFile}" & disown`,
submit: true,
})
const result = await background.done
const childPid = Number(/CHILD=(\d+)/.exec(result.viewport)?.[1])
if (childPid > 0) pid = childPid
expectReadyForNextSend(result.waitReason)
expect(childPid, result.viewport).toBeGreaterThan(0)
await background.done
const pidDeadline = Date.now() + 2_000
let childPid = 0
while (childPid === 0 && Date.now() < pidDeadline) {
if (existsSync(pidFile)) childPid = Number(readFileSync(pidFile, 'utf8'))
if (childPid > 0) break
await new Promise(resolve => setTimeout(resolve, 10))
}
expect(existsSync(pidFile), ctx.terminals.read(agent, created.sessionId, { offset: 0, count: 100 }).text).toBe(true)
expect(childPid).toBeGreaterThan(0)
pid = childPid
expect(() => process.kill(childPid, 0)).not.toThrow()
await ctx.terminals.startSend(agent, created.sessionId, { text: 'exit', submit: true }).done
const deadline = Date.now() + 2_000
@@ -272,8 +275,6 @@ describe.skipIf(process.platform === 'win32')('terminal-bash real shell', () =>
} catch (_alreadyReaped) {
// Product cleanup is the expected path; this only contains a failed regression.
}
const disposedPid = pid
await expect.poll(() => processIsRunning(disposedPid)).toBe(false)
}
}
}, 10_000)