tt-help-cli-ycl 1.4.183 → 1.4.185

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.
@@ -31,8 +31,10 @@ const RESTART_SPAWN_STAGGER_MS = 60000; // 启动错峰 60s
31
31
  const STALE_LOG_TIMEOUT_MS = 15 * 60 * 1000; // 15 分钟无日志输出视为假死
32
32
 
33
33
  // === 控制台日志格式化(仅显示层增强,不影响任何功能链路) ===
34
- // 目标:多进程日志交错打印时,通过时间戳 + 固定宽度彩色 [cmdId] 前缀方便人眼分流。
34
+ // 目标:多进程日志交错打印时,通过时间戳 + 固定宽度彩色 [程序名:端口] 前缀方便人眼分流。
35
35
  // 兼容:Windows(Windows Terminal 彩色 / 旧 conhost 自动降级纯文本)与 macOS(原生 ANSI 彩色)。
36
+ // 前缀标签由 deriveDisplayLabel 从命令行派生(如 [explore:9223]/[explore:9223-9225]),
37
+ // 解析失败退回 cmdDef.name || id。内部 cmdId 完全不动(stop/toggle/日志拉取/LLM 分析按它索引)。
36
38
 
37
39
  // 每进程固定一种颜色(按 cmdId 哈希取色),新增进程自动分配且不重复
38
40
  const PROC_COLORS = [32, 36, 33, 35, 34, 92, 96, 93, 95, 94];
@@ -141,6 +143,61 @@ function splitCommand(cmdLine) {
141
143
  return parts;
142
144
  }
143
145
 
