@deepwhale/coding-agent 1.0.11 → 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.
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/modes/tui.d.ts +0 -5
- package/dist/modes/tui.d.ts.map +1 -1
- package/dist/modes/tui.js +4 -57
- package/dist/modes/tui.js.map +1 -1
- package/dist/repl/repl-command-router.d.ts +78 -0
- package/dist/repl/repl-command-router.d.ts.map +1 -0
- package/dist/repl/repl-command-router.js +112 -0
- package/dist/repl/repl-command-router.js.map +1 -0
- package/dist/repl/repl-session.d.ts +79 -0
- package/dist/repl/repl-session.d.ts.map +1 -0
- package/dist/repl/repl-session.js +129 -0
- package/dist/repl/repl-session.js.map +1 -0
- package/dist/repl/repl-signal-coordinator.d.ts +74 -0
- package/dist/repl/repl-signal-coordinator.d.ts.map +1 -0
- package/dist/repl/repl-signal-coordinator.js +73 -0
- package/dist/repl/repl-signal-coordinator.js.map +1 -0
- package/dist/repl.d.ts +3 -40
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +59 -213
- package/dist/repl.js.map +1 -1
- package/dist/tui-ink-bundle.js +78 -595
- package/package.json +1 -1
|
@@ -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"}
|
package/dist/repl.d.ts
CHANGED
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
* - startRepl: 接 tool loop + session 的入口
|
|
19
19
|
*/
|
|
20
20
|
import { SessionWriter } from '@deepwhale/core';
|
|
21
|
-
import { ChatMessage, LLMClient
|
|
21
|
+
import { ChatMessage, LLMClient } from '@deepwhale/llm';
|
|
22
22
|
import { type AgentCompactionConfig } from './agent/index.js';
|
|
23
23
|
import { type Provider } from './llm-factory.js';
|
|
24
24
|
export { createReplConfirm } from './repl/repl-confirm.js';
|
|
25
|
+
import { type UsageEmaState } from './repl/repl-session.js';
|
|
26
|
+
export { formatUsageStatus, appendUsageStatus, type UsageEmaState } from './repl/repl-session.js';
|
|
25
27
|
import type { ToolPolicy } from './policy/types.js';
|
|
26
28
|
import type { SandboxRunner } from './sandbox/types.js';
|
|
27
29
|
export interface ReplOptions {
|
|
@@ -112,43 +114,4 @@ export declare function startRepl(options?: ReplOptions): Promise<number>;
|
|
|
112
114
|
* 单测通过 export 暴露,直接注入 mock LLMClient + WritableStream 验证行为。
|
|
113
115
|
*/
|
|
114
116
|
export declare function runAgentTurn(client: LLMClient, userInput: string, workingMessages: ChatMessage[], writer: SessionWriter | null, out: NodeJS.WritableStream, err: NodeJS.WritableStream, signal: AbortSignal, compactionConfig?: AgentCompactionConfig | null, sandboxRunner?: SandboxRunner, yes?: boolean, policy?: ToolPolicy, emaState?: UsageEmaState): Promise<void>;
|
|
115
|
-
/**
|
|
116
|
-
* Sprint 1b: 把 usage 翻译成人类可读的一行 status, 写到 stderr (不污染 stdout 流式输出)。
|
|
117
|
-
*
|
|
118
|
-
* 显示规则 (Hermes footer 教训应用 — 多字段同值时去冗余):
|
|
119
|
-
* - 满 usage (有 cached_tokens) → 完整 4 字段: cache: 90% | ¥0.05/turn | prompt 1.2k (1.1k cached)
|
|
120
|
-
* - 无 cached_tokens → 简化为: usage: 1.2k prompt / 200 completion
|
|
121
|
-
* (不打 cache% / cost, 避免没数据时显示 0% 误导)
|
|
122
|
-
* - 无 usage → 完全不打印 (LLM 没返 usage 时不污染 stderr)
|
|
123
|
-
*
|
|
124
|
-
* Sprint 1c 抽 pricing 到 config.toml, 此函数签名不变。
|
|
125
|
-
*
|
|
126
|
-
* Sprint 1c-revive-2-D-21.1 (2026-06-06, 修 cache 96%↔85% 跳变 footer 焦虑):
|
|
127
|
-
* 增 emaState 形参. 形参 hitRateEMA 是过去 5-turn 滚动平均 (α=0.5 平滑).
|
|
128
|
-
* 输出: `cache: 90% (avg 85%)` — per-turn 数字 + 趋势均值并列. user 视角:
|
|
129
|
-
* 1) 知道当 turn 真值, 2) 知道趋势 (avg 不会跟着单 turn 抖动).
|
|
130
|
-
* 边界:
|
|
131
|
-
* - sampleCount < 3: 不显示 (avg) 段 (样本太少, 趋势没意义, 不污染)
|
|
132
|
-
* - sampleCount >= 3: 显示 (avg NN%)
|
|
133
|
-
* - ema 永远存在 (闭包外, startRepl 持有), 跨 turn 累积
|
|
134
|
-
* 行为兼容: 旧 caller 不传 emaState 用默认 { hitRateEMA: undefined, sampleCount: 0 }
|
|
135
|
-
* → 行为跟改前一致 (不显示 avg 段). 现有单测 (formatUsageStatus) 不破.
|
|
136
|
-
*/
|
|
137
|
-
export interface UsageEmaState {
|
|
138
|
-
hitRateEMA?: number;
|
|
139
|
-
sampleCount: number;
|
|
140
|
-
}
|
|
141
|
-
export declare function formatUsageStatus(usage: Usage | undefined, emaState?: UsageEmaState): string | null;
|
|
142
|
-
/**
|
|
143
|
-
* Sprint 1c-revive-2-D-21.1 (2026-06-06): emaState 接受 mutable 引用 (闭包),
|
|
144
|
-
* 在 sampleCount < 5 用 cold-start EMA (直接赋值), 之后 α=0.5 平滑:
|
|
145
|
-
* newEMA = α * current + (1 - α) * oldEMA = 0.5 * current + 0.5 * oldEMA
|
|
146
|
-
* α=0.5 是 "等权平滑" (5-turn 半衰期 ≈ 1 turn, 快速响应 + 适度平滑).
|
|
147
|
-
* 数学: sampleCount=5 时, 5 turn 前的数据权重 = 0.5^5 = 3.1% (基本忘掉),
|
|
148
|
-
* 跟 5-turn rolling window 趋势一致, 但 EMA 实现更轻.
|
|
149
|
-
*
|
|
150
|
-
* export 出来供单测 (test/unit/usage-ema.test.ts) 验证 state machine.
|
|
151
|
-
* 之前是 local function, D-21.1 改成 export — 单测需要直接调它验 in-place update.
|
|
152
|
-
*/
|
|
153
|
-
export declare function appendUsageStatus(usage: Usage | undefined, err: NodeJS.WritableStream, emaState: UsageEmaState): void;
|
|
154
117
|
//# sourceMappingURL=repl.d.ts.map
|
package/dist/repl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAAiB,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAEL,WAAW,EAGX,SAAS,
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAAiB,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAEL,WAAW,EAGX,SAAS,EAMV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAOL,KAAK,qBAAqB,EAG3B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAItE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAwC,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAElG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oHAAoH;IACpH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IACpC,mCAAmC;IACnC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1E;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,mBAAmB,EAAE,WAAW,EAAE,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrC,OAAO,CACR;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAC3F,CAcA;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmX1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,WAAW,EAAE,EAC9B,MAAM,EAAE,aAAa,GAAG,IAAI,EAC5B,GAAG,EAAE,MAAM,CAAC,cAAc,EAC1B,GAAG,EAAE,MAAM,CAAC,cAAc,EAC1B,MAAM,EAAE,WAAW,EACnB,gBAAgB,GAAE,qBAAqB,GAAG,IAAW,EAGrD,aAAa,CAAC,EAAE,aAAa,EAE7B,GAAG,CAAC,EAAE,OAAO,EAGb,MAAM,CAAC,EAAE,UAAU,EAInB,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,IAAI,CAAC,CAoHf"}
|