@hyzyn/dsh-tty 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,7 @@
15
15
  * 用法:pnpm --filter @hyzyn/dsh-tty windows-smoke
16
16
  * 退出码:0 = 全部 PASS 或跳过,1 = 任一 FAIL。
17
17
  */
18
+ import { existsSync } from 'node:fs'
18
19
  import { Context } from '@deepseek-ai/cordis'
19
20
  import WebServerRuntime from '@deepseek-ai/dsh-host-webserver'
20
21
  import { LocalSubprocessRuntime } from '@deepseek-ai/dsh-subprocess-local'
@@ -85,6 +86,8 @@ process.on('exit', (code) => {
85
86
  const RESULTS = []
86
87
  function pass(label) { RESULTS.push(['PASS', label]); console.log(' ✔ PASS ' + label) }
87
88
  function fail(label, detail) { RESULTS.push(['FAIL', label, detail]); console.error(' ✘ FAIL ' + label + (detail ? ' — ' + detail : '')) }
89
+ /** SKIP 单列一档:**不许混进 PASS 计数**,否则「跳过」会伪装成 12/12(W8 在本机没有 PowerShell 时走这条)。 */
90
+ function skip(label) { RESULTS.push(['SKIP', label]); console.log(' ⏭ SKIP ' + label) }
88
91
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
89
92
 
90
93
  /** 简易 ws 会话客户端(与 integration.mjs 同形,只保留本脚本要用的部分)。 */
@@ -142,6 +145,22 @@ const ENTER = '\r\n'
142
145
  */
143
146
  const TOOLS = new Map()
144
147
 
148
+ /** 本机可用的 PowerShell(优先 PowerShell 7,再 Windows PowerShell 5.1);都没有则 null(W8 跳过)。 */
149
+ function resolvePowerShell() {
150
+ const candidates = [
151
+ 'C:/Program Files/PowerShell/7/pwsh.exe',
152
+ 'C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe',
153
+ ]
154
+ for (const candidate of candidates) {
155
+ try {
156
+ if (existsSync(candidate)) return candidate
157
+ } catch {
158
+ /* 权限等异常一律当「没有」 */
159
+ }
160
+ }
161
+ return null
162
+ }
163
+
145
164
  async function run() {
146
165
  const app = new Context()
147
166
  const wsFiber = app.plugin(WebServerRuntime, { host: '127.0.0.1', port: 0 })
@@ -341,6 +360,77 @@ async function run() {
341
360
  }
342
361
  }
343
362
  tracer.leave('W7 退出后的只读保留')
363
+
364
+ // W8:PowerShell 家族的 shell 集成(OSC 133/7)必须真的从 ConPTY 里穿出来
365
+ //
366
+ // 为什么在这一档而不只在单测里:单测只能断言「spawn 计划长什么样」,而这条链路真正
367
+ // 容易断的地方在 ConPTY —— 标记是 conhost **直通转发**的旁路(渲染路径之外),被吞掉
368
+ // 或晚到只有真机才看得出。另起一个 app 实例是为了改 shell 配置:W1–W7 必须保持真实的
369
+ // `%COMSPEC%` 链路(它们钉的正是「没有 POSIX 包装层、跑的就是 cmd.exe」)。
370
+ //
371
+ // **不再按 CI 跳过**(2026-10-13):2026-10-12 那版「CI 上默认跳过 W8」是**绕开**当时未定性的
372
+ // `0xC0000374`,而根因已经定位并修好(同一个 PTY 被两个调用方各原生 kill 一次 → 见
373
+ // packages/tty/DEFECTS.md 的 D100 / ROADMAP 的 2026-10-13 一节)。装上闸门后真机(Win11 ARM64)
374
+ // 强制带 W8 连跑 6 轮全部 `12/12 PASS`。**动这里之前先读那条**:它记着当时的崩点分布与 A/B。
375
+ console.log('\n[W8] PowerShell + shell 集成(OSC 133/7)')
376
+ tracer.enter('W8 PowerShell 集成')
377
+ try {
378
+ const shellPath = resolvePowerShell()
379
+ if (shellPath === null) {
380
+ // 本机没有 PowerShell:这是**真跳过**(不是「按设计绕过」),如实进 SKIP 一档——
381
+ // 分母扣掉它、汇总行点名,不许装成 12/12。
382
+ skip('W8 PowerShell shell 集成(本机没找到 pwsh.exe / powershell.exe)')
383
+ } else {
384
+ const app2 = new Context()
385
+ const ws2Fiber = app2.plugin(WebServerRuntime, { host: '127.0.0.1', port: 0 })
386
+ const sub2Fiber = app2.plugin(LocalSubprocessRuntime)
387
+ const stub2Fiber = app2.plugin({
388
+ name: 'windows-smoke-stub-2',
389
+ apply: (ctx) => {
390
+ ctx.provide('settings', { describe: () => [], update: async () => {}, configure: () => () => {} })
391
+ ctx.provide('tools', { register: () => () => {} })
392
+ },
393
+ })
394
+ await stub2Fiber.await()
395
+ const plugin2Fiber = app2.plugin({ name, inject, apply }, { shell: shellPath, term: 'xterm-256color', colorTerm: 'truecolor' })
396
+ await ws2Fiber.await()
397
+ await sub2Fiber.await()
398
+ await plugin2Fiber.await()
399
+ await sleep(400)
400
+
401
+ const s3 = openSession(app2.webServer.port)
402
+ await s3.open()
403
+ s3.client.send(JSON.stringify({ t: 'spawn', cols: 100, rows: 30 }))
404
+ await s3.waitFor(() => s3.state.ready !== null, 25000, 'PowerShell ready')
405
+ // 桩装好 prompt 就发首个 A 标记:到了 = `-File` 桩真的被载入并生效
406
+ await s3.waitFor(() => /\]133;A/.test(s3.state.text), 20000, 'PowerShell 首个 A 标记')
407
+ pass('W8a PowerShell 桩生效(ConPTY 里收到 OSC 133;A)')
408
+
409
+ // `$env:OS` 由 PowerShell 展开(回显里只有字面量),与 W2/W3 同一条判据
410
+ s3.client.send(JSON.stringify({ t: 'input', d: `echo IT_PS_$env:OS${ENTER}` }))
411
+ await s3.waitFor(
412
+ () => /IT_PS_Windows_NT/.test(s3.state.text) && /\]133;D;0/.test(s3.state.text),
413
+ 25000,
414
+ 'PowerShell 的 B/D 与命令输出',
415
+ )
416
+ pass('W8b PowerShell 发全 B/D(退出码 0),且命令真的被执行')
417
+
418
+ s3.client.send(JSON.stringify({ t: 'kill' }))
419
+ try {
420
+ await s3.waitFor(() => s3.state.exited !== null, 15000, 'PowerShell exit')
421
+ } catch {
422
+ /* 没收到 exit 也不影响上面两条结论 */
423
+ }
424
+ try {
425
+ s3.client.close()
426
+ } catch {
427
+ /* 已关闭 */
428
+ }
429
+ }
430
+ } catch (error) {
431
+ fail('W8 PowerShell shell 集成', error.message)
432
+ }
433
+ tracer.leave('W8 PowerShell 集成')
344
434
  }