146
+ // === 显示层标签派生(仅显示层增强,不影响任何功能链路;2026-11-04 定稿) ===
147
+ // 规则:[程序名:端口],端口解析不到就只显示程序名,程序名也解析不到退回 fallbackId。
148
+ // 端口提取复用 extractCdpPorts 原样逻辑(多端口区间压缩为 首-尾,端口天然连续)。
149
+
150
+ // 启动器 token(basename 去掉 .exe/.cmd/.bat 后比对):其后的 token 才是程序名/子命令
151
+ const LAUNCHER_RE = /^(?:tt-help|node|npx)(?:\.(?:exe|cmd|bat))?$/i;
152
+ const SCRIPT_EXT_RE = /\.(?:mjs|cjs|js)$/i;
153
+
154
+ // 跨平台 basename(同时处理 / 与 Windows \)
155
+ function pathBaseName(p) {
156
+ const s = String(p || "");
157
+ const idx = Math.max(s.lastIndexOf("/"), s.lastIndexOf("\\"));
158
+ return idx >= 0 ? s.slice(idx + 1) : s;
159
+ }
160
+
161
+ /**
162
+ * 从拆分后的 token 中解析程序名。
163
+ * token[0] 为启动器(tt-help/node/npx)时取 token[1];
164
+ * 取到的 token 是脚本文件路径(.js/.mjs/.cjs)时:
165
+ * - 后面还有非 flag token(子命令)→ 取子命令(node src/main.js watch → watch)
166
+ * - 否则取 basename 去扩展名(node scraper.js --port X → scraper)
167
+ * 解析不出返回 null(调用方退回 fallbackId)。
168
+ */
169
+ function deriveProgramName(tokens) {
170
+ if (!Array.isArray(tokens) || tokens.length === 0) return null;
171
+ let idx = 0;
172
+ const firstBase = pathBaseName(tokens[0]).replace(/\.(?:exe|cmd|bat)$/i, "");
173
+ if (LAUNCHER_RE.test(firstBase) && tokens.length > 1) idx = 1;
174
+ const base = pathBaseName(tokens[idx]);
175
+ if (SCRIPT_EXT_RE.test(base)) {
176
+ const next = tokens[idx + 1];
177
+ if (next && !next.startsWith("-")) return next;
178
+ return base.replace(SCRIPT_EXT_RE, "") || null;
179
+ }
180
+ return base || null;
181
+ }
182
+
183
+ /**
184
+ * 派生日志前缀显示标签:[程序名:端口](纯函数,导出供单测覆盖全部分支)。
185
+ * - tt-help explore --base-port 9223 --port-count 3 → "explore:9223-9225"
186
+ * - tt-help refresh-mini --port 5222 → "refresh-mini:5222"(单端口仅浏览器类命令提取)
187
+ * - tt-help comments(无端口参数) → "comments"(不猜默认端口,避免误导)
188
+ * - 全部解析不出 → fallbackId(即 cmdDef.name || id,兜底回现状)
189
+ */
190
+ export function deriveDisplayLabel(cmdLine, fallbackId) {
191
+ const tokens = splitCommand(String(cmdLine || ""));
192
+ if (tokens.length === 0) return fallbackId;
193
+ const prog = deriveProgramName(tokens);
194
+ if (!prog) return fallbackId;
195
+ const ports = extractCdpPorts(String(cmdLine || ""));
196
+ if (ports.length === 1) return `${prog}:${ports[0]}`;
197
+ if (ports.length > 1) return `${prog}:${ports[0]}-${ports[ports.length - 1]}`;
198
+ return prog;
199
+ }
200
+
144
201
  function getProcessStats(pid) {
145
202
  try {
146
203
  if (IS_WIN) {
@@ -244,7 +301,7 @@ function getChildPid(parentPid) {
244
301
  export class ProcessManager extends EventEmitter {
245
302
  constructor() {
246
303
  super();
247
- /** @type {Map<string, {cmdId:string, cmdDef:object, child:ChildProcess, startTime:number, restartCount:number, thresholds:object, stopped:boolean}>} */
304
+ /** @type {Map<string, {cmdId:string, cmdDef:object, displayLabel:string, child:ChildProcess, startTime:number, restartCount:number, thresholds:object, stopped:boolean}>} */
248
305
  this.processes = new Map();
249
306
  /** @type {Map<string, number>} cmdId → 连续异常重启次数(跨实例累计;稳定运行后清零)
250
307
  * 2026-09-25 修复: 原先计数存在每个实例的 info 上,自动重启重建 info 后归零,
@@ -258,6 +315,12 @@ export class ProcessManager extends EventEmitter {
258
315
  */
259
316
  startCommand(cmdDef) {
260
317
  let { id, command, args = [] } = cmdDef;
318
+ // 显示层标签(仅显示用;cmdId 不动)。在 splitCommand 改写 command/args 前派生,
319
+ // 重启路径(自动重启/假死/阈值)都经 startCommand,标签随之重建。
320
+ const displayLabel = deriveDisplayLabel(
321
+ [command, ...(Array.isArray(args) ? args : [])].join(" "),
322
+ (cmdDef && cmdDef.name) || id,
323
+ );
261
324
  if (this.processes.has(id)) {
262
325
  // 已有进程在运行(或 stopCommand 还未清理),跳过
263
326
  const existing = this.processes.get(id);
@@ -309,6 +372,7 @@ export class ProcessManager extends EventEmitter {
309
372
  this.processes.set(id, {
310
373
  cmdId: id,
311
374
  cmdDef,
375
+ displayLabel,
312
376
  child: null,
313
377
  startTime,
314
378
  restartCount: 0,
@@ -327,9 +391,8 @@ export class ProcessManager extends EventEmitter {
327
391
  if (msg) {
328
392
  const short = msg.substring(0, 200);
329
393
  // 仅显示层格式化:时间戳 + 彩色固定宽度前缀,内容不变。
330
- // 前缀优先用命令名(如 explore-F1),未命名时退回 cmdId。
331
- const displayId = (cmdDef && cmdDef.name) || id;
332
- printProcessLine(displayId, short);
394
+ // 前缀用启动时派生的 [程序名:端口] 标签,解析失败已退回 name||id。
395
+ printProcessLine(displayLabel, short);
333
396
  // 环形缓冲:保留最近 N 行供 LLM 分析
334
397
  const info = this.processes.get(id);
335
398
  if (info) {
@@ -388,6 +451,7 @@ export class ProcessManager extends EventEmitter {
388
451
  this.processes.set(id, {
389
452
  cmdId: id,
390
453
  cmdDef,
454
+ displayLabel,
391
455
  child,
392
456
  startTime,
393
457
  restartCount: 0,
@@ -466,7 +530,8 @@ export class ProcessManager extends EventEmitter {
466
530
 
467
531
  result.push({
468
532
  cmdId,
469
- name: info.cmdDef.name || cmdId,
533
+ // 显示层 name 与日志前缀同源([程序名:端口]),web 页面进程列表与日志一致
534
+ name: info.displayLabel || info.cmdDef.name || cmdId,
470
535
  pid,
471
536
  status: info.child
472
537
  ? info.child.exitCode === null
@@ -88,6 +88,8 @@ export function getClientConfig() {
88
88
  maxComments: cfg.maxComments,
89
89
  maxFollowing: cfg.maxFollowing,
90
90
  maxFollowers: cfg.maxFollowers,
91
+ // 内存阈值覆盖(HealthChecker 用;顶层级键——cfg.browser 是 Edge 路径字符串,无法嵌套)
92
+ memoryThreshold: cfg.memoryThreshold,
91
93
  };
92
94
  }
93
95
  } catch {