dsh-plugin-teamflow 0.1.8 → 0.1.9

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/lib/client.js CHANGED
@@ -51,6 +51,15 @@ window.__ModuleLoader__.load({
51
51
  parameters: [],
52
52
  result: strict
53
53
  },
54
+ {
55
+ id: "dsh-plugin-teamflow#teamflow/setLocale",
56
+ service: "teamflow",
57
+ namespace: "teamflow",
58
+ method: "setLocale",
59
+ invocation: { kind: "direct" },
60
+ parameters: [p("locale")],
61
+ result: strict
62
+ },
54
63
  {
55
64
  id: "dsh-plugin-teamflow#teamflow/list",
56
65
  service: "teamflow",
@@ -287,7 +296,52 @@ window.__ModuleLoader__.load({
287
296
  * 会话内工作台(index.tsx)与全局面板(panel.tsx)共用:**只放无状态纯展示件**,
288
297
  * 不放任何 Remote 调用或会话上下文逻辑——两边数据来源不同(sessionId / productKey),
289
298
  * 展示语言必须一致。
299
+ *
300
+ * 双语(v0.1.9):文案统一走宿主 locale 服务(机制说明见 client/locales.ts)。
301
+ * 组件里能拿到注入的 `t`,但**词表/格式化/折叠件是纯函数**,拿不到 prop,
302
+ * 故由 `apply()` 调 `setTranslator()` 注入模块级翻译函数:`bind()` 每次调用都读当前语言,
303
+ * 且宿主在切语言时会重渲染每个 slot outlet(ui-renderer `useLocaleRevision`),
304
+ * 因此模块级函数不会持有过期语言。
290
305
  */
306
+ /** 翻译函数(默认恒等:未注入时显示 key 本身,便于发现漏注册)。 */
307
+ let translate = (key) => key;
308
+ /** 当前语言 id 的读取器(默认 en)。 */
309
+ let localeIdOf = () => "en";
310
+ /**
311
+ * 注入翻译函数与语言读取器(apply 时调用一次)。
312
+ * @param fn - `ctx.locale.bind(NS)` 的返回值(调用时读当前语言)。
313
+ * @param idOf - 返回当前语言 id 的函数(如 `() => ctx.locale.getSnapshot().active`)。
314
+ */
315
+ function setTranslator(fn, idOf) {
316
+ if (typeof fn === "function") translate = fn;
317
+ if (typeof idOf === "function") localeIdOf = idOf;
318
+ }
319
+ /** 翻译(`{name}` 占位符由宿主替换)。 */
320
+ const t = (key, params) => translate(key, params);
321
+ /** 当前语言的 BCP 47 标签(时间格式化用;未知语言回退 en)。 */
322
+ function localeTag() {
323
+ let id = "en";
324
+ try {
325
+ id = String(localeIdOf() || "en");
326
+ } catch (e) {}
327
+ return id === "zh" ? "zh-CN" : id;
328
+ }
329
+ /** 词表查表:命中返回译文,未命中回退原始值(未知状态/角色等)。 */
330
+ function vocab(prefix, raw) {
331
+ const key = `${prefix}.${raw}`;
332
+ const hit = t(key);
333
+ return hit === key ? String(raw === null || raw === void 0 ? "" : raw) : hit;
334
+ }
335
+ const stText = (s) => vocab("status", s);
336
+ const runStatusText = (s) => vocab("runStatus", s);
337
+ const kindTitle = (k) => vocab("kind", k);
338
+ const roleName = (r) => vocab("role", r);
339
+ /** 角色 chip(带图标;未知角色回退「⚙️ <raw>」)。 */
340
+ function roleChip(r) {
341
+ const key = `roleChip.${r}`;
342
+ const hit = t(key);
343
+ return hit === key ? `⚙️ ${String(r)}` : hit;
344
+ }
291
345
  const T = {
292
346
  bg: "var(--dsw-alias-bg-base)",
293
347
  layer1: "var(--dsw-alias-bg-layer-1)",
@@ -301,30 +355,7 @@ window.__ModuleLoader__.load({
301
355
  success: "var(--dsw-alias-state-success-primary)",
302
356
  warn: "var(--dsw-alias-state-warn-primary)"
303
357
  };
304
- const STATUS_TEXT = {
305
- created: "立项",
306
- "in-progress": "进行中",
307
- "pending-acceptance": "待验收",
308
- accepted: "已验收",
309
- closed: "已关闭",
310
- pending: "待办",
311
- running: "开发中",
312
- testable: "待测试",
313
- testing: "测试中",
314
- rework: "打回",
315
- "needs-human": "需人工",
316
- cancelled: "已关闭",
317
- open: "待认领",
318
- claimed: "处理中",
319
- fixed: "已修复待验",
320
- verified: "已关闭",
321
- reopened: "重开",
322
- done: "已完成",
323
- completed: "已完成",
324
- failed: "失败",
325
- interrupted: "已中断",
326
- superseded: "已取代"
327
- };
358
+ /** 状态色表(与语言无关的纯视觉映射)。 */
328
359
  const STATUS_COLOR = {
329
360
  created: T.text2,
330
361
  pending: T.text2,
@@ -349,7 +380,7 @@ window.__ModuleLoader__.load({
349
380
  interrupted: T.warn,
350
381
  superseded: T.text2
351
382
  };
352
- /** 阶段英文键 → 图标/中文展示名(2026-09-06 英文化:journal.phase 为英文键,展示名统一走映射——未来 i18n 换表即换语言)。 */
383
+ /** 阶段英文键 → 图标(2026-09-06 英文化:journal.phase 为英文键,展示名统一走词表——换语言即换表)。 */
353
384
  const PHASE_ICON = {
354
385
  prd: "📋",
355
386
  design: "🎨",
@@ -359,17 +390,6 @@ window.__ModuleLoader__.load({
359
390
  qa: "🧪",
360
391
  acceptance: "✅"
361
392
  };
362
- const PHASE_NAME = {
363
- prd: "PRD 产品需求",
364
- design: "UI/UX 设计",
365
- scaffold: "架构规划",
366
- tech: "技术方案",
367
- dev: "开发",
368
- qa: "QA 测试",
369
- acceptance: "产品验收"
370
- };
371
- const phaseNameOf = (p) => PHASE_NAME[p] || p || "—";
372
- const phaseIconOf = (p) => PHASE_ICON[p] || "⚙️";
373
393
  /** phase 归一:英文键直通;存量中文映射(防御性——新数据全英文)。 */
374
394
  const phaseKeyOf = (p) => ({
375
395
  "PRD 产品需求": "prd",
@@ -380,15 +400,21 @@ window.__ModuleLoader__.load({
380
400
  "QA 测试": "qa",
381
401
  "产品验收": "acceptance"
382
402
  })[p] || String(p || "");
383
- const RUN_STATUS_TEXT = {
384
- pending: "等待中",
385
- running: "进行中",
386
- completed: "已完成",
387
- failed: "失败",
388
- cancelled: "已取消",
389
- interrupted: "已中断",
390
- superseded: "已取代"
391
- };
403
+ const phaseNameOf = (p) => vocab("phase", phaseKeyOf(p));
404
+ const phaseIconOf = (p) => PHASE_ICON[phaseKeyOf(p)] || "⚙️";
405
+ /**
406
+ * 阶段的展示名(双语安全的取法)。
407
+ *
408
+ * journal 里 `stage.label` 是**持久化中文**(teams.json 配置 + 历史数据),而每个阶段都带
409
+ * 英文 `phase` 键——所以展示时:任务级阶段(dev 子卡,`taskKey` 有值)保留任务名(LLM 数据,
410
+ * 不该翻译),其余阶段用 `phase` 键查当前语言词表。**只影响展示,不动数据**:
411
+ * `__taskKey`/`taskKeyOf` 的任务聚合身份仍走 label 清理(聚合语义不能随语言变)。
412
+ */
413
+ function stageLabelOf(s) {
414
+ const raw = String(s && s.label || "");
415
+ if (s && s.taskKey) return raw || String(s.taskKey);
416
+ return (s && s.phase ? phaseNameOf(s.phase) : "") || raw;
417
+ }
392
418
  const COLUMNS = {
393
419
  req: [
394
420
  "created",
@@ -418,11 +444,6 @@ window.__ModuleLoader__.load({
418
444
  "needs-human"
419
445
  ]
420
446
  };
421
- const KIND_TITLE = {
422
- req: "需求",
423
- task: "任务",
424
- bug: "缺陷"
425
- };
426
447
  const h = react.default.createElement;
427
448
  const MONO = "ui-monospace, SFMono-Regular, Consolas, \"Cascadia Mono\", monospace";
428
449
  const SANS = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif";
@@ -461,9 +482,9 @@ window.__ModuleLoader__.load({
461
482
  function FoldableText({ text, charLimit = 280, lineLimit = 5, style }) {
462
483
  const [open, setOpen] = react.default.useState(false);
463
484
  if (!text) return null;
464
- const t = String(text);
465
- const lines = t.split("\n");
466
- const compact = lines.length <= lineLimit && t.length <= charLimit;
485
+ const s = String(text);
486
+ const lines = s.split("\n");
487
+ const compact = lines.length <= lineLimit && s.length <= charLimit;
467
488
  const body = (txt) => h("div", { style: {
468
489
  fontSize: 11.5,
469
490
  color: T.text,
@@ -472,10 +493,10 @@ window.__ModuleLoader__.load({
472
493
  wordBreak: "break-word",
473
494
  ...style || {}
474
495
  } }, txt);
475
- if (compact) return body(t);
476
- if (open) return h("div", null, body(t), h("button", {
496
+ if (compact) return body(s);
497
+ if (open) return h("div", null, body(s), h("button", {
477
498
  onClick: () => setOpen(false),
478
- title: "收起全文",
499
+ title: t("common.collapseFull"),
479
500
  style: {
480
501
  marginTop: 3,
481
502
  font: "inherit",
@@ -487,12 +508,12 @@ window.__ModuleLoader__.load({
487
508
  padding: 0,
488
509
  cursor: "pointer"
489
510
  }
490
- }, "收起"));
491
- const pre = lines.length > lineLimit ? lines.slice(0, lineLimit).join("\n") : t.slice(0, charLimit);
492
- const more = lines.length > lineLimit ? `… +${lines.length - lineLimit} 行` : "…";
511
+ }, t("common.collapse")));
512
+ const pre = lines.length > lineLimit ? lines.slice(0, lineLimit).join("\n") : s.slice(0, charLimit);
513
+ const more = lines.length > lineLimit ? t("common.moreLines", { n: lines.length - lineLimit }) : "…";
493
514
  return h("div", null, body(pre), h("button", {
494
515
  onClick: () => setOpen(true),
495
- title: "点击查看全文",
516
+ title: t("common.clickToExpand"),
496
517
  style: {
497
518
  marginTop: 3,
498
519
  font: "inherit",
@@ -504,11 +525,11 @@ window.__ModuleLoader__.load({
504
525
  padding: 0,
505
526
  cursor: "pointer"
506
527
  }
507
- }, `展开全文${more}`));
528
+ }, `${t("common.expandFull")}${more}`));
508
529
  }
509
- function fmtTime(t) {
510
- if (!t) return "—";
511
- const d = new Date(t);
530
+ function fmtTime(tm) {
531
+ if (!tm) return "—";
532
+ const d = new Date(tm);
512
533
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
513
534
  }
514
535
  function fmtDur(a, b) {
@@ -545,9 +566,15 @@ window.__ModuleLoader__.load({
545
566
  if (s.usage) {
546
567
  const u = s.usage;
547
568
  const hit = hitRate(u);
548
- return `输入(未命中) ${k(u.input)} / 输入(命中) ${k(u.cacheRead)} / 写缓存 ${k(u.cacheWrite)} / 输出 ${k(u.output)} · ${u.calls} 次调用${hit !== null ? ` · 缓存命中 ${hit}%` : ""}`;
569
+ return t("token.usageLine", {
570
+ input: k(u.input),
571
+ cacheRead: k(u.cacheRead),
572
+ cacheWrite: k(u.cacheWrite),
573
+ output: k(u.output),
574
+ calls: u.calls
575
+ }) + (hit !== null ? t("token.usageHit", { hit }) : "");
549
576
  }
550
- return "无 usage 明细";
577
+ return t("token.usageMissing");
551
578
  }
552
579
  /** 节点卡主 token 行:官方口径 —— 输入(未命中)/输入(命中)/输出 + 缓存命中率。 */
553
580
  function stageUsageLine(s) {
@@ -558,16 +585,6 @@ window.__ModuleLoader__.load({
558
585
  }
559
586
  return null;
560
587
  }
561
- const ROLE_NAME = {
562
- pm: "产品",
563
- design: "设计",
564
- arch: "架构",
565
- tech: "方案",
566
- dev: "开发",
567
- qa: "测试",
568
- acceptance: "验收",
569
- other: "其他"
570
- };
571
588
  const roleUsage = (u) => {
572
589
  if (!u) return "";
573
590
  const hit = hitRate(u);
@@ -576,11 +593,11 @@ window.__ModuleLoader__.load({
576
593
  /** 任务卡按角色累计的真实 token 摘要(官方口径:未命中/命中输入 + 输出 + 命中率)。 */
577
594
  function byRoleLine(task) {
578
595
  const roles = task && task.byRole || {};
579
- return Object.keys(roles).filter((k) => roles[k] && roles[k].input + roles[k].output + roles[k].cacheRead + roles[k].cacheWrite > 0).map((k) => `${ROLE_NAME[k] || k} ${roleUsage(roles[k])}`).join(" · ");
596
+ return Object.keys(roles).filter((k) => roles[k] && roles[k].input + roles[k].output + roles[k].cacheRead + roles[k].cacheWrite > 0).map((k) => `${roleName(k)} ${roleUsage(roles[k])}`).join(" · ");
580
597
  }
581
598
  /** 多阶段 usage 汇总(官方口径)。 */
582
599
  function totalUsage(stages) {
583
- const t = {
600
+ const sum = {
584
601
  input: 0,
585
602
  cacheRead: 0,
586
603
  cacheWrite: 0,
@@ -590,17 +607,559 @@ window.__ModuleLoader__.load({
590
607
  for (const s of stages || []) {
591
608
  const u = s && s.usage;
592
609
  if (!u) continue;
593
- t.input += u.input || 0;
594
- t.cacheRead += u.cacheRead || 0;
595
- t.cacheWrite += u.cacheWrite || 0;
596
- t.output += u.output || 0;
597
- t.calls += u.calls || 0;
610
+ sum.input += u.input || 0;
611
+ sum.cacheRead += u.cacheRead || 0;
612
+ sum.cacheWrite += u.cacheWrite || 0;
613
+ sum.output += u.output || 0;
614
+ sum.calls += u.calls || 0;
598
615
  }
599
- return t;
616
+ return sum;
600
617
  }
601
- const stText = (s) => STATUS_TEXT[s] || s;
602
618
  const stColor = (s) => STATUS_COLOR[s] || T.text2;
603
619
  //#endregion
620
+ //#region client/locales.ts
621
+ /**
622
+ * dsh-plugin-teamflow — 客户端文案词典(zh / en)。
623
+ *
624
+ * 机制:走宿主 `ctx.locale`(@deepseek-ai/dsh-client-locale),不自建 i18n:
625
+ * - `apply()` 里 `ctx.locale.register(NS, { zh, en })` 注册两个词典;
626
+ * - `ctx.locale.bind(NS)` 取翻译函数(调用时读当前语言,故可常驻模块变量);
627
+ * - slot 注册项声明 `locale: NS`,切语言时宿主重渲染每个 outlet
628
+ * (ui-renderer `useLocaleRevision`),面板/tab 名称用 thunk `() => t(...)`。
629
+ *
630
+ * 约定:
631
+ * - key 扁平点号命名,按区域前缀分组(common/status/phase/kind/role/stage/…);
632
+ * - 占位符用 `{name}`(宿主 `translate` 的替换语法);
633
+ * - **zh 与 en 的 key 集合必须完全一致**(en 是兜底语言,缺 key 会直接显示 key 本身;
634
+ * 由 test/smoke.js 断言守门);
635
+ * - 数据面文案(LLM 产出的标题/摘要、teams.json 的团队名、journal 里既有中文 label)不在此表,
636
+ * 属 host/产物语言层,见 AGENTS §5「客户端面」锚点。
637
+ */
638
+ /** 词典命名空间(宿主 LocaleRuntime 的单占位命名空间)。 */
639
+ const NS = "teamflow";
640
+ /** 中文词典(key 为单一事实来源,en 必须与之同形)。 */
641
+ const zh = {
642
+ "common.close": "关闭",
643
+ "common.loading": "加载中…",
644
+ "common.retry": "重试",
645
+ "common.collapse": "收起",
646
+ "common.collapseFull": "收起全文",
647
+ "common.clickToExpand": "点击查看全文",
648
+ "common.expandFull": "展开全文",
649
+ "common.moreLines": "… +{n} 行",
650
+ "common.none": "无",
651
+ "common.noneDash": "(暂无)",
652
+ "common.noSummary": "(无摘要)",
653
+ "common.failed": "失败",
654
+ "common.running": "进行中",
655
+ "common.needsHuman": "需人工介入",
656
+ "common.unknownError": "未知错误",
657
+ "common.gotIt": "知道了",
658
+ "common.remoteNotReady": "remote 未就绪",
659
+ "common.remoteCallFailed": "{what} 调用失败:{detail}",
660
+ "common.remoteEmptyResult": "{what} 返回空结果(ok=true 但无 value;原始信封={raw})",
661
+ "common.calls": "{n} 次",
662
+ "common.callsCount": "{n} 次调用",
663
+ "common.noUsage": "无 token 数据",
664
+ "status.created": "立项",
665
+ "status.in-progress": "进行中",
666
+ "status.pending-acceptance": "待验收",
667
+ "status.accepted": "已验收",
668
+ "status.closed": "已关闭",
669
+ "status.pending": "待办",
670
+ "status.running": "开发中",
671
+ "status.testable": "待测试",
672
+ "status.testing": "测试中",
673
+ "status.rework": "打回",
674
+ "status.needs-human": "需人工",
675
+ "status.cancelled": "已关闭",
676
+ "status.open": "待认领",
677
+ "status.claimed": "处理中",
678
+ "status.fixed": "已修复待验",
679
+ "status.verified": "已关闭",
680
+ "status.reopened": "重开",
681
+ "status.done": "已完成",
682
+ "status.completed": "已完成",
683
+ "status.failed": "失败",
684
+ "status.interrupted": "已中断",
685
+ "status.superseded": "已取代",
686
+ "runStatus.pending": "等待中",
687
+ "runStatus.running": "进行中",
688
+ "runStatus.completed": "已完成",
689
+ "runStatus.failed": "失败",
690
+ "runStatus.cancelled": "已取消",
691
+ "runStatus.interrupted": "已中断",
692
+ "runStatus.superseded": "已取代",
693
+ "phase.prd": "PRD 产品需求",
694
+ "phase.design": "UI/UX 设计",
695
+ "phase.scaffold": "架构规划",
696
+ "phase.tech": "技术方案",
697
+ "phase.dev": "开发",
698
+ "phase.qa": "QA 测试",
699
+ "phase.acceptance": "产品验收",
700
+ "kind.req": "需求",
701
+ "kind.task": "任务",
702
+ "kind.bug": "缺陷",
703
+ "role.pm": "产品",
704
+ "role.design": "设计",
705
+ "role.arch": "架构",
706
+ "role.tech": "方案",
707
+ "role.dev": "开发",
708
+ "role.qa": "测试",
709
+ "role.acceptance": "验收",
710
+ "role.other": "其他",
711
+ "roleChip.pm": "📌 产品",
712
+ "roleChip.design": "🎨 设计",
713
+ "roleChip.arch": "🏗 架构",
714
+ "roleChip.tech": "📐 方案",
715
+ "roleChip.dev": "👨‍💻 开发",
716
+ "roleChip.qa": "🧪 QA",
717
+ "roleChip.acceptance": "✅ 验收",
718
+ "token.officialTitle": "TOKEN · 官方口径",
719
+ "token.usageLine": "输入(未命中) {input} / 输入(命中) {cacheRead} / 写缓存 {cacheWrite} / 输出 {output} · {calls} 次调用",
720
+ "token.usageHit": " · 缓存命中 {hit}%",
721
+ "token.usageMissing": "无 usage 明细",
722
+ "token.inputMiss": "输入(未命中) {v}",
723
+ "token.inputHit": "输入(命中) {v}",
724
+ "token.cacheWrite": "写缓存 {v}",
725
+ "token.output": "输出 {v}",
726
+ "token.total": "合计 {v}",
727
+ "token.allStagesTip": "输入(未命中)/输入(命中)/输出 全部阶段合计",
728
+ "token.stageLine": "阶段 token:{v}",
729
+ "stage.cardTip": "{label} —— 点击查看阶段详情",
730
+ "stage.retryTip": "重试 {n} 次(共 {m} 次尝试)",
731
+ "stage.detailTitle": "阶段详情",
732
+ "stage.summaryFallback": "(该 run 未保存完整正文,展示摘要)\n\n{summary}",
733
+ "stage.loadFailed": "⚠ 加载失败:{err}",
734
+ "stage.noOutput": "(无产物正文)",
735
+ "stage.attemptHistory": "↻ 尝试历史({n} 次 · 点击查看该次详情)",
736
+ "stage.attemptDone": "✅ 成功",
737
+ "stage.attemptFailed": "❌ {outcome}",
738
+ "stage.attemptRunning": "⏳ 进行中",
739
+ "stage.childJumpBtn": "🎬 跳转子代理会话",
740
+ "stage.childJumpTip": "跳转到该阶段子代理会话(完整推理与工具调用轨迹);跳转后请切换「对话」tab 查看",
741
+ "stage.childNoneTip": "该阶段无可用子代理会话",
742
+ "stage.childCrossSessionTip": "该子代理由会话 {sid} 发起,跨会话跳转暂不支持——请打开其发起会话的团队工作台查看",
743
+ "stage.childCrossSessionNote": "跨会话暂不支持:该子代理由会话 {sid} 发起。如需查看轨迹,请打开其发起会话的团队工作台。",
744
+ "stage.childJumpNote": "跳转成功后,请切「对话」tab 查看该子代理的完整会话轨迹",
745
+ "stage.evidenceTitle": "🔬 验证证据",
746
+ "stage.evidenceMissing": "(缺失——契约未兑现,host 已记警告;可与该 run 的命令日志对照:run 期间在 logs/teamflow/<runId>/,结束后由 host 归档到 $DSH_HOME/teamflow/<workspace>/logs/<runId>/)",
747
+ "stage.evidenceMissingShort": "(缺失——契约未兑现,host 已记警告)",
748
+ "stage.artifactsTitle": "📄 阶段性产物",
749
+ "pipeline.empty": "暂无运行中的流水线——让模型调用 teamflow_start,或在上方输入需求",
750
+ "pipeline.noNodes": "流水线还没有开始执行节点",
751
+ "pipeline.zoomOut": "缩小",
752
+ "pipeline.zoomIn": "放大",
753
+ "pipeline.fitCanvas": "适应画布",
754
+ "pipeline.canvasHint": "✥ 拖动画布 · 滚轮缩放",
755
+ "workbench.title": "团队工作台",
756
+ "workbench.running": "流水线运行中",
757
+ "workbench.idle": "空闲",
758
+ "workbench.refresh": "🔄 刷新",
759
+ "workbench.resumeTip": "断点续跑 {id}\n当前状态:{status};跳过已完成阶段,从第一个未完成阶段重跑",
760
+ "workbench.resuming": "续跑中…",
761
+ "workbench.resumeBtn": "↻ 从断点重跑 #{id}",
762
+ "workbench.loadFailed": "⚠ {err}(确认已安装 dsh-plugin-teamflow 且 web 已重启)",
763
+ "workbench.needsHumanBanner": "⚠ {n} 项需人工介入",
764
+ "workbench.handle": "处理 {id}",
765
+ "workbench.manualReason": "人工处理",
766
+ "workbench.tabPipeline": "🔄 流水线",
767
+ "workbench.tabBoard": "📋 Backlog 看板",
768
+ "workbench.workspaceTip": "当前工作区(workspace 级隔离):{path}",
769
+ "workbench.noWorkspace": "未连接工作区",
770
+ "workbench.history": "历史",
771
+ "workbench.openRightBarTip": "在右侧栏打开该 run 详情(与任务夹产物并排看)",
772
+ "workbench.openRightBarBtn": "⇥ 右栏打开",
773
+ "board.empty": "backlog 为空(还没有流水线运行过)",
774
+ "board.dragReason": "看板拖拽流转",
775
+ "board.cardTip": "{id} · {status}{summary}(点击查看详情)",
776
+ "board.assignDevTip": "dev 分配\n{who}",
777
+ "board.assignQaTip": "qa 分配\n{who}",
778
+ "board.acceptTip": "验收/汇报人\n{who}",
779
+ "board.subtaskCount": "📦 {n} 子卡",
780
+ "board.retries": "重试 {n}",
781
+ "item.overview": "概览",
782
+ "item.acceptRow": "✅ 验收",
783
+ "item.retryRow": "↻ 重试",
784
+ "item.humanRow": "⚠ 人工介入",
785
+ "item.updatedAt": "更新于",
786
+ "item.runSection": "运行",
787
+ "item.runBtn": "▶ 流水线",
788
+ "item.jumpRunTip": "跳转到该需求的流水线视图 #{id}",
789
+ "item.requirement": "需求原文",
790
+ "item.noRequirement": "(无原文)",
791
+ "item.runDocs": "任务夹",
792
+ "item.previewTip": "在右侧栏预览 {path}",
793
+ "item.subtasks": "关联子卡({n})",
794
+ "item.bugs": "关联缺陷({n})",
795
+ "item.timeline": "流转时间线({n})",
796
+ "team.currentTip": "当前团队:{name}(点击切换)",
797
+ "team.pick": "选择团队",
798
+ "team.label": "团队",
799
+ "team.none": "无团队(直接对话)",
800
+ "team.noneNote": "不走 teamflow,模型直接工作",
801
+ "panel.title": "🏭 团队工作台",
802
+ "panel.subtitle": "全局面板 · 按产品线",
803
+ "panel.currentSession": "当前会话 {sid}",
804
+ "panel.noSession": "无当前会话",
805
+ "panel.reload": "刷新数据",
806
+ "panel.backToChat": "回到对话",
807
+ "panel.backToChatTip": "回到对话(再点侧边栏图标即可切回本面板)",
808
+ "panel.railTitle": "产品线 · {n}",
809
+ "panel.rescanTip": "重新扫描 $DSH_HOME/teamflow",
810
+ "panel.refreshing": "刷新中",
811
+ "panel.refresh": "刷新",
812
+ "panel.noProducts": "还没有产品线。在某个工作区跑过一次流水线后,这里会出现对应产品线($DSH_HOME/teamflow/<key>)。",
813
+ "panel.activeRuns": "运行 {n}",
814
+ "panel.updated": "更新 {time}",
815
+ "panel.reading": "读取中…",
816
+ "panel.verdict": "验收 {v}",
817
+ "panel.pickProduct": "选择左侧产品线查看 backlog 与 run(首次进入默认选最近更新的产品线)。",
818
+ "panel.loadingView": "读取产品线数据中…",
819
+ "panel.runChip": "run {n}",
820
+ "panel.activeChip": "活跃 {n}",
821
+ "panel.verdictChip": "验收 {v}",
822
+ "panel.tabRuns": "🚀 流水线 run · {n}",
823
+ "panel.tabBacklog": "📋 Backlog · {n}",
824
+ "panel.pinnedActive": "已置顶进行中 {n}",
825
+ "panel.showRecent": "只看最近 {n} 条",
826
+ "panel.showAll": "展开全部 {n} 条",
827
+ "panel.clearRunFilterTip": "清除 run 状态筛选",
828
+ "panel.filtered": "筛选中 {sel} 项 · 显示 {shown}/{total} × 清除",
829
+ "panel.emptyRunFilter": "该筛选下没有 run。",
830
+ "panel.runHint": "点一行看详情浮层;「去会话右栏」= 跳到该 run 的发起会话并在其右侧栏打开(与任务夹产物并排)",
831
+ "panel.boardHint": "点状态徽章可筛选(多选);终态卡片默认收起,筛选时自动显示",
832
+ "panel.runDetail": "run 详情",
833
+ "panel.goOwnerSession": "去发起会话",
834
+ "panel.goOwnerSessionTip": "跳到该 run 的发起会话,并在那个会话的右侧栏打开(与任务夹产物并排看)",
835
+ "panel.remoteProductsUnavailable": "remote.products 不可用(插件未挂载或版本过旧)",
836
+ "panel.hintNoAddress": "{label}:host 未生成可打开的地址(可能缺少会话上下文)",
837
+ "panel.hintRightbarFailed": "右侧栏打开失败(宿主只在对话视图挂载它):{label}——已在本面板内联显示",
838
+ "panel.hintSessionGone": "发起会话 {sid}… 不在会话列表里(可能已被清理)——已在本面板展示详情",
839
+ "panel.hintSwitchedNoRightbar": "已切到发起会话,但右栏没打开({label})——可在该会话里用「⇥ 右栏打开」重试",
840
+ "panel.hintInlineSuffix": "——已在本面板内联显示",
841
+ "runList.empty": "该产品线还没有 run 记录。",
842
+ "runList.running": "进行中",
843
+ "runList.noRequirement": "(无需求描述)",
844
+ "runList.stageProgress": "阶段 {done}/{total}",
845
+ "runList.openRightBar": "去会话右栏",
846
+ "runList.openRightBarTip": "跳到该 run 的发起会话,并在那个会话的右侧栏打开详情(右侧栏是会话级的:挂到无关会话上没有意义)",
847
+ "runList.callsSuffix": " · {n} 次",
848
+ "panelBoard.empty": "backlog 为空(该产品线还没有立项卡片)。",
849
+ "panelBoard.groupCount": "{kind} · {n}",
850
+ "panelBoard.activeCount": "活动 {n}",
851
+ "panelBoard.clearFilterTip": "清除本组筛选",
852
+ "panelBoard.collapseDone": "收起已完成 {n}",
853
+ "panelBoard.expandDone": "已完成 {n} ▸",
854
+ "panelBoard.collapseDoneTip": "收起已完成/已关闭卡片",
855
+ "panelBoard.expandDoneTip": "展开已完成/已关闭卡片",
856
+ "panelBoard.emptyFiltered": "该筛选下没有卡片。",
857
+ "panelBoard.filterChipOn": "点击取消该状态筛选",
858
+ "panelBoard.filterChipOff": "点击只看该状态(可多选)",
859
+ "panelItem.row.req": "需求",
860
+ "panelItem.row.owner": "负责人",
861
+ "panelItem.row.dev": "开发",
862
+ "panelItem.row.qa": "测试",
863
+ "panelItem.row.accept": "验收",
864
+ "panelItem.row.retries": "重试",
865
+ "panelItem.row.runDocs": "任务夹",
866
+ "panelItem.defectSection": "缺陷详情(QA 报告导入)",
867
+ "panelItem.row.defectId": "缺陷编号",
868
+ "panelItem.row.severity": "严重级",
869
+ "panelItem.row.module": "功能模块",
870
+ "panelItem.row.reproduce": "复现步骤",
871
+ "panelItem.row.expected": "期望行为",
872
+ "panelItem.row.actual": "实际行为",
873
+ "panelItem.row.defectAc": "关联验收项",
874
+ "panelItem.row.defectCheck": "检测命令",
875
+ "panelItem.row.defectCriterion": "通过判据",
876
+ "panelItem.noDefectDetail": "(该卡无缺陷描述:登记时 QA 报告只给了三要素,或为旧版本登记的卡;细节见关联 run 的 QA-REPORT.md)",
877
+ "panelItem.spec": "规格",
878
+ "panelItem.summary": "结论摘要",
879
+ "panelItem.artifacts": "任务夹产物 · {n}",
880
+ "panelItem.artifactTip": "{address}\n(跳到产物所属会话后在该会话右侧栏打开)",
881
+ "panelItem.noArtifacts": "该条目没有可预览的任务夹产物(或缺少会话上下文,无法生成文件地址)。",
882
+ "panelItem.subtasks": "子卡 · {n}",
883
+ "panelItem.bugs": "关联缺陷 · {n}",
884
+ "panelItem.events": "流转时间线 · 最近 {n} 条",
885
+ "detail.runMissing": "未找到该 run(可能已被清理,或地址已过期)。",
886
+ "detail.subagents": "子代理 {n}",
887
+ "detail.stages": "阶段 · {n}",
888
+ "detail.stageFailed": "阶段详情读取失败:{err}",
889
+ "detail.stageTitle": "阶段 #{seq} 详情",
890
+ "detail.output": "阶段产出",
891
+ "detail.attempts": "同任务尝试 · {n}",
892
+ "detail.logs": "日志 · 最近 {n} 条",
893
+ "detail.endedRunning": "进行中",
894
+ "detail.subagentChip": "子代理 {id}",
895
+ "tab.resolveFailed": "无法解析 run 地址:{address}",
896
+ "tab.readingAddress": "读取 tab 地址中…",
897
+ "tab.noHook": "宿主 tab 信息钩子不可用(useTabInfo 缺失)",
898
+ "tab.remoteUnavailable": "remote 不可用(插件未挂载或版本过旧)",
899
+ "tab.readingRun": "读取 run 详情中…"
900
+ };
901
+ /** 英文词典(key 集合必须与 zh 完全一致)。 */
902
+ const en = {
903
+ "common.close": "Close",
904
+ "common.loading": "Loading…",
905
+ "common.retry": "Retry",
906
+ "common.collapse": "Collapse",
907
+ "common.collapseFull": "Collapse",
908
+ "common.clickToExpand": "Click to expand",
909
+ "common.expandFull": "Show more",
910
+ "common.moreLines": "… +{n} lines",
911
+ "common.none": "none",
912
+ "common.noneDash": "(none)",
913
+ "common.noSummary": "(no summary)",
914
+ "common.failed": "Failed",
915
+ "common.running": "Running",
916
+ "common.needsHuman": "needs human intervention",
917
+ "common.unknownError": "unknown error",
918
+ "common.gotIt": "Got it",
919
+ "common.remoteNotReady": "remote not ready",
920
+ "common.remoteCallFailed": "{what} call failed: {detail}",
921
+ "common.remoteEmptyResult": "{what} returned an empty result (ok=true but no value; raw envelope={raw})",
922
+ "common.calls": "{n} calls",
923
+ "common.callsCount": "{n} calls",
924
+ "common.noUsage": "no token data",
925
+ "status.created": "Created",
926
+ "status.in-progress": "In progress",
927
+ "status.pending-acceptance": "Pending acceptance",
928
+ "status.accepted": "Accepted",
929
+ "status.closed": "Closed",
930
+ "status.pending": "Todo",
931
+ "status.running": "Developing",
932
+ "status.testable": "Ready for test",
933
+ "status.testing": "Testing",
934
+ "status.rework": "Rework",
935
+ "status.needs-human": "Needs human",
936
+ "status.cancelled": "Closed",
937
+ "status.open": "Unclaimed",
938
+ "status.claimed": "In progress",
939
+ "status.fixed": "Fixed (pending verify)",
940
+ "status.verified": "Closed",
941
+ "status.reopened": "Reopened",
942
+ "status.done": "Done",
943
+ "status.completed": "Completed",
944
+ "status.failed": "Failed",
945
+ "status.interrupted": "Interrupted",
946
+ "status.superseded": "Superseded",
947
+ "runStatus.pending": "Queued",
948
+ "runStatus.running": "Running",
949
+ "runStatus.completed": "Completed",
950
+ "runStatus.failed": "Failed",
951
+ "runStatus.cancelled": "Cancelled",
952
+ "runStatus.interrupted": "Interrupted",
953
+ "runStatus.superseded": "Superseded",
954
+ "phase.prd": "PRD",
955
+ "phase.design": "UI/UX design",
956
+ "phase.scaffold": "Architecture",
957
+ "phase.tech": "Technical design",
958
+ "phase.dev": "Development",
959
+ "phase.qa": "QA testing",
960
+ "phase.acceptance": "Acceptance",
961
+ "kind.req": "Requirements",
962
+ "kind.task": "Tasks",
963
+ "kind.bug": "Defects",
964
+ "role.pm": "Product",
965
+ "role.design": "Design",
966
+ "role.arch": "Architecture",
967
+ "role.tech": "Tech design",
968
+ "role.dev": "Dev",
969
+ "role.qa": "QA",
970
+ "role.acceptance": "Acceptance",
971
+ "role.other": "Other",
972
+ "roleChip.pm": "📌 Product",
973
+ "roleChip.design": "🎨 Design",
974
+ "roleChip.arch": "🏗 Architecture",
975
+ "roleChip.tech": "📐 Tech design",
976
+ "roleChip.dev": "👨‍💻 Dev",
977
+ "roleChip.qa": "🧪 QA",
978
+ "roleChip.acceptance": "✅ Acceptance",
979
+ "token.officialTitle": "TOKEN · official metering",
980
+ "token.usageLine": "Input (miss) {input} / Input (hit) {cacheRead} / Cache write {cacheWrite} / Output {output} · {calls} calls",
981
+ "token.usageHit": " · cache hit {hit}%",
982
+ "token.usageMissing": "no usage detail",
983
+ "token.inputMiss": "Input (miss) {v}",
984
+ "token.inputHit": "Input (hit) {v}",
985
+ "token.cacheWrite": "Cache write {v}",
986
+ "token.output": "Output {v}",
987
+ "token.total": "Total {v}",
988
+ "token.allStagesTip": "Input (miss) / Input (hit) / Output totals across all stages",
989
+ "token.stageLine": "Stage tokens: {v}",
990
+ "stage.cardTip": "{label} — click for stage detail",
991
+ "stage.retryTip": "Retried {n}× ({m} attempts in total)",
992
+ "stage.detailTitle": "Stage detail",
993
+ "stage.summaryFallback": "(Full text was not saved for this run — showing the summary)\n\n{summary}",
994
+ "stage.loadFailed": "⚠ Load failed: {err}",
995
+ "stage.noOutput": "(no output text)",
996
+ "stage.attemptHistory": "↻ Attempt history ({n} · click to inspect one)",
997
+ "stage.attemptDone": "✅ OK",
998
+ "stage.attemptFailed": "❌ {outcome}",
999
+ "stage.attemptRunning": "⏳ Running",
1000
+ "stage.childJumpBtn": "🎬 Open subagent session",
1001
+ "stage.childJumpTip": "Open this stage's subagent session (full reasoning and tool-call trail); then switch to the \"Chat\" tab",
1002
+ "stage.childNoneTip": "No subagent session is available for this stage",
1003
+ "stage.childCrossSessionTip": "This subagent was started by session {sid}; cross-session navigation is not supported yet — open the Team Workbench in its originating session",
1004
+ "stage.childCrossSessionNote": "Cross-session navigation is not supported yet: this subagent was started by session {sid}. Open the Team Workbench in its originating session to see the trail.",
1005
+ "stage.childJumpNote": "After jumping, switch to the \"Chat\" tab to see this subagent's full session trail",
1006
+ "stage.evidenceTitle": "🔬 Verification evidence",
1007
+ "stage.evidenceMissing": "(missing — the contract was not honored and the host logged a warning; cross-check with this run's command logs: logs/teamflow/<runId>/ while the run is live, archived by the host to $DSH_HOME/teamflow/<workspace>/logs/<runId>/ afterwards)",
1008
+ "stage.evidenceMissingShort": "(missing — the contract was not honored and the host logged a warning)",
1009
+ "stage.artifactsTitle": "📄 Stage artifacts",
1010
+ "pipeline.empty": "No active pipeline — ask the model to call teamflow_start, or type a requirement above",
1011
+ "pipeline.noNodes": "The pipeline has not started any stage node yet",
1012
+ "pipeline.zoomOut": "Zoom out",
1013
+ "pipeline.zoomIn": "Zoom in",
1014
+ "pipeline.fitCanvas": "Fit canvas",
1015
+ "pipeline.canvasHint": "✥ Drag to pan · scroll to zoom",
1016
+ "workbench.title": "Team Workbench",
1017
+ "workbench.running": "Pipeline running",
1018
+ "workbench.idle": "Idle",
1019
+ "workbench.refresh": "🔄 Refresh",
1020
+ "workbench.resumeTip": "Resume {id}\nCurrent status: {status}; completed stages are skipped and the run restarts from the first unfinished stage",
1021
+ "workbench.resuming": "Resuming…",
1022
+ "workbench.resumeBtn": "↻ Resume from #{id}",
1023
+ "workbench.loadFailed": "⚠ {err} (make sure dsh-plugin-teamflow is installed and the web host was restarted)",
1024
+ "workbench.needsHumanBanner": "⚠ {n} item(s) need human intervention",
1025
+ "workbench.handle": "Handle {id}",
1026
+ "workbench.manualReason": "manual handling",
1027
+ "workbench.tabPipeline": "🔄 Pipeline",
1028
+ "workbench.tabBoard": "📋 Backlog board",
1029
+ "workbench.workspaceTip": "Current workspace (workspace-level isolation): {path}",
1030
+ "workbench.noWorkspace": "no workspace connected",
1031
+ "workbench.history": "History",
1032
+ "workbench.openRightBarTip": "Open this run's detail in the right sidebar (side by side with task-folder artifacts)",
1033
+ "workbench.openRightBarBtn": "⇥ Open in right bar",
1034
+ "board.empty": "Backlog is empty (no pipeline has run yet)",
1035
+ "board.dragReason": "kanban drag transition",
1036
+ "board.cardTip": "{id} · {status}{summary} (click for details)",
1037
+ "board.assignDevTip": "dev assignee\n{who}",
1038
+ "board.assignQaTip": "qa assignee\n{who}",
1039
+ "board.acceptTip": "acceptance/report owner\n{who}",
1040
+ "board.subtaskCount": "📦 {n} subtasks",
1041
+ "board.retries": "retries {n}",
1042
+ "item.overview": "Overview",
1043
+ "item.acceptRow": "✅ Acceptance",
1044
+ "item.retryRow": "↻ Retries",
1045
+ "item.humanRow": "⚠ Human intervention",
1046
+ "item.updatedAt": "Updated",
1047
+ "item.runSection": "Run",
1048
+ "item.runBtn": "▶ Pipeline",
1049
+ "item.jumpRunTip": "Open this requirement's pipeline view #{id}",
1050
+ "item.requirement": "Raw requirement",
1051
+ "item.noRequirement": "(no raw text)",
1052
+ "item.runDocs": "Task folder",
1053
+ "item.previewTip": "Preview {path} in the right sidebar",
1054
+ "item.subtasks": "Linked subtasks ({n})",
1055
+ "item.bugs": "Linked defects ({n})",
1056
+ "item.timeline": "Transition timeline ({n})",
1057
+ "team.currentTip": "Current team: {name} (click to switch)",
1058
+ "team.pick": "Select team",
1059
+ "team.label": "Team",
1060
+ "team.none": "No team (chat directly)",
1061
+ "team.noneNote": "teamflow is not used; the model works directly",
1062
+ "panel.title": "🏭 Team Workbench",
1063
+ "panel.subtitle": "Global panel · by product line",
1064
+ "panel.currentSession": "Session {sid}",
1065
+ "panel.noSession": "No active session",
1066
+ "panel.reload": "Reload data",
1067
+ "panel.backToChat": "Back to chat",
1068
+ "panel.backToChatTip": "Back to chat (click the sidebar icon again to return to this panel)",
1069
+ "panel.railTitle": "Product lines · {n}",
1070
+ "panel.rescanTip": "Rescan $DSH_HOME/teamflow",
1071
+ "panel.refreshing": "Refreshing",
1072
+ "panel.refresh": "Refresh",
1073
+ "panel.noProducts": "No product lines yet. Once a pipeline has run in a workspace, its product line shows up here ($DSH_HOME/teamflow/<key>).",
1074
+ "panel.activeRuns": "{n} running",
1075
+ "panel.updated": "updated {time}",
1076
+ "panel.reading": "Loading…",
1077
+ "panel.verdict": "acceptance {v}",
1078
+ "panel.pickProduct": "Pick a product line on the left to see its backlog and runs (the most recently updated one is selected on first entry).",
1079
+ "panel.loadingView": "Loading product-line data…",
1080
+ "panel.runChip": "runs {n}",
1081
+ "panel.activeChip": "active {n}",
1082
+ "panel.verdictChip": "acceptance {v}",
1083
+ "panel.tabRuns": "🚀 Pipeline runs · {n}",
1084
+ "panel.tabBacklog": "📋 Backlog · {n}",
1085
+ "panel.pinnedActive": "{n} running pinned",
1086
+ "panel.showRecent": "Show latest {n}",
1087
+ "panel.showAll": "Show all {n}",
1088
+ "panel.clearRunFilterTip": "Clear the run status filter",
1089
+ "panel.filtered": "Filtered {sel} · showing {shown}/{total} × clear",
1090
+ "panel.emptyRunFilter": "No runs match this filter.",
1091
+ "panel.runHint": "Click a row for the detail overlay; \"Open in session right bar\" jumps to the run's originating session and opens the right bar there (side by side with task-folder artifacts)",
1092
+ "panel.boardHint": "Click a status badge to filter (multi-select); terminal cards are collapsed by default and appear automatically while filtering",
1093
+ "panel.runDetail": "Run detail",
1094
+ "panel.goOwnerSession": "Go to originating session",
1095
+ "panel.goOwnerSessionTip": "Jump to the run's originating session and open its right bar there (side by side with task-folder artifacts)",
1096
+ "panel.remoteProductsUnavailable": "remote.products unavailable (plugin not mounted or outdated)",
1097
+ "panel.hintNoAddress": "{label}: the host produced no openable address (session context may be missing)",
1098
+ "panel.hintRightbarFailed": "Failed to open the right sidebar (the host mounts it only in the chat view): {label} — shown inline in this panel instead",
1099
+ "panel.hintSessionGone": "Originating session {sid}… is not in the session list (it may have been cleaned up) — showing the detail in this panel",
1100
+ "panel.hintSwitchedNoRightbar": "Switched to the originating session but the right bar did not open ({label}) — retry with \"⇥ Open in right bar\" in that session",
1101
+ "panel.hintInlineSuffix": " — shown inline in this panel instead",
1102
+ "runList.empty": "No runs recorded for this product line yet.",
1103
+ "runList.running": "Running",
1104
+ "runList.noRequirement": "(no requirement text)",
1105
+ "runList.stageProgress": "Stages {done}/{total}",
1106
+ "runList.openRightBar": "Open in session right bar",
1107
+ "runList.openRightBarTip": "Jump to the run's originating session and open the detail in that session's right bar (the right bar is session-scoped: attaching it to an unrelated session is meaningless)",
1108
+ "runList.callsSuffix": " · {n} calls",
1109
+ "panelBoard.empty": "Backlog is empty (this product line has no cards yet).",
1110
+ "panelBoard.groupCount": "{kind} · {n}",
1111
+ "panelBoard.activeCount": "Active {n}",
1112
+ "panelBoard.clearFilterTip": "Clear this group's filter",
1113
+ "panelBoard.collapseDone": "Hide {n} done",
1114
+ "panelBoard.expandDone": "{n} done ▸",
1115
+ "panelBoard.collapseDoneTip": "Hide done/closed cards",
1116
+ "panelBoard.expandDoneTip": "Show done/closed cards",
1117
+ "panelBoard.emptyFiltered": "No cards match this filter.",
1118
+ "panelBoard.filterChipOn": "Click to clear this status filter",
1119
+ "panelBoard.filterChipOff": "Click to filter by this status only (multi-select)",
1120
+ "panelItem.row.req": "Requirement",
1121
+ "panelItem.row.owner": "Owner",
1122
+ "panelItem.row.dev": "Dev",
1123
+ "panelItem.row.qa": "QA",
1124
+ "panelItem.row.accept": "Acceptance",
1125
+ "panelItem.row.retries": "Retries",
1126
+ "panelItem.row.runDocs": "Task folder",
1127
+ "panelItem.defectSection": "Defect detail (imported from the QA report)",
1128
+ "panelItem.row.defectId": "Defect id",
1129
+ "panelItem.row.severity": "Severity",
1130
+ "panelItem.row.module": "Module",
1131
+ "panelItem.row.reproduce": "Steps to reproduce",
1132
+ "panelItem.row.expected": "Expected",
1133
+ "panelItem.row.actual": "Actual",
1134
+ "panelItem.row.defectAc": "Related AC",
1135
+ "panelItem.row.defectCheck": "Check command",
1136
+ "panelItem.row.defectCriterion": "Pass criterion",
1137
+ "panelItem.noDefectDetail": "(No defect description on this card: the QA report only carried the three key fields when it was registered, or the card predates this version; see QA-REPORT.md in the linked run)",
1138
+ "panelItem.spec": "Spec",
1139
+ "panelItem.summary": "Summary",
1140
+ "panelItem.artifacts": "Task-folder artifacts · {n}",
1141
+ "panelItem.artifactTip": "{address}\n(opens in the right sidebar of the session that owns this artifact)",
1142
+ "panelItem.noArtifacts": "This item has no previewable task-folder artifacts (or no session context to build a file address).",
1143
+ "panelItem.subtasks": "Subtasks · {n}",
1144
+ "panelItem.bugs": "Linked defects · {n}",
1145
+ "panelItem.events": "Transition timeline · last {n}",
1146
+ "detail.runMissing": "Run not found (it may have been cleaned up, or the address is stale).",
1147
+ "detail.subagents": "Subagents {n}",
1148
+ "detail.stages": "Stages · {n}",
1149
+ "detail.stageFailed": "Failed to load stage detail: {err}",
1150
+ "detail.stageTitle": "Stage #{seq} detail",
1151
+ "detail.output": "Stage output",
1152
+ "detail.attempts": "Attempts for this task · {n}",
1153
+ "detail.logs": "Logs · last {n}",
1154
+ "detail.endedRunning": "running",
1155
+ "detail.subagentChip": "Subagent {id}",
1156
+ "tab.resolveFailed": "Cannot parse the run address: {address}",
1157
+ "tab.readingAddress": "Reading the tab address…",
1158
+ "tab.noHook": "Host tab-info hook unavailable (useTabInfo missing)",
1159
+ "tab.remoteUnavailable": "remote unavailable (plugin not mounted or outdated)",
1160
+ "tab.readingRun": "Loading run detail…"
1161
+ };
1162
+ //#endregion
604
1163
  //#region client/panel.tsx
605
1164
  /**
606
1165
  * dsh-plugin-teamflow — 全局面板 + 右栏 run 详情 tab(v0.1.8 ①)。
@@ -649,14 +1208,17 @@ window.__ModuleLoader__.load({
649
1208
  * 此时不能静默返回 undefined(调用方会把它再包成「未知错误」,丢掉定位信息),故显式报出原始信封。 */
650
1209
  function unwrap$1(res, what) {
651
1210
  if (!res || !res.ok) {
652
- let detail = "未知错误";
1211
+ let detail = t("common.unknownError");
653
1212
  try {
654
- detail = res && res.error && (res.error.message || res.error.code) || JSON.stringify(res) || "未知错误";
1213
+ detail = res && res.error && (res.error.message || res.error.code) || JSON.stringify(res) || t("common.unknownError");
655
1214
  } catch (e) {}
656
1215
  try {
657
1216
  console.warn("[teamflow] remote 调用失败", what, res);
658
1217
  } catch (e) {}
659
- throw new Error(`${what || "remote"} 调用失败:${detail}`);
1218
+ throw new Error(t("common.remoteCallFailed", {
1219
+ what: what || "remote",
1220
+ detail
1221
+ }));
660
1222
  }
661
1223
  if (res.value === void 0) {
662
1224
  try {
@@ -668,7 +1230,10 @@ window.__ModuleLoader__.load({
668
1230
  } catch (e) {
669
1231
  raw = String(res);
670
1232
  }
671
- throw new Error(`${what || "remote"} 返回空结果(ok=true 但无 value;原始信封=${raw})`);
1233
+ throw new Error(t("common.remoteEmptyResult", {
1234
+ what: what || "remote",
1235
+ raw
1236
+ }));
672
1237
  }
673
1238
  return res.value;
674
1239
  }
@@ -744,9 +1309,9 @@ window.__ModuleLoader__.load({
744
1309
  ...style
745
1310
  } }, text);
746
1311
  const runUsageText = (u) => {
747
- if (!u || !(u.input || u.cacheRead || u.cacheWrite || u.output)) return "无 token 数据";
1312
+ if (!u || !(u.input || u.cacheRead || u.cacheWrite || u.output)) return t("common.noUsage");
748
1313
  const hit = hitRate(u);
749
- return `⇅${fmtTokens(u.input)} ⇅${fmtTokens(u.cacheRead)} ⬆${fmtTokens(u.output)}${hit !== null ? ` ·${hit}%` : ""} · ${u.calls} 次`;
1314
+ return `⇅${fmtTokens(u.input)} ⇅${fmtTokens(u.cacheRead)} ⬆${fmtTokens(u.output)}${hit !== null ? ` ·${hit}%` : ""} · ${t("common.calls", { n: u.calls })}`;
750
1315
  };
751
1316
  const RUN_PREVIEW = 8;
752
1317
  const TERMINAL_STATUSES = [
@@ -772,17 +1337,17 @@ window.__ModuleLoader__.load({
772
1337
  fontSize: 11.5,
773
1338
  fontWeight: 700,
774
1339
  color: T.text2
775
- } }, `产品线 · ${products.length}`), h("button", {
1340
+ } }, t("panel.railTitle", { n: products.length })), h("button", {
776
1341
  style: panelBtn,
777
1342
  onClick: onRefresh,
778
1343
  disabled: busy,
779
- title: "重新扫描 $DSH_HOME/teamflow"
780
- }, busy ? "刷新中" : "刷新")), h("div", { style: {
1344
+ title: t("panel.rescanTip")
1345
+ }, busy ? t("panel.refreshing") : t("panel.refresh"))), h("div", { style: {
781
1346
  flex: 1,
782
1347
  minHeight: 0,
783
1348
  overflowY: "auto",
784
1349
  padding: "0 8px 12px"
785
- } }, products.length === 0 ? muted("还没有产品线。在某个工作区跑过一次流水线后,这里会出现对应产品线($DSH_HOME/teamflow/<key>)。", { padding: "10px 4px" }) : products.map((p) => {
1350
+ } }, products.length === 0 ? muted(t("panel.noProducts"), { padding: "10px 4px" }) : products.map((p) => {
786
1351
  const on = p.key === current;
787
1352
  return h("div", {
788
1353
  key: p.key,
@@ -806,7 +1371,7 @@ window.__ModuleLoader__.load({
806
1371
  overflow: "hidden",
807
1372
  textOverflow: "ellipsis",
808
1373
  whiteSpace: "nowrap"
809
- } }, p.title || p.key), p.activeRuns > 0 ? chip(`运行 ${p.activeRuns}`, T.brand, { dot: true }) : null), h("div", { style: {
1374
+ } }, p.title || p.key), p.activeRuns > 0 ? chip(t("panel.activeRuns", { n: p.activeRuns }), T.brand, { dot: true }) : null), h("div", { style: {
810
1375
  fontSize: 10,
811
1376
  color: T.text2,
812
1377
  fontFamily: MONO,
@@ -820,10 +1385,10 @@ window.__ModuleLoader__.load({
820
1385
  marginTop: 3,
821
1386
  fontSize: 10,
822
1387
  color: T.text2
823
- } }, h("span", null, `run ${p.totalRuns}`), p.updatedAt ? h("span", null, `更新 ${fmtTime(p.updatedAt)}`) : null, p.key === loadingKey ? h("span", { style: {
1388
+ } }, h("span", null, `run ${p.totalRuns}`), p.updatedAt ? h("span", null, t("panel.updated", { time: fmtTime(p.updatedAt) })) : null, p.key === loadingKey ? h("span", { style: {
824
1389
  color: T.brand,
825
1390
  fontWeight: 600
826
- } }, "读取中…") : null), p.lastRequirement ? h("div", { style: {
1391
+ } }, t("panel.reading")) : null), p.lastRequirement ? h("div", { style: {
827
1392
  fontSize: 10.5,
828
1393
  color: T.text2,
829
1394
  marginTop: 3,
@@ -832,11 +1397,11 @@ window.__ModuleLoader__.load({
832
1397
  WebkitLineClamp: 2,
833
1398
  WebkitBoxOrient: "vertical",
834
1399
  overflow: "hidden"
835
- } }, p.lastRequirement) : null, p.lastVerdict ? h("div", { style: { marginTop: 4 } }, chip(`验收 ${p.lastVerdict}`, stColor(p.lastVerdict === "accepted" ? "accepted" : p.lastVerdict))) : null);
1400
+ } }, p.lastRequirement) : null, p.lastVerdict ? h("div", { style: { marginTop: 4 } }, chip(t("panel.verdict", { v: p.lastVerdict }), stColor(p.lastVerdict === "accepted" ? "accepted" : p.lastVerdict))) : null);
836
1401
  })));
837
1402
  }
838
1403
  function RunList({ runs, activeRunId, onOpenRun, onInlineRun }) {
839
- if (!runs.length) return muted("该产品线还没有 run 记录。", { padding: "2px 2px 8px" });
1404
+ if (!runs.length) return muted(t("runList.empty"), { padding: "2px 2px 8px" });
840
1405
  return h("div", { style: {
841
1406
  display: "flex",
842
1407
  flexDirection: "column",
@@ -864,35 +1429,38 @@ window.__ModuleLoader__.load({
864
1429
  } }, h("div", { style: {
865
1430
  ...flexRow,
866
1431
  gap: 6
867
- } }, chip(RUN_STATUS_TEXT[r.status] || r.status, stColor(r.status), { dot: true }), r.mode ? chip(String(r.mode), T.text2) : null, h("span", { style: {
1432
+ } }, chip(runStatusText(r.status), stColor(r.status), { dot: true }), r.mode ? chip(String(r.mode), T.text2) : null, h("span", { style: {
868
1433
  fontFamily: MONO,
869
1434
  fontSize: 10,
870
1435
  color: T.text2
871
1436
  } }, r.id), active ? h("span", { style: {
872
1437
  fontSize: 10,
873
1438
  color: T.brand
874
- } }, "进行中") : null), h("div", { style: {
1439
+ } }, t("runList.running")) : null), h("div", { style: {
875
1440
  fontSize: 11.5,
876
1441
  color: T.text,
877
1442
  marginTop: 3,
878
1443
  overflow: "hidden",
879
1444
  textOverflow: "ellipsis",
880
1445
  whiteSpace: "nowrap"
881
- } }, r.requirement || "(无需求描述)"), h("div", { style: {
1446
+ } }, r.requirement || t("runList.noRequirement")), h("div", { style: {
882
1447
  ...flexRow,
883
1448
  gap: 10,
884
1449
  marginTop: 3,
885
1450
  fontSize: 10,
886
1451
  color: T.text2,
887
1452
  fontFamily: MONO
888
- } }, h("span", null, `阶段 ${r.doneStages}/${r.stageCount}`), h("span", null, runUsageText(r.usage)), h("span", null, `${fmtTime(r.startedAt)}${r.endedAt ? ` → ${fmtTime(r.endedAt)}` : ""} ${fmtDur(r.startedAt, r.endedAt)}`))), h("button", {
1453
+ } }, h("span", null, t("runList.stageProgress", {
1454
+ done: r.doneStages,
1455
+ total: r.stageCount
1456
+ })), h("span", null, runUsageText(r.usage)), h("span", null, `${fmtTime(r.startedAt)}${r.endedAt ? ` → ${fmtTime(r.endedAt)}` : ""} ${fmtDur(r.startedAt, r.endedAt)}`))), h("button", {
889
1457
  style: brandBtn,
890
- title: "跳到该 run 的发起会话,并在那个会话的右侧栏打开详情(右侧栏是会话级的:挂到无关会话上没有意义)",
1458
+ title: t("runList.openRightBarTip"),
891
1459
  onClick: (e) => {
892
1460
  e.stopPropagation();
893
1461
  onOpenRun(r);
894
1462
  }
895
- }, "去会话右栏"));
1463
+ }, t("runList.openRightBar")));
896
1464
  }));
897
1465
  }
898
1466
  function BacklogCard({ kind, item, onOpen }) {
@@ -950,12 +1518,12 @@ window.__ModuleLoader__.load({
950
1518
  marginTop: 3,
951
1519
  fontSize: 10,
952
1520
  color: T.text2
953
- } }, item.devAssign ? h("span", null, `dev ${item.devAssign}`) : null, item.qaAssign ? h("span", null, `qa ${item.qaAssign}`) : null, item.acceptBy ? h("span", null, `验收 ${item.acceptBy}`) : null, item.retries ? h("span", { style: { color: T.warn } }, `重试 ${item.retries}`) : null, item.humanIntervention ? h("span", { style: { color: T.error } }, "需人工") : null));
1521
+ } }, item.devAssign ? h("span", null, `dev ${item.devAssign}`) : null, item.qaAssign ? h("span", null, `qa ${item.qaAssign}`) : null, item.acceptBy ? h("span", null, t("panel.verdict", { v: item.acceptBy })) : null, item.retries ? h("span", { style: { color: T.warn } }, t("board.retries", { n: item.retries })) : null, item.humanIntervention ? h("span", { style: { color: T.error } }, t("status.needs-human")) : null));
954
1522
  }
955
1523
  /** 可点筛选徽章(多选):单击切换该状态,选中态用状态色实心;再点取消。 */
956
1524
  const filterChip = (text, color, on, onToggle) => h("button", {
957
1525
  onClick: onToggle,
958
- title: on ? "点击取消该状态筛选" : "点击只看该状态(可多选)",
1526
+ title: on ? t("panelBoard.filterChipOn") : t("panelBoard.filterChipOff"),
959
1527
  style: {
960
1528
  font: "inherit",
961
1529
  fontSize: 11,
@@ -993,7 +1561,7 @@ window.__ModuleLoader__.load({
993
1561
  ["task", (backlog.tasks || []).filter((t) => t.type !== "subtask")],
994
1562
  ["bug", backlog.bugs || []]
995
1563
  ];
996
- if (!groups.reduce((a, [, arr]) => a + arr.length, 0)) return muted("backlog 为空(该产品线还没有立项卡片)。", { padding: "2px 2px 8px" });
1564
+ if (!groups.reduce((a, [, arr]) => a + arr.length, 0)) return muted(t("panelBoard.empty"), { padding: "2px 2px 8px" });
997
1565
  return h("div", null, groups.map(([kind, list]) => {
998
1566
  if (!list.length) return null;
999
1567
  const done = list.filter((it) => TERMINAL_STATUSES.indexOf(it.status) !== -1 && !it.humanIntervention);
@@ -1028,33 +1596,40 @@ window.__ModuleLoader__.load({
1028
1596
  fontWeight: 700,
1029
1597
  color: T.text,
1030
1598
  flex: "0 0 auto"
1031
- } }, `${KIND_TITLE[kind]} · ${list.length}`), h("span", { style: {
1599
+ } }, t("panelBoard.groupCount", {
1600
+ kind: kindTitle(kind),
1601
+ n: list.length
1602
+ })), h("span", { style: {
1032
1603
  fontSize: 10,
1033
1604
  color: T.text2,
1034
1605
  flex: "0 0 auto"
1035
- } }, `活动 ${active.length}`), ...Object.keys(byStatus).map((s) => filterChip(`${stText(s)} ${byStatus[s]}`, stColor(s), selSet.has(s), () => toggle(s))), filtering ? h("button", {
1606
+ } }, t("panelBoard.activeCount", { n: active.length })), ...Object.keys(byStatus).map((s) => filterChip(`${stText(s)} ${byStatus[s]}`, stColor(s), selSet.has(s), () => toggle(s))), filtering ? h("button", {
1036
1607
  style: {
1037
1608
  ...panelBtn,
1038
1609
  marginLeft: "auto",
1039
1610
  flex: "0 0 auto"
1040
1611
  },
1041
- title: "清除本组筛选",
1612
+ title: t("panelBoard.clearFilterTip"),
1042
1613
  onClick: () => setFilters((m) => ({
1043
1614
  ...m,
1044
1615
  [kind]: []
1045
1616
  }))
1046
- }, `筛选中 ${sel.length} 项 · 显示 ${matched.length}/${list.length} × 清除`) : done.length ? h("button", {
1617
+ }, t("panel.filtered", {
1618
+ sel: sel.length,
1619
+ shown: matched.length,
1620
+ total: list.length
1621
+ })) : done.length ? h("button", {
1047
1622
  style: {
1048
1623
  ...panelBtn,
1049
1624
  marginLeft: "auto",
1050
1625
  flex: "0 0 auto"
1051
1626
  },
1052
- title: open ? "收起已完成/已关闭卡片" : "展开已完成/已关闭卡片",
1627
+ title: open ? t("panelBoard.collapseDoneTip") : t("panelBoard.expandDoneTip"),
1053
1628
  onClick: () => setShowDone((m) => ({
1054
1629
  ...m,
1055
1630
  [kind]: !open
1056
1631
  }))
1057
- }, open ? `收起已完成 ${done.length}` : `已完成 ${done.length} ▸`) : null), visible.length ? h("div", { style: {
1632
+ }, open ? t("panelBoard.collapseDone", { n: done.length }) : t("panelBoard.expandDone", { n: done.length })) : null), visible.length ? h("div", { style: {
1058
1633
  display: "grid",
1059
1634
  gridTemplateColumns: "repeat(auto-fill, minmax(228px, 1fr))",
1060
1635
  gap: 6
@@ -1063,7 +1638,7 @@ window.__ModuleLoader__.load({
1063
1638
  kind,
1064
1639
  item: it,
1065
1640
  onOpen
1066
- }))) : muted("该筛选下没有卡片。", { fontSize: 10.5 }));
1641
+ }))) : muted(t("panelBoard.emptyFiltered"), { fontSize: 10.5 }));
1067
1642
  }));
1068
1643
  }
1069
1644
  function ItemDetailPane({ det, openArtifact, onClose }) {
@@ -1099,43 +1674,62 @@ window.__ModuleLoader__.load({
1099
1674
  } }, det.id), chip(stText(det.status), stColor(det.status), { dot: true }), h("span", { style: {
1100
1675
  fontSize: 10.5,
1101
1676
  color: T.text2
1102
- } }, KIND_TITLE[det.kind] || det.kind)), h("button", {
1677
+ } }, kindTitle(det.kind))), h("button", {
1103
1678
  style: panelBtn,
1104
1679
  onClick: onClose
1105
- }, "关闭")), h("div", { style: {
1680
+ }, t("common.close"))), h("div", { style: {
1106
1681
  fontSize: 13,
1107
1682
  fontWeight: 600,
1108
1683
  color: T.text,
1109
1684
  lineHeight: 1.45
1110
- } }, det.title), h("div", { style: {
1685
+ } }, det.title), det.kind === "bug" ? h("div", { style: {
1686
+ display: "flex",
1687
+ flexDirection: "column",
1688
+ gap: 4,
1689
+ padding: "8px 10px",
1690
+ borderRadius: 8,
1691
+ background: T.layer2,
1692
+ border: `1px solid ${T.border}`
1693
+ } }, h("div", { style: {
1694
+ ...flexRow,
1695
+ gap: 6
1696
+ } }, h("span", { style: {
1697
+ fontSize: 11,
1698
+ fontWeight: 700,
1699
+ color: T.text
1700
+ } }, t("panelItem.defectSection")), det.severity ? chip(String(det.severity), String(det.severity) === "P0" ? T.error : String(det.severity) === "P1" ? T.warn : T.text2) : null), row(t("panelItem.row.defectId"), det.defectId), row(t("panelItem.row.module"), det.module), row(t("panelItem.row.reproduce"), det.reproduce), row(t("panelItem.row.expected"), det.expected), row(t("panelItem.row.actual"), det.actual), row(t("panelItem.row.defectCheck"), det.defectCheck), row(t("panelItem.row.defectCriterion"), det.defectCriterion), row(t("panelItem.row.defectAc"), det.defectAc), det.reproduce || det.expected || det.actual ? null : h("div", { style: {
1701
+ fontSize: 10.5,
1702
+ color: T.warn,
1703
+ lineHeight: 1.5
1704
+ } }, t("panelItem.noDefectDetail"))) : null, h("div", { style: {
1111
1705
  display: "flex",
1112
1706
  flexDirection: "column",
1113
1707
  gap: 3
1114
- } }, row("需求", det.reqId), row("负责人", det.owner), row("开发", det.devAssign), row("测试", det.qaAssign), row("验收", det.assignBy), row("重试", det.retries), row("任务夹", det.runDocs)), det.spec ? h("div", null, h("div", { style: {
1708
+ } }, row(t("panelItem.row.req"), det.reqId), row(t("panelItem.row.owner"), det.owner), row(t("panelItem.row.dev"), det.devAssign), row(t("panelItem.row.qa"), det.qaAssign), row(t("panelItem.row.accept"), det.assignBy), row(t("panelItem.row.retries"), det.retries), row(t("panelItem.row.runDocs"), det.runDocs)), det.spec ? h("div", null, h("div", { style: {
1115
1709
  fontSize: 11,
1116
1710
  color: T.text2,
1117
1711
  marginBottom: 3
1118
- } }, "规格"), h(FoldableText, { text: det.spec })) : null, det.summary ? h("div", null, h("div", { style: {
1712
+ } }, t("panelItem.spec")), h(FoldableText, { text: det.spec })) : null, det.summary ? h("div", null, h("div", { style: {
1119
1713
  fontSize: 11,
1120
1714
  color: T.text2,
1121
1715
  marginBottom: 3
1122
- } }, "结论摘要"), h(FoldableText, { text: det.summary })) : null, det.artifacts && det.artifacts.length ? h("div", null, h("div", { style: {
1716
+ } }, t("panelItem.summary")), h(FoldableText, { text: det.summary })) : null, det.artifacts && det.artifacts.length ? h("div", null, h("div", { style: {
1123
1717
  fontSize: 11,
1124
1718
  color: T.text2,
1125
1719
  marginBottom: 4
1126
- } }, `任务夹产物 · ${det.artifacts.length}`), h("div", { style: {
1720
+ } }, t("panelItem.artifacts", { n: det.artifacts.length })), h("div", { style: {
1127
1721
  ...flexRow,
1128
1722
  gap: 5
1129
1723
  } }, det.artifacts.map((a) => h("button", {
1130
1724
  key: a.name,
1131
1725
  style: brandBtn,
1132
- title: `${a.address}\n(跳到产物所属会话后在该会话右侧栏打开)`,
1726
+ title: t("panelItem.artifactTip", { address: a.address }),
1133
1727
  onClick: () => openArtifact && openArtifact(a.address, a.name, det.runInfo && det.runInfo.ownerSession || null)
1134
- }, a.name)))) : muted("该条目没有可预览的任务夹产物(或缺少会话上下文,无法生成文件地址)。"), det.subtasks && det.subtasks.length ? h("div", null, h("div", { style: {
1728
+ }, a.name)))) : muted(t("panelItem.noArtifacts")), det.subtasks && det.subtasks.length ? h("div", null, h("div", { style: {
1135
1729
  fontSize: 11,
1136
1730
  color: T.text2,
1137
1731
  marginBottom: 4
1138
- } }, `子卡 · ${det.subtasks.length}`), h("div", { style: {
1732
+ } }, t("panelItem.subtasks", { n: det.subtasks.length })), h("div", { style: {
1139
1733
  display: "flex",
1140
1734
  flexDirection: "column",
1141
1735
  gap: 4
@@ -1160,11 +1754,11 @@ window.__ModuleLoader__.load({
1160
1754
  overflow: "hidden",
1161
1755
  textOverflow: "ellipsis",
1162
1756
  whiteSpace: "nowrap"
1163
- } }, s.title), s.failed ? chip("失败", T.error) : null)))) : null, det.bugs && det.bugs.length ? h("div", null, h("div", { style: {
1757
+ } }, s.title), s.failed ? chip(t("common.failed"), T.error) : null)))) : null, det.bugs && det.bugs.length ? h("div", null, h("div", { style: {
1164
1758
  fontSize: 11,
1165
1759
  color: T.text2,
1166
1760
  marginBottom: 4
1167
- } }, `关联缺陷 · ${det.bugs.length}`), h("div", { style: {
1761
+ } }, t("panelItem.bugs", { n: det.bugs.length })), h("div", { style: {
1168
1762
  display: "flex",
1169
1763
  flexDirection: "column",
1170
1764
  gap: 4
@@ -1193,7 +1787,7 @@ window.__ModuleLoader__.load({
1193
1787
  fontSize: 11,
1194
1788
  color: T.text2,
1195
1789
  marginBottom: 4
1196
- } }, `流转时间线 · 最近 ${Math.min(det.events.length, 30)} 条`), h("div", { style: {
1790
+ } }, t("panelItem.events", { n: Math.min(det.events.length, 30) })), h("div", { style: {
1197
1791
  display: "flex",
1198
1792
  flexDirection: "column",
1199
1793
  gap: 3,
@@ -1209,7 +1803,7 @@ window.__ModuleLoader__.load({
1209
1803
  setSel(null);
1210
1804
  setErr(null);
1211
1805
  }, [snap && snap.id]);
1212
- if (!snap) return muted("未找到该 run(可能已被清理,或地址已过期)。", { padding: 12 });
1806
+ if (!snap) return muted(t("detail.runMissing"), { padding: 12 });
1213
1807
  const stages = snap.stages || [];
1214
1808
  const totals = stages.reduce((a, s) => {
1215
1809
  const u = s.usage;
@@ -1248,7 +1842,7 @@ window.__ModuleLoader__.load({
1248
1842
  } }, h("div", { style: {
1249
1843
  ...flexRow,
1250
1844
  gap: 6
1251
- } }, chip(RUN_STATUS_TEXT[snap.status] || snap.status, stColor(snap.status), { dot: true }), snap.options && snap.options.mode ? chip(String(snap.options.mode), T.text2) : null, h("span", { style: {
1845
+ } }, chip(runStatusText(snap.status), stColor(snap.status), { dot: true }), snap.options && snap.options.mode ? chip(String(snap.options.mode), T.text2) : null, h("span", { style: {
1252
1846
  fontFamily: MONO,
1253
1847
  fontSize: 10.5,
1254
1848
  color: T.text2
@@ -1260,13 +1854,16 @@ window.__ModuleLoader__.load({
1260
1854
  fontSize: 12,
1261
1855
  color: T.text,
1262
1856
  lineHeight: 1.5
1263
- } }, snap.requirement || "(无需求描述)"), h("div", { style: {
1857
+ } }, snap.requirement || t("runList.noRequirement")), h("div", { style: {
1264
1858
  ...flexRow,
1265
1859
  gap: 12,
1266
1860
  fontSize: 10.5,
1267
1861
  color: T.text2,
1268
1862
  fontFamily: MONO
1269
- } }, h("span", null, `${fmtTime(snap.startedAt)}${snap.endedAt ? ` → ${fmtTime(snap.endedAt)}` : " → 进行中"} · ${fmtDur(snap.startedAt, snap.endedAt)}`), h("span", null, `阶段 ${stages.filter((s) => s.status === "done").length}/${stages.length}`), h("span", null, `子代理 ${snap.agentsStarted || 0}`)), h("div", { style: {
1863
+ } }, h("span", null, `${fmtTime(snap.startedAt)}${snap.endedAt ? ` → ${fmtTime(snap.endedAt)}` : " → " + t("detail.endedRunning")} · ${fmtDur(snap.startedAt, snap.endedAt)}`), h("span", null, t("runList.stageProgress", {
1864
+ done: stages.filter((s) => s.status === "done").length,
1865
+ total: stages.length
1866
+ })), h("span", null, t("detail.subagents", { n: snap.agentsStarted || 0 }))), h("div", { style: {
1270
1867
  ...flexRow,
1271
1868
  gap: 10,
1272
1869
  fontSize: 10.5,
@@ -1276,7 +1873,7 @@ window.__ModuleLoader__.load({
1276
1873
  borderRadius: 8,
1277
1874
  background: `color-mix(in srgb, ${T.layer2} 60%, transparent)`,
1278
1875
  border: `1px solid ${T.border}`
1279
- } }, h("span", null, `输入(未命中) ${fmtTokens(totals.input) || "0"}`), h("span", null, `输入(命中) ${fmtTokens(totals.cacheRead) || "0"}`), h("span", null, `写缓存 ${fmtTokens(totals.cacheWrite) || "0"}`), h("span", null, `输出 ${fmtTokens(totals.output) || "0"}`), h("span", null, `${totals.calls} 次调用`), h("span", null, `合计 ${fmtTokens(totalTokens(totals)) || "0"}`)), sectionTitle(`阶段 · ${stages.length}`), h("div", { style: {
1876
+ } }, h("span", null, t("token.inputMiss", { v: fmtTokens(totals.input) || "0" })), h("span", null, t("token.inputHit", { v: fmtTokens(totals.cacheRead) || "0" })), h("span", null, t("token.cacheWrite", { v: fmtTokens(totals.cacheWrite) || "0" })), h("span", null, t("token.output", { v: fmtTokens(totals.output) || "0" })), h("span", null, t("common.callsCount", { n: totals.calls })), h("span", null, t("token.total", { v: fmtTokens(totalTokens(totals)) || "0" }))), sectionTitle(t("detail.stages", { n: stages.length })), h("div", { style: {
1280
1877
  display: "flex",
1281
1878
  flexDirection: "column",
1282
1879
  gap: 4
@@ -1305,7 +1902,7 @@ window.__ModuleLoader__.load({
1305
1902
  fontSize: 11.5,
1306
1903
  color: T.text,
1307
1904
  fontWeight: 500
1308
- } }, s.label || phaseNameOf(s.phase)), chip(stText(s.status), color, { dot: true }), s.outcome && s.outcome !== "completed" ? chip(String(s.outcome), stColor(s.outcome)) : null, h("span", { style: {
1905
+ } }, stageLabelOf(s)), chip(stText(s.status), color, { dot: true }), s.outcome && s.outcome !== "completed" ? chip(String(s.outcome), stColor(s.outcome)) : null, h("span", { style: {
1309
1906
  marginLeft: "auto",
1310
1907
  fontFamily: MONO,
1311
1908
  fontSize: 10,
@@ -1317,13 +1914,13 @@ window.__ModuleLoader__.load({
1317
1914
  fontFamily: MONO,
1318
1915
  fontSize: 10,
1319
1916
  color: T.text2
1320
- } }, h("span", null, stageUsageLine(s) || "无 token 数据"), s.childId ? h("span", { title: s.childId }, `子代理 ${String(s.childId).slice(0, 14)}`) : null), s.summary ? h("div", { style: {
1917
+ } }, h("span", null, stageUsageLine(s) || t("common.noUsage")), s.childId ? h("span", { title: s.childId }, t("detail.subagentChip", { id: String(s.childId).slice(0, 14) })) : null), s.summary ? h("div", { style: {
1321
1918
  fontSize: 10.5,
1322
1919
  color: T.text2,
1323
1920
  marginTop: 3,
1324
1921
  lineHeight: 1.45
1325
1922
  } }, String(s.summary).slice(0, 200)) : null);
1326
- })), err ? muted(`阶段详情读取失败:${err}`, { color: T.error }) : null, sel ? h("div", { style: {
1923
+ })), err ? muted(t("detail.stageFailed", { err }), { color: T.error }) : null, sel ? h("div", { style: {
1327
1924
  padding: "9px 10px",
1328
1925
  borderRadius: 9,
1329
1926
  border: `1px solid ${T.border}`,
@@ -1341,18 +1938,18 @@ window.__ModuleLoader__.load({
1341
1938
  fontSize: 11.5,
1342
1939
  fontWeight: 700,
1343
1940
  color: T.text
1344
- } }, `阶段 #${sel.seq} 详情`), chip(stText(sel.status), stColor(sel.status))), h("button", {
1941
+ } }, t("detail.stageTitle", { seq: sel.seq })), chip(stText(sel.status), stColor(sel.status))), h("button", {
1345
1942
  style: panelBtn,
1346
1943
  onClick: () => setSel(null)
1347
- }, "收起")), h("div", { style: {
1944
+ }, t("common.collapse"))), h("div", { style: {
1348
1945
  fontSize: 10.5,
1349
1946
  color: T.text2,
1350
1947
  fontFamily: MONO
1351
- } }, `阶段 token:${stageUsageLine(sel) || "无"}`), sel.verifyEvidence ? h("div", null, h("div", { style: {
1948
+ } }, t("token.stageLine", { v: stageUsageLine(sel) || t("common.none") })), sel.verifyEvidence ? h("div", null, h("div", { style: {
1352
1949
  fontSize: 11,
1353
1950
  color: T.success,
1354
1951
  marginBottom: 3
1355
- } }, "🔬 验证证据"), h("div", { style: {
1952
+ } }, t("stage.evidenceTitle")), h("div", { style: {
1356
1953
  whiteSpace: "pre-wrap",
1357
1954
  wordBreak: "break-word",
1358
1955
  fontSize: 11,
@@ -1365,11 +1962,11 @@ window.__ModuleLoader__.load({
1365
1962
  maxHeight: 200,
1366
1963
  overflowY: "auto",
1367
1964
  fontFamily: MONO
1368
- } }, sel.verifyEvidence)) : muted("(缺失——契约未兑现,host 已记警告)", { color: T.warn }), sel.output ? h("div", null, h("div", { style: {
1965
+ } }, sel.verifyEvidence)) : muted(t("stage.evidenceMissingShort"), { color: T.warn }), sel.output ? h("div", null, h("div", { style: {
1369
1966
  fontSize: 11,
1370
1967
  color: T.text2,
1371
1968
  marginBottom: 3
1372
- } }, "阶段产出"), h(FoldableText, {
1969
+ } }, t("detail.output")), h(FoldableText, {
1373
1970
  text: sel.output,
1374
1971
  charLimit: 400,
1375
1972
  lineLimit: 8,
@@ -1381,7 +1978,7 @@ window.__ModuleLoader__.load({
1381
1978
  fontSize: 11,
1382
1979
  color: T.text2,
1383
1980
  marginBottom: 3
1384
- } }, `同任务尝试 · ${sel.attempts.length}`), h("div", { style: {
1981
+ } }, t("detail.attempts", { n: sel.attempts.length })), h("div", { style: {
1385
1982
  display: "flex",
1386
1983
  flexDirection: "column",
1387
1984
  gap: 3
@@ -1394,7 +1991,7 @@ window.__ModuleLoader__.load({
1394
1991
  fontFamily: MONO,
1395
1992
  color: T.text2
1396
1993
  }
1397
- }, h("span", null, `#${a.seq}`), chip(stText(a.status), stColor(a.status)), a.outcome ? h("span", null, a.outcome) : null, h("span", { style: { marginLeft: "auto" } }, `${fmtTime(a.startedAt)} · ${fmtDur(a.startedAt, a.endedAt)}`))))) : null) : null, logs.length ? h("div", null, sectionTitle(`日志 · 最近 ${logs.length} 条`), h("div", { style: {
1994
+ }, h("span", null, `#${a.seq}`), chip(stText(a.status), stColor(a.status)), a.outcome ? h("span", null, a.outcome) : null, h("span", { style: { marginLeft: "auto" } }, `${fmtTime(a.startedAt)} · ${fmtDur(a.startedAt, a.endedAt)}`))))) : null) : null, logs.length ? h("div", null, sectionTitle(t("detail.logs", { n: logs.length })), h("div", { style: {
1398
1995
  display: "flex",
1399
1996
  flexDirection: "column",
1400
1997
  gap: 2,
@@ -1447,12 +2044,12 @@ window.__ModuleLoader__.load({
1447
2044
  let alive = true;
1448
2045
  if (!runId) {
1449
2046
  setSnap(null);
1450
- setErr(address ? `无法解析 run 地址:${address}` : readTab ? "读取 tab 地址中…" : "宿主 tab 信息钩子不可用(useTabInfo 缺失)");
2047
+ setErr(address ? t("tab.resolveFailed", { address }) : readTab ? t("tab.readingAddress") : t("tab.noHook"));
1451
2048
  return;
1452
2049
  }
1453
2050
  if (!api) {
1454
2051
  setSnap(null);
1455
- setErr("remote 不可用(插件未挂载或版本过旧)");
2052
+ setErr(t("tab.remoteUnavailable"));
1456
2053
  return;
1457
2054
  }
1458
2055
  api.runDetail(runId).then((v) => {
@@ -1488,15 +2085,15 @@ window.__ModuleLoader__.load({
1488
2085
  display: "flex",
1489
2086
  flexDirection: "column",
1490
2087
  gap: 8
1491
- } }, muted(pending ? "读取 tab 地址中…" : err, { color: pending ? T.text2 : T.error }), pending ? null : h("button", {
2088
+ } }, muted(pending ? t("tab.readingAddress") : err, { color: pending ? T.text2 : T.error }), pending ? null : h("button", {
1492
2089
  style: panelBtn,
1493
2090
  onClick: () => {
1494
2091
  setErr(null);
1495
2092
  setNonce((n) => n + 1);
1496
2093
  }
1497
- }, "重试"));
2094
+ }, t("common.retry")));
1498
2095
  }
1499
- if (!snap) return muted("读取 run 详情中…", { padding: 12 });
2096
+ if (!snap) return muted(t("tab.readingRun"), { padding: 12 });
1500
2097
  return h(RunDetailPane, {
1501
2098
  snap,
1502
2099
  product,
@@ -1545,7 +2142,7 @@ window.__ModuleLoader__.load({
1545
2142
  const attempt = (quiet) => !!(props.openResource && address && props.openResource(address, label, quiet));
1546
2143
  if (attempt(false)) return;
1547
2144
  if (!address) {
1548
- setHint(`${label}:host 未生成可打开的地址(可能缺少会话上下文)`);
2145
+ setHint(t("panel.hintNoAddress", { label }));
1549
2146
  if (fallback) fallback();
1550
2147
  return;
1551
2148
  }
@@ -1560,7 +2157,7 @@ window.__ModuleLoader__.load({
1560
2157
  setTimeout(tick, 120);
1561
2158
  return;
1562
2159
  }
1563
- setHint(`右侧栏打开失败(宿主只在对话视图挂载它):${label}${fallback ? "——已在本面板内联显示" : ""}`);
2160
+ setHint(t("panel.hintRightbarFailed", { label }) + (fallback ? t("panel.hintInlineSuffix") : ""));
1564
2161
  if (fallback) fallback();
1565
2162
  };
1566
2163
  setTimeout(tick, 140);
@@ -1585,7 +2182,7 @@ window.__ModuleLoader__.load({
1585
2182
  try {
1586
2183
  sessions.open(ownerSession);
1587
2184
  } catch (e) {
1588
- setHint(`发起会话 ${String(ownerSession).slice(0, 8)}… 不在会话列表里(可能已被清理)——已在本面板展示详情`);
2185
+ setHint(t("panel.hintSessionGone", { sid: String(ownerSession).slice(0, 8) }));
1589
2186
  if (fallback) fallback();
1590
2187
  return;
1591
2188
  }
@@ -1606,7 +2203,7 @@ window.__ModuleLoader__.load({
1606
2203
  setTimeout(tick, 130);
1607
2204
  return;
1608
2205
  }
1609
- setHint(`已切到发起会话,但右栏没打开(${label})——可在该会话里用「⇥ 右栏打开」重试`);
2206
+ setHint(t("panel.hintSwitchedNoRightbar", { label }));
1610
2207
  };
1611
2208
  setTimeout(tick, 140);
1612
2209
  };
@@ -1614,7 +2211,7 @@ window.__ModuleLoader__.load({
1614
2211
  if (!remote || typeof remote.products !== "function") {
1615
2212
  setState((s) => ({
1616
2213
  ...s,
1617
- err: "remote.products 不可用(插件未挂载或版本过旧)"
2214
+ err: t("panel.remoteProductsUnavailable")
1618
2215
  }));
1619
2216
  return;
1620
2217
  }
@@ -1802,32 +2399,32 @@ window.__ModuleLoader__.load({
1802
2399
  } }, h("span", { style: {
1803
2400
  fontSize: 13,
1804
2401
  fontWeight: 700
1805
- } }, "🏭 团队工作台"), h("span", { style: {
2402
+ } }, t("panel.title")), h("span", { style: {
1806
2403
  fontSize: 11,
1807
2404
  color: T.text2
1808
- } }, "全局面板 · 按产品线"), currentSessionId ? h("span", { style: {
2405
+ } }, t("panel.subtitle")), currentSessionId ? h("span", { style: {
1809
2406
  fontSize: 10,
1810
2407
  color: T.text2,
1811
2408
  fontFamily: MONO
1812
- } }, `当前会话 ${String(currentSessionId).slice(0, 8)}`) : h("span", { style: {
2409
+ } }, t("panel.currentSession", { sid: String(currentSessionId).slice(0, 8) })) : h("span", { style: {
1813
2410
  fontSize: 10,
1814
2411
  color: T.text2
1815
- } }, "无当前会话")), h("div", { style: {
2412
+ } }, t("panel.noSession"))), h("div", { style: {
1816
2413
  ...flexRow,
1817
2414
  gap: 6
1818
2415
  } }, product ? h("button", {
1819
2416
  style: panelBtn,
1820
2417
  onClick: () => loadView(state.current)
1821
- }, "刷新数据") : null, h("button", {
2418
+ }, t("panel.reload")) : null, h("button", {
1822
2419
  style: panelBtn,
1823
- title: "回到对话(再点侧边栏图标即可切回本面板)",
2420
+ title: t("panel.backToChatTip"),
1824
2421
  onClick: () => {
1825
2422
  try {
1826
2423
  const layout = props.layout;
1827
2424
  if (layout && layout.selectPanel) layout.selectPanel(null);
1828
2425
  } catch (e) {}
1829
2426
  }
1830
- }, "回到对话"))), state.err ? h("div", { style: {
2427
+ }, t("panel.backToChat")))), state.err ? h("div", { style: {
1831
2428
  padding: "6px 14px",
1832
2429
  fontSize: 11,
1833
2430
  color: T.error,
@@ -1844,7 +2441,7 @@ window.__ModuleLoader__.load({
1844
2441
  } }, h("span", null, hint), h("button", {
1845
2442
  style: panelBtn,
1846
2443
  onClick: () => setHint(null)
1847
- }, "知道了")) : null, h("div", { style: {
2444
+ }, t("common.gotIt"))) : null, h("div", { style: {
1848
2445
  flex: 1,
1849
2446
  minHeight: 0,
1850
2447
  display: "flex",
@@ -1862,7 +2459,7 @@ window.__ModuleLoader__.load({
1862
2459
  minHeight: 0,
1863
2460
  display: "flex",
1864
2461
  flexDirection: "column"
1865
- } }, !state.current ? h("div", { style: { padding: "12px 14px" } }, muted("选择左侧产品线查看 backlog 与 run(首次进入默认选最近更新的产品线)。")) : !view ? h("div", { style: { padding: "12px 14px" } }, muted("读取产品线数据中…")) : h(react.default.Fragment, null, h("div", { style: {
2462
+ } }, !state.current ? h("div", { style: { padding: "12px 14px" } }, muted(t("panel.pickProduct"))) : !view ? h("div", { style: { padding: "12px 14px" } }, muted(t("panel.loadingView"))) : h(react.default.Fragment, null, h("div", { style: {
1866
2463
  ...flexRow,
1867
2464
  justifyContent: "space-between",
1868
2465
  gap: 10,
@@ -1883,21 +2480,21 @@ window.__ModuleLoader__.load({
1883
2480
  ...flexRow,
1884
2481
  gap: 6,
1885
2482
  flex: "0 0 auto"
1886
- } }, chip(`run ${product.totalRuns}`, T.text2), product.activeRuns > 0 ? chip(`活跃 ${product.activeRuns}`, T.brand, { dot: true }) : null, product.lastVerdict ? chip(`验收 ${product.lastVerdict}`, stColor("accepted")) : null)), h("div", { style: {
2483
+ } }, chip(t("panel.runChip", { n: product.totalRuns }), T.text2), product.activeRuns > 0 ? chip(t("panel.activeChip", { n: product.activeRuns }), T.brand, { dot: true }) : null, product.lastVerdict ? chip(t("panel.verdictChip", { v: product.lastVerdict }), stColor("accepted")) : null)), h("div", { style: {
1887
2484
  display: "flex",
1888
2485
  alignItems: "flex-end",
1889
2486
  gap: 2,
1890
2487
  padding: "0 14px",
1891
2488
  borderBottom: `1px solid ${T.border}`
1892
- } }, panelTabBtn("run", `🚀 流水线 run · ${runs.length}`), panelTabBtn("backlog", `📋 Backlog · ${backlogCount}`), h("div", { style: {
2489
+ } }, panelTabBtn("run", t("panel.tabRuns", { n: runs.length })), panelTabBtn("backlog", t("panel.tabBacklog", { n: backlogCount })), h("div", { style: {
1893
2490
  marginLeft: "auto",
1894
2491
  ...flexRow,
1895
2492
  gap: 6,
1896
2493
  paddingBottom: 7
1897
- } }, panelTab === "run" && !runsExpanded && pinnedActive.length > 0 ? chip(`已置顶进行中 ${pinnedActive.length}`, T.brand, { dot: true }) : null, panelTab === "run" && runsMatched.length > RUN_PREVIEW ? h("button", {
2494
+ } }, panelTab === "run" && !runsExpanded && pinnedActive.length > 0 ? chip(t("panel.pinnedActive", { n: pinnedActive.length }), T.brand, { dot: true }) : null, panelTab === "run" && runsMatched.length > RUN_PREVIEW ? h("button", {
1898
2495
  style: panelBtn,
1899
2496
  onClick: () => setRunsExpanded((v) => !v)
1900
- }, runsExpanded ? `只看最近 ${RUN_PREVIEW} 条` : `展开全部 ${runsMatched.length} 条`) : null)), h("div", { style: {
2497
+ }, runsExpanded ? t("panel.showRecent", { n: RUN_PREVIEW }) : t("panel.showAll", { n: runsMatched.length })) : null)), h("div", { style: {
1901
2498
  flex: 1,
1902
2499
  minHeight: 0,
1903
2500
  overflowY: "auto",
@@ -1907,19 +2504,23 @@ window.__ModuleLoader__.load({
1907
2504
  ...flexRow,
1908
2505
  gap: 6,
1909
2506
  marginBottom: 8
1910
- } }, ...RUN_STATUS_ORDER.filter((st) => runStatusCounts[st]).map((st) => filterChip(`${RUN_STATUS_TEXT[st] || st} ${runStatusCounts[st]}`, stColor(st), runSelSet.has(st), () => toggleRunStatus(st))), runFiltering ? h("button", {
2507
+ } }, ...RUN_STATUS_ORDER.filter((st) => runStatusCounts[st]).map((st) => filterChip(`${runStatusText(st)} ${runStatusCounts[st]}`, stColor(st), runSelSet.has(st), () => toggleRunStatus(st))), runFiltering ? h("button", {
1911
2508
  style: panelBtn,
1912
- title: "清除 run 状态筛选",
2509
+ title: t("panel.clearRunFilterTip"),
1913
2510
  onClick: () => setRunFilter([])
1914
- }, `筛选中 ${runSel.length} 项 · 显示 ${runsMatched.length}/${runs.length} × 清除`) : null), visibleRuns.length ? h(RunList, {
2511
+ }, t("panel.filtered", {
2512
+ sel: runSel.length,
2513
+ shown: runsMatched.length,
2514
+ total: runs.length
2515
+ })) : null), visibleRuns.length ? h(RunList, {
1915
2516
  runs: visibleRuns,
1916
2517
  activeRunId: detail && detail.kind === "run" && detail.run ? detail.run.id : null,
1917
2518
  onOpenRun: openRun,
1918
2519
  onInlineRun: showInline
1919
- }) : muted("该筛选下没有 run。", { fontSize: 10.5 }), muted("点一行看详情浮层;「去会话右栏」= 跳到该 run 的发起会话并在其右侧栏打开(与任务夹产物并排)", {
2520
+ }) : muted(t("panel.emptyRunFilter"), { fontSize: 10.5 }), muted(t("panel.runHint"), {
1920
2521
  fontSize: 10,
1921
2522
  marginTop: 8
1922
- })) : h(react.default.Fragment, null, muted("点状态徽章可筛选(多选);终态卡片默认收起,筛选时自动显示", {
2523
+ })) : h(react.default.Fragment, null, muted(t("panel.boardHint"), {
1923
2524
  fontSize: 10,
1924
2525
  marginBottom: 8
1925
2526
  }), h(BacklogGroups, {
@@ -1952,21 +2553,21 @@ window.__ModuleLoader__.load({
1952
2553
  fontSize: 11.5,
1953
2554
  fontWeight: 700,
1954
2555
  color: T.text
1955
- } }, "run 详情"), h("div", { style: {
2556
+ } }, t("panel.runDetail")), h("div", { style: {
1956
2557
  ...flexRow,
1957
2558
  gap: 6
1958
2559
  } }, detail && detail.data && detail.data.address ? h("button", {
1959
2560
  style: brandBtn,
1960
- title: "跳到该 run 的发起会话,并在那个会话的右侧栏打开(与任务夹产物并排看)",
2561
+ title: t("panel.goOwnerSessionTip"),
1961
2562
  onClick: () => goOwnerSessionAndOpen({
1962
2563
  ownerSession: detail.data.ownerSession,
1963
2564
  address: detail.data.address,
1964
2565
  label: detail.data.id
1965
2566
  })
1966
- }, "去发起会话") : null, h("button", {
2567
+ }, t("panel.goOwnerSession")) : null, h("button", {
1967
2568
  style: panelBtn,
1968
2569
  onClick: closeDetail
1969
- }, "关闭"))), h(RunDetailPane, {
2570
+ }, t("common.close")))), h(RunDetailPane, {
1970
2571
  snap: detail && detail.data,
1971
2572
  product: state.current,
1972
2573
  api
@@ -2059,7 +2660,7 @@ window.__ModuleLoader__.load({
2059
2660
  const usage = stageUsageLine(s);
2060
2661
  return h("div", {
2061
2662
  key,
2062
- title: `${s.label} —— 点击查看阶段详情`,
2663
+ title: t("stage.cardTip", { label: stageLabelOf(s) }),
2063
2664
  onMouseDown: (e) => e.stopPropagation(),
2064
2665
  onClick: () => onOpen && onOpen(s),
2065
2666
  style: {
@@ -2108,8 +2709,11 @@ window.__ModuleLoader__.load({
2108
2709
  textOverflow: "ellipsis",
2109
2710
  whiteSpace: "nowrap"
2110
2711
  }
2111
- }, s.__taskKey || s.label), s.attempts && s.attempts.length > 1 ? h("span", {
2112
- title: `重试 ${s.attempts.length - 1} 次(共 ${s.attempts.length} 次尝试)`,
2712
+ }, stageLabelOf(s)), s.attempts && s.attempts.length > 1 ? h("span", {
2713
+ title: t("stage.retryTip", {
2714
+ n: s.attempts.length - 1,
2715
+ m: s.attempts.length
2716
+ }),
2113
2717
  style: {
2114
2718
  fontFamily: MONO,
2115
2719
  fontSize: 10,
@@ -2256,7 +2860,7 @@ window.__ModuleLoader__.load({
2256
2860
  });
2257
2861
  } catch (e) {}
2258
2862
  };
2259
- const outText = cur && cur.output ? cur.output : cur && cur.summary ? `(该 run 未保存完整正文,展示摘要)\n\n${cur.summary}` : det.err ? `⚠ 加载失败:${det.err}` : det.loading ? "加载中…" : "(无产物正文)";
2863
+ const outText = cur && cur.output ? cur.output : cur && cur.summary ? t("stage.summaryFallback", { summary: cur.summary }) : det.err ? t("stage.loadFailed", { err: det.err }) : det.loading ? t("common.loading") : t("stage.noOutput");
2260
2864
  const closeBtn = {
2261
2865
  font: "inherit",
2262
2866
  width: 26,
@@ -2318,7 +2922,7 @@ window.__ModuleLoader__.load({
2318
2922
  textOverflow: "ellipsis",
2319
2923
  whiteSpace: "nowrap"
2320
2924
  }
2321
- }, st ? st.label : "阶段详情"), h("div", { style: {
2925
+ }, st ? stageLabelOf(st) : t("stage.detailTitle")), h("div", { style: {
2322
2926
  fontSize: 10.5,
2323
2927
  color: T.text2,
2324
2928
  marginTop: 1,
@@ -2327,7 +2931,7 @@ window.__ModuleLoader__.load({
2327
2931
  } }, `${st ? `#${st.seq} · ${st.phase}` : ""}${st && (st.startedAt || st.endedAt) ? ` · ${fmtDur(st.startedAt, st.endedAt)}` : ""}`)), st ? chip(stText(st.status), color, { dot: true }) : null, h("button", {
2328
2932
  onClick: onClose,
2329
2933
  style: closeBtn,
2330
- title: "关闭"
2934
+ title: t("common.close")
2331
2935
  }, "✕")), h("div", { style: {
2332
2936
  flex: 1,
2333
2937
  overflowY: "auto",
@@ -2344,7 +2948,7 @@ window.__ModuleLoader__.load({
2344
2948
  fontWeight: 700,
2345
2949
  color: T.text2,
2346
2950
  letterSpacing: .3
2347
- } }, "TOKEN · 官方口径"), h("span", { style: {
2951
+ } }, t("token.officialTitle")), h("span", { style: {
2348
2952
  fontSize: 11.5,
2349
2953
  fontFamily: MONO,
2350
2954
  color: T.text,
@@ -2358,7 +2962,7 @@ window.__ModuleLoader__.load({
2358
2962
  fontWeight: 700,
2359
2963
  color: T.text2,
2360
2964
  letterSpacing: .3
2361
- } }, `↻ 尝试历史(${attempts.length} 次 · 点击查看该次详情)`), h("div", { style: {
2965
+ } }, t("stage.attemptHistory", { n: attempts.length })), h("div", { style: {
2362
2966
  display: "flex",
2363
2967
  flexDirection: "column",
2364
2968
  gap: 4
@@ -2389,7 +2993,7 @@ window.__ModuleLoader__.load({
2389
2993
  color: aColor,
2390
2994
  flex: "0 0 64px",
2391
2995
  fontWeight: 700
2392
- } }, a.status === "done" ? "✅ 成功" : a.status === "failed" ? `❌ ${a.outcome || "失败"}` : "⏳ 进行中"), h("span", { style: {
2996
+ } }, a.status === "done" ? t("stage.attemptDone") : a.status === "failed" ? t("stage.attemptFailed", { outcome: a.outcome || t("common.failed") }) : t("stage.attemptRunning")), h("span", { style: {
2393
2997
  flex: 1,
2394
2998
  minWidth: 0,
2395
2999
  fontSize: 10.5,
@@ -2397,7 +3001,7 @@ window.__ModuleLoader__.load({
2397
3001
  overflow: "hidden",
2398
3002
  textOverflow: "ellipsis",
2399
3003
  whiteSpace: "nowrap"
2400
- } }, (a.summary || a.outcome || "(无摘要)").slice(0, 80)), h("span", { style: {
3004
+ } }, (a.summary || a.outcome || t("common.noSummary")).slice(0, 80)), h("span", { style: {
2401
3005
  fontFamily: MONO,
2402
3006
  fontSize: 10,
2403
3007
  color: T.text2,
@@ -2415,7 +3019,7 @@ window.__ModuleLoader__.load({
2415
3019
  } }, h("button", {
2416
3020
  onClick: openChild,
2417
3021
  disabled: !hasChild,
2418
- title: crossSession ? `该子代理由会话 ${ownerSession.slice(-6)} 发起,跨会话跳转暂不支持——请打开其发起会话的团队工作台查看` : hasChild ? "跳转到该阶段子代理会话(完整推理与工具调用轨迹);跳转后请切换「对话」tab 查看" : "该阶段无可用子代理会话",
3022
+ title: crossSession ? t("stage.childCrossSessionTip", { sid: ownerSession.slice(-6) }) : hasChild ? t("stage.childJumpTip") : t("stage.childNoneTip"),
2419
3023
  style: {
2420
3024
  font: "inherit",
2421
3025
  fontSize: 12,
@@ -2432,17 +3036,17 @@ window.__ModuleLoader__.load({
2432
3036
  color: T.brand,
2433
3037
  opacity: hasChild ? 1 : .45
2434
3038
  }
2435
- }, "🎬 跳转子代理会话"), crossSession ? h("div", { style: {
3039
+ }, t("stage.childJumpBtn")), crossSession ? h("div", { style: {
2436
3040
  fontSize: 10.5,
2437
3041
  color: T.text2,
2438
3042
  textAlign: "center",
2439
3043
  lineHeight: 1.55
2440
- } }, `跨会话暂不支持:该子代理由会话 ${ownerSession ? ownerSession.slice(-6) : ""} 发起。如需查看轨迹,请打开其发起会话的团队工作台。`) : hasChild ? h("div", { style: {
3044
+ } }, t("stage.childCrossSessionNote", { sid: ownerSession ? ownerSession.slice(-6) : "" })) : hasChild ? h("div", { style: {
2441
3045
  fontSize: 10.5,
2442
3046
  color: T.text2,
2443
3047
  textAlign: "center",
2444
3048
  lineHeight: 1.55
2445
- } }, "跳转成功后,请切「对话」tab 查看该子代理的完整会话轨迹") : null), st && phaseKeyOf(st.phase) === "dev" ? h("div", { style: {
3049
+ } }, t("stage.childJumpNote")) : null), st && phaseKeyOf(st.phase) === "dev" ? h("div", { style: {
2446
3050
  display: "flex",
2447
3051
  flexDirection: "column",
2448
3052
  gap: 5
@@ -2451,7 +3055,7 @@ window.__ModuleLoader__.load({
2451
3055
  fontWeight: 700,
2452
3056
  color: T.text2,
2453
3057
  letterSpacing: .3
2454
- } }, "🔬 验证证据"), cur && cur.verifyEvidence ? h("div", { style: {
3058
+ } }, t("stage.evidenceTitle")), cur && cur.verifyEvidence ? h("div", { style: {
2455
3059
  whiteSpace: "pre-wrap",
2456
3060
  wordBreak: "break-word",
2457
3061
  fontSize: 11.5,
@@ -2472,7 +3076,7 @@ window.__ModuleLoader__.load({
2472
3076
  borderRadius: 10,
2473
3077
  padding: "8px 12px",
2474
3078
  lineHeight: 1.55
2475
- } }, "(缺失——契约未兑现,host 已记警告;可与 logs/teamflow/<runId>/ 命令输出日志对照)")) : null, h("div", { style: {
3079
+ } }, t("stage.evidenceMissing"))) : null, h("div", { style: {
2476
3080
  display: "flex",
2477
3081
  flexDirection: "column",
2478
3082
  gap: 5
@@ -2481,7 +3085,7 @@ window.__ModuleLoader__.load({
2481
3085
  fontWeight: 700,
2482
3086
  color: T.text2,
2483
3087
  letterSpacing: .3
2484
- } }, "📄 阶段性产物"), h("div", { style: {
3088
+ } }, t("stage.artifactsTitle")), h("div", { style: {
2485
3089
  whiteSpace: "pre-wrap",
2486
3090
  wordBreak: "break-word",
2487
3091
  fontSize: 12,
@@ -2504,7 +3108,7 @@ window.__ModuleLoader__.load({
2504
3108
  } }, h("div", { style: {
2505
3109
  fontSize: 28,
2506
3110
  marginBottom: 8
2507
- } }, "🏭"), "暂无运行中的流水线——让模型调用 teamflow_start,或在上方输入需求");
3111
+ } }, "🏭"), t("pipeline.empty"));
2508
3112
  const groups = [];
2509
3113
  const taskKeyOf = (s) => String(s.taskKey || String(s.label || "").replace(/^开发 · /, "").replace(/((?:第 \d+ 次重试|补跑))$/, "").trim());
2510
3114
  for (const st of active.stages || []) {
@@ -2779,7 +3383,7 @@ window.__ModuleLoader__.load({
2779
3383
  justifyContent: "center",
2780
3384
  color: T.text2,
2781
3385
  fontSize: 13
2782
- } }, "流水线还没有开始执行节点") : null, h("div", {
3386
+ } }, t("pipeline.noNodes")) : null, h("div", {
2783
3387
  onMouseDown: (e) => e.stopPropagation(),
2784
3388
  style: {
2785
3389
  position: "absolute",
@@ -2797,7 +3401,7 @@ window.__ModuleLoader__.load({
2797
3401
  boxShadow: "0 6px 20px rgba(0,0,0,.16)"
2798
3402
  }
2799
3403
  }, h("button", {
2800
- title: "缩小",
3404
+ title: t("pipeline.zoomOut"),
2801
3405
  onClick: () => zoomBy(.86),
2802
3406
  style: zoomStyle
2803
3407
  }, "−"), h("span", { style: {
@@ -2807,7 +3411,7 @@ window.__ModuleLoader__.load({
2807
3411
  minWidth: 34,
2808
3412
  textAlign: "center"
2809
3413
  } }, `${Math.round(view.s * 100)}%`), h("button", {
2810
- title: "放大",
3414
+ title: t("pipeline.zoomIn"),
2811
3415
  onClick: () => zoomBy(1.16),
2812
3416
  style: zoomStyle
2813
3417
  }, "+"), h("span", { style: {
@@ -2815,7 +3419,7 @@ window.__ModuleLoader__.load({
2815
3419
  height: 14,
2816
3420
  background: T.border
2817
3421
  } }), h("button", {
2818
- title: "适应画布",
3422
+ title: t("pipeline.fitCanvas"),
2819
3423
  onClick: fitNow,
2820
3424
  style: {
2821
3425
  ...zoomStyle,
@@ -2830,7 +3434,7 @@ window.__ModuleLoader__.load({
2830
3434
  color: T.text2,
2831
3435
  paddingRight: 4,
2832
3436
  opacity: .85
2833
- } }, "✥ 拖动画布 · 滚轮缩放")), det ? h(StageDetailDrawer, {
3437
+ } }, t("pipeline.canvasHint"))), det ? h(StageDetailDrawer, {
2834
3438
  det,
2835
3439
  onClose: closeDet,
2836
3440
  sessionId,
@@ -2876,12 +3480,12 @@ window.__ModuleLoader__.load({
2876
3480
  } }, h("div", { style: {
2877
3481
  fontSize: 28,
2878
3482
  marginBottom: 8
2879
- } }, "📋"), "backlog 为空(还没有流水线运行过)");
3483
+ } }, "📋"), t("board.empty"));
2880
3484
  const subtaskMap = {};
2881
3485
  for (const t of backlog.tasks || []) if (t.type === "subtask" && t.id) subtaskMap[t.id] = t;
2882
3486
  const move = async (kind, id, to) => {
2883
3487
  try {
2884
- await api.backlogUpdate(kind, id, to, sessionId, "看板拖拽流转");
3488
+ await api.backlogUpdate(kind, id, to, sessionId, t("board.dragReason"));
2885
3489
  } catch (e) {}
2886
3490
  onRefresh();
2887
3491
  };
@@ -2911,7 +3515,11 @@ window.__ModuleLoader__.load({
2911
3515
  transition: "opacity .1s ease, transform .12s ease",
2912
3516
  boxShadow: "0 1px 2px rgba(0,0,0,.05)"
2913
3517
  },
2914
- title: `${item.id} · ${item.status}${item.summary ? "\n" + item.summary : ""}(点击查看详情)`
3518
+ title: t("board.cardTip", {
3519
+ id: item.id,
3520
+ status: item.status,
3521
+ summary: item.summary ? "\n" + item.summary : ""
3522
+ })
2915
3523
  }, h("div", { style: {
2916
3524
  display: "flex",
2917
3525
  alignItems: "center",
@@ -2961,7 +3569,7 @@ window.__ModuleLoader__.load({
2961
3569
  fontFamily: MONO,
2962
3570
  minWidth: 0
2963
3571
  } }, item.devAssign ? h("span", {
2964
- title: `dev 分配\n${item.devAssign}`,
3572
+ title: t("board.assignDevTip", { who: item.devAssign }),
2965
3573
  style: {
2966
3574
  display: "inline-flex",
2967
3575
  alignItems: "center",
@@ -2973,7 +3581,7 @@ window.__ModuleLoader__.load({
2973
3581
  whiteSpace: "nowrap"
2974
3582
  }
2975
3583
  }, `👨‍💻${item.devAssign}`) : null, item.qaAssign ? h("span", {
2976
- title: `qa 分配\n${item.qaAssign}`,
3584
+ title: t("board.assignQaTip", { who: item.qaAssign }),
2977
3585
  style: {
2978
3586
  display: "inline-flex",
2979
3587
  alignItems: "center",
@@ -2985,7 +3593,7 @@ window.__ModuleLoader__.load({
2985
3593
  whiteSpace: "nowrap"
2986
3594
  }
2987
3595
  }, `🧪${item.qaAssign}`) : null, item.acceptBy ? h("span", {
2988
- title: `验收/汇报人\n${item.acceptBy}`,
3596
+ title: t("board.acceptTip", { who: item.acceptBy }),
2989
3597
  style: {
2990
3598
  display: "inline-flex",
2991
3599
  alignItems: "center",
@@ -3027,7 +3635,7 @@ window.__ModuleLoader__.load({
3027
3635
  color: T.text2,
3028
3636
  fontFamily: MONO,
3029
3637
  marginBottom: 3
3030
- } }, h("span", null, `📦 ${subs.length} 子卡`), done > 0 ? h("span", { style: { color: T.success } }, `${done}✓`) : null, running > 0 ? h("span", { style: { color: T.brand } }, `${running}⟳`) : null, failed > 0 ? h("span", { style: { color: T.error } }, `${failed}✗`) : null), subs.map((sub) => h("div", {
3638
+ } }, h("span", null, t("board.subtaskCount", { n: subs.length })), done > 0 ? h("span", { style: { color: T.success } }, `${done}✓`) : null, running > 0 ? h("span", { style: { color: T.brand } }, `${running}⟳`) : null, failed > 0 ? h("span", { style: { color: T.error } }, `${failed}✗`) : null), subs.map((sub) => h("div", {
3031
3639
  key: sub.id,
3032
3640
  style: {
3033
3641
  display: "flex",
@@ -3057,7 +3665,7 @@ window.__ModuleLoader__.load({
3057
3665
  whiteSpace: "nowrap"
3058
3666
  }
3059
3667
  }, (sub.title || "").replace(/^开发 · /, "")), sub.devAssign ? h("span", {
3060
- title: `dev 分配\n${sub.devAssign}`,
3668
+ title: t("board.assignDevTip", { who: sub.devAssign }),
3061
3669
  style: {
3062
3670
  flex: "0 1 auto",
3063
3671
  color: T.text2,
@@ -3112,7 +3720,7 @@ window.__ModuleLoader__.load({
3112
3720
  fontWeight: 700,
3113
3721
  fontSize: 13,
3114
3722
  padding: "6px 0 8px"
3115
- } }, h("span", { style: { fontSize: 14 } }, kind === "req" ? "📌" : kind === "task" ? "🔧" : "🐞"), KIND_TITLE[kind], h("span", { style: {
3723
+ } }, h("span", { style: { fontSize: 14 } }, kind === "req" ? "📌" : kind === "task" ? "🔧" : "🐞"), kindTitle(kind), h("span", { style: {
3116
3724
  fontSize: 11,
3117
3725
  fontWeight: 600,
3118
3726
  color: T.text2,
@@ -3188,7 +3796,7 @@ window.__ModuleLoader__.load({
3188
3796
  }) : null);
3189
3797
  }
3190
3798
  function fmtAt(ts) {
3191
- return ts ? new Date(ts).toLocaleTimeString("zh-CN", {
3799
+ return ts ? new Date(ts).toLocaleTimeString(localeTag(), {
3192
3800
  hour: "2-digit",
3193
3801
  minute: "2-digit"
3194
3802
  }) : "—";
@@ -3266,7 +3874,7 @@ window.__ModuleLoader__.load({
3266
3874
  color: T.warn,
3267
3875
  flex: "0 0 auto"
3268
3876
  } }, `⛽${fmtTokens((extra.usage.input || 0) + (extra.usage.cacheRead || 0) + (extra.usage.cacheWrite || 0) + (extra.usage.output || 0))}`) : null, extra && extra.assignee ? h("span", {
3269
- title: `dev 分配\n${extra.assignee}`,
3877
+ title: t("board.assignDevTip", { who: extra.assignee }),
3270
3878
  style: {
3271
3879
  display: "inline-flex",
3272
3880
  alignItems: "center",
@@ -3345,7 +3953,7 @@ window.__ModuleLoader__.load({
3345
3953
  } }, `${d.id} · ${d.kind}${d.severity ? " · " + d.severity : ""}`) : null), d ? chip(stText(d.status), color, { dot: true }) : null, h("button", {
3346
3954
  onClick: onClose,
3347
3955
  style: closeBtn,
3348
- title: "关闭"
3956
+ title: t("common.close")
3349
3957
  }, "✕")), h("div", { style: {
3350
3958
  flex: 1,
3351
3959
  overflowY: "auto",
@@ -3357,7 +3965,7 @@ window.__ModuleLoader__.load({
3357
3965
  color: T.text2,
3358
3966
  fontSize: 12,
3359
3967
  padding: 12
3360
- } }, "加载中…") : err ? h("div", { style: {
3968
+ } }, t("common.loading")) : err ? h("div", { style: {
3361
3969
  color: T.error,
3362
3970
  fontSize: 12,
3363
3971
  padding: 12
@@ -3366,7 +3974,7 @@ window.__ModuleLoader__.load({
3366
3974
  display: "flex",
3367
3975
  flexDirection: "column",
3368
3976
  gap: 4
3369
- } }, secTitle("概览"), d.spec ? h(FoldableText, {
3977
+ } }, secTitle(t("item.overview")), d.spec ? h(FoldableText, {
3370
3978
  text: d.spec,
3371
3979
  charLimit: 300,
3372
3980
  lineLimit: 4,
@@ -3384,7 +3992,28 @@ window.__ModuleLoader__.load({
3384
3992
  flexDirection: "column",
3385
3993
  gap: 2,
3386
3994
  marginTop: 2
3387
- } }, d.devAssign ? kv("👨‍💻 dev", d.devAssign.slice(0, 26), true) : null, d.qaAssign ? kv("🧪 qa", d.qaAssign.slice(0, 26), true) : null, d.assignBy ? kv("✅ 验收", d.assignBy.slice(0, 26), true) : null, d.owner ? kv("👤 owner", d.owner.slice(0, 26), true) : null, typeof d.retries === "number" && d.retries > 0 ? kv("↻ 重试", String(d.retries)) : null, d.humanIntervention ? kv("⚠ 人工介入", "需人工介入") : null, kv("更新于", fmtAt(d.updatedAt) || "—"))),
3995
+ } }, d.devAssign ? kv("👨‍💻 dev", d.devAssign.slice(0, 26), true) : null, d.qaAssign ? kv("🧪 qa", d.qaAssign.slice(0, 26), true) : null, d.assignBy ? kv(t("item.acceptRow"), d.assignBy.slice(0, 26), true) : null, d.owner ? kv("👤 owner", d.owner.slice(0, 26), true) : null, typeof d.retries === "number" && d.retries > 0 ? kv(t("item.retryRow"), String(d.retries)) : null, d.humanIntervention ? kv(t("item.humanRow"), t("common.needsHuman")) : null, kv(t("item.updatedAt"), fmtAt(d.updatedAt) || "—"))),
3996
+ kind === "bug" ? h("div", { style: {
3997
+ display: "flex",
3998
+ flexDirection: "column",
3999
+ gap: 4,
4000
+ padding: "8px 10px",
4001
+ borderRadius: 8,
4002
+ background: T.layer2,
4003
+ border: `1px solid ${T.border}`
4004
+ } }, secTitle(t("panelItem.defectSection")), d.spec ? h(FoldableText, {
4005
+ text: d.spec,
4006
+ charLimit: 300,
4007
+ lineLimit: 4
4008
+ }) : null, h("div", { style: {
4009
+ display: "flex",
4010
+ flexDirection: "column",
4011
+ gap: 2
4012
+ } }, d.defectId ? kv(t("panelItem.row.defectId"), String(d.defectId)) : null, d.severity ? kv(t("panelItem.row.severity"), String(d.severity)) : null, d.module ? kv(t("panelItem.row.module"), String(d.module)) : null, d.reproduce ? kv(t("panelItem.row.reproduce"), String(d.reproduce)) : null, d.expected ? kv(t("panelItem.row.expected"), String(d.expected)) : null, d.actual ? kv(t("panelItem.row.actual"), String(d.actual)) : null, d.defectCheck ? kv(t("panelItem.row.defectCheck"), String(d.defectCheck)) : null, d.defectCriterion ? kv(t("panelItem.row.defectCriterion"), String(d.defectCriterion)) : null, d.defectAc ? kv(t("panelItem.row.defectAc"), String(d.defectAc)) : null), d.reproduce || d.expected || d.actual ? null : h("div", { style: {
4013
+ fontSize: 10.5,
4014
+ color: T.warn,
4015
+ lineHeight: 1.5
4016
+ } }, t("panelItem.noDefectDetail"))) : null,
3388
4017
  d.runInfo ? (() => {
3389
4018
  const ri = d.runInfo;
3390
4019
  return h("div", { style: {
@@ -3399,14 +4028,14 @@ window.__ModuleLoader__.load({
3399
4028
  display: "flex",
3400
4029
  alignItems: "center",
3401
4030
  gap: 8
3402
- } }, secTitle("运行"), h("span", { style: {
4031
+ } }, secTitle(t("item.runSection")), h("span", { style: {
3403
4032
  fontFamily: MONO,
3404
4033
  fontSize: 10.5,
3405
4034
  color: T.text2,
3406
4035
  whiteSpace: "nowrap"
3407
4036
  } }, ri.runId), chip(stText(ri.status), stColor(ri.status)), onShowRun ? h("button", {
3408
4037
  onClick: () => onShowRun(ri.runId),
3409
- title: `跳转到该需求的流水线视图 #${String(ri.runId).slice(-6)}`,
4038
+ title: t("item.jumpRunTip", { id: String(ri.runId).slice(-6) }),
3410
4039
  style: {
3411
4040
  marginLeft: "auto",
3412
4041
  flex: "0 0 auto",
@@ -3421,7 +4050,7 @@ window.__ModuleLoader__.load({
3421
4050
  color: T.brand,
3422
4051
  lineHeight: "16px"
3423
4052
  }
3424
- }, "▶ 流水线") : null), ri.startedAt || ri.endedAt ? h("span", { style: {
4053
+ }, t("item.runBtn")) : null), ri.startedAt || ri.endedAt ? h("span", { style: {
3425
4054
  fontSize: 10.5,
3426
4055
  color: T.text2,
3427
4056
  fontFamily: MONO
@@ -3429,8 +4058,8 @@ window.__ModuleLoader__.load({
3429
4058
  display: "flex",
3430
4059
  flexDirection: "column",
3431
4060
  gap: 4
3432
- } }, secTitle("需求原文"), h(FoldableText, {
3433
- text: ri.requirement || "(无原文)",
4061
+ } }, secTitle(t("item.requirement")), h(FoldableText, {
4062
+ text: ri.requirement || t("item.noRequirement"),
3434
4063
  charLimit: 300,
3435
4064
  lineLimit: 4,
3436
4065
  style: {
@@ -3444,7 +4073,7 @@ window.__ModuleLoader__.load({
3444
4073
  display: "flex",
3445
4074
  flexDirection: "column",
3446
4075
  gap: 4
3447
- } }, secTitle("任务夹"), h("div", { style: {
4076
+ } }, secTitle(t("item.runDocs")), h("div", { style: {
3448
4077
  fontSize: 11.5,
3449
4078
  fontFamily: MONO,
3450
4079
  color: T.brand,
@@ -3462,7 +4091,7 @@ window.__ModuleLoader__.load({
3462
4091
  onClick: () => {
3463
4092
  if (openArtifact) openArtifact(a.address, a.name);
3464
4093
  },
3465
- title: `在右侧栏预览 ${d.runDocs}/${a.name}`,
4094
+ title: t("item.previewTip", { path: `${d.runDocs}/${a.name}` }),
3466
4095
  style: {
3467
4096
  fontSize: 10.5,
3468
4097
  fontWeight: 600,
@@ -3480,7 +4109,7 @@ window.__ModuleLoader__.load({
3480
4109
  display: "flex",
3481
4110
  flexDirection: "column",
3482
4111
  gap: 4
3483
- } }, secTitle("TOKEN · 官方口径"), h("span", { style: {
4112
+ } }, secTitle(t("token.officialTitle")), h("span", { style: {
3484
4113
  fontSize: 11.5,
3485
4114
  fontFamily: MONO,
3486
4115
  color: T.text,
@@ -3492,7 +4121,7 @@ window.__ModuleLoader__.load({
3492
4121
  marginTop: 2
3493
4122
  } }, Object.entries(d.byRole).sort((a, b) => totalTokens(b[1]) - totalTokens(a[1])).map(([role, uRaw]) => {
3494
4123
  const u = uRaw;
3495
- const label = role === "dev" ? "👨‍💻 开发" : role === "qa" ? "🧪 QA" : role === "acceptance" ? "✅ 验收" : role === "pm" ? "📌 产品" : role === "design" ? "🎨 设计" : role === "arch" ? "🏗 架构" : "⚙️ " + role;
4124
+ const label = roleChip(role);
3496
4125
  return h("div", {
3497
4126
  key: role,
3498
4127
  style: {
@@ -3503,13 +4132,13 @@ window.__ModuleLoader__.load({
3503
4132
  fontFamily: MONO,
3504
4133
  color: T.text2
3505
4134
  }
3506
- }, h("span", { style: { flex: "0 0 64px" } }, label), h("span", { style: { color: T.text } }, `⛽${fmtTokens(totalTokens(u))}`), h("span", null, `${fmtTokens(u.input || 0)}i / ${fmtTokens(u.cacheRead || 0)}c / ${fmtTokens(u.output || 0)}o · ${u.calls || 0} 次`));
4135
+ }, h("span", { style: { flex: "0 0 64px" } }, label), h("span", { style: { color: T.text } }, `⛽${fmtTokens(totalTokens(u))}`), h("span", null, `${fmtTokens(u.input || 0)}i / ${fmtTokens(u.cacheRead || 0)}c / ${fmtTokens(u.output || 0)}o · ${t("common.calls", { n: u.calls || 0 })}`));
3507
4136
  })) : null) : null,
3508
4137
  d.subtasks && d.subtasks.length > 0 ? h("div", { style: {
3509
4138
  display: "flex",
3510
4139
  flexDirection: "column",
3511
4140
  gap: 4
3512
- } }, secTitle(`关联子卡(${d.subtasks.length})`), h("div", { style: {
4141
+ } }, secTitle(t("item.subtasks", { n: d.subtasks.length })), h("div", { style: {
3513
4142
  display: "flex",
3514
4143
  flexDirection: "column",
3515
4144
  gap: 5
@@ -3521,7 +4150,7 @@ window.__ModuleLoader__.load({
3521
4150
  display: "flex",
3522
4151
  flexDirection: "column",
3523
4152
  gap: 4
3524
- } }, secTitle(`关联缺陷(${d.bugs.length})`), h("div", { style: {
4153
+ } }, secTitle(t("item.bugs", { n: d.bugs.length })), h("div", { style: {
3525
4154
  display: "flex",
3526
4155
  flexDirection: "column",
3527
4156
  gap: 5
@@ -3530,7 +4159,7 @@ window.__ModuleLoader__.load({
3530
4159
  display: "flex",
3531
4160
  flexDirection: "column",
3532
4161
  gap: 4
3533
- } }, secTitle(`流转时间线(${d.events.length})`), h("div", { style: {
4162
+ } }, secTitle(t("item.timeline", { n: d.events.length })), h("div", { style: {
3534
4163
  display: "flex",
3535
4164
  flexDirection: "column",
3536
4165
  gap: 0
@@ -3566,7 +4195,7 @@ window.__ModuleLoader__.load({
3566
4195
  }, ev.reason || ""))))) : null
3567
4196
  ]));
3568
4197
  }
3569
- function TeamSelector({ sessionId, remote }) {
4198
+ function TeamSelector({ sessionId, remote, locale }) {
3570
4199
  const [teams, setTeams] = react.default.useState([]);
3571
4200
  const [active, setActive] = react.default.useState(null);
3572
4201
  const [open, setOpen] = react.default.useState(false);
@@ -3579,7 +4208,11 @@ window.__ModuleLoader__.load({
3579
4208
  const at = unwrap(await remote.getActiveTeam(sessionId), "getActiveTeam");
3580
4209
  setActive(at && at.team ? at.team : null);
3581
4210
  } catch (e) {}
3582
- }, [remote, sessionId]);
4211
+ }, [
4212
+ remote,
4213
+ sessionId,
4214
+ locale
4215
+ ]);
3583
4216
  react.default.useEffect(() => {
3584
4217
  load();
3585
4218
  }, [load]);
@@ -3610,7 +4243,7 @@ window.__ModuleLoader__.load({
3610
4243
  style: { position: "relative" }
3611
4244
  }, h("button", {
3612
4245
  onClick: () => setOpen(!open),
3613
- title: active ? `当前团队:${active.name}(点击切换)` : "选择团队",
4246
+ title: active ? t("team.currentTip", { name: active.name }) : t("team.pick"),
3614
4247
  style: {
3615
4248
  display: "inline-flex",
3616
4249
  alignItems: "center",
@@ -3627,14 +4260,14 @@ window.__ModuleLoader__.load({
3627
4260
  transition: "all .12s ease"
3628
4261
  }
3629
4262
  }, h("span", { style: { fontSize: 12 } }, active ? active.icon : "🏭"), h("span", {
3630
- title: active && active.name ? String(active.name) : "团队",
4263
+ title: active && active.name ? String(active.name) : t("team.label"),
3631
4264
  style: {
3632
4265
  maxWidth: 80,
3633
4266
  overflow: "hidden",
3634
4267
  textOverflow: "ellipsis",
3635
4268
  whiteSpace: "nowrap"
3636
4269
  }
3637
- }, active ? active.name : "团队"), h("span", { style: {
4270
+ }, active ? active.name : t("team.label")), h("span", { style: {
3638
4271
  fontSize: 8,
3639
4272
  opacity: .6
3640
4273
  } }, open ? "▲" : "▼")), open ? h("div", { style: {
@@ -3654,7 +4287,7 @@ window.__ModuleLoader__.load({
3654
4287
  fontSize: 10,
3655
4288
  color: T.text2,
3656
4289
  borderBottom: `1px solid ${T.border}`
3657
- } }, "选择团队"), h("button", {
4290
+ } }, t("team.pick")), h("button", {
3658
4291
  onClick: () => select(null),
3659
4292
  style: {
3660
4293
  display: "flex",
@@ -3683,14 +4316,14 @@ window.__ModuleLoader__.load({
3683
4316
  } }, h("div", { style: {
3684
4317
  fontWeight: 600,
3685
4318
  lineHeight: 1.35
3686
- } }, "无团队(直接对话)"), h("div", { style: {
4319
+ } }, t("team.none")), h("div", { style: {
3687
4320
  fontSize: 10.5,
3688
4321
  color: T.text2,
3689
4322
  marginTop: 3,
3690
4323
  lineHeight: 1.45,
3691
4324
  whiteSpace: "normal",
3692
4325
  wordBreak: "break-word"
3693
- } }, "不走 teamflow,模型直接工作")), !active ? h("span", { style: {
4326
+ } }, t("team.noneNote"))), !active ? h("span", { style: {
3694
4327
  marginLeft: "auto",
3695
4328
  color: T.text2,
3696
4329
  fontSize: 12,
@@ -3739,7 +4372,10 @@ window.__ModuleLoader__.load({
3739
4372
  }
3740
4373
  /** 解包 remote 信封:失败抛错;成功返回 value。 */
3741
4374
  function unwrap(res, what) {
3742
- if (!res || !res.ok) throw new Error(`${what || "remote"} 调用失败:${res && res.error && (res.error.message || res.error.code) || "未知错误"}`);
4375
+ if (!res || !res.ok) throw new Error(t("common.remoteCallFailed", {
4376
+ what: what || "remote",
4377
+ detail: res && res.error && (res.error.message || res.error.code) || t("common.unknownError")
4378
+ }));
3743
4379
  return res.value;
3744
4380
  }
3745
4381
  function TeamFlowView(props) {
@@ -3758,7 +4394,7 @@ window.__ModuleLoader__.load({
3758
4394
  if (!api) {
3759
4395
  setState((s) => ({
3760
4396
  ...s,
3761
- err: "remote 未就绪"
4397
+ err: t("common.remoteNotReady")
3762
4398
  }));
3763
4399
  return;
3764
4400
  }
@@ -3887,7 +4523,7 @@ window.__ModuleLoader__.load({
3887
4523
  fontWeight: 700,
3888
4524
  fontSize: 14,
3889
4525
  lineHeight: "18px"
3890
- } }, "团队工作台"), h("span", { style: {
4526
+ } }, t("workbench.title")), h("span", { style: {
3891
4527
  fontSize: 11,
3892
4528
  color: T.text2,
3893
4529
  display: "flex",
@@ -3900,7 +4536,7 @@ window.__ModuleLoader__.load({
3900
4536
  display: "inline-block",
3901
4537
  background: anyRunning ? T.success : T.text2,
3902
4538
  animation: anyRunning ? "tf-pulse 1.6s ease-in-out infinite" : "none"
3903
- } }), anyRunning ? "流水线运行中" : "空闲")), h("button", {
4539
+ } }), anyRunning ? t("workbench.running") : t("workbench.idle"))), h("button", {
3904
4540
  onClick: refresh,
3905
4541
  style: {
3906
4542
  ...btn,
@@ -3909,10 +4545,13 @@ window.__ModuleLoader__.load({
3909
4545
  alignItems: "center",
3910
4546
  gap: 5
3911
4547
  }
3912
- }, "🔄 刷新"), canResume ? h("button", {
4548
+ }, t("workbench.refresh")), canResume ? h("button", {
3913
4549
  onClick: onResume,
3914
4550
  disabled: busy,
3915
- title: `断点续跑 ${activeRun.id}\n当前状态:${RUN_STATUS_TEXT[activeRun.status] || activeRun.status};跳过已完成阶段,从第一个未完成阶段重跑`,
4551
+ title: t("workbench.resumeTip", {
4552
+ id: activeRun.id,
4553
+ status: runStatusText(activeRun.status)
4554
+ }),
3916
4555
  style: {
3917
4556
  ...btn,
3918
4557
  background: T.error,
@@ -3920,14 +4559,14 @@ window.__ModuleLoader__.load({
3920
4559
  border: "none",
3921
4560
  fontWeight: 600
3922
4561
  }
3923
- }, busy ? "续跑中…" : `↻ 从断点重跑 #${String(activeRun.id).slice(-6)}`) : null), err ? h("div", { style: {
4562
+ }, busy ? t("workbench.resuming") : t("workbench.resumeBtn", { id: String(activeRun.id).slice(-6) })) : null), err ? h("div", { style: {
3924
4563
  color: T.error,
3925
4564
  fontSize: 12,
3926
4565
  background: `color-mix(in srgb, ${T.error} 8%, transparent)`,
3927
4566
  border: `1px solid color-mix(in srgb, ${T.error} 30%, transparent)`,
3928
4567
  borderRadius: 8,
3929
4568
  padding: "7px 11px"
3930
- } }, `⚠ ${err}(确认已安装 dsh-plugin-teamflow 且 web 已重启)`) : null, needHuman.length > 0 ? h("div", { style: {
4569
+ } }, t("workbench.loadFailed", { err })) : null, needHuman.length > 0 ? h("div", { style: {
3931
4570
  display: "flex",
3932
4571
  alignItems: "center",
3933
4572
  gap: 10,
@@ -3940,13 +4579,13 @@ window.__ModuleLoader__.load({
3940
4579
  fontWeight: 700,
3941
4580
  color: T.warn,
3942
4581
  fontSize: 12.5
3943
- } }, `⚠ ${needHuman.length} 项需人工介入`), needHuman.slice(0, 5).map((item) => {
4582
+ } }, t("workbench.needsHumanBanner", { n: needHuman.length })), needHuman.slice(0, 5).map((item) => {
3944
4583
  const kind = (backlog.requirements || []).some((r) => r.id === item.id) ? "req" : (backlog.tasks || []).some((t) => t.id === item.id) ? "task" : "bug";
3945
4584
  const fin = kind === "bug" ? "verified" : "accepted";
3946
4585
  return h("button", {
3947
4586
  key: item.id,
3948
4587
  onClick: async () => {
3949
- await api.backlogUpdate(kind, item.id, fin, props.sessionId, "人工处理");
4588
+ await api.backlogUpdate(kind, item.id, fin, props.sessionId, t("workbench.manualReason"));
3950
4589
  refresh();
3951
4590
  },
3952
4591
  style: {
@@ -3956,7 +4595,7 @@ window.__ModuleLoader__.load({
3956
4595
  border: "none",
3957
4596
  fontWeight: 600
3958
4597
  }
3959
- }, `处理 ${item.id}`);
4598
+ }, t("workbench.handle", { id: item.id }));
3960
4599
  })) : null, h("div", { style: {
3961
4600
  display: "flex",
3962
4601
  alignItems: "center",
@@ -3965,10 +4604,10 @@ window.__ModuleLoader__.load({
3965
4604
  } }, h("button", {
3966
4605
  onClick: () => setTab("pipeline"),
3967
4606
  style: tabBtn(tab === "pipeline")
3968
- }, "🔄 流水线"), h("button", {
4607
+ }, t("workbench.tabPipeline")), h("button", {
3969
4608
  onClick: () => setTab("board"),
3970
4609
  style: tabBtn(tab === "board")
3971
- }, "📋 Backlog 看板"), h("div", { style: {
4610
+ }, t("workbench.tabBoard")), h("div", { style: {
3972
4611
  marginLeft: "auto",
3973
4612
  display: "flex",
3974
4613
  alignItems: "center",
@@ -3984,14 +4623,14 @@ window.__ModuleLoader__.load({
3984
4623
  color: activeRun.status === "interrupted" ? T.warn : T.text2,
3985
4624
  border: `1px solid ${T.border}`
3986
4625
  }
3987
- }, `#${String(activeRun.id).slice(-8)} · ${RUN_STATUS_TEXT[activeRun.status] || activeRun.status}`) : null, total && total.input + total.cacheRead + total.cacheWrite + total.output > 0 ? h("span", {
4626
+ }, `#${String(activeRun.id).slice(-8)} · ${runStatusText(activeRun.status)}`) : null, total && total.input + total.cacheRead + total.cacheWrite + total.output > 0 ? h("span", {
3988
4627
  style: {
3989
4628
  fontSize: 11.5,
3990
4629
  fontFamily: MONO,
3991
4630
  color: T.text2,
3992
4631
  cursor: "help"
3993
4632
  },
3994
- title: "输入(未命中)/输入(命中)/输出 全部阶段合计"
4633
+ title: t("token.allStagesTip")
3995
4634
  }, `∑ ⇅${fmtTokens(total.input)}/⇅${fmtTokens(total.cacheRead)}·⬆${fmtTokens(total.output)}`) : null)), h("div", { style: {
3996
4635
  display: "flex",
3997
4636
  alignItems: "center",
@@ -3999,7 +4638,7 @@ window.__ModuleLoader__.load({
3999
4638
  fontSize: 12,
4000
4639
  flexWrap: "wrap"
4001
4640
  } }, h("span", {
4002
- title: `当前工作区(workspace 级隔离):${workspace && workspace.path || "未连接工作区"}`,
4641
+ title: t("workbench.workspaceTip", { path: workspace && workspace.path || t("workbench.noWorkspace") }),
4003
4642
  style: {
4004
4643
  display: "inline-flex",
4005
4644
  alignItems: "center",
@@ -4024,7 +4663,7 @@ window.__ModuleLoader__.load({
4024
4663
  display: "flex",
4025
4664
  alignItems: "center",
4026
4665
  gap: 5
4027
- } }, h("span", { style: { color: T.text2 } }, "历史"), runs.length ? runs.map((r) => {
4666
+ } }, h("span", { style: { color: T.text2 } }, t("workbench.history")), runs.length ? runs.map((r) => {
4028
4667
  const sel = r.id === (runId || runs[0] && runs[0].id);
4029
4668
  return h("button", {
4030
4669
  key: r.id,
@@ -4035,13 +4674,13 @@ window.__ModuleLoader__.load({
4035
4674
  }) : h("span", { style: {
4036
4675
  color: T.text2,
4037
4676
  fontSize: 11.5
4038
- } }, "(暂无)")) : null, tab === "pipeline" && activeRun && activeRun.address ? h("button", {
4677
+ } }, t("common.noneDash"))) : null, tab === "pipeline" && activeRun && activeRun.address ? h("button", {
4039
4678
  style: chipBtn(false),
4040
- title: "在右侧栏打开该 run 详情(与任务夹产物并排看)",
4679
+ title: t("workbench.openRightBarTip"),
4041
4680
  onClick: () => {
4042
4681
  if (!(props.openResource && props.openResource(activeRun.address, activeRun.id))) console.warn("[teamflow] 右侧栏不可用,run 详情请在画布节点里查看");
4043
4682
  }
4044
- }, "⇥ 右栏打开") : null), h("div", { style: {
4683
+ }, t("workbench.openRightBarBtn")) : null), h("div", { style: {
4045
4684
  flex: 1,
4046
4685
  minHeight: 0,
4047
4686
  display: "flex",
@@ -4082,6 +4721,24 @@ window.__ModuleLoader__.load({
4082
4721
  async function apply(ctx) {
4083
4722
  await ctx.remote.$mount(TEAMFLOW_REMOTE_CONTRIBUTION);
4084
4723
  const teamflow = ctx.get("remote.teamflow");
4724
+ const t = ctx.locale.bind(NS);
4725
+ ctx.effect(() => ctx.locale.register(NS, {
4726
+ zh,
4727
+ en
4728
+ }), "teamflow: dictionaries");
4729
+ ctx.effect(() => ctx.locale.subscribe(() => setTranslator(t, () => ctx.locale.getSnapshot().active)), "teamflow: translator sync");
4730
+ setTranslator(t, () => ctx.locale.getSnapshot().active);
4731
+ const pushLocaleToHost = (active) => {
4732
+ try {
4733
+ if (typeof active !== "string" || !active) return;
4734
+ const p = teamflow.setLocale(active);
4735
+ if (p && typeof p.catch === "function") p.catch(() => {});
4736
+ } catch (e) {}
4737
+ };
4738
+ ctx.effect(() => {
4739
+ pushLocaleToHost(ctx.locale.getSnapshot().active);
4740
+ return ctx.locale.subscribe(() => pushLocaleToHost(ctx.locale.getSnapshot().active));
4741
+ }, "teamflow: host locale push");
4085
4742
  const openResourceSafe = (address, label, quiet) => {
4086
4743
  try {
4087
4744
  const sidebarRight = ctx.get("sidebarRight");
@@ -4103,11 +4760,13 @@ window.__ModuleLoader__.load({
4103
4760
  name: "sidebar.panellist",
4104
4761
  id: "teamflow",
4105
4762
  order: 60,
4106
- label: "团队工作台"
4763
+ locale: NS,
4764
+ label: () => t("workbench.title")
4107
4765
  }, TeamflowPanelIcon));
4108
4766
  ctx.slots.inject("main", () => ctx.slots.register({
4109
4767
  name: "main",
4110
4768
  key: "teamflow",
4769
+ locale: NS,
4111
4770
  inject: () => ({
4112
4771
  remote: teamflow,
4113
4772
  sessions: ctx.get("sessions"),
@@ -4126,6 +4785,7 @@ window.__ModuleLoader__.load({
4126
4785
  ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
4127
4786
  name: "sidebar.right.pane.tab",
4128
4787
  key: RUN_TAB_ID,
4788
+ locale: NS,
4129
4789
  inject: () => ({
4130
4790
  remote: teamflow,
4131
4791
  openArtifact
@@ -4135,7 +4795,8 @@ window.__ModuleLoader__.load({
4135
4795
  name: "conversation.view",
4136
4796
  id: "teamflow",
4137
4797
  order: 20,
4138
- label: "🏭 团队工作台",
4798
+ locale: NS,
4799
+ label: () => `🏭 ${t("workbench.title")}`,
4139
4800
  inject: (sessionId) => ({
4140
4801
  sessionId,
4141
4802
  remote: teamflow,
@@ -4148,9 +4809,11 @@ window.__ModuleLoader__.load({
4148
4809
  name: "conversation.input.right",
4149
4810
  id: "teamflow-team-select",
4150
4811
  order: 5,
4812
+ locale: NS,
4151
4813
  inject: (sessionId) => ({
4152
4814
  sessionId,
4153
- remote: teamflow
4815
+ remote: teamflow,
4816
+ locale: ctx.locale.getSnapshot().active
4154
4817
  })
4155
4818
  }, TeamSelector));
4156
4819
  }