@deepwhale/coding-agent 1.0.12 → 1.0.13

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.
Files changed (162) hide show
  1. package/dist/agent/agent-compaction.d.ts +74 -0
  2. package/dist/agent/agent-compaction.d.ts.map +1 -0
  3. package/dist/agent/agent-compaction.js +145 -0
  4. package/dist/agent/agent-compaction.js.map +1 -0
  5. package/dist/agent/index.d.ts +16 -0
  6. package/dist/agent/index.d.ts.map +1 -0
  7. package/dist/agent/index.js +17 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/agent/session-adapter.d.ts +177 -0
  10. package/dist/agent/session-adapter.d.ts.map +1 -0
  11. package/dist/agent/session-adapter.js +365 -0
  12. package/dist/agent/session-adapter.js.map +1 -0
  13. package/dist/agent/tool-loop.d.ts +123 -0
  14. package/dist/agent/tool-loop.d.ts.map +1 -0
  15. package/dist/agent/tool-loop.js +436 -0
  16. package/dist/agent/tool-loop.js.map +1 -0
  17. package/dist/env/load-project-env.d.ts +40 -0
  18. package/dist/env/load-project-env.d.ts.map +1 -0
  19. package/dist/env/load-project-env.js +80 -0
  20. package/dist/env/load-project-env.js.map +1 -0
  21. package/dist/index.d.ts +31 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +30 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/llm-factory.d.ts +50 -0
  26. package/dist/llm-factory.d.ts.map +1 -0
  27. package/dist/llm-factory.js +110 -0
  28. package/dist/llm-factory.js.map +1 -0
  29. package/dist/modes/index.d.ts +14 -0
  30. package/dist/modes/index.d.ts.map +1 -0
  31. package/dist/modes/index.js +14 -0
  32. package/dist/modes/index.js.map +1 -0
  33. package/dist/modes/print.d.ts +50 -0
  34. package/dist/modes/print.d.ts.map +1 -0
  35. package/dist/modes/print.js +236 -0
  36. package/dist/modes/print.js.map +1 -0
  37. package/dist/modes/rpc.d.ts +52 -0
  38. package/dist/modes/rpc.d.ts.map +1 -0
  39. package/dist/modes/rpc.js +316 -0
  40. package/dist/modes/rpc.js.map +1 -0
  41. package/dist/modes/tui.d.ts +107 -0
  42. package/dist/modes/tui.d.ts.map +1 -0
  43. package/dist/modes/tui.js +680 -0
  44. package/dist/modes/tui.js.map +1 -0
  45. package/dist/policy/args-digest.d.ts +13 -0
  46. package/dist/policy/args-digest.d.ts.map +1 -0
  47. package/dist/policy/args-digest.js +29 -0
  48. package/dist/policy/args-digest.js.map +1 -0
  49. package/dist/policy/chain.d.ts +19 -0
  50. package/dist/policy/chain.d.ts.map +1 -0
  51. package/dist/policy/chain.js +24 -0
  52. package/dist/policy/chain.js.map +1 -0
  53. package/dist/policy/index.d.ts +17 -0
  54. package/dist/policy/index.d.ts.map +1 -0
  55. package/dist/policy/index.js +16 -0
  56. package/dist/policy/index.js.map +1 -0
  57. package/dist/policy/sanitize-reason.d.ts +11 -0
  58. package/dist/policy/sanitize-reason.d.ts.map +1 -0
  59. package/dist/policy/sanitize-reason.js +24 -0
  60. package/dist/policy/sanitize-reason.js.map +1 -0
  61. package/dist/policy/static-rules.d.ts +32 -0
  62. package/dist/policy/static-rules.d.ts.map +1 -0
  63. package/dist/policy/static-rules.js +106 -0
  64. package/dist/policy/static-rules.js.map +1 -0
  65. package/dist/policy/types.d.ts +56 -0
  66. package/dist/policy/types.d.ts.map +1 -0
  67. package/dist/policy/types.js +13 -0
  68. package/dist/policy/types.js.map +1 -0
  69. package/dist/repl/repl-command-router.d.ts +78 -0
  70. package/dist/repl/repl-command-router.d.ts.map +1 -0
  71. package/dist/repl/repl-command-router.js +112 -0
  72. package/dist/repl/repl-command-router.js.map +1 -0
  73. package/dist/repl/repl-confirm.d.ts +49 -0
  74. package/dist/repl/repl-confirm.d.ts.map +1 -0
  75. package/dist/repl/repl-confirm.js +88 -0
  76. package/dist/repl/repl-confirm.js.map +1 -0
  77. package/dist/repl/repl-session.d.ts +79 -0
  78. package/dist/repl/repl-session.d.ts.map +1 -0
  79. package/dist/repl/repl-session.js +129 -0
  80. package/dist/repl/repl-session.js.map +1 -0
  81. package/dist/repl/repl-signal-coordinator.d.ts +74 -0
  82. package/dist/repl/repl-signal-coordinator.d.ts.map +1 -0
  83. package/dist/repl/repl-signal-coordinator.js +73 -0
  84. package/dist/repl/repl-signal-coordinator.js.map +1 -0
  85. package/dist/repl.d.ts +117 -0
  86. package/dist/repl.d.ts.map +1 -0
  87. package/dist/repl.js +626 -0
  88. package/dist/repl.js.map +1 -0
  89. package/dist/sandbox/docker-runner.d.ts +147 -0
  90. package/dist/sandbox/docker-runner.d.ts.map +1 -0
  91. package/dist/sandbox/docker-runner.js +426 -0
  92. package/dist/sandbox/docker-runner.js.map +1 -0
  93. package/dist/sandbox/env-gate.d.ts +28 -0
  94. package/dist/sandbox/env-gate.d.ts.map +1 -0
  95. package/dist/sandbox/env-gate.js +65 -0
  96. package/dist/sandbox/env-gate.js.map +1 -0
  97. package/dist/sandbox/local-runner.d.ts +29 -0
  98. package/dist/sandbox/local-runner.d.ts.map +1 -0
  99. package/dist/sandbox/local-runner.js +79 -0
  100. package/dist/sandbox/local-runner.js.map +1 -0
  101. package/dist/sandbox/types.d.ts +80 -0
  102. package/dist/sandbox/types.d.ts.map +1 -0
  103. package/dist/sandbox/types.js +25 -0
  104. package/dist/sandbox/types.js.map +1 -0
  105. package/dist/tools/bash.d.ts +35 -0
  106. package/dist/tools/bash.d.ts.map +1 -0
  107. package/dist/tools/bash.js +233 -0
  108. package/dist/tools/bash.js.map +1 -0
  109. package/dist/tools/edit-file.d.ts +22 -0
  110. package/dist/tools/edit-file.d.ts.map +1 -0
  111. package/dist/tools/edit-file.js +79 -0
  112. package/dist/tools/edit-file.js.map +1 -0
  113. package/dist/tools/find.d.ts +21 -0
  114. package/dist/tools/find.d.ts.map +1 -0
  115. package/dist/tools/find.js +168 -0
  116. package/dist/tools/find.js.map +1 -0
  117. package/dist/tools/grep.d.ts +19 -0
  118. package/dist/tools/grep.d.ts.map +1 -0
  119. package/dist/tools/grep.js +170 -0
  120. package/dist/tools/grep.js.map +1 -0
  121. package/dist/tools/index.d.ts +10 -0
  122. package/dist/tools/index.d.ts.map +1 -0
  123. package/dist/tools/index.js +10 -0
  124. package/dist/tools/index.js.map +1 -0
  125. package/dist/tools/read-file.d.ts +18 -0
  126. package/dist/tools/read-file.d.ts.map +1 -0
  127. package/dist/tools/read-file.js +52 -0
  128. package/dist/tools/read-file.js.map +1 -0
  129. package/dist/tools/registry.d.ts +39 -0
  130. package/dist/tools/registry.d.ts.map +1 -0
  131. package/dist/tools/registry.js +67 -0
  132. package/dist/tools/registry.js.map +1 -0
  133. package/dist/tools/write-file.d.ts +18 -0
  134. package/dist/tools/write-file.d.ts.map +1 -0
  135. package/dist/tools/write-file.js +47 -0
  136. package/dist/tools/write-file.js.map +1 -0
  137. package/dist/tui-ink-bundle.js +38587 -0
  138. package/dist/types.d.ts +89 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +5 -0
  141. package/dist/types.js.map +1 -0
  142. package/dist/util/index.d.ts +16 -0
  143. package/dist/util/index.d.ts.map +1 -0
  144. package/dist/util/index.js +16 -0
  145. package/dist/util/index.js.map +1 -0
  146. package/dist/util/tui-history.d.ts +37 -0
  147. package/dist/util/tui-history.d.ts.map +1 -0
  148. package/dist/util/tui-history.js +93 -0
  149. package/dist/util/tui-history.js.map +1 -0
  150. package/dist/verify/format-report.d.ts +57 -0
  151. package/dist/verify/format-report.d.ts.map +1 -0
  152. package/dist/verify/format-report.js +128 -0
  153. package/dist/verify/format-report.js.map +1 -0
  154. package/dist/verify/index.d.ts +8 -0
  155. package/dist/verify/index.d.ts.map +1 -0
  156. package/dist/verify/index.js +8 -0
  157. package/dist/verify/index.js.map +1 -0
  158. package/dist/verify/verify-runner.d.ts +186 -0
  159. package/dist/verify/verify-runner.d.ts.map +1 -0
  160. package/dist/verify/verify-runner.js +707 -0
  161. package/dist/verify/verify-runner.js.map +1 -0
  162. package/package.json +1 -1