345
435
 
346
436
  await run()
@@ -348,9 +438,14 @@ await run()
348
438
  // **异常终止**用的,断言失败被说成「崩溃」只会误导。
349
439
  exitingNormally = true
350
440
  const failed = RESULTS.filter((row) => row[0] === 'FAIL')
351
- console.log('\n==== Windows 冒烟:' + String(RESULTS.length - failed.length) + '/' + String(RESULTS.length) + ' PASS ====')
441
+ const skipped = RESULTS.filter((row) => row[0] === 'SKIP')
442
+ /** 分母扣掉 SKIP:**跳过的档不许算进 PASS 数**(否则「跳过」看起来像全过,正是要防的假绿)。 */
443
+ const counted = RESULTS.length - skipped.length
444
+ const summary = String(counted - failed.length) + '/' + String(counted) + ' PASS' +
445
+ (skipped.length > 0 ? '(另 ' + String(skipped.length) + ' 项 SKIP:' + skipped.map((row) => row[1]).join(';') + ')' : '')
446
+ console.log('\n==== Windows 冒烟:' + summary + ' ====')
352
447
  // 同步再打一份结论:万一下面的 flush 屏障之前进程就没了(原生崩溃),至少这一行一定在。
353
- traceSync(`Windows 冒烟结论:${String(RESULTS.length - failed.length)}/${String(RESULTS.length)} PASS`)
448
+ traceSync(`Windows 冒烟结论:${summary}`)
354
449
  for (const row of failed) console.error(' ✘ ' + row[1] + (row[2] ? ' — ' + row[2] : ''))
355
450
  clearTimeout(watchdog)
356
451
  /**