diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml index 618e9e2221..65875fa9f0 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.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/process/2026-08-08-native-windows-pull-request-ci.md -2026-08-08-native-windows-pull-request-ci.md: 16d1aaea0df14e1d8804564090860e20da7054c0 -2026-08-08-native-windows-pull-request-ci.zh.md: e99cb4ab0fec62d8ba84955a570db58e7dbf88c2 +2026-08-08-native-windows-pull-request-ci.md: 12334e8b4a3397dd3fb13597097d39ef7e793835 +2026-08-08-native-windows-pull-request-ci.zh.md: 728c0852bd14994a0ed6051edd981d3a63eec908 diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md index 16d1aaea0d..12334e8b4a 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md @@ -48,6 +48,8 @@ The follow-up exact-head run passed all 10,937 instrumented tests and narrowed t The next run reached the repaired branches but one real PowerShell executor composition case timed out at Vitest's five-second ceiling before the coverage report. That fixture had configured the product timeout to the same five seconds as the test timeout, leaving no budget for the executor to return either its owned result or its owned timeout classification under instrumentation. The command now has a ten-second product budget and the integration test a fifteen-second ceiling; its exit code, output, and resolved-timeout assertions are unchanged. +The following exact-head run passed all 10,938 instrumented tests and isolated four remaining locations whose existing fixtures depended on host scheduling. The E2B service fixture now injects and observes an immediate automatic terminal-release rejection before proving disposal retries it. The pi-ai discovery fixture drives cancellation from a controlled response body read instead of racing a local socket timer, and the persistent-bash fixture makes an incremental PTY delta the only recoverable output before asserting the rendered fallback. These cases exercise the supported branches directly on every host; the coverage inventory and denominator remain unchanged. + POSIX mode bits, chmod-based unreadability, and chmod-based writer-lock refusal do not exist as equivalent Windows facilities. Those acceptance cases remain enforced on POSIX and are skipped on Windows; content, atomic replacement, symlink safety, rollback and recovery through platform-independent filesystem conflicts, and native Windows long-path behavior remain covered. Only intrinsically POSIX source arms carry narrow, explained denominator ignores; no source file or platform-independent branch is excluded from Windows coverage to accommodate these differences. Wine-only infrastructure is absent from the supported workflow: there is no apt-cache producer, compatibility script, hoisted snapshot install, Windows Node download, or local `check:windows-wine` command. The [archived Wine experiment](../../archived/process/2026-07-27-wine-windows-gates-experiment.md) remains historical evidence for its measured latency and fidelity trade-offs, not a current execution path. diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md index e99cb4ab0f..728c0852bd 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md @@ -48,6 +48,8 @@ Status: implemented 再下一次运行已到达修复后的分支,但一项真实 PowerShell executor 组合用例会在生成覆盖率报告前,触及 Vitest 的 5 秒上限。该 fixture 把产品超时和测试超时都配置成了同样的 5 秒;插桩环境下,executor 因而没有余量返回其自有结果或自有超时分类。现在,命令的产品预算为 10 秒,集成测试上限为 15 秒;退出码、输出和解析后超时值的断言均未改变。 +随后的分支头精确运行通过了全部 10,938 项插桩测试,并隔离出 4 个现有 fixture 依赖宿主调度的剩余位置。E2B 服务 fixture 现在会注入并观察一次立即发生的终端自动释放拒绝,再证明服务释放会重试该终端。pi-ai 发现 fixture 改为从受控响应 body 的读取过程触发取消,不再与本地 socket 定时器竞速;persistent-bash fixture 则让 PTY 增量片段成为唯一可恢复输出,再断言渲染后的回退结果。这些用例会在每种宿主上直接执行受支持分支;覆盖率清单与分母均未改变。 + POSIX 模式位、基于 chmod 的不可读状态和基于 chmod 的 writer lock 拒绝在 Windows 上没有等价机制。这些验收场景继续在 POSIX 上强制执行,并在 Windows 上跳过;内容、原子替换、符号链接安全、通过平台无关文件系统冲突验证的回滚与恢复,以及原生 Windows 长路径行为仍保有覆盖。只有本质上属于 POSIX 的源码分支带有窄范围且说明明确的分母忽略;没有任何源码文件或平台无关分支为适应这些差异而从 Windows 覆盖率中排除。 受支持的工作流不含 Wine 专属基础设施:不存在 apt 缓存生产者、兼容性脚本、对仓库快照执行的 hoisted 安装、Windows Node 下载或本地 `check:windows-wine` 命令。[已归档的 Wine 实验](../../archived/process/2026-07-27-wine-windows-gates-experiment.md)仍作为其实测延迟与保真度取舍的历史证据,而非当前执行路径。 diff --git a/packages/e2b/subprocess-e2b/tests/terminal.spec.ts b/packages/e2b/subprocess-e2b/tests/terminal.spec.ts index 5472b2cfa7..26762fcda8 100644 --- a/packages/e2b/subprocess-e2b/tests/terminal.spec.ts +++ b/packages/e2b/subprocess-e2b/tests/terminal.spec.ts @@ -911,16 +911,17 @@ describe('E2B subprocess terminal service', () => { it('contains a failed automatic terminal release until service disposal retries it', async () => { const { fiber, fake } = await service() - fake.clearOnTerm = false - fake.clearOnKill = false - const terminal = await (fiber.ctx).subprocess.spawnTerminal(spec({ graceMs: 1 })) + fake.groups = [] + const terminal = await (fiber.ctx).subprocess.spawnTerminal(spec()) + const terminate = vi.spyOn(terminal, 'terminate') + .mockRejectedValueOnce(new Error('automatic release failed')) fake.handle.succeed(0) await terminal.done - await new Promise(resolve => setTimeout(resolve, 10)) - expect(fake.commands).toContain('kill -KILL -- -123') + await vi.waitFor(() => { expect(terminate).toHaveBeenCalledTimes(1) }) + await new Promise(resolve => setTimeout(resolve, 0)) - fake.groups = [] await fiber.dispose() - await expect(terminal.terminate()).resolves.toBeUndefined() + expect(terminate).toHaveBeenCalledTimes(2) + expect(fake.handle.disconnects).toBe(1) }) }) diff --git a/packages/llm/llm-pi-ai/tests/discovery.spec.ts b/packages/llm/llm-pi-ai/tests/discovery.spec.ts index 85221e7ca2..02b859d5f2 100644 --- a/packages/llm/llm-pi-ai/tests/discovery.spec.ts +++ b/packages/llm/llm-pi-ai/tests/discovery.spec.ts @@ -275,10 +275,28 @@ describe('draft-provider model discovery', () => { it('reports cancellation during the body read as an abort, not a raw reason', async () => { const ctx = await harness() const controller = new AbortController() - // Chunked, so the headers arrive and the cancellation lands mid-body. - const slow = await listingServer({ chunks: ['{"data":[', '{"id":"a"}'], holdOpenMs: 400 }) - const probe = ctx.llm.discoverModels('llm-pi-ai', { baseURL: slow.url, signal: controller.signal }) - setTimeout(() => { controller.abort('test cancellation') }, 40) + const bodyRead = Promise.withResolvers() + vi.stubGlobal('fetch', async (_url: string | URL, init?: RequestInit) => { + const signal = init?.signal + if (signal === undefined || signal === null) throw new Error('expected a discovery signal') + return new Response(new ReadableStream({ + pull(stream) { + bodyRead.resolve(undefined) + return new Promise((resolve) => { + signal.addEventListener('abort', () => { + stream.error(signal.reason) + resolve() + }, { once: true }) + }) + }, + })) + }) + const probe = ctx.llm.discoverModels('llm-pi-ai', { + baseURL: 'https://slow.example/v1', + signal: controller.signal, + }) + await bodyRead.promise + controller.abort('test cancellation') await expect(probe).rejects.toMatchObject({ code: 'ABORTED' }) }) diff --git a/packages/pty/tool-bash-persistent/tests/tools.spec.ts b/packages/pty/tool-bash-persistent/tests/tools.spec.ts index bcff35b2a4..28a950eacf 100644 --- a/packages/pty/tool-bash-persistent/tests/tools.spec.ts +++ b/packages/pty/tool-bash-persistent/tests/tools.spec.ts @@ -95,6 +95,7 @@ type StubMode = | 'spawn-error' | 'send-error' | 'prompt-after-idle' + | 'incremental-fallback' | 'empty-page-after-latest' | 'paged-scrollback' @@ -166,6 +167,10 @@ class StubPtySession implements PtyBackendSession { this.pendingText = '' const start = /__DSH_PERSISTENT_BASH_START_[^_]+(?:-[^_]+)*__/.exec(sent)?.[0] const end = /__DSH_PERSISTENT_BASH_END_[^:]+:/.exec(sent)?.[0] + if (this.mode === 'incremental-fallback') { + const incremental = `${start ?? ''}\nincrement\n${this.motd}` + return this.operation(Promise.resolve(this.result(this.motd, 'stdin_read')), incremental) + } if (this.mode === 'torn-status') { const output = `${start ?? ''}\nhello from stub\n${end ?? ''}` this.scrollback += output @@ -251,10 +256,10 @@ class StubPtySession implements PtyBackendSession { return { viewport, waitReason, sessionStatus: this.statusValue, truncated: false } } - private operation(done: Promise>): PtySendOperation { + private operation(done: Promise>, delta = ''): PtySendOperation { return { done, - readOutput: () => ({ delta: '', truncated: false }), + readOutput: () => ({ delta, truncated: false }), cancel: () => false, } } @@ -331,6 +336,10 @@ describe('tool-bash-persistent', () => { session.mode = 'idle-then-normal' expect(text(await call(ctx, owner, 'silent then complete'))).toContain('hello from') + session.mode = 'incremental-fallback' + session.scrollback = '' + expect(text(await call(ctx, owner, 'incremental fallback'))).toBe('increment') + session.mode = 'prompt-only' const promptFallback = text(await call(ctx, owner, 'bad {')) expect(promptFallback).toContain('bash: synt')