@xdxer/dingtalk-agent 0.1.5-beta.4 → 0.1.5-beta.6

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 (36) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/bin/dingtalk-agent.js +339 -124
  3. package/dist/bin/dingtalk-agent.js.map +1 -1
  4. package/dist/src/actions.js +3 -2
  5. package/dist/src/actions.js.map +1 -1
  6. package/dist/src/doctor.js +1 -1
  7. package/dist/src/doctor.js.map +1 -1
  8. package/dist/src/dws.js +2 -1
  9. package/dist/src/dws.js.map +1 -1
  10. package/dist/src/init.js +2 -1
  11. package/dist/src/init.js.map +1 -1
  12. package/dist/src/map.js +157 -0
  13. package/dist/src/map.js.map +1 -0
  14. package/dist/src/skill-manager.js +5 -0
  15. package/dist/src/skill-manager.js.map +1 -1
  16. package/dist/src/tui.js +369 -0
  17. package/dist/src/tui.js.map +1 -0
  18. package/dist/src/upgrade.js +113 -33
  19. package/dist/src/upgrade.js.map +1 -1
  20. package/dist/src/waits.js +2 -1
  21. package/dist/src/waits.js.map +1 -1
  22. package/package.json +3 -2
  23. package/skills/core/dingtalk-agent-compose/SKILL.md +19 -1
  24. package/skills/core/dingtalk-people-group-memory/COMPLETENESS.md +36 -0
  25. package/skills/core/dingtalk-people-group-memory/SKILL.md +69 -0
  26. package/skills/core/dingtalk-people-group-memory/references/adapters.md +273 -0
  27. package/skills/core/dingtalk-people-group-memory/references/assembly-guidance.md +40 -0
  28. package/skills/core/dingtalk-people-group-memory/references/binding.md +110 -0
  29. package/skills/core/dingtalk-people-group-memory/references/cold-start.md +70 -0
  30. package/skills/core/dingtalk-people-group-memory/references/config-binding.md +89 -0
  31. package/skills/core/dingtalk-people-group-memory/references/consent-and-visibility.md +83 -0
  32. package/skills/core/dingtalk-people-group-memory/references/consolidation.md +162 -0
  33. package/skills/core/dingtalk-people-group-memory/references/event-ingest.md +103 -0
  34. package/skills/core/dingtalk-people-group-memory/references/guided-setup.md +70 -0
  35. package/skills/core/dingtalk-people-group-memory/references/model.md +148 -0
  36. package/skills/core/dingtalk-people-group-memory/references/storage-port.md +107 -0
