From d5e6b4e2b2e81cb0f790996ff13a3d1042ed87dc Mon Sep 17 00:00:00 2001 From: _Kerman Date: Tue, 8 Sep 2026 20:19:09 +0800 Subject: [PATCH] chore: remove unrelated PTY test repair from agent refactor --- ...26-07-16-persistent-pty-sessions.i18n.yaml | 4 +-- .../2026-07-16-persistent-pty-sessions.md | 2 -- .../2026-07-16-persistent-pty-sessions.zh.md | 2 -- .../terminal-bash/tests/local.spec.ts | 29 ++++++++++--------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml index 1a3fc023a5..b1b7365597 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md index 834f0e0033..d62587ced7 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md @@ -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. diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md index fea4d0d025..a26c3ed852 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md @@ -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` 与 readiness;fallback 测试套件继续覆盖带身份围栏的观察式清理。 diff --git a/packages/terminal/terminal-bash/tests/local.spec.ts b/packages/terminal/terminal-bash/tests/local.spec.ts index 1c03e5e1a9..9800a79d54 100644 --- a/packages/terminal/terminal-bash/tests/local.spec.ts +++ b/packages/terminal/terminal-bash/tests/local.spec.ts @@ -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)