@@ -0,0 +1,112 @@
1
+ /**
2
+ * REPL slash builtin command router — Sprint 1c-revive-3-D-29.1.3 (2026-06-07).
3
+ *
4
+ * 历史:
5
+ * Sprint 0.3: REPL 接 /help / /exit / exit / quit 4 个内建命令, 派发写在
6
+ * rl.on('line') 闭包内 (原 repl.ts L434-481). 后续 D-11 加 /verify (走
7
+ * runVerify + 写 verification event 到 session), D-19 加 confirm 期间 /exit
8
+ * dismiss-then-pendingExit 顺序红线, D-19.6.1 加 slash builtin guard
9
+ * (turnInFlight 时除 /exit /quit 外 deny 其它 slash builtin).
10
+ * D-29.1.3 (2026-06-07): 抽到独立文件, 跟 repl-confirm.ts / repl-signal-coordinator.ts
11
+ * / repl-session.ts 工厂形态对齐.
12
+ *
13
+ * 拍板 (D-29.1.3):
14
+ * - 入口: dispatchSlashBuiltin(line, ctx) → { handled: boolean }
15
+ * - 闭包内不持 state, 所有副作用 (out / err / writer / prompt) 通过 ctx 注入
16
+ * (跟 D-29.1.1 signal-coordinator 抽法 1:1 一致, 单测 mock ctx 即可).
17
+ * - 派发顺序保 1:1 (跟原 rl.on('line') L434-481): /help → /verify → /unknown slash.
18
+ * - 行为 1:1 等价, 5 红线 0 改: turnInFlight/lineQueue state machine 仍在 repl.ts
19
+ * (slash guard L409-418 + chat path L490+), router 只搬 dispatch 闭包.
20
+ * - 公共 API 0 改: 5 caller (src/index.ts, modes/print.ts, modes/tui.ts,
21
+ * test/repl/*, test/unit/repl-verify.test.ts) 全部走 '../repl.js' 公共 re-export
22
+ * 路径, 0 改 import.
23
+ * - 0 加新依赖, runVerify / buildSummaryAndNext / formatReport / appendVerificationEvent
24
+ * 走 ctx 注入 (test mock 用, prod 走 ./verify/index.js 真实 export).
25
+ *
26
+ * 拍板 (D-29.1.3 §out of scope):
27
+ * - 不接 turnInFlight guard — 那在 repl.ts L409-418, 跟 6afccc8 / D-19.5p 拍板
28
+ * "deny 非 /exit /quit slash builtin" 红线绑定, 不在本 router scope.
29
+ * - 不接 lineQueue defer — D-19.5 P1 "只排 chat line, 不排 slash builtin", 跟 router
30
+ * "handled: boolean" 二态返值绑定, 调用方决定 defer / deny / handle.
31
+ * - 不接 confirm 期间 /exit dismiss — D-19.5 P2-dismiss, 在 repl.ts L370-373
32
+ * confirm path, 不在本 router scope.
33
+ */
34
+ import { t } from '@deepwhale/core';
35
+ import { appendVerificationEvent } from '../agent/index.js';
36
+ import { runVerify, buildSummaryAndNext, formatReport } from '../verify/index.js';
37
+ /**
38
+ * Dispatch a slash builtin command (/help, /verify, unknown slash).
39
+ *
40
+ * Returns `handled: true` if the line was a recognized slash builtin and was
41
+ * processed. Returns `handled: false` if the line should fall through to the
42
+ * chat / lineQueue path.
43
+ *
44
+ * Order (跟原 repl.ts L434-481 1:1 保):
45
+ * 1. /help → out.write(i18n cli.builtin_help) + prompt + return
46
+ * 2. /verify → runVerify → formatReport → out.write → if (writer)
47
+ * appendVerificationEvent + try/catch wrap (1ceef94 红线)
48
+ * 3. /unknown slash (line.startsWith('/') 但不命中 /help /verify)
49
+ * → out.write(i18n cli.builtin_unknown) + prompt + return
50
+ *
51
+ * 5 红线 0 改 (跟 ship-quality-checks §7a + D-19/19.5/19.5p/6afccc8/1ceef94 一致):
52
+ * - /verify try/finally + appendVerificationEvent (1ceef94): 修法不在 try/finally,
53
+ * 而在 try/catch — verify 失败 (e.g. 命令不存在) 不污染 session, 走 stderr i18n
54
+ * 提示 + prompt 继续. appendVerificationEvent 仅 status='passed' 或 'failed' 时
55
+ * 写, 不在 catch 块里 (跟 D-11-4 拍板一致).
56
+ * - turnInFlight guard (D-19.6.1 + 6afccc8): 不在本 router, 调用方 repl.ts L409-418
57
+ * 决定是否调 dispatchSlashBuiltin.
58
+ * - confirm 期间 /exit dismiss (D-19.5 P2-dismiss): 不在本 router, repl.ts L370-373
59
+ * confirm path 处理.
60
+ * - /exit fast-path (D-19.5 P1): 不在本 router (line === '/exit' 走 turnInFlight guard
61
+ * L412 exclude, 不入 dispatch).
62
+ * - line.startsWith('/') unknown i18n (D-21.1): 跟原 L477-481 1:1 保.
63
+ */
64
+ export async function dispatchSlashBuiltin(line, ctx) {
65
+ if (line === '/help') {
66
+ ctx.out.write(`${t('cli.builtin_help')}\n`);
67
+ ctx.prompt();
68
+ return { handled: true };
69
+ }
70
+ if (line === '/verify') {
71
+ // Sprint 1c-revive-2-D-11-4 (2026-06-04): REPL `/verify` 内建命令.
72
+ // 跟 CLI `deepwhale --verify` 走同一 runVerify() — 不走 LLM / tool loop.
73
+ // 拍板 (D-11-4 review, 2026-06-04): REPL 里 /verify 走**异步** runVerify,
74
+ // 跑完打 formatReport 到 out (跟其它内建命令风格一致), 然后**写 verification
75
+ // event 到 session JSONL** (因为用户在 REPL 里跑了 verify, session 走 audit
76
+ // 轨迹, 跟 CLI 不写 session 形成差异).
77
+ // 退出: REPL 不退, 跑完回到 prompt 继续.
78
+ try {
79
+ const report = await runVerify(ctx.verifyChecks !== undefined ? { checks: ctx.verifyChecks } : {});
80
+ const filled = buildSummaryAndNext(report);
81
+ const text = formatReport({
82
+ ...report,
83
+ summary: filled.summary,
84
+ nextSuggestedAction: filled.nextSuggestedAction,
85
+ });
86
+ ctx.out.write(`${text}\n`);
87
+ if (ctx.writer) {
88
+ // 写 verification event 到 session (跟 CLI 不同: REPL 用户有 session, 应该审计)
89
+ const failedCount = report.checks.filter((c) => c.status !== 'passed').length;
90
+ await appendVerificationEvent(ctx.writer, {
91
+ status: report.overallStatus,
92
+ durationMs: report.durationMs,
93
+ commandCount: report.checks.length,
94
+ failedCount,
95
+ summary: filled.summary,
96
+ });
97
+ }
98
+ }
99
+ catch (e) {
100
+ ctx.err.write(`error: verify failed to start: ${e instanceof Error ? e.message : String(e)}\n\n`);
101
+ }
102
+ ctx.prompt();
103
+ return { handled: true };
104
+ }
105
+ if (line.startsWith('/')) {
106
+ ctx.out.write(`${t('cli.builtin_unknown', line)}\n`);
107
+ ctx.prompt();
108
+ return { handled: true };
109
+ }
110
+ return { handled: false };
111
+ }
112
+ //# sourceMappingURL=repl-command-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-command-router.js","sourceRoot":"","sources":["../../src/repl/repl-command-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,CAAC,EAAsB,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,YAAY,EAAoB,MAAM,oBAAoB,CAAA;AAenG;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAY,EACZ,GAAiB;IAEjB,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;QAC3C,GAAG,CAAC,MAAM,EAAE,CAAA;QACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC1B,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,+DAA+D;QAC/D,mEAAmE;QACnE,oEAAoE;QACpE,2DAA2D;QAC3D,kEAAkE;QAClE,8BAA8B;QAC9B,+BAA+B;QAC/B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAC5B,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CACnE,CAAA;YACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA;YAC1C,MAAM,IAAI,GAAG,YAAY,CAAC;gBACxB,GAAG,MAAM;gBACT,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;aAChD,CAAC,CAAA;YACF,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAA;YAC1B,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBACf,oEAAoE;gBACpE,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAA;gBAC7E,MAAM,uBAAuB,CAAC,GAAG,CAAC,MAAM,EAAE;oBACxC,MAAM,EAAE,MAAM,CAAC,aAAa;oBAC5B,UAAU,EAAE,MAAM,CAAC,UAAU;oBAC7B,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;oBAClC,WAAW;oBACX,OAAO,EAAE,MAAM,CAAC,OAAO;iBACxB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,GAAG,CAAC,KAAK,CACX,kCAAkC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CACnF,CAAA;QACH,CAAC;QACD,GAAG,CAAC,MAAM,EAAE,CAAA;QACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC1B,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,qBAAqB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QACpD,GAAG,CAAC,MAAM,EAAE,CAAA;QACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC1B,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;AAC3B,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * REPL y/N confirmation prompt — Sprint 1c-revive-3-D-19 (2026-06-05).
3
+ *
4
+ * 历史:
5
+ * D-15 (2026-06-05): 工厂函数 + 内部 createInterface, 用 rl.question 收 y/N.
6
+ * 留 P1 (review blocker): 同流上主 rl + 子 rl 抢同一行, 用户输 y 时主 rl
7
+ * 会把 y 当新 chat turn 启动 (实测 Node repro 确认).
8
+ * D-19 (2026-06-05): 拆掉自创 readline, 改成 "caller 喂 line" 的纯 resolver.
9
+ * 主 REPL 的 rl.on('line') 是唯一 stdin 消费者, 确认期间把 line 喂给
10
+ * pending resolver, 解析完才放行, P1 串行化彻底.
11
+ *
12
+ * 拍板 (D-19, 2026-06-05):
13
+ * - API 形状: createReplConfirm(opts) 返回的 confirm() 不再内部读 stdin,
14
+ * 而是用 offerLine(rawLine) + 内部 Promise<boolean | null> 状态机.
15
+ * - prompt 格式: caller 拼好 "Allow <tool_name>? (<reason>) [y/N]: ",
16
+ * confirm() 内部只负责 "收一行 → 解析 → resolve".
17
+ * - 输入识别: y/yes/Y/YES → true; n/no/N/NO/空/other → false; abort → null.
18
+ * - abort signal 触发立即 resolve null (dismissed).
19
+ * - 同时只能有一个 pending 确认 (REPL 串行 chat-turn 拓扑保证).
20
+ * - offerLine 二次调用 = 抛错 (caller bug, 不能丢 silent).
21
+ *
22
+ * 拍板 (D-19 §out of scope): 不接 RPC confirmedTools (D-17), 不接 user policy
23
+ * config (D-16), 不做 TUI (D-18).
24
+ */
25
+ export interface ReplConfirmOptions {
26
+ /**
27
+ * REPL 的 out (拿 prompt 字符出口). 用 NodeJS.WritableStream 是为了兼容
28
+ * startRepl 传进来的 NodeJS.WritableStream 类型 (vs node:stream Writable).
29
+ * D-19 内部只写 prompt, 不读.
30
+ */
31
+ output: NodeJS.WritableStream;
32
+ }
33
+ export interface ReplConfirmCallOptions {
34
+ /** AbortSignal — 触发时 confirm 立即 resolve null (dismissed). D-19 修 Ctrl+C 链路. */
35
+ signal?: AbortSignal;
36
+ }
37
+ export type ReplConfirm = (prompt: string, options?: ReplConfirmCallOptions) => Promise<boolean | null>;
38
+ export interface ReplConfirmController {
39
+ /** 提示用户 (写到 output, 加 [y/N]: 后缀). 内部 start 一个 pending. */
40
+ confirm: ReplConfirm;
41
+ /** REPL 主 line handler 拿到 line 后调: 若有 pending → 喂给 confirm; 若无 → false (caller 走 chat). */
42
+ offerLine: (rawLine: string) => boolean;
43
+ /** 当前是否有 in-flight 确认 (caller 用此守卫主 rl.line). */
44
+ hasPending: () => boolean;
45
+ /** 强制取消 (caller 进程退出/EOF/cleanup). */
46
+ dismiss: () => void;
47
+ }
48
+ export declare function createReplConfirm(opts: ReplConfirmOptions): ReplConfirmController;
49
+ //# sourceMappingURL=repl-confirm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-confirm.d.ts","sourceRoot":"","sources":["../../src/repl/repl-confirm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,MAAM,WAAW,GAAG,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,sBAAsB,KAC7B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AAQ7B,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,OAAO,EAAE,WAAW,CAAC;IACrB,2FAA2F;IAC3F,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,iDAAiD;IACjD,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,qBAAqB,CAqEjF"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * REPL y/N confirmation prompt — Sprint 1c-revive-3-D-19 (2026-06-05).
3
+ *
4
+ * 历史:
5
+ * D-15 (2026-06-05): 工厂函数 + 内部 createInterface, 用 rl.question 收 y/N.
6
+ * 留 P1 (review blocker): 同流上主 rl + 子 rl 抢同一行, 用户输 y 时主 rl
7
+ * 会把 y 当新 chat turn 启动 (实测 Node repro 确认).
8
+ * D-19 (2026-06-05): 拆掉自创 readline, 改成 "caller 喂 line" 的纯 resolver.
9
+ * 主 REPL 的 rl.on('line') 是唯一 stdin 消费者, 确认期间把 line 喂给
10
+ * pending resolver, 解析完才放行, P1 串行化彻底.
11
+ *
12
+ * 拍板 (D-19, 2026-06-05):
13
+ * - API 形状: createReplConfirm(opts) 返回的 confirm() 不再内部读 stdin,
14
+ * 而是用 offerLine(rawLine) + 内部 Promise<boolean | null> 状态机.
15
+ * - prompt 格式: caller 拼好 "Allow <tool_name>? (<reason>) [y/N]: ",
16
+ * confirm() 内部只负责 "收一行 → 解析 → resolve".
17
+ * - 输入识别: y/yes/Y/YES → true; n/no/N/NO/空/other → false; abort → null.
18
+ * - abort signal 触发立即 resolve null (dismissed).
19
+ * - 同时只能有一个 pending 确认 (REPL 串行 chat-turn 拓扑保证).
20
+ * - offerLine 二次调用 = 抛错 (caller bug, 不能丢 silent).
21
+ *
22
+ * 拍板 (D-19 §out of scope): 不接 RPC confirmedTools (D-17), 不接 user policy
23
+ * config (D-16), 不做 TUI (D-18).
24
+ */
25
+ export function createReplConfirm(opts) {
26
+ let pending = null;
27
+ const settle = (v) => {
28
+ if (!pending)
29
+ return;
30
+ const p = pending;
31
+ pending = null;
32
+ if (p.abortHandler && p.abortHandler !== null) {
33
+ // no-op: signal listener 已被 offerLine / abortHandler 清理
34
+ }
35
+ p.resolve(v);
36
+ };
37
+ const confirm = (prompt, callOpts) => {
38
+ if (pending) {
39
+ // 拍板 (D-19): 同时只能有一个 pending, 二次 confirm 抛错 (caller bug).
40
+ return Promise.reject(new Error('repl-confirm: confirm() called while another confirmation is in flight. ' +
41
+ 'Caller must serialize via hasPending() guard.'));
42
+ }
43
+ // 拍板 (D-19): caller 拼好 prompt, 我们只追加 [y/N]: 后缀.
44
+ const fullPrompt = `${prompt} [y/N]: `;
45
+ opts.output.write(fullPrompt);
46
+ return new Promise((resolve) => {
47
+ const p = {
48
+ prompt: fullPrompt,
49
+ resolve,
50
+ abortHandler: null,
51
+ };
52
+ pending = p;
53
+ // abort signal — 立即 resolve null (dismissed)
54
+ if (callOpts?.signal) {
55
+ if (callOpts.signal.aborted) {
56
+ settle(null);
57
+ return;
58
+ }
59
+ const handler = () => settle(null);
60
+ callOpts.signal.addEventListener('abort', handler, { once: true });
61
+ p.abortHandler = handler;
62
+ }
63
+ });
64
+ };
65
+ const offerLine = (rawLine) => {
66
+ if (!pending)
67
+ return false;
68
+ const answer = rawLine.trim().toLowerCase();
69
+ if (answer === 'y' || answer === 'yes') {
70
+ settle(true);
71
+ }
72
+ else if (answer === 'n' || answer === 'no' || answer === '') {
73
+ settle(false);
74
+ }
75
+ else {
76
+ // 拍板 (D-15 §Decision 3, D-19 沿用): other 当 N 处理 (不打扰)
77
+ settle(false);
78
+ }
79
+ return true;
80
+ };
81
+ const hasPending = () => pending !== null;
82
+ const dismiss = () => {
83
+ if (pending)
84
+ settle(null);
85
+ };
86
+ return { confirm, offerLine, hasPending, dismiss };
87
+ }
88
+ //# sourceMappingURL=repl-confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-confirm.js","sourceRoot":"","sources":["../../src/repl/repl-confirm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAsCH,MAAM,UAAU,iBAAiB,CAAC,IAAwB;IACxD,IAAI,OAAO,GAA0B,IAAI,CAAC;IAE1C,MAAM,MAAM,GAAG,CAAC,CAAiB,EAAQ,EAAE;QACzC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,CAAC,GAAG,OAAO,CAAC;QAClB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAC9C,wDAAwD;QAC1D,CAAC;QACD,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,OAAO,GAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;QAChD,IAAI,OAAO,EAAE,CAAC;YACZ,0DAA0D;YAC1D,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,KAAK,CACP,0EAA0E;gBACxE,+CAA+C,CAClD,CACF,CAAC;QACJ,CAAC;QACD,gDAAgD;QAChD,MAAM,UAAU,GAAG,GAAG,MAAM,UAAU,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAE9B,OAAO,IAAI,OAAO,CAAiB,CAAC,OAAO,EAAE,EAAE;YAC7C,MAAM,CAAC,GAAmB;gBACxB,MAAM,EAAE,UAAU;gBAClB,OAAO;gBACP,YAAY,EAAE,IAAI;aACnB,CAAC;YACF,OAAO,GAAG,CAAC,CAAC;YAEZ,6CAA6C;YAC7C,IAAI,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACrB,IAAI,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC5B,MAAM,CAAC,IAAI,CAAC,CAAC;oBACb,OAAO;gBACT,CAAC;gBACD,MAAM,OAAO,GAAG,GAAS,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACzC,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBACnE,CAAC,CAAC,YAAY,GAAG,OAAO,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,OAAe,EAAW,EAAE;QAC7C,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5C,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;aAAM,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;YAC9D,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;aAAM,CAAC;YACN,qDAAqD;YACrD,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,GAAY,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC;IAEnD,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * REPL session / usage-status utilities — Sprint 1c-revive-3-D-29.1.2 (2026-06-07).
3
+ *
4
+ * 历史:
5
+ * Sprint 1b: 把 usage 翻译成人类可读的一行 status, 写到 stderr (不污染 stdout 流式输出).
6
+ * Sprint 1c-revive-2-D-21.1 (2026-06-06, 修 cache 96%↔85% 跳变 footer 焦虑): 增 EMA
7
+ * 平滑闭包 state — appendUsageStatus 每 turn in-place 更新, formatUsageStatus 读
8
+ * ema 显示 (avg NN%). 跨 turn 累积, 闭包内 mutable, 不持久化 (session reload 后
9
+ * sampleCount 重置为 0, 避免误导 — user 看到 avg 段消失就知道 reload 过了).
10
+ *
11
+ * 拍板 (D-29.1.2):
12
+ * - 文件: `repl-session.ts` (跟 `repl-confirm.ts` / `repl-signal-coordinator.ts` 同
13
+ * kebab-case 工厂形态命名, 本次抽 usage-status 三件套, 后续 D-29.1.5 可能扩展到
14
+ * session loading / state 持久化).
15
+ * - 公共 API 0 改: repl.ts re-export `formatUsageStatus` / `appendUsageStatus` /
16
+ * `type UsageEmaState` 跟现状 1:1, src/index.ts / modes/print.ts / modes/tui.ts /
17
+ * test/unit/usage-ema.test.ts 4 caller import path 不变.
18
+ * - 行为 1:1: formatUsageStatus 输出字符串逐字保持, appendUsageStatus in-place
19
+ * update 顺序 (update ema → format → write) 保持. 旧 caller 不传 emaState 用
20
+ * 默认 `{ sampleCount: 0 }` 行为兼容 (不显示 avg 段).
21
+ * - 本文件**不**抽 session loading / state 持久化 (Sprint 1a 那段 L250-263 还在
22
+ * repl.ts 闭包内, 跟 startRepl 强耦合, 留给 D-29.1.5).
23
+ *
24
+ * 拍板 (D-29.1.2 §out of scope):
25
+ * - 不抽 SessionReader / SessionWriter 构造 (L250-251) — 跟 startRepl 闭包
26
+ * 强耦合, 抽需要拆出 loadSessionOptions factory, 收益小, 留给 D-29.1.5.
27
+ * - 不动 /verify / /help / /exit dispatcher (L437-479) — 那是 6afccc8 slash
28
+ * builtin 红线, 留给 D-29.1.4.
29
+ * - 不动 runAgentTurn 主体 (L618-753) — 留给 D-29.1.5.
30
+ */
31
+ import type { Usage } from '@deepwhale/llm';
32
+ /**
33
+ * EMA 平滑 state (Sprint 1c-revive-2-D-21.1).
34
+ *
35
+ * - `hitRateEMA`: 过去 5-turn 滚动平均 (α=0.5 平滑). undefined = cold start, 第一次
36
+ * sample 直接赋值. 跨 turn 累积, 闭包内 mutable, 不持久化.
37
+ * - `sampleCount`: 已 sample 的 turn 数. `sampleCount < 3` 时不显示 (avg) 段 (样本
38
+ * 太少, 趋势没意义). 跟 `hitRateEMA` 一起被 appendUsageStatus in-place 更新.
39
+ */
40
+ export interface UsageEmaState {
41
+ hitRateEMA?: number;
42
+ sampleCount: number;
43
+ }
44
+ /**
45
+ * 把 usage 翻译成人类可读的一行 status 字符串.
46
+ *
47
+ * 显示规则 (Hermes footer 教训应用 — 多字段同值时去冗余):
48
+ * - 满 usage (有 cached_tokens) → 完整 4 字段: cache: 90% | ¥0.05/turn | prompt 1.2k (1.1k cached)
49
+ * - 无 cached_tokens → 简化为: usage: 1.2k prompt / 200 completion
50
+ * (不打 cache% / cost, 避免没数据时显示 0% 误导)
51
+ * - 无 usage → 完全不打印 (LLM 没返 usage 时不污染 stderr)
52
+ *
53
+ * Sprint 1c 抽 pricing 到 config.toml, 此函数签名不变。
54
+ *
55
+ * Sprint 1c-revive-2-D-21.1 (2026-06-06, 修 cache 96%↔85% 跳变 footer 焦虑):
56
+ * 增 emaState 形参. 形参 hitRateEMA 是过去 5-turn 滚动平均 (α=0.5 平滑).
57
+ * 输出: `cache: 90% (avg 85%)` — per-turn 数字 + 趋势均值并列. user 视角:
58
+ * 1) 知道当 turn 真值, 2) 知道趋势 (avg 不会跟着单 turn 抖动).
59
+ * 边界:
60
+ * - sampleCount < 3: 不显示 (avg) 段 (样本太少, 趋势没意义, 不污染)
61
+ * - sampleCount >= 3: 显示 (avg NN%)
62
+ * - ema 永远存在 (闭包外, startRepl 持有), 跨 turn 累积
63
+ * 行为兼容: 旧 caller 不传 emaState 用默认 { hitRateEMA: undefined, sampleCount: 0 }
64
+ * → 行为跟改前一致 (不显示 avg 段). 现有单测 (formatUsageStatus) 不破.
65
+ */
66
+ export declare function formatUsageStatus(usage: Usage | undefined, emaState?: UsageEmaState): string | null;
67
+ /**
68
+ * Sprint 1c-revive-2-D-21.1 (2026-06-06): emaState 接受 mutable 引用 (闭包),
69
+ * 在 sampleCount < 5 用 cold-start EMA (直接赋值), 之后 α=0.5 平滑:
70
+ * newEMA = α * current + (1 - α) * oldEMA = 0.5 * current + 0.5 * oldEMA
71
+ * α=0.5 是 "等权平滑" (5-turn 半衰期 ≈ 1 turn, 快速响应 + 适度平滑).
72
+ * 数学: sampleCount=5 时, 5 turn 前的数据权重 = 0.5^5 = 3.1% (基本忘掉),
73
+ * 跟 5-turn rolling window 趋势一致, 但 EMA 实现更轻.
74
+ *
75
+ * export 出来供单测 (test/unit/usage-ema.test.ts) 验证 state machine.
76
+ * 之前是 local function, D-21.1 改成 export — 单测需要直接调它验 in-place update.
77
+ */
78
+ export declare function appendUsageStatus(usage: Usage | undefined, err: NodeJS.WritableStream, emaState: UsageEmaState): void;
79
+ //# sourceMappingURL=repl-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-session.d.ts","sourceRoot":"","sources":["../../src/repl/repl-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAKD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,QAAQ,GAAE,aAAyB,GAClC,MAAM,GAAG,IAAI,CA2Bf;AAmBD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,GAAG,EAAE,MAAM,CAAC,cAAc,EAC1B,QAAQ,EAAE,aAAa,GACtB,IAAI,CAgBN"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * REPL session / usage-status utilities — Sprint 1c-revive-3-D-29.1.2 (2026-06-07).
3
+ *
4
+ * 历史:
5
+ * Sprint 1b: 把 usage 翻译成人类可读的一行 status, 写到 stderr (不污染 stdout 流式输出).
6
+ * Sprint 1c-revive-2-D-21.1 (2026-06-06, 修 cache 96%↔85% 跳变 footer 焦虑): 增 EMA
7
+ * 平滑闭包 state — appendUsageStatus 每 turn in-place 更新, formatUsageStatus 读
8
+ * ema 显示 (avg NN%). 跨 turn 累积, 闭包内 mutable, 不持久化 (session reload 后
9
+ * sampleCount 重置为 0, 避免误导 — user 看到 avg 段消失就知道 reload 过了).
10
+ *
11
+ * 拍板 (D-29.1.2):
12
+ * - 文件: `repl-session.ts` (跟 `repl-confirm.ts` / `repl-signal-coordinator.ts` 同
13
+ * kebab-case 工厂形态命名, 本次抽 usage-status 三件套, 后续 D-29.1.5 可能扩展到
14
+ * session loading / state 持久化).
15
+ * - 公共 API 0 改: repl.ts re-export `formatUsageStatus` / `appendUsageStatus` /
16
+ * `type UsageEmaState` 跟现状 1:1, src/index.ts / modes/print.ts / modes/tui.ts /
17
+ * test/unit/usage-ema.test.ts 4 caller import path 不变.
18
+ * - 行为 1:1: formatUsageStatus 输出字符串逐字保持, appendUsageStatus in-place
19
+ * update 顺序 (update ema → format → write) 保持. 旧 caller 不传 emaState 用
20
+ * 默认 `{ sampleCount: 0 }` 行为兼容 (不显示 avg 段).
21
+ * - 本文件**不**抽 session loading / state 持久化 (Sprint 1a 那段 L250-263 还在
22
+ * repl.ts 闭包内, 跟 startRepl 强耦合, 留给 D-29.1.5).
23
+ *
24
+ * 拍板 (D-29.1.2 §out of scope):
25
+ * - 不抽 SessionReader / SessionWriter 构造 (L250-251) — 跟 startRepl 闭包
26
+ * 强耦合, 抽需要拆出 loadSessionOptions factory, 收益小, 留给 D-29.1.5.
27
+ * - 不动 /verify / /help / /exit dispatcher (L437-479) — 那是 6afccc8 slash
28
+ * builtin 红线, 留给 D-29.1.4.
29
+ * - 不动 runAgentTurn 主体 (L618-753) — 留给 D-29.1.5.
30
+ */
31
+ /** 默认 (空) EMA state — 旧 caller 不传 emaState 时用, 行为兼容. */
32
+ const EMPTY_EMA = { sampleCount: 0 };
33
+ /**
34
+ * 把 usage 翻译成人类可读的一行 status 字符串.
35
+ *
36
+ * 显示规则 (Hermes footer 教训应用 — 多字段同值时去冗余):
37
+ * - 满 usage (有 cached_tokens) → 完整 4 字段: cache: 90% | ¥0.05/turn | prompt 1.2k (1.1k cached)
38
+ * - 无 cached_tokens → 简化为: usage: 1.2k prompt / 200 completion
39
+ * (不打 cache% / cost, 避免没数据时显示 0% 误导)
40
+ * - 无 usage → 完全不打印 (LLM 没返 usage 时不污染 stderr)
41
+ *
42
+ * Sprint 1c 抽 pricing 到 config.toml, 此函数签名不变。
43
+ *
44
+ * Sprint 1c-revive-2-D-21.1 (2026-06-06, 修 cache 96%↔85% 跳变 footer 焦虑):
45
+ * 增 emaState 形参. 形参 hitRateEMA 是过去 5-turn 滚动平均 (α=0.5 平滑).
46
+ * 输出: `cache: 90% (avg 85%)` — per-turn 数字 + 趋势均值并列. user 视角:
47
+ * 1) 知道当 turn 真值, 2) 知道趋势 (avg 不会跟着单 turn 抖动).
48
+ * 边界:
49
+ * - sampleCount < 3: 不显示 (avg) 段 (样本太少, 趋势没意义, 不污染)
50
+ * - sampleCount >= 3: 显示 (avg NN%)
51
+ * - ema 永远存在 (闭包外, startRepl 持有), 跨 turn 累积
52
+ * 行为兼容: 旧 caller 不传 emaState 用默认 { hitRateEMA: undefined, sampleCount: 0 }
53
+ * → 行为跟改前一致 (不显示 avg 段). 现有单测 (formatUsageStatus) 不破.
54
+ */
55
+ export function formatUsageStatus(usage, emaState = EMPTY_EMA) {
56
+ if (usage === undefined)
57
+ return null;
58
+ const { prompt_tokens, completion_tokens } = usage;
59
+ // 无 cached_tokens: 简版
60
+ if (usage.cached_tokens === undefined) {
61
+ return `usage: ${formatTokens(prompt_tokens)} prompt / ${formatTokens(completion_tokens)} completion`;
62
+ }
63
+ // 满 usage: 完整 status
64
+ const hitRatePct = ((usage.cache_hit_rate ?? 0) * 100).toFixed(0);
65
+ const uncached = formatTokens(usage.tokens_uncached ?? prompt_tokens);
66
+ // Sprint 1c-revive-2-D-21.1: EMA 平滑尾部段. sampleCount >= 3 才显示 avg
67
+ // (样本太少趋势不稳). 不更新 caller state, 只读 (state update 在
68
+ // appendUsageStatus, 这是纯函数好测).
69
+ const avgSegment = emaState.sampleCount >= 3 && emaState.hitRateEMA !== undefined
70
+ ? ` (avg ${(emaState.hitRateEMA * 100).toFixed(0)}%)`
71
+ : '';
72
+ // Sprint 1b.5 Step 2.5 (F5 拍板, review 2026-06-03 找到): cost_turn/cost_currency 都 absent
73
+ // (R7 中间路径 / F4 保守) → 安静少显示字段, **不**显示 'cost ?'. 跟 1b 拍板 "absent 安静"
74
+ // 一致. user 视角看 'cost ?/turn' 是 'UI 不知道' 不是 '这次没算', 显示 '?' 反而误导.
75
+ if (usage.cost_turn === undefined || usage.cost_currency === undefined) {
76
+ return `cache: ${hitRatePct}%${avgSegment} | prompt ${formatTokens(prompt_tokens)} (${uncached} new)`;
77
+ }
78
+ // cost 字段齐: 读 cost_currency 决 symbol
79
+ const symbol = formatCostSymbol(usage.cost_currency);
80
+ const cost = usage.cost_turn; // narrowed by 上面 if guard (cost_turn !== undefined)
81
+ const costStr = cost < 0.01 ? `${symbol}${cost.toFixed(4)}` : `${symbol}${cost.toFixed(3)}`;
82
+ return `cache: ${hitRatePct}%${avgSegment} | ${costStr}/turn | prompt ${formatTokens(prompt_tokens)} (${uncached} new)`;
83
+ }
84
+ /** cost_currency → 显示 symbol. 不在 UI 层做汇率换算. */
85
+ function formatCostSymbol(currency) {
86
+ switch (currency) {
87
+ case 'CNY':
88
+ return '¥';
89
+ case 'USD':
90
+ return '$';
91
+ case undefined:
92
+ return '?';
93
+ }
94
+ }
95
+ function formatTokens(n) {
96
+ if (n >= 1000)
97
+ return `${(n / 1000).toFixed(1)}k`;
98
+ return String(n);
99
+ }
100
+ /**
101
+ * Sprint 1c-revive-2-D-21.1 (2026-06-06): emaState 接受 mutable 引用 (闭包),
102
+ * 在 sampleCount < 5 用 cold-start EMA (直接赋值), 之后 α=0.5 平滑:
103
+ * newEMA = α * current + (1 - α) * oldEMA = 0.5 * current + 0.5 * oldEMA
104
+ * α=0.5 是 "等权平滑" (5-turn 半衰期 ≈ 1 turn, 快速响应 + 适度平滑).
105
+ * 数学: sampleCount=5 时, 5 turn 前的数据权重 = 0.5^5 = 3.1% (基本忘掉),
106
+ * 跟 5-turn rolling window 趋势一致, 但 EMA 实现更轻.
107
+ *
108
+ * export 出来供单测 (test/unit/usage-ema.test.ts) 验证 state machine.
109
+ * 之前是 local function, D-21.1 改成 export — 单测需要直接调它验 in-place update.
110
+ */
111
+ export function appendUsageStatus(usage, err, emaState) {
112
+ // 同步更新 EMA state (in-place). 调 formatUsageStatus 之前先 update,
113
+ // 防止 "刚 sample 1 个, display 当 turn 仍显示 sample 0 的 ema".
114
+ if (usage !== undefined && usage.cached_tokens !== undefined) {
115
+ const current = usage.cache_hit_rate ?? 0;
116
+ if (emaState.hitRateEMA === undefined) {
117
+ emaState.hitRateEMA = current;
118
+ }
119
+ else {
120
+ emaState.hitRateEMA = 0.5 * current + 0.5 * emaState.hitRateEMA;
121
+ }
122
+ emaState.sampleCount += 1;
123
+ }
124
+ const line = formatUsageStatus(usage, emaState);
125
+ if (line !== null) {
126
+ err.write(` ${line}\n`);
127
+ }
128
+ }
129
+ //# sourceMappingURL=repl-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-session.js","sourceRoot":"","sources":["../../src/repl/repl-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * REPL SIGINT + turn AbortController coordinator — Sprint 1c-revive-3-D-29.1.1 (2026-06-07).
3
+ *
4
+ * 历史:
5
+ * D-19 (2026-06-05): SIGINT handler + turnAbortController 写在 repl.ts L342-363 闭包内,
6
+ * rl.on('line') chat turn 入口 new 一个新 controller (L510), close handler 调
7
+ * controller.abort() (L583-585), finish() 调 process.off (L305). 拍板: AbortController
8
+ * 单次 abort 语义 — 上一个 turn 被 abort 后, 必须 new 新 controller 才能再 abort.
9
+ * D-19.5 (2026-06-05): 修 P2.5 — finish() 入口先 process.off, 防止嵌入式 / 多次启动
10
+ * REPL 累积 listener. 顺序红线: off 必须在 rl.close() 之前, 否则 close 派发 'close'
11
+ * 期间 Ctrl+C 还能触达 onSigint 闭包.
12
+ * D-19.6 (2026-06-05): close handler 改成 dismiss + abort + pendingExit + exitTimer
13
+ * 状态机根治, 不用 try/catch absorb race.
14
+ * D-29.1.1 (2026-06-07): 抽到独立文件, 跟 repl-confirm.ts 工厂形态对齐.
15
+ *
16
+ * 拍板 (D-29.1.1):
17
+ * - 工厂函数: createSignalCoordinator(opts) → ReplSignalCoordinator.
18
+ * - 闭包形态: 内部 let controller 持有当前 turnAbortController, getSignal() / refresh()
19
+ * / abortIfActive() 三方法对外暴露, caller 不用直接看 controller 变量.
20
+ * - SIGINT 行为 1:1 等价 — 先 dismiss in-flight confirm (D-19 P2-dismiss 顺序), 再
21
+ * abort 当前 turnAbortController. 进程不退出, 跟 D-19 拍板一致.
22
+ * - dispose() 幂等 — 多次调安全 (disposed 闭包 boolean 守卫), 防止 finish() 重入.
23
+ * - process 来源: opts.process 注入 (单测 mock), 默认 node:process. 跟 D-19 红线
24
+ * "测 SIGINT 走 abort 直接调, 不挂真 process" 一致 — 单测不需 mock process.
25
+ * - dispose 顺序红线: process.off 必须在 rl.close() 之前 (D-19.5 拍板) — coordinator
26
+ * 内部按这个顺序处理, caller 只需在 finish() 入口调 dispose().
27
+ *
28
+ * 拍板 (D-29.1.1 §out of scope):
29
+ * - 不接 exitTimer 兜底 timer (那是 D-19.6 P1 close 路径的, 跟 pendingExit state
30
+ * machine 绑定, 留给 D-29.1.3 turn-guard 抽).
31
+ * - 不接 pendingExit / lineQueue (那是 6afccc8 6 红线段的职责, 留给 D-29.1.3).
32
+ * - 不抽 rl.on('close') 路径里的 dismiss + abort (L580-585) — close handler
33
+ * 跟 lineQueue / exitTimer 强耦合, 整体抽风险大于收益, 留给 D-29.1.3+.
34
+ */
35
+ import type { ReplConfirmController } from './repl-confirm.js';
36
+ export interface ReplSignalCoordinatorOptions {
37
+ /**
38
+ * REPL 的 confirm controller (D-19 P2-dismiss 顺序的源).
39
+ * SIGINT 触发时先 confirmController.dismiss() (落 user_denied 审计),
40
+ * 再 abort turnAbortController.
41
+ */
42
+ confirmController: ReplConfirmController;
43
+ /**
44
+ * 注入 process (单测 mock 用). 默认 node:process.
45
+ * D-19 红线: 测 SIGINT 行为走 abort() 直接调, 不挂真 process.
46
+ */
47
+ process?: NodeJS.Process;
48
+ }
49
+ export interface ReplSignalCoordinator {
50
+ /** 当前 in-flight turn 的 AbortSignal. 派给 runAgentTurn / runOneTurn. */
51
+ getSignal: () => AbortSignal;
52
+ /**
53
+ * 续命 controller — turn 入口 new 一个新 controller, 旧的被 SIGINT abort 后
54
+ * 第二次 abort 无效, 必须 refresh. D-19 拍板: onSigint 闭包持有的是 coordinator
55
+ * 内部 let 变量, refresh 后下次 SIGINT 自动 abort 新的, 不需要重建 handler.
56
+ * 红线: 不要 add 多份 SIGINT listener 重复触发.
57
+ */
58
+ refresh: () => void;
59
+ /**
60
+ * 主动 abort (close 路径 / pendingExit 兜底). 幂等 — 已 abort 的 controller 不再
61
+ * 派发 abort 事件. D-19.6 拍板: close handler 走 dismiss + abort + pendingExit
62
+ * 状态机, abort 顺序在 dismiss 之后.
63
+ */
64
+ abortIfActive: () => void;
65
+ /**
66
+ * finish() 入口调 — process.off('SIGINT', onSigint) + 清 disposed 守卫.
67
+ * 顺序红线 (D-19.5): dispose() 必须在 caller 的 rl.close() 之前, 否则 close
68
+ * 派发 'close' 期间 Ctrl+C 还能触达 onSigint 闭包.
69
+ * 幂等: 多次调安全.
70
+ */
71
+ dispose: () => void;
72
+ }
73
+ export declare function createSignalCoordinator(opts: ReplSignalCoordinatorOptions): ReplSignalCoordinator;
74
+ //# sourceMappingURL=repl-signal-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-signal-coordinator.d.ts","sourceRoot":"","sources":["../../src/repl/repl-signal-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/D,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,iBAAiB,EAAE,qBAAqB,CAAC;IACzC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,SAAS,EAAE,MAAM,WAAW,CAAC;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;;OAIG;IACH,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,4BAA4B,GACjC,qBAAqB,CAwCvB"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * REPL SIGINT + turn AbortController coordinator — Sprint 1c-revive-3-D-29.1.1 (2026-06-07).
3
+ *
4
+ * 历史:
5
+ * D-19 (2026-06-05): SIGINT handler + turnAbortController 写在 repl.ts L342-363 闭包内,
6
+ * rl.on('line') chat turn 入口 new 一个新 controller (L510), close handler 调
7
+ * controller.abort() (L583-585), finish() 调 process.off (L305). 拍板: AbortController
8
+ * 单次 abort 语义 — 上一个 turn 被 abort 后, 必须 new 新 controller 才能再 abort.
9
+ * D-19.5 (2026-06-05): 修 P2.5 — finish() 入口先 process.off, 防止嵌入式 / 多次启动
10
+ * REPL 累积 listener. 顺序红线: off 必须在 rl.close() 之前, 否则 close 派发 'close'
11
+ * 期间 Ctrl+C 还能触达 onSigint 闭包.
12
+ * D-19.6 (2026-06-05): close handler 改成 dismiss + abort + pendingExit + exitTimer
13
+ * 状态机根治, 不用 try/catch absorb race.
14
+ * D-29.1.1 (2026-06-07): 抽到独立文件, 跟 repl-confirm.ts 工厂形态对齐.
15
+ *
16
+ * 拍板 (D-29.1.1):
17
+ * - 工厂函数: createSignalCoordinator(opts) → ReplSignalCoordinator.
18
+ * - 闭包形态: 内部 let controller 持有当前 turnAbortController, getSignal() / refresh()
19
+ * / abortIfActive() 三方法对外暴露, caller 不用直接看 controller 变量.
20
+ * - SIGINT 行为 1:1 等价 — 先 dismiss in-flight confirm (D-19 P2-dismiss 顺序), 再
21
+ * abort 当前 turnAbortController. 进程不退出, 跟 D-19 拍板一致.
22
+ * - dispose() 幂等 — 多次调安全 (disposed 闭包 boolean 守卫), 防止 finish() 重入.
23
+ * - process 来源: opts.process 注入 (单测 mock), 默认 node:process. 跟 D-19 红线
24
+ * "测 SIGINT 走 abort 直接调, 不挂真 process" 一致 — 单测不需 mock process.
25
+ * - dispose 顺序红线: process.off 必须在 rl.close() 之前 (D-19.5 拍板) — coordinator
26
+ * 内部按这个顺序处理, caller 只需在 finish() 入口调 dispose().
27
+ *
28
+ * 拍板 (D-29.1.1 §out of scope):
29
+ * - 不接 exitTimer 兜底 timer (那是 D-19.6 P1 close 路径的, 跟 pendingExit state
30
+ * machine 绑定, 留给 D-29.1.3 turn-guard 抽).
31
+ * - 不接 pendingExit / lineQueue (那是 6afccc8 6 红线段的职责, 留给 D-29.1.3).
32
+ * - 不抽 rl.on('close') 路径里的 dismiss + abort (L580-585) — close handler
33
+ * 跟 lineQueue / exitTimer 强耦合, 整体抽风险大于收益, 留给 D-29.1.3+.
34
+ */
35
+ export function createSignalCoordinator(opts) {
36
+ const proc = opts.process ?? process;
37
+ let controller = new AbortController();
38
+ let disposed = false;
39
+ // 拍板 (D-19 P2-dismiss): SIGINT 先 dismiss in-flight confirm 落 user_denied 审计,
40
+ // 再 abort turnAbortController. 顺序: dismiss 先于 abort — confirm resolve 后
41
+ // runToolLoop 才检查 signal, 调换会丢 audit 路径. 进程不退出, 用户可继续.
42
+ const onSigint = () => {
43
+ if (opts.confirmController.hasPending()) {
44
+ opts.confirmController.dismiss();
45
+ }
46
+ if (!controller.signal.aborted) {
47
+ controller.abort();
48
+ }
49
+ };
50
+ proc.on('SIGINT', onSigint);
51
+ return {
52
+ getSignal: () => controller.signal,
53
+ refresh: () => {
54
+ if (disposed)
55
+ return;
56
+ controller = new AbortController();
57
+ },
58
+ abortIfActive: () => {
59
+ if (!controller.signal.aborted) {
60
+ controller.abort();
61
+ }
62
+ },
63
+ dispose: () => {
64
+ if (disposed)
65
+ return;
66
+ disposed = true;
67
+ // 红线 (D-19.5): off 必须在 caller 的 rl.close() 之前. Coordinator 在
68
+ // dispose() 同步内 off, caller 调用顺序 = dispose() → rl.close() → ...
69
+ proc.off('SIGINT', onSigint);
70
+ },
71
+ };
72
+ }
73
+ //# sourceMappingURL=repl-signal-coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repl-signal-coordinator.js","sourceRoot":"","sources":["../../src/repl/repl-signal-coordinator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AA2CH,MAAM,UAAU,uBAAuB,CACrC,IAAkC;IAElC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC;IACrC,IAAI,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACvC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,6EAA6E;IAC7E,wEAAwE;IACxE,uDAAuD;IACvD,MAAM,QAAQ,GAAG,GAAS,EAAE;QAC1B,IAAI,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;QACnC,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE5B,OAAO;QACL,SAAS,EAAE,GAAgB,EAAE,CAAC,UAAU,CAAC,MAAM;QAE/C,OAAO,EAAE,GAAS,EAAE;YAClB,IAAI,QAAQ;gBAAE,OAAO;YACrB,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,CAAC;QAED,aAAa,EAAE,GAAS,EAAE;YACxB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC/B,UAAU,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;QAED,OAAO,EAAE,GAAS,EAAE;YAClB,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,6DAA6D;YAC7D,gEAAgE;YAChE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC;AACJ,CAAC"}