@@ -0,0 +1,369 @@
1
+ // dta 的输出层:同一份内容,人看一套,机器读另一套。
2
+ //
3
+ // 三条规则,实现时不要绕过:
4
+ // 1. **颜色轴与动效轴是两个独立开关。** NO_COLOR 只关颜色;\r 重绘、光标控制、
5
+ // 列对齐一律只由 stream.isTTY 决定。半数 spinner 库正是在这里搞错的。
6
+ // 2. **颜色永远是可丢弃的装饰。** 剥掉之后语义必须完整——靠行首状态词和
7
+ // [n/N] 编号承载,绝不出现"红色那行是错误"这种只有颜色能表达的语义。
8
+ // 3. **环境变量只能关色,不能开色。** 管道里想上色只有命令行显式 --color 一条路,
9
+ // 这样"不显式要就没有 ANSI"是硬保证,而不是"取决于 harness 有没有预置
10
+ // FORCE_COLOR"。--json 另外还结构性绕开本模块,双重保险。
11
+ import { createInterface } from 'node:readline/promises';
12
+ // ── 判定层 ────────────────────────────────────────────────────────────
13
+ const OFF = new Set(['0', 'false', 'no', 'off']);
14
+ /**
15
+ * 颜色轴:**环境变量只能关色,不能开色。**
16
+ *
17
+ * 优先级 NO_COLOR / NODE_DISABLE_COLORS > FORCE_COLOR=0 等显式关 > CLICOLOR=0
18
+ * > TERM=dumb > isTTY。想在管道里上色只有一条路:命令行显式 `--color`。
19
+ *
20
+ * 这一条刻意偏离 npm 生态惯例(那边 FORCE_COLOR=1 能在管道里强制上色)。
21
+ * 理由是 dta 的非 TTY 读者主要是 Agent——它通过 Bash 工具捕获 stdout 喂给
22
+ * 模型,一旦 harness 预置了 FORCE_COLOR,转义序列就会变成模型读到的噪声,
23
+ * 而这个泄漏在有色终端里肉眼完全看不出来。把"不显式要就没有 ANSI"做成
24
+ * 硬保证,比跟惯例对齐更值钱;CI 里真想要彩色日志,用 `--color` 即可。
25
+ *
26
+ * TERM / COLORTERM 同理不作为开色依据:Agent 调用时它们会被原样继承
27
+ * (实测 Claude Code 下 stdout 非 TTY 但 COLORTERM=truecolor 仍在)。
28
+ */
29
+ function colorFromEnv(stream, env) {
30
+ // no-color.org:变量存在且非空即禁用,与值无关(NO_COLOR=0 也禁)
31
+ if (env.NO_COLOR)
32
+ return false;
33
+ if (env.NODE_DISABLE_COLORS)
34
+ return false;
35
+ // 关色方向仍然尊重;开色方向一律无效,落到 isTTY
36
+ const force = env.CLICOLOR_FORCE ?? env.FORCE_COLOR;
37
+ if (force !== undefined && force !== '' && OFF.has(force.toLowerCase()))
38
+ return false;
39
+ if (env.CLICOLOR !== undefined && OFF.has(env.CLICOLOR.toLowerCase()))
40
+ return false;
41
+ if (env.TERM === 'dumb')
42
+ return false;
43
+ return Boolean(stream && stream.isTTY);
44
+ }
45
+ export function decide(options = {}) {
46
+ const stream = options.stream || process.stdout;
47
+ const flag = options.flag || 'auto';
48
+ const env = options.env || process.env;
49
+ const color = flag === 'always' ? true : flag === 'never' ? false : colorFromEnv(stream, env);
50
+ // 动效轴只看流本身,永远不看环境变量
51
+ const rich = Boolean(stream && stream.isTTY);
52
+ // columns 报 0 是真实存在的坑:据此算重绘宽度会得到负数或零长度
53
+ const cols = stream && stream.columns > 0 ? stream.columns : 80;
54
+ return { color, rich, cols };
55
+ }
56
+ // ── 渲染层 ────────────────────────────────────────────────────────────
57
+ // 只用 16 色基础子集:任何终端主题下都不会失效,也不需要探测色深。
58
+ const SGR = {
59
+ dim: ['\x1b[2m', '\x1b[22m'],
60
+ bold: ['\x1b[1m', '\x1b[22m'],
61
+ success: ['\x1b[32;1m', '\x1b[39;22m'],
62
+ danger: ['\x1b[31;1m', '\x1b[39;22m'],
63
+ warn: ['\x1b[33;1m', '\x1b[39;22m'],
64
+ noun: ['\x1b[36m', '\x1b[39m'],
65
+ };
66
+ /** 两套 Theme 必须共用它,否则同一份数据在有色/无色终端会显示成不同内容。 */
67
+ function normalize(value) {
68
+ return String(value ?? '');
69
+ }
70
+ const PLAIN = {
71
+ dim: normalize, bold: normalize, success: normalize,
72
+ danger: normalize, warn: normalize, noun: normalize,
73
+ };
74
+ export function makeTheme(ctx) {
75
+ if (!ctx.color)
76
+ return PLAIN;
77
+ const paint = (kind) => (value) => {
78
+ const text = normalize(value);
79
+ const [open, close] = SGR[kind];
80
+ // bold 与 dim 共用 22 复位,嵌套时内层的 close 会把外层也关掉;补回外层。
81
+ return open + text.split(close).join(close + open) + close;
82
+ };
83
+ return {
84
+ dim: paint('dim'), bold: paint('bold'), success: paint('success'),
85
+ danger: paint('danger'), warn: paint('warn'), noun: paint('noun'),
86
+ };
87
+ }
88
+ // ── 显示宽度 ──────────────────────────────────────────────────────────
89
+ // CSI(私有参数 / 冒号参数 / intermediate bytes 全覆盖,按 ECMA-48 的形状)与
90
+ // OSC(BEL 或 ST 收尾)。未终止的序列吃到串尾——宁可少算也不把裸转义算进宽度。
91
+ // 注意:本函数只承诺处理 CLI 自己产生的文本,不是通用终端流解析器。
92
+ const ANSI = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][\s\S]*?(?:\x07|\x1b\\|$)|\x1b[@-Z\\-_]/g;
93
+ const SEGMENTER = new Intl.Segmenter('zh', { granularity: 'grapheme' });
94
+ // 占两列的码点区间。**由 string-width@8 逐码点扫描 0x0..0x3FFFF 生成**,
95
+ // 并剔除了 Emoji_Presentation(那部分下面用正则判,放进表里只会让表白白变大)。
96
+ // 手写区间表是这套东西最容易出错的地方:上一版把 [0x3041,0x33ff] 整段当 Wide,
97
+ // 混进了 U+3248..U+324F 这些 Ambiguous 字符,同时又漏掉西夏、假名补充、
98
+ // 女书、圈起表意补充等真正的 Wide 区段。
99
+ const WIDE = [
100
+ [0x1100, 0x115e], [0x2329, 0x232a], [0x2630, 0x2637], [0x268a, 0x268f],
101
+ [0x2e80, 0x2e99], [0x2e9b, 0x2ef3], [0x2f00, 0x2fd5], [0x2ff0, 0x3029],
102
+ [0x302e, 0x303e], [0x3041, 0x3096], [0x309b, 0x30ff], [0x3105, 0x312f],
103
+ [0x3131, 0x3163], [0x3165, 0x318e], [0x3190, 0x31e5], [0x31ef, 0x321e],
104
+ [0x3220, 0x3247], [0x3250, 0xa48c], [0xa490, 0xa4c6], [0xa960, 0xa97c],
105
+ [0xac00, 0xd7a3], [0xf900, 0xfaff], [0xfe10, 0xfe19], [0xfe30, 0xfe52],
106
+ [0xfe54, 0xfe66], [0xfe68, 0xfe6b], [0xff01, 0xff60], [0xffe0, 0xffe6],
107
+ [0x16fe0, 0x16fe3], [0x16ff0, 0x16ff6], [0x17000, 0x18cd5], [0x18cff, 0x18d1e],
108
+ [0x18d80, 0x18df2], [0x1aff0, 0x1aff3], [0x1aff5, 0x1affb], [0x1affd, 0x1affe],
109
+ [0x1b000, 0x1b122], [0x1b132, 0x1b132], [0x1b150, 0x1b152], [0x1b155, 0x1b155],
110
+ [0x1b164, 0x1b167], [0x1b170, 0x1b2fb], [0x1d300, 0x1d356], [0x1d360, 0x1d376],
111
+ [0x1f200, 0x1f200], [0x1f202, 0x1f202], [0x1f210, 0x1f219], [0x1f21b, 0x1f22e],
112
+ [0x1f230, 0x1f231], [0x1f237, 0x1f237], [0x1f23b, 0x1f23b], [0x1f240, 0x1f248],
113
+ [0x1f260, 0x1f265], [0x1f6d8, 0x1f6d8], [0x1fa8a, 0x1fa8a], [0x1fa8e, 0x1fa8e],
114
+ [0x1fac8, 0x1fac8], [0x1facd, 0x1facd], [0x1faea, 0x1faea], [0x1faef, 0x1faef],
115
+ [0x20000, 0x2fffd], [0x30000, 0x3fffd]
116
+ ];
117
+ function isWide(codePoint) {
118
+ let low = 0;
119
+ let high = WIDE.length - 1;
120
+ while (low <= high) {
121
+ const mid = (low + high) >> 1;
122
+ if (codePoint < WIDE[mid][0])
123
+ high = mid - 1;
124
+ else if (codePoint > WIDE[mid][1])
125
+ low = mid + 1;
126
+ else
127
+ return true;
128
+ }
129
+ return false;
130
+ }
131
+ const VS16 = '\ufe0f';
132
+ const ZWJ = '\u200d';
133
+ // 只有真的含图形 emoji 的字素簇才按 2 列算。"含 ZWJ 就是 emoji" 是错的:
134
+ // a + ZWJ + b 会被切成两簇,按旧规则算出 3 列。
135
+ const EXTENDED_PICTOGRAPHIC = /\p{Extended_Pictographic}/u;
136
+ const EMOJI_PRESENTATION = /\p{Emoji_Presentation}/u;
137
+ // 肤色修饰符单独出现时也占 2 列(附着在基字符上时会与基字符同属一簇)
138
+ const EMOJI_MODIFIER = /\p{Emoji_Modifier}/u;
139
+ const LONE_REGIONAL_INDICATOR = /^\p{Regional_Indicator}$/u;
140
+ // 整簇只由非间距/包围标记、格式字符、默认可忽略码点组成时不占列:
141
+ // 单独的 U+0301、软连字符 U+00AD、阿拉伯数字标记 U+0600 都属于这一类。
142
+ // **只能用 Mn/Me,不能用 Mark**:Mc(间距组合标记,如梵文 U+0903)实际占 1 列。
143
+ const ZERO_WIDTH = /^[\p{Mn}\p{Me}\p{Cf}\p{Default_Ignorable_Code_Point}]+$/u;
144
+ /**
145
+ * 终端显示宽度。先剥 ANSI,再按字素簇计算——不是 String.length。
146
+ *
147
+ * 四类都会翻车,缺一不可:
148
+ * 中文 '新版本可用:' length=6 但占 11 列
149
+ * 变体符 ✔(U+2714) 占 1 列,✔️(U+2714 U+FE0F) 占 2 列
150
+ * emoji ✅ 不在 EAW Wide 区间内,靠 Emoji_Presentation 才判得出 2 列
151
+ * 组合标记 单独出现的 U+0301 不占列
152
+ */
153
+ export function displayWidth(input) {
154
+ const text = normalize(input).replace(ANSI, '');
155
+ let width = 0;
156
+ for (const { segment } of SEGMENTER.segment(text)) {
157
+ const codePoint = segment.codePointAt(0);
158
+ if (codePoint === undefined)
159
+ continue;
160
+ // 控制字符不占列
161
+ if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0))
162
+ continue;
163
+ // 落单的地区指示符只占 1 列;成对时(🇨🇳)两个码点属于同一簇,走下面的 emoji 分支占 2 列。
164
+ if (LONE_REGIONAL_INDICATOR.test(segment)) {
165
+ width += 1;
166
+ continue;
167
+ }
168
+ // 按 emoji 呈现的簇占 2 列。三个条件缺一不可,而且都不能靠
169
+ // Extended_Pictographic 兜底——肤色修饰符 U+1F3FB 是 Emoji_Presentation
170
+ // 但 **不是** Extended_Pictographic,拿后者当外层 gate 会直接漏掉。
171
+ const asEmoji = EMOJI_PRESENTATION.test(segment) || EMOJI_MODIFIER.test(segment) ||
172
+ (EXTENDED_PICTOGRAPHIC.test(segment) &&
173
+ (segment.includes(VS16) || segment.includes(ZWJ)));
174
+ if (asEmoji) {
175
+ width += 2;
176
+ continue;
177
+ }
178
+ // 文字呈现的图形符不能直接判 1:八卦符 U+2630 这类本身就是 EAW Wide,
179
+ // 要落回下面的区间表,否则会把 2 列算成 1 列。
180
+ // 零宽簇(附着在基字符上时会和基字符同属一簇,走下面的分支)
181
+ if (ZERO_WIDTH.test(segment))
182
+ continue;
183
+ width += isWide(codePoint) ? 2 : 1;
184
+ }
185
+ return width;
186
+ }
187
+ /** 按显示宽度截断;宁可少一列也不把宽字符劈成半个。 */
188
+ export function truncateToWidth(text, limit) {
189
+ if (limit <= 0)
190
+ return '';
191
+ if (displayWidth(text) <= limit)
192
+ return text;
193
+ let out = '';
194
+ let width = 0;
195
+ for (const { segment } of SEGMENTER.segment(normalize(text))) {
196
+ const next = displayWidth(segment);
197
+ if (width + next > limit - 1)
198
+ break;
199
+ out += segment;
200
+ width += next;
201
+ }
202
+ return `${out}…`;
203
+ }
204
+ /** 按显示宽度右填充;字符串里的 ANSI 不计入宽度。 */
205
+ export function padRight(text, width) {
206
+ const pad = width - displayWidth(text);
207
+ return pad > 0 ? text + ' '.repeat(pad) : text;
208
+ }
209
+ // ── 进程级配置 ────────────────────────────────────────────────────────
210
+ let flagOverride = 'auto';
211
+ let outCache = null;
212
+ let errCache = null;
213
+ /** 由 bin 在解析完 argv 后调用一次,把 --color/--no-color 交给整个进程。 */
214
+ export function configureTui(flag) {
215
+ flagOverride = flag;
216
+ outCache = null;
217
+ errCache = null;
218
+ }
219
+ /** 人类报告通道(stdout)。--json 分支不得经过这里。 */
220
+ export function out() {
221
+ if (!outCache) {
222
+ const ctx = decide({ stream: process.stdout, flag: flagOverride });
223
+ outCache = { ctx, theme: makeTheme(ctx) };
224
+ }
225
+ return outCache;
226
+ }
227
+ /** 进度与诊断通道(stderr)。 */
228
+ export function err() {
229
+ if (!errCache) {
230
+ const ctx = decide({ stream: process.stderr, flag: flagOverride });
231
+ errCache = { ctx, theme: makeTheme(ctx) };
232
+ }
233
+ return errCache;
234
+ }
235
+ // ── 分步进度 ──────────────────────────────────────────────────────────
236
+ const MARK_OK = '✔';
237
+ const MARK_FAIL = '✗';
238
+ /**
239
+ * 同一个事件流,两套 renderer:
240
+ * TTY —— 先打进行态,完成后原地重绘塌缩成一行终态。
241
+ * 非 TTY —— 进行态完全不打,只在终态输出一行;行首状态词可 grep,零控制码。
242
+ *
243
+ * 进度一律走 stderr,stdout 留给数据(AGENTS.md:--json 输出不得混入日志)。
244
+ */
245
+ export class StepReporter {
246
+ stream;
247
+ ctx;
248
+ theme;
249
+ total;
250
+ index = 0;
251
+ label = '';
252
+ startedAt = 0;
253
+ /** 只有 running 时才允许 end();防止漏 begin 打出 [0/N] 和从 epoch 起算的耗时 */
254
+ running = false;
255
+ /** 进行态实际写出的显示宽度,决定要擦几行 */
256
+ drawnWidth = 0;
257
+ constructor(options) {
258
+ this.stream = options.stream || process.stderr;
259
+ this.total = options.total;
260
+ this.ctx = decide({ stream: this.stream, flag: options.flag || flagOverride });
261
+ this.theme = makeTheme(this.ctx);
262
+ }
263
+ get rich() { return this.ctx.rich; }
264
+ begin(label) {
265
+ if (this.running)
266
+ this.end(true, '未收尾');
267
+ this.index += 1;
268
+ this.label = label;
269
+ this.startedAt = Date.now();
270
+ this.running = true;
271
+ this.drawnWidth = 0;
272
+ if (this.ctx.rich) {
273
+ const head = ` [${this.index}/${this.total}] `;
274
+ // 截断到单行:进行态一旦折行,end() 的 \r 擦除只能覆盖最后一个物理行,
275
+ // 剩下的会留成残影。安装源可以由环境变量拉得很长,这不是理论问题。
276
+ const body = truncateToWidth(`${label}...`, Math.max(0, this.ctx.cols - displayWidth(head) - 1));
277
+ this.drawnWidth = displayWidth(head) + displayWidth(body);
278
+ this.stream.write(` ${this.theme.dim(`[${this.index}/${this.total}]`)} ${body}`);
279
+ }
280
+ return this;
281
+ }
282
+ /** meta 例如 '2.1MB, 0.3s';不传时自动补耗时。 */
283
+ end(ok = true, meta = '') {
284
+ // 没 begin 过就 end 是调用方的 bug;静默吞掉比打出 [0/N] 和 1974 万秒好
285
+ if (!this.running)
286
+ return this;
287
+ this.running = false;
288
+ const seconds = ((Date.now() - this.startedAt) / 1000).toFixed(1);
289
+ const info = meta || `${seconds}s`;
290
+ const counter = `[${this.index}/${this.total}]`;
291
+ if (this.ctx.rich) {
292
+ // 擦掉进行态:\r 回行首 + 覆盖实际写过的宽度 + \r。
293
+ // 不用 \e[K 是为了在窄窗口下行为更可预期。
294
+ this.stream.write(`\r${' '.repeat(Math.min(this.ctx.cols - 1, Math.max(0, this.drawnWidth)))}\r`);
295
+ const mark = ok ? this.theme.success(MARK_OK) : this.theme.danger(MARK_FAIL);
296
+ this.stream.write(` ${this.theme.dim(counter)} ${this.label} ${mark} ${this.theme.dim(`(${info})`)}\n`);
297
+ }
298
+ else {
299
+ this.stream.write(`${counter} ${ok ? 'OK ' : 'FAIL'} ${this.label} (${info})\n`);
300
+ }
301
+ return this;
302
+ }
303
+ /** 挂在当前步骤下的子结果。 */
304
+ detail(text) {
305
+ if (this.ctx.rich) {
306
+ this.stream.write(` ${this.theme.dim('→')} ${this.theme.noun(text)}\n`);
307
+ }
308
+ else {
309
+ this.stream.write(`[${this.index}/${this.total}] ${text}\n`);
310
+ }
311
+ return this;
312
+ }
313
+ /** 一行旁白,两种形态都输出,只有颜色不同。 */
314
+ note(text) {
315
+ this.stream.write(this.ctx.rich ? ` ${this.theme.dim(text)}\n` : `${text}\n`);
316
+ return this;
317
+ }
318
+ }
319
+ /**
320
+ * 标签值对齐。**对齐是给人的渲染变换,不是数据结构**——非 TTY 下退回
321
+ * `key: value` 单空格,Agent 读到的没有一个多余空格。
322
+ */
323
+ export function renderPairs(rows) {
324
+ const { ctx, theme } = err();
325
+ if (!ctx.rich)
326
+ return rows.map(([key, value]) => `${key}: ${value}`);
327
+ const width = Math.max(...rows.map(([key]) => displayWidth(`${key}:`)));
328
+ return rows.map(([key, value]) => ` ${theme.bold(padRight(`${key}:`, width))} ${value}`);
329
+ }
330
+ // ── 确认 ──────────────────────────────────────────────────────────────
331
+ /**
332
+ * 非交互环境下**绝不能挂起**——Agent 调用时表现为超时而不是报错,极难排查。
333
+ * stdin 不是终端时直接返回 fallback,一个字节都不读。
334
+ */
335
+ export async function confirm(question, options = {}) {
336
+ if (options.yes)
337
+ return true;
338
+ const stdin = options.stdin || process.stdin;
339
+ const stream = options.stream || process.stderr;
340
+ const fallback = options.fallback ?? false;
341
+ if (!stdin.isTTY)
342
+ return fallback;
343
+ const rl = createInterface({ input: stdin, output: stream });
344
+ try {
345
+ const answered = rl.question(`${question} [y/N] `);
346
+ // 光靠 try/catch 不够:输入流结束(而不是 Ctrl+D 中断)时 question() 的
347
+ // Promise **永不 settle**,进程会在事件循环排空后静默退出——没有报错、
348
+ // 没有退出码线索,是最难排查的一类失败。必须同时盯着 close 事件。
349
+ const closed = new Promise((resolve) => rl.once('close', () => resolve(null)));
350
+ answered.catch(() => { }); // 竞速落定后迟到的 reject 不该变成 unhandled
351
+ const answer = await Promise.race([answered, closed]);
352
+ if (answer === null) {
353
+ stream.write('\n');
354
+ return false;
355
+ }
356
+ return /^y(es)?$/i.test(String(answer).trim());
357
+ }
358
+ catch {
359
+ // Ctrl+D 会让 readline 抛错。在确认提示上这是"拒绝",不是故障——
360
+ // 冒到顶层只会变成一条 `错误: Aborted`,既吓人又没信息。
361
+ // 这里返回 false 而不是 fallback:人明确中断了,不能当成默认同意。
362
+ stream.write('\n');
363
+ return false;
364
+ }
365
+ finally {
366
+ rl.close();
367
+ }
368
+ }
369
+ //# sourceMappingURL=tui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../../src/tui.ts"],"names":[],"mappings":"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E,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACtE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACtE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IAC9E,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAA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","sourcesContent":["// dta 的输出层:同一份内容,人看一套,机器读另一套。\n//\n// 三条规则,实现时不要绕过:\n// 1. **颜色轴与动效轴是两个独立开关。** NO_COLOR 只关颜色;\\r 重绘、光标控制、\n// 列对齐一律只由 stream.isTTY 决定。半数 spinner 库正是在这里搞错的。\n// 2. **颜色永远是可丢弃的装饰。** 剥掉之后语义必须完整——靠行首状态词和\n// [n/N] 编号承载,绝不出现\"红色那行是错误\"这种只有颜色能表达的语义。\n// 3. **环境变量只能关色,不能开色。** 管道里想上色只有命令行显式 --color 一条路,\n// 这样\"不显式要就没有 ANSI\"是硬保证,而不是\"取决于 harness 有没有预置\n// FORCE_COLOR\"。--json 另外还结构性绕开本模块,双重保险。\n\nimport { createInterface } from 'node:readline/promises'\n\nexport type ColorFlag = 'auto' | 'always' | 'never'\n\nexport interface TuiContext {\n /** 允许发 SGR 颜色 */\n color: boolean\n /** 允许发 \\r 重绘、光标控制、列对齐——只看 isTTY */\n rich: boolean\n /** 终端宽度;columns 报 0 时兜底 80 */\n cols: number\n}\n\nexport interface Theme {\n dim(value: unknown): string\n bold(value: unknown): string\n success(value: unknown): string\n danger(value: unknown): string\n warn(value: unknown): string\n /** 可复制的名词:路径、版本号、ID、命令 */\n noun(value: unknown): string\n}\n\n// ── 判定层 ────────────────────────────────────────────────────────────\n\nconst OFF = new Set(['0', 'false', 'no', 'off'])\n\n/**\n * 颜色轴:**环境变量只能关色,不能开色。**\n *\n * 优先级 NO_COLOR / NODE_DISABLE_COLORS > FORCE_COLOR=0 等显式关 > CLICOLOR=0\n * > TERM=dumb > isTTY。想在管道里上色只有一条路:命令行显式 `--color`。\n *\n * 这一条刻意偏离 npm 生态惯例(那边 FORCE_COLOR=1 能在管道里强制上色)。\n * 理由是 dta 的非 TTY 读者主要是 Agent——它通过 Bash 工具捕获 stdout 喂给\n * 模型,一旦 harness 预置了 FORCE_COLOR,转义序列就会变成模型读到的噪声,\n * 而这个泄漏在有色终端里肉眼完全看不出来。把\"不显式要就没有 ANSI\"做成\n * 硬保证,比跟惯例对齐更值钱;CI 里真想要彩色日志,用 `--color` 即可。\n *\n * TERM / COLORTERM 同理不作为开色依据:Agent 调用时它们会被原样继承\n * (实测 Claude Code 下 stdout 非 TTY 但 COLORTERM=truecolor 仍在)。\n */\nfunction colorFromEnv(stream: NodeJS.WriteStream | undefined, env: NodeJS.ProcessEnv): boolean {\n // no-color.org:变量存在且非空即禁用,与值无关(NO_COLOR=0 也禁)\n if (env.NO_COLOR) return false\n if (env.NODE_DISABLE_COLORS) return false\n\n // 关色方向仍然尊重;开色方向一律无效,落到 isTTY\n const force = env.CLICOLOR_FORCE ?? env.FORCE_COLOR\n if (force !== undefined && force !== '' && OFF.has(force.toLowerCase())) return false\n if (env.CLICOLOR !== undefined && OFF.has(env.CLICOLOR.toLowerCase())) return false\n if (env.TERM === 'dumb') return false\n\n return Boolean(stream && stream.isTTY)\n}\n\nexport function decide(options: {\n stream?: NodeJS.WriteStream\n flag?: ColorFlag\n env?: NodeJS.ProcessEnv\n} = {}): TuiContext {\n const stream = options.stream || process.stdout\n const flag = options.flag || 'auto'\n const env = options.env || process.env\n const color = flag === 'always' ? true : flag === 'never' ? false : colorFromEnv(stream, env)\n // 动效轴只看流本身,永远不看环境变量\n const rich = Boolean(stream && stream.isTTY)\n // columns 报 0 是真实存在的坑:据此算重绘宽度会得到负数或零长度\n const cols = stream && stream.columns > 0 ? stream.columns : 80\n return { color, rich, cols }\n}\n\n// ── 渲染层 ────────────────────────────────────────────────────────────\n\n// 只用 16 色基础子集:任何终端主题下都不会失效,也不需要探测色深。\nconst SGR: Record<keyof Theme, [string, string]> = {\n dim: ['\\x1b[2m', '\\x1b[22m'],\n bold: ['\\x1b[1m', '\\x1b[22m'],\n success: ['\\x1b[32;1m', '\\x1b[39;22m'],\n danger: ['\\x1b[31;1m', '\\x1b[39;22m'],\n warn: ['\\x1b[33;1m', '\\x1b[39;22m'],\n noun: ['\\x1b[36m', '\\x1b[39m'],\n}\n\n/** 两套 Theme 必须共用它,否则同一份数据在有色/无色终端会显示成不同内容。 */\nfunction normalize(value: unknown): string {\n return String(value ?? '')\n}\n\nconst PLAIN: Theme = {\n dim: normalize, bold: normalize, success: normalize,\n danger: normalize, warn: normalize, noun: normalize,\n}\n\nexport function makeTheme(ctx: TuiContext): Theme {\n if (!ctx.color) return PLAIN\n const paint = (kind: keyof Theme) => (value: unknown): string => {\n const text = normalize(value)\n const [open, close] = SGR[kind]\n // bold 与 dim 共用 22 复位,嵌套时内层的 close 会把外层也关掉;补回外层。\n return open + text.split(close).join(close + open) + close\n }\n return {\n dim: paint('dim'), bold: paint('bold'), success: paint('success'),\n danger: paint('danger'), warn: paint('warn'), noun: paint('noun'),\n }\n}\n\n// ── 显示宽度 ──────────────────────────────────────────────────────────\n\n// CSI(私有参数 / 冒号参数 / intermediate bytes 全覆盖,按 ECMA-48 的形状)与\n// OSC(BEL 或 ST 收尾)。未终止的序列吃到串尾——宁可少算也不把裸转义算进宽度。\n// 注意:本函数只承诺处理 CLI 自己产生的文本,不是通用终端流解析器。\nconst ANSI = /\\x1b\\[[0-?]*[ -/]*[@-~]|\\x1b\\][\\s\\S]*?(?:\\x07|\\x1b\\\\|$)|\\x1b[@-Z\\\\-_]/g\n\nconst SEGMENTER = new Intl.Segmenter('zh', { granularity: 'grapheme' })\n\n// 占两列的码点区间。**由 string-width@8 逐码点扫描 0x0..0x3FFFF 生成**,\n// 并剔除了 Emoji_Presentation(那部分下面用正则判,放进表里只会让表白白变大)。\n// 手写区间表是这套东西最容易出错的地方:上一版把 [0x3041,0x33ff] 整段当 Wide,\n// 混进了 U+3248..U+324F 这些 Ambiguous 字符,同时又漏掉西夏、假名补充、\n// 女书、圈起表意补充等真正的 Wide 区段。\nconst WIDE: ReadonlyArray<readonly [number, number]> = [\n [0x1100, 0x115e], [0x2329, 0x232a], [0x2630, 0x2637], [0x268a, 0x268f],\n [0x2e80, 0x2e99], [0x2e9b, 0x2ef3], [0x2f00, 0x2fd5], [0x2ff0, 0x3029],\n [0x302e, 0x303e], [0x3041, 0x3096], [0x309b, 0x30ff], [0x3105, 0x312f],\n [0x3131, 0x3163], [0x3165, 0x318e], [0x3190, 0x31e5], [0x31ef, 0x321e],\n [0x3220, 0x3247], [0x3250, 0xa48c], [0xa490, 0xa4c6], [0xa960, 0xa97c],\n [0xac00, 0xd7a3], [0xf900, 0xfaff], [0xfe10, 0xfe19], [0xfe30, 0xfe52],\n [0xfe54, 0xfe66], [0xfe68, 0xfe6b], [0xff01, 0xff60], [0xffe0, 0xffe6],\n [0x16fe0, 0x16fe3], [0x16ff0, 0x16ff6], [0x17000, 0x18cd5], [0x18cff, 0x18d1e],\n [0x18d80, 0x18df2], [0x1aff0, 0x1aff3], [0x1aff5, 0x1affb], [0x1affd, 0x1affe],\n [0x1b000, 0x1b122], [0x1b132, 0x1b132], [0x1b150, 0x1b152], [0x1b155, 0x1b155],\n [0x1b164, 0x1b167], [0x1b170, 0x1b2fb], [0x1d300, 0x1d356], [0x1d360, 0x1d376],\n [0x1f200, 0x1f200], [0x1f202, 0x1f202], [0x1f210, 0x1f219], [0x1f21b, 0x1f22e],\n [0x1f230, 0x1f231], [0x1f237, 0x1f237], [0x1f23b, 0x1f23b], [0x1f240, 0x1f248],\n [0x1f260, 0x1f265], [0x1f6d8, 0x1f6d8], [0x1fa8a, 0x1fa8a], [0x1fa8e, 0x1fa8e],\n [0x1fac8, 0x1fac8], [0x1facd, 0x1facd], [0x1faea, 0x1faea], [0x1faef, 0x1faef],\n [0x20000, 0x2fffd], [0x30000, 0x3fffd]\n]\n\nfunction isWide(codePoint: number): boolean {\n let low = 0\n let high = WIDE.length - 1\n while (low <= high) {\n const mid = (low + high) >> 1\n if (codePoint < WIDE[mid][0]) high = mid - 1\n else if (codePoint > WIDE[mid][1]) low = mid + 1\n else return true\n }\n return false\n}\n\nconst VS16 = '\\ufe0f'\nconst ZWJ = '\\u200d'\n// 只有真的含图形 emoji 的字素簇才按 2 列算。\"含 ZWJ 就是 emoji\" 是错的:\n// a + ZWJ + b 会被切成两簇,按旧规则算出 3 列。\nconst EXTENDED_PICTOGRAPHIC = /\\p{Extended_Pictographic}/u\nconst EMOJI_PRESENTATION = /\\p{Emoji_Presentation}/u\n// 肤色修饰符单独出现时也占 2 列(附着在基字符上时会与基字符同属一簇)\nconst EMOJI_MODIFIER = /\\p{Emoji_Modifier}/u\nconst LONE_REGIONAL_INDICATOR = /^\\p{Regional_Indicator}$/u\n// 整簇只由非间距/包围标记、格式字符、默认可忽略码点组成时不占列:\n// 单独的 U+0301、软连字符 U+00AD、阿拉伯数字标记 U+0600 都属于这一类。\n// **只能用 Mn/Me,不能用 Mark**:Mc(间距组合标记,如梵文 U+0903)实际占 1 列。\nconst ZERO_WIDTH = /^[\\p{Mn}\\p{Me}\\p{Cf}\\p{Default_Ignorable_Code_Point}]+$/u\n\n/**\n * 终端显示宽度。先剥 ANSI,再按字素簇计算——不是 String.length。\n *\n * 四类都会翻车,缺一不可:\n * 中文 '新版本可用:' length=6 但占 11 列\n * 变体符 ✔(U+2714) 占 1 列,✔️(U+2714 U+FE0F) 占 2 列\n * emoji ✅ 不在 EAW Wide 区间内,靠 Emoji_Presentation 才判得出 2 列\n * 组合标记 单独出现的 U+0301 不占列\n */\nexport function displayWidth(input: unknown): number {\n const text = normalize(input).replace(ANSI, '')\n let width = 0\n for (const { segment } of SEGMENTER.segment(text)) {\n const codePoint = segment.codePointAt(0)\n if (codePoint === undefined) continue\n // 控制字符不占列\n if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0)) continue\n // 落单的地区指示符只占 1 列;成对时(🇨🇳)两个码点属于同一簇,走下面的 emoji 分支占 2 列。\n if (LONE_REGIONAL_INDICATOR.test(segment)) { width += 1; continue }\n // 按 emoji 呈现的簇占 2 列。三个条件缺一不可,而且都不能靠\n // Extended_Pictographic 兜底——肤色修饰符 U+1F3FB 是 Emoji_Presentation\n // 但 **不是** Extended_Pictographic,拿后者当外层 gate 会直接漏掉。\n const asEmoji = EMOJI_PRESENTATION.test(segment) || EMOJI_MODIFIER.test(segment) ||\n (EXTENDED_PICTOGRAPHIC.test(segment) &&\n (segment.includes(VS16) || segment.includes(ZWJ)))\n if (asEmoji) { width += 2; continue }\n // 文字呈现的图形符不能直接判 1:八卦符 U+2630 这类本身就是 EAW Wide,\n // 要落回下面的区间表,否则会把 2 列算成 1 列。\n // 零宽簇(附着在基字符上时会和基字符同属一簇,走下面的分支)\n if (ZERO_WIDTH.test(segment)) continue\n width += isWide(codePoint) ? 2 : 1\n }\n return width\n}\n\n/** 按显示宽度截断;宁可少一列也不把宽字符劈成半个。 */\nexport function truncateToWidth(text: string, limit: number): string {\n if (limit <= 0) return ''\n if (displayWidth(text) <= limit) return text\n let out = ''\n let width = 0\n for (const { segment } of SEGMENTER.segment(normalize(text))) {\n const next = displayWidth(segment)\n if (width + next > limit - 1) break\n out += segment\n width += next\n }\n return `${out}…`\n}\n\n/** 按显示宽度右填充;字符串里的 ANSI 不计入宽度。 */\nexport function padRight(text: string, width: number): string {\n const pad = width - displayWidth(text)\n return pad > 0 ? text + ' '.repeat(pad) : text\n}\n\n// ── 进程级配置 ────────────────────────────────────────────────────────\n\nlet flagOverride: ColorFlag = 'auto'\nlet outCache: { ctx: TuiContext; theme: Theme } | null = null\nlet errCache: { ctx: TuiContext; theme: Theme } | null = null\n\n/** 由 bin 在解析完 argv 后调用一次,把 --color/--no-color 交给整个进程。 */\nexport function configureTui(flag: ColorFlag): void {\n flagOverride = flag\n outCache = null\n errCache = null\n}\n\n/** 人类报告通道(stdout)。--json 分支不得经过这里。 */\nexport function out(): { ctx: TuiContext; theme: Theme } {\n if (!outCache) {\n const ctx = decide({ stream: process.stdout, flag: flagOverride })\n outCache = { ctx, theme: makeTheme(ctx) }\n }\n return outCache\n}\n\n/** 进度与诊断通道(stderr)。 */\nexport function err(): { ctx: TuiContext; theme: Theme } {\n if (!errCache) {\n const ctx = decide({ stream: process.stderr, flag: flagOverride })\n errCache = { ctx, theme: makeTheme(ctx) }\n }\n return errCache\n}\n\n// ── 分步进度 ──────────────────────────────────────────────────────────\n\nconst MARK_OK = '✔'\nconst MARK_FAIL = '✗'\n\n/**\n * 同一个事件流,两套 renderer:\n * TTY —— 先打进行态,完成后原地重绘塌缩成一行终态。\n * 非 TTY —— 进行态完全不打,只在终态输出一行;行首状态词可 grep,零控制码。\n *\n * 进度一律走 stderr,stdout 留给数据(AGENTS.md:--json 输出不得混入日志)。\n */\nexport class StepReporter {\n private readonly stream: NodeJS.WriteStream\n private readonly ctx: TuiContext\n private readonly theme: Theme\n private readonly total: number\n private index = 0\n private label = ''\n private startedAt = 0\n /** 只有 running 时才允许 end();防止漏 begin 打出 [0/N] 和从 epoch 起算的耗时 */\n private running = false\n /** 进行态实际写出的显示宽度,决定要擦几行 */\n private drawnWidth = 0\n\n constructor(options: { total: number; stream?: NodeJS.WriteStream; flag?: ColorFlag }) {\n this.stream = options.stream || process.stderr\n this.total = options.total\n this.ctx = decide({ stream: this.stream, flag: options.flag || flagOverride })\n this.theme = makeTheme(this.ctx)\n }\n\n get rich(): boolean { return this.ctx.rich }\n\n begin(label: string): this {\n if (this.running) this.end(true, '未收尾')\n this.index += 1\n this.label = label\n this.startedAt = Date.now()\n this.running = true\n this.drawnWidth = 0\n if (this.ctx.rich) {\n const head = ` [${this.index}/${this.total}] `\n // 截断到单行:进行态一旦折行,end() 的 \\r 擦除只能覆盖最后一个物理行,\n // 剩下的会留成残影。安装源可以由环境变量拉得很长,这不是理论问题。\n const body = truncateToWidth(`${label}...`, Math.max(0, this.ctx.cols - displayWidth(head) - 1))\n this.drawnWidth = displayWidth(head) + displayWidth(body)\n this.stream.write(` ${this.theme.dim(`[${this.index}/${this.total}]`)} ${body}`)\n }\n return this\n }\n\n /** meta 例如 '2.1MB, 0.3s';不传时自动补耗时。 */\n end(ok = true, meta = ''): this {\n // 没 begin 过就 end 是调用方的 bug;静默吞掉比打出 [0/N] 和 1974 万秒好\n if (!this.running) return this\n this.running = false\n const seconds = ((Date.now() - this.startedAt) / 1000).toFixed(1)\n const info = meta || `${seconds}s`\n const counter = `[${this.index}/${this.total}]`\n if (this.ctx.rich) {\n // 擦掉进行态:\\r 回行首 + 覆盖实际写过的宽度 + \\r。\n // 不用 \\e[K 是为了在窄窗口下行为更可预期。\n this.stream.write(`\\r${' '.repeat(Math.min(this.ctx.cols - 1, Math.max(0, this.drawnWidth)))}\\r`)\n const mark = ok ? this.theme.success(MARK_OK) : this.theme.danger(MARK_FAIL)\n this.stream.write(` ${this.theme.dim(counter)} ${this.label} ${mark} ${this.theme.dim(`(${info})`)}\\n`)\n } else {\n this.stream.write(`${counter} ${ok ? 'OK ' : 'FAIL'} ${this.label} (${info})\\n`)\n }\n return this\n }\n\n /** 挂在当前步骤下的子结果。 */\n detail(text: string): this {\n if (this.ctx.rich) {\n this.stream.write(` ${this.theme.dim('→')} ${this.theme.noun(text)}\\n`)\n } else {\n this.stream.write(`[${this.index}/${this.total}] ${text}\\n`)\n }\n return this\n }\n\n /** 一行旁白,两种形态都输出,只有颜色不同。 */\n note(text: string): this {\n this.stream.write(this.ctx.rich ? ` ${this.theme.dim(text)}\\n` : `${text}\\n`)\n return this\n }\n}\n\n/**\n * 标签值对齐。**对齐是给人的渲染变换,不是数据结构**——非 TTY 下退回\n * `key: value` 单空格,Agent 读到的没有一个多余空格。\n */\nexport function renderPairs(rows: ReadonlyArray<readonly [string, string]>): string[] {\n const { ctx, theme } = err()\n if (!ctx.rich) return rows.map(([key, value]) => `${key}: ${value}`)\n const width = Math.max(...rows.map(([key]) => displayWidth(`${key}:`)))\n return rows.map(([key, value]) => ` ${theme.bold(padRight(`${key}:`, width))} ${value}`)\n}\n\n// ── 确认 ──────────────────────────────────────────────────────────────\n\n/**\n * 非交互环境下**绝不能挂起**——Agent 调用时表现为超时而不是报错,极难排查。\n * stdin 不是终端时直接返回 fallback,一个字节都不读。\n */\nexport async function confirm(question: string, options: {\n yes?: boolean\n /** 非交互时的取值;upgrade 传 true 以保持既有的\"敲下去就装\"行为。 */\n fallback?: boolean\n stdin?: NodeJS.ReadStream\n stream?: NodeJS.WriteStream\n} = {}): Promise<boolean> {\n if (options.yes) return true\n const stdin = options.stdin || process.stdin\n const stream = options.stream || process.stderr\n const fallback = options.fallback ?? false\n if (!stdin.isTTY) return fallback\n\n const rl = createInterface({ input: stdin, output: stream })\n try {\n const answered = rl.question(`${question} [y/N] `)\n // 光靠 try/catch 不够:输入流结束(而不是 Ctrl+D 中断)时 question() 的\n // Promise **永不 settle**,进程会在事件循环排空后静默退出——没有报错、\n // 没有退出码线索,是最难排查的一类失败。必须同时盯着 close 事件。\n const closed = new Promise<null>((resolve) => rl.once('close', () => resolve(null)))\n answered.catch(() => {}) // 竞速落定后迟到的 reject 不该变成 unhandled\n const answer = await Promise.race([answered, closed])\n if (answer === null) { stream.write('\\n'); return false }\n return /^y(es)?$/i.test(String(answer).trim())\n } catch {\n // Ctrl+D 会让 readline 抛错。在确认提示上这是\"拒绝\",不是故障——\n // 冒到顶层只会变成一条 `错误: Aborted`,既吓人又没信息。\n // 这里返回 false 而不是 fallback:人明确中断了,不能当成默认同意。\n stream.write('\\n')\n return false\n } finally {\n rl.close()\n }\n}\n"]}
@@ -14,8 +14,39 @@ export function upgradeAgent(packageRoot, options = {}) {
14
14
  const prefix = options.prefix || join(home, '.local');
15
15
  const binDir = process.platform === 'win32' ? prefix : join(prefix, 'bin');
16
16
  const executable = join(binDir, process.platform === 'win32' ? 'dta.cmd' : 'dta');
17
+ // 阶段追踪:只发事件,不改任何返回值。不传 onEvent 时整段是纯开销为零的空转。
18
+ let step = 0;
19
+ const total = options.dryRun ? 3 : 7;
20
+ // 观察者抛错不得影响升级:begin 抛错会挡住业务,end 抛错会把已经成功的
21
+ // 安装变成失败抛出,可能诱导用户重试一次真实安装。一律隔离并就地禁用。
22
+ let observer = options.onEvent;
23
+ const track = (id, label, run, meta,
24
+ /** 不给时"没抛错就算成功";verify-env 这种要看内容才知道成没成的必须自定 */
25
+ okOf) => {
26
+ step += 1;
27
+ const emit = (phase, ok, note) => {
28
+ if (!observer)
29
+ return;
30
+ try {
31
+ observer({ step, total, id, label, phase, ok, meta: note });
32
+ }
33
+ catch {
34
+ observer = undefined;
35
+ }
36
+ };
37
+ emit('begin');
38
+ try {
39
+ const value = run();
40
+ emit('end', okOf ? okOf(value) : true, meta?.(value));
41
+ return value;
42
+ }
43
+ catch (error) {
44
+ emit('end', false);
45
+ throw error;
46
+ }
47
+ };
17
48
  // 升级前先冻结本机 Skill 版本,否则重装之后无从核对到底动了什么。
18
- const skillsBefore = skillSuiteStatus(packageRoot, { home }).skills;
49
+ const skillsBefore = track('freeze', '冻结当前 Skill 版本', () => skillSuiteStatus(packageRoot, { home }).skills, (skills) => `${skills.length} 个 Skill`);
19
50
  if (options.channel && options.version) {
20
51
  throw new Error('upgrade 的 --channel 与 --version 不能同时使用');
21
52
  }
@@ -25,11 +56,13 @@ export function upgradeAgent(packageRoot, options = {}) {
25
56
  }
26
57
  const requested = cleanVersion(options.version) || channel;
27
58
  const packageSource = env.DTA_UPGRADE_PACKAGE_SOURCE || `${PACKAGE_NAME}@${requested}`;
28
- const targetVersion = env.DTA_UPGRADE_TARGET_VERSION || resolveTargetVersion(packageSource, env);
29
- if (compareVersions(targetVersion, currentVersion) < 0 && !(options.version && options.force)) {
30
- throw new Error(`升级目标 ${targetVersion} 低于当前版本 ${currentVersion},拒绝自动降级;` +
31
- '如确需降级,请同时使用 --version --force');
32
- }
59
+ const targetVersion = track('resolve', '解析升级目标', () => env.DTA_UPGRADE_TARGET_VERSION || resolveTargetVersion(packageSource, env), (version) => version);
60
+ track('guard', '检查版本关系', () => {
61
+ if (compareVersions(targetVersion, currentVersion) < 0 && !(options.version && options.force)) {
62
+ throw new Error(`升级目标 ${targetVersion} 低于当前版本 ${currentVersion},拒绝自动降级;` +
63
+ '如确需降级,请同时使用 --version 与 --force');
64
+ }
65
+ }, () => `${currentVersion} → ${targetVersion}`);
33
66
  const installArgs = [
34
67
  'install', '--global', '--prefix', prefix, '--registry', REGISTRY, packageSource,
35
68
  ];
@@ -52,47 +85,63 @@ export function upgradeAgent(packageRoot, options = {}) {
52
85
  commands: { install: installCommand, verify: verifyCommand },
53
86
  };
54
87
  }
55
- if (changed) {
88
+ track('install', `安装 ${packageSource}`, () => {
89
+ if (!changed)
90
+ return;
56
91
  const install = spawnSync('npm', installArgs, { encoding: 'utf8', env, timeout: 120_000 });
57
92
  const output = [install.stdout, install.stderr].filter(Boolean).join('\n').trim();
58
93
  if (install.error || install.status !== 0) {
59
94
  throw new Error(`CLI 升级失败: ${install.error?.message || output || `退出码 ${install.status}`}\n` +
60
95
  `可手动执行: ${installCommand}`);
61
96
  }
62
- }
63
- if (!existsSync(executable)) {
64
- throw new Error(`npm 升级完成但找不到新 CLI: ${executable}\n可手动执行: ${installCommand}`);
65
- }
97
+ }, () => changed ? targetVersion : '未变更,跳过');
98
+ track('locate', '定位新 CLI', () => {
99
+ if (!existsSync(executable)) {
100
+ throw new Error(`npm 升级完成但找不到新 CLI: ${executable}\n可手动执行: ${installCommand}`);
101
+ }
102
+ }, () => executable);
66
103
  const effectiveEnv = {
67
104
  ...env,
68
105
  HOME: home,
69
106
  PATH: [binDir, env.PATH || ''].filter(Boolean).join(delimiter),
70
107
  };
71
- const versionRun = spawnSync(executable, ['--version'], {
72
- encoding: 'utf8', env: effectiveEnv, timeout: 8_000,
73
- });
74
- const installedVersion = String(versionRun.stdout || '').trim();
75
- if (versionRun.status !== 0 || installedVersion !== targetVersion) {
76
- throw new Error(`新 CLI 版本校验失败: expected=${targetVersion}, actual=${installedVersion || 'unknown'}\n` +
77
- `可重试: ${installCommand}`);
78
- }
79
- const setupRun = spawnSync(executable, ['setup', '--skip-cli-install', '--json'], {
80
- encoding: 'utf8', env: effectiveEnv, timeout: 180_000,
81
- });
82
- let setup = null;
83
- try {
84
- setup = setupRun.stdout ? JSON.parse(setupRun.stdout) : null;
85
- }
86
- catch { /* reported below */ }
87
- if (!setup) {
88
- throw new Error(`CLI 已升级到 ${installedVersion},但 setup 输出无法解析: ` +
89
- `${String(setupRun.stderr || setupRun.stdout).trim() || `退出码 ${setupRun.status}`}\n` +
90
- `可手动执行: ${verifyCommand}`);
91
- }
108
+ const installedVersion = track('verify-cli', '校验新 CLI 版本', () => {
109
+ const versionRun = spawnSync(executable, ['--version'], {
110
+ encoding: 'utf8', env: effectiveEnv, timeout: 8_000,
111
+ });
112
+ const actual = String(versionRun.stdout || '').trim();
113
+ if (versionRun.status !== 0 || actual !== targetVersion) {
114
+ throw new Error(`新 CLI 版本校验失败: expected=${targetVersion}, actual=${actual || 'unknown'}\n` +
115
+ `可重试: ${installCommand}`);
116
+ }
117
+ return actual;
118
+ }, (version) => version);
119
+ // spawn 必须在 track 内部:这是整条链路最长的一步(180 秒预算),
120
+ // 放到外面等于最该有进度的地方反而静默。
121
+ let setupStatus = 0;
122
+ const setup = track('verify-env', '复核环境', () => {
123
+ const setupRun = spawnSync(executable, ['setup', '--skip-cli-install', '--json'], {
124
+ encoding: 'utf8', env: effectiveEnv, timeout: 180_000,
125
+ });
126
+ setupStatus = setupRun.status ?? 1;
127
+ let parsed = null;
128
+ try {
129
+ parsed = setupRun.stdout ? JSON.parse(setupRun.stdout) : null;
130
+ }
131
+ catch { /* reported below */ }
132
+ if (!parsed) {
133
+ throw new Error(`CLI 已升级到 ${installedVersion},但 setup 输出无法解析: ` +
134
+ `${String(setupRun.stderr || setupRun.stdout).trim() || `退出码 ${setupRun.status}`}\n` +
135
+ `可手动执行: ${verifyCommand}`);
136
+ }
137
+ return parsed;
138
+ }, (parsed) => parsed?.doctor?.ready === true ? '全部 ready' : '部分未 ready',
139
+ // 不能只看"JSON 解析出来了"就打 ✔:那会渲染成自相矛盾的 "✔ 部分未 ready"
140
+ (parsed) => setupStatus === 0 && parsed?.doctor?.ready === true);
92
141
  return {
93
142
  $schema: 'dingtalk-agent/upgrade@1', currentVersion, targetVersion,
94
143
  target: packageSource, prefix, executable, changed, dryRun: false,
95
- ready: setupRun.status === 0 && setup.doctor?.ready === true,
144
+ ready: setupStatus === 0 && setup.doctor?.ready === true,
96
145
  setup,
97
146
  skills: skillsBefore.map((item) => {
98
147
  const after = (setup?.skills || []).find((entry) => entry?.name === item.name);
@@ -108,6 +157,37 @@ export function upgradeAgent(packageRoot, options = {}) {
108
157
  commands: { install: installCommand, verify: verifyCommand },
109
158
  };
110
159
  }
160
+ /**
161
+ * 只解析升级目标,不碰磁盘、不装任何东西。
162
+ *
163
+ * 独立于 upgradeAgent 的 Schema:`upgrade@1` 是 release-readiness 和契约
164
+ * eval 的解析对象,不能为了一个只读命令给它加字段。
165
+ */
166
+ export function checkUpgrade(packageRoot, options = {}) {
167
+ const env = { ...process.env, ...(options.env || {}) };
168
+ const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
169
+ const currentVersion = String(pkg.version || '');
170
+ if (options.channel && options.version) {
171
+ throw new Error('upgrade 的 --channel 与 --version 不能同时使用');
172
+ }
173
+ const channel = options.channel || (currentVersion.includes('-') ? 'beta' : 'latest');
174
+ if (!['beta', 'latest'].includes(channel)) {
175
+ throw new Error(`不支持的升级通道: ${channel};只能是 beta 或 latest`);
176
+ }
177
+ const requested = cleanVersion(options.version) || channel;
178
+ const packageSource = env.DTA_UPGRADE_PACKAGE_SOURCE || `${PACKAGE_NAME}@${requested}`;
179
+ const targetVersion = env.DTA_UPGRADE_TARGET_VERSION || resolveTargetVersion(packageSource, env);
180
+ const order = compareVersions(targetVersion, currentVersion);
181
+ return {
182
+ $schema: 'dingtalk-agent/upgrade-check@1',
183
+ currentVersion, targetVersion, target: packageSource,
184
+ // 用户显式给过 source(本地路径、tarball 等)就原样保留,只有默认的
185
+ // `pkg@tag` 这种可变来源才需要钉死。
186
+ pinnedSource: env.DTA_UPGRADE_PACKAGE_SOURCE || `${PACKAGE_NAME}@${targetVersion}`,
187
+ channel: channel,
188
+ relation: order > 0 ? 'upgrade' : order < 0 ? 'downgrade' : 'same',
189
+ };
190
+ }
111
191
  function resolveTargetVersion(packageSource, env) {
112
192
  const run = spawnSync('npm', ['view', packageSource, 'version', '--json', '--registry', REGISTRY], {
113
193
  encoding: 'utf8', env, timeout: 30_000,