dsh-taskboard 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +27 -160
  2. package/lib/client.js +2564 -678
  3. package/lib/host/execution.js +3 -0
  4. package/lib/host/execution.js.map +1 -1
  5. package/lib/host/routes.js +31 -1
  6. package/lib/host/routes.js.map +1 -1
  7. package/lib/host/session-sync.js +449 -0
  8. package/lib/host/session-sync.js.map +1 -0
  9. package/lib/host/store.js +9 -2
  10. package/lib/host/store.js.map +1 -1
  11. package/lib/index.js +109 -2
  12. package/lib/index.js.map +1 -1
  13. package/lib/shared/api.js.map +1 -1
  14. package/lib/shared/protocol.js +27 -1
  15. package/lib/shared/protocol.js.map +1 -1
  16. package/package.json +75 -75
  17. package/src/client/api.ts +8 -0
  18. package/src/client/board/AlertModal.tsx +3 -1
  19. package/src/client/board/ImportModal.tsx +26 -24
  20. package/src/client/board/SettingsModal.tsx +98 -22
  21. package/src/client/board/SlashPromptInput.tsx +272 -0
  22. package/src/client/board/TaskBoard.tsx +53 -49
  23. package/src/client/board/TaskCard.tsx +33 -21
  24. package/src/client/board/TaskDetail.tsx +169 -104
  25. package/src/client/board/TaskFormModal.tsx +254 -202
  26. package/src/client/board/TemplateManager.tsx +32 -29
  27. package/src/client/board/labels.ts +36 -27
  28. package/src/client/controller.ts +62 -2
  29. package/src/client/i18n/en.ts +455 -0
  30. package/src/client/i18n/runtime.ts +155 -0
  31. package/src/client/i18n/zh.ts +460 -0
  32. package/src/client/index.ts +182 -42
  33. package/src/client/sidebar-entry.ts +13 -3
  34. package/src/client/styles.ts +131 -0
  35. package/src/host/execution.ts +14 -1
  36. package/src/host/routes.ts +49 -1
  37. package/src/host/session-sync.ts +650 -0
  38. package/src/host/store.ts +15 -1
  39. package/src/index.ts +125 -1
  40. package/src/shared/api.ts +49 -0
  41. package/src/shared/protocol.ts +54 -0
  42. package/src/shared/version.ts +1 -1
package/lib/client.js CHANGED
@@ -5,8 +5,8 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
8
- let react_dom_client = require("react-dom/client");
9
8
  let react = require("react");
9
+ let react_dom_client = require("react-dom/client");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
 
12
12
  //#region src/client/api.ts
@@ -68,6 +68,8 @@ window.__ModuleLoader__.load({
68
68
  templateDelete: (id) => post("/dsh-taskboard/templates/delete", { id }),
69
69
  settings: () => get("/dsh-taskboard/settings"),
70
70
  updateSettings: (body) => post("/dsh-taskboard/settings/update", body),
71
+ promptCompletions: () => get("/dsh-taskboard/prompt-completions"),
72
+ modelCatalog: () => get("/dsh-taskboard/model-catalog"),
71
73
  stream(onChange, onGap) {
72
74
  const es = new EventSource("/dsh-taskboard/events");
73
75
  let revision;
@@ -157,10 +159,29 @@ window.__ModuleLoader__.load({
157
159
  * says otherwise. Before 0.5.0 the implicit default was 'worktree'.
158
160
  */
159
161
  const DEFAULT_ISOLATION = "none";
162
+ /** Factory default permission preset (0.5.5). */
163
+ const DEFAULT_PERMISSION = "workspace-write";
164
+ /** Validate and normalize a permission string into a valid {@link PermissionMode}. */
165
+ function asPermission(raw) {
166
+ if (typeof raw !== "string") return DEFAULT_PERMISSION;
167
+ const normalized = raw.trim();
168
+ if (normalized === "workspace-write" || normalized === "workspaceWrite") return "workspace-write";
169
+ if (normalized === "read-only" || normalized === "readOnly") return "read-only";
170
+ if (normalized === "danger-full-access" || normalized === "fullAccess") return "danger-full-access";
171
+ throw new Error("permission must be 'workspace-write', 'read-only', or 'danger-full-access'");
172
+ }
160
173
  /** The effective default isolation for NEW tasks (board setting → factory default). */
161
174
  function defaultIsolationOf(settings) {
162
175
  return settings?.defaultIsolation ?? "none";
163
176
  }
177
+ /** The effective external session sync switch (board setting → factory default false). */
178
+ function defaultSyncExternalSessionsOf(settings) {
179
+ return settings?.syncExternalSessions ?? false;
180
+ }
181
+ /** The effective default permission preset for NEW tasks (board setting → factory default 'workspace-write'). */
182
+ function defaultPermissionOf(settings) {
183
+ return settings?.defaultPermission ?? "workspace-write";
184
+ }
164
185
  /**
165
186
  * Parse a five-field cron expression. Supported field syntax: star, star/step
166
187
  * (`* / n` without spaces), a single number, an `a-b` range, and comma lists
@@ -262,6 +283,974 @@ window.__ModuleLoader__.load({
262
283
  };
263
284
  }
264
285
 
286
+ //#endregion
287
+ //#region src/client/i18n/zh.ts
288
+ /**
289
+ * zh-CN dictionary — the taskboard UI namespace's key canon.
290
+ *
291
+ * Every key the board renders goes here; en.ts is type-checked against
292
+ * this file's key set (Dict), so the two dictionaries can never drift
293
+ * apart at compile time. Keys are flat '<domain>.<object>[.<detail>'
294
+ * strings; values may carry {name} placeholders (translate() substitutes
295
+ * them; unknown names are left verbatim — {{lastExecution}} survives).
296
+ *
297
+ * @module dsh-taskboard/client/i18n/zh
298
+ */
299
+ const zh = {
300
+ "shared.close": "关闭",
301
+ "shared.cancel": "取消",
302
+ "shared.loading": "读取中…",
303
+ "shared.blocked": "受阻",
304
+ "shared.permission": "权限",
305
+ "shared.confirmDelete": "确认删除",
306
+ "shared.current": "(当前:{name})",
307
+ "shared.entry.aria": "Agent 任务看板",
308
+ "shared.entry.label": "任务看板",
309
+ "shared.stats.title": "待办 {todo} | 进行中 {doing} | 待验收 {review}(待办|进行中|待验收)",
310
+ "shared.duplicate.suffix": "(副本)",
311
+ "status.column.backlog": "待规划",
312
+ "status.column.todo": "待办",
313
+ "status.column.in_progress": "进行中",
314
+ "status.column.in_review": "待验收",
315
+ "status.column.done": "已完成",
316
+ "status.column.canceled": "已取消",
317
+ "status.column.archived": "已归档",
318
+ "status.pill.backlog": "待规划",
319
+ "status.pill.todo": "待办",
320
+ "status.pill.in_progress": "进行中",
321
+ "status.pill.in_review": "待验收",
322
+ "status.pill.done": "完成",
323
+ "status.pill.canceled": "取消",
324
+ "status.pill.archived": "归档",
325
+ "status.move.backlog": "待规划",
326
+ "status.move.todo": "待办",
327
+ "status.move.in_progress": "进行中",
328
+ "status.move.in_review": "待验收",
329
+ "status.move.done": "完成",
330
+ "status.move.canceled": "取消",
331
+ "status.move.archived": "归档",
332
+ "urgency.urgent": "紧急",
333
+ "urgency.normal": "一般",
334
+ "urgency.relaxed": "不急",
335
+ "outcome.running": "执行中",
336
+ "outcome.succeeded": "成功",
337
+ "outcome.failed": "失败",
338
+ "outcome.cancelled": "已取消",
339
+ "board.title": "Agent 任务看板",
340
+ "board.count.tasks": "{n} 任务 · rev {rev}",
341
+ "board.action.newTask": "+ 新建任务 ▼",
342
+ "board.action.blankTask": "空白任务",
343
+ "board.action.manageTemplates": "⌗ 管理模板…",
344
+ "board.search.placeholder": "搜索标题 / ID…",
345
+ "board.filter.allProjects": "全部项目",
346
+ "board.sort.title": "列内排序",
347
+ "board.sort.default": "默认排序",
348
+ "board.sort.updated": "最近更新",
349
+ "board.sort.urgency": "按紧急度",
350
+ "board.sort.created": "创建时间",
351
+ "board.sort.byTitle": "按标题",
352
+ "board.action.backToBoard": "返回看板",
353
+ "board.action.otherTasks": "其它任务",
354
+ "board.action.settingsTitle": "看板设置:新建任务的默认执行隔离等",
355
+ "board.action.settings": "🛠 设置",
356
+ "board.action.diagTitle": "健康诊断:遗留 worktree、台账基本项",
357
+ "board.action.diag": "⚙ 诊断",
358
+ "board.action.importTitle": "从 JSON 备份文件导入台账(预览后合并或整册替换)",
359
+ "board.action.import": "⬆ 导入",
360
+ "board.action.exportTitle": "导出台账:完整 JSON 备份或任务清单 CSV",
361
+ "board.action.export": "⬇ 导出 ▼",
362
+ "board.export.jsonTitle": "完整台账备份(含执行历史与看板设置),可用于导入恢复",
363
+ "board.export.json": "完整台账(JSON)",
364
+ "board.export.csvTitle": "任务清单表格(Excel 可直接打开,中文已加 BOM)",
365
+ "board.export.csv": "任务清单(CSV)",
366
+ "board.drag.forbidden": "无法从「{from}」拖至「{to}」",
367
+ "board.empty": "无任务",
368
+ "board.secondary.empty": "无已取消 / 已归档 / 已删除任务",
369
+ "board.group.trashed": "已删除",
370
+ "diag.title": "健康诊断",
371
+ "diag.subtitle": "台账基本项与 worktree 遗留清理",
372
+ "diag.revision": "台账修订号",
373
+ "diag.tasks": "任务总数",
374
+ "diag.running": "执行中",
375
+ "diag.orphans": "遗留 worktree",
376
+ "diag.orphans.heading": "遗留 worktree(台账无主但目录存在)",
377
+ "diag.orphans.none": "无遗留 — 各项目 .dsh-worktrees 目录干净",
378
+ "diag.orphans.cleanup": "清理",
379
+ "diag.orphans.hint": "提示:有未提交修改的遗留目录会被拒绝清理,请先手动处理其内容。live 任务的 worktree 请在任务详情页删除。",
380
+ "diag.gitignore.heading": "gitignore 建议",
381
+ "diag.gitignore.none": "无待办 — 各 git 项目已忽略 .dsh-worktrees 目录",
382
+ "diag.gitignore.suggestA": "建议在 .gitignore 加入一行",
383
+ "diag.gitignore.suggestB": "(不会自动修改)",
384
+ "card.drag.running": "该任务正由会话执行中({title}),不能拖动",
385
+ "card.badge.stale": "⏱ 认领超时",
386
+ "card.badge.modelTitle": "固定模型: {model}",
387
+ "card.badge.modelEffort": " · 思考强度: {effort}",
388
+ "card.badge.checklistReview": "待验收:清单未全部勾选",
389
+ "card.badge.checklist": "验收清单进度",
390
+ "card.badge.pendingPurge": "待清除",
391
+ "card.session.jumpTitle": "点击一键跳转到会话:{id}",
392
+ "card.session.missing": "该会话已被删除({id}),无法打开",
393
+ "card.session.archived": "该会话已归档({id}),已从会话列表隐藏",
394
+ "card.session.unavailable": "会话导航不可用,会话 ID:{id}",
395
+ "card.reject.placeholder": "退回原因(可选,agent 开工前会读)…",
396
+ "card.reject.confirm": "退回待办",
397
+ "card.action.doneTitle": "验收完成:移至已完成",
398
+ "card.action.done": "✓ 完成",
399
+ "card.action.rejectTitle": "退回待办,可附退回原因",
400
+ "card.action.reject": "✗ 退回",
401
+ "alert.ok": "知道了",
402
+ "diff.commit": "提交 {hash}",
403
+ "diff.file": "文件 {path}",
404
+ "diff.truncated": "⚠ 内容过长已截断",
405
+ "diff.failed": "获取失败(原因见看板顶部错误条;对象可能已随 worktree 删除丢失)",
406
+ "checklist.title": "验收清单(DoD)",
407
+ "checklist.unchecked": " · {n} 项未完成",
408
+ "checklist.allDone": " · 全部完成",
409
+ "checklist.byUser": "👤 用户",
410
+ "checklist.uncheckedItem": "未完成",
411
+ "checklist.evidence": "证据:{note}",
412
+ "report.title": "执行报告",
413
+ "report.submitted": "由执行会话提交 · {time}",
414
+ "report.changedFiles": "改动文件",
415
+ "report.checks": "自验情况",
416
+ "report.artifacts": "产物",
417
+ "report.risk": "剩余风险",
418
+ "iso.title": "执行隔离",
419
+ "iso.none": "📁 原目录执行",
420
+ "iso.worktreeTitle": "执行隔离 · Worktree",
421
+ "iso.branch": "🌿 分支",
422
+ "iso.baseline": "基线 {base} → {head}",
423
+ "iso.changed": "改动 {n} 个文件",
424
+ "iso.commit.openTitle": "点击展开该提交的 diff",
425
+ "iso.commits.more": "… 共 {n} 个提交",
426
+ "iso.nocommit": "该次执行没有产生提交(改动可能未提交,见下方警告)",
427
+ "iso.dirty.toggle": "⚠ 有 {n} 处未提交修改(合并前请让 agent 提交,或手动处理)",
428
+ "iso.dirty.expand": " ▼ 查看文件",
429
+ "iso.dirty.collapse": " ▲",
430
+ "iso.dirty.openTitle": "点击查看该文件的未提交 diff",
431
+ "iso.dirty.more": "… 共 {n} 处(完整列表见任务台账)",
432
+ "iso.hint.running": "执行中 — 结束后可合并或清理",
433
+ "iso.merge.confirm": "将分支以 --no-ff 合并到主工作区?",
434
+ "iso.merge.go": "确认合并",
435
+ "iso.merge.title": "在主工作区 git merge --no-ff 该任务分支(要求主区干净;冲突会原样报告)",
436
+ "iso.merge.button": "⇥ 合并到主工作区",
437
+ "iso.merge.failed": "合并失败:{error}",
438
+ "iso.merge.noop": "该分支没有领先主工作区的新提交,无需合并(可退回续跑或直接清理)",
439
+ "iso.remove.wt": "🗑 删除 worktree",
440
+ "iso.remove.wtTitle": "git worktree remove(有未提交修改时拒绝)",
441
+ "iso.remove.wtb": "🗑 删 worktree + 分支",
442
+ "iso.remove.wtbTitle": "删除 worktree 并删除任务分支(有未提交修改时拒绝)",
443
+ "iso.remove.confirmWt": "删除 worktree 目录?",
444
+ "iso.remove.confirmWtb": "删除 worktree 并删除分支?",
445
+ "iso.remove.failed": "删除失败:{error}",
446
+ "iso.remove.branchFailed": "worktree 已删除,但分支删除失败:{error}",
447
+ "iso.hint.keep": "分支与 worktree 保留中 — 可退回继续修改",
448
+ "detail.session.jumpTitle": "一键跳转到对应会话:{id}",
449
+ "detail.session.jump": "🤖 跳转会话 ↗",
450
+ "detail.action.edit": "✎ 编辑",
451
+ "detail.action.duplicateTitle": "复制此任务的全部配置为一张新卡(待办列)",
452
+ "detail.action.duplicate": "⧉ 复制",
453
+ "detail.action.saveTplTitle": "把此任务的配置(含清单)保存为模板,新建任务时可用",
454
+ "detail.action.saveTplDone": "已存为模板(新建任务 ▼ 下拉可用,可在模板管理中改名)",
455
+ "detail.action.saveTpl": "⌗ 存为模板",
456
+ "detail.action.reuseTitle": "续跑:保留现有 worktree 与分支(上次的改动和提交都在原处),在其上继续执行;默认「立即执行」会重置为全新基线",
457
+ "detail.action.reuse": "↻ 续跑",
458
+ "detail.action.runTitleModel": "新会话执行({model})",
459
+ "detail.action.runTitleDefault": "新会话执行(默认模型)",
460
+ "detail.action.run": "▶ 立即执行",
461
+ "detail.action.stopConfirm": "停止该执行会话?",
462
+ "detail.action.stop": "停止",
463
+ "detail.action.stopTitle": "停止执行会话 {id}(任务回到待办)",
464
+ "detail.action.stopExec": "■ 停止执行",
465
+ "detail.chip.nextRun": "{cron} · 下次 {time}",
466
+ "detail.chip.checklist": "清单 {done}/{total}",
467
+ "detail.chip.isolated": "Worktree 隔离",
468
+ "detail.chip.holderTitle": "点击跳转至该会话:{id}",
469
+ "detail.chip.holderStale": "认领超时 · ",
470
+ "detail.chip.holderBy": "由 ",
471
+ "detail.chip.holderSuffix": " 持有 ↗",
472
+ "detail.chip.trashed": "已删除待清除",
473
+ "detail.sub.line": "更新 {time} · 最近操作 {who}",
474
+ "detail.updatedBy.system": "⚙️ 系统",
475
+ "detail.updatedBy.user": "👤 用户",
476
+ "detail.field.description": "描述",
477
+ "detail.field.prompt": "执行 Prompt",
478
+ "detail.move.to": "移至→{status}",
479
+ "detail.move.confirmDoneUnchecked": "仍有 {n} 项清单未勾选,确认完成?",
480
+ "detail.move.confirmDone": "确认完成?",
481
+ "detail.move.confirm": "确认",
482
+ "detail.blocked.unmark": "✓ 解除受阻",
483
+ "detail.blocked.mark": "⛔ 标记受阻",
484
+ "detail.release.title": "释放 {id} 的认领:任务回到待办(持有会话可能仍在工作,确认它已停止后再释放)",
485
+ "detail.release.button": "🔓 释放认领",
486
+ "detail.comments.title": "评论",
487
+ "detail.comments.empty": "暂无评论 — agent 交接时会在这里汇报改动与验证结果",
488
+ "detail.comments.user": "用户",
489
+ "detail.composer.placeholder": "以用户身份留言(agent 开工前会读)…",
490
+ "detail.composer.send": "发表",
491
+ "detail.exec.title": "执行记录",
492
+ "detail.exec.prunedTitle": "更早的 {n} 条执行记录已按保留上限清理",
493
+ "detail.exec.pruned": "+{n} 已清理",
494
+ "detail.exec.trigger.manual": "手动",
495
+ "detail.exec.trigger.scheduled": "定时",
496
+ "detail.exec.openTitle": "点击打开该执行会话:{id}",
497
+ "detail.danger.delete": "🗑 删除(标记待清除)",
498
+ "detail.danger.purgeConfirm": "物理清除不可恢复",
499
+ "detail.danger.purgeGo": "确认清除",
500
+ "detail.danger.purge": "🔥 物理清除(需确认)",
501
+ "form.title.create": "新建任务",
502
+ "form.title.edit": "编辑任务",
503
+ "form.subtitle.create": "推入看板,项目内会话可认领执行",
504
+ "form.subtitle.edit": "调整任务内容与执行配置",
505
+ "form.field.title": "标题",
506
+ "form.field.titlePlaceholder": "一句话说清要做什么",
507
+ "form.field.project": "项目",
508
+ "form.field.model": "模型(默认 = 会话默认模型)",
509
+ "form.field.modelDefault": "默认模型",
510
+ "form.model.option": "{name}({provider})",
511
+ "form.field.effort": "思考强度(Reasoning Effort)",
512
+ "form.field.effortTitle": "设置模型的思考强度(如 low/medium/high);默认 = 跟随模型/提供商默认",
513
+ "form.effort.follow": "跟随模型默认",
514
+ "form.effort.low": "低 (low)",
515
+ "form.effort.medium": "中 (medium)",
516
+ "form.effort.high": "高 (high)",
517
+ "form.effort.none": "关闭思考 (none)",
518
+ "form.field.preset": "执行模式(preset)",
519
+ "form.field.presetTitle": "执行会话按该 preset 组合(决定工具集与人设);默认 = 部署默认 preset",
520
+ "form.preset.follow": "跟随部署默认",
521
+ "form.preset.defaultTag": "(部署默认)",
522
+ "form.field.urgency": "紧急度",
523
+ "form.urgency.urgent": "紧急",
524
+ "form.urgency.urgentHint": "优先处理",
525
+ "form.urgency.normal": "一般",
526
+ "form.urgency.normalHint": "正常排期",
527
+ "form.urgency.relaxed": "不急",
528
+ "form.urgency.relaxedHint": "有空再做",
529
+ "form.field.description": "描述",
530
+ "form.field.descriptionOptional": "描述(可选)",
531
+ "form.desc.placeholder": "需求细节、背景说明、验收标准…",
532
+ "form.field.prompt": "执行 Prompt(实际 Prompt = 标题+任务描述+Prompt)",
533
+ "form.field.promptOptional": "执行 Prompt(可选;实际 Prompt = 标题+任务描述+Prompt)",
534
+ "form.prompt.placeholder": "追加在「标题+任务描述」之后发给执行会话的补充指令。支持模板变量:{{lastExecution}}(上次执行结果)、{{lastComments}}(最近 3 条评论)",
535
+ "form.field.mode": "执行方式",
536
+ "form.mode.claim": "🤝 认领制",
537
+ "form.mode.claimHint": "项目内会话认领",
538
+ "form.mode.scheduled": "⏰ 定时执行",
539
+ "form.mode.scheduledHint": "到点自动开跑",
540
+ "form.field.cron": "Cron 表达式",
541
+ "form.cron.placeholder": "分 时 日 月 周",
542
+ "form.cron.daily": "每天 09:00",
543
+ "form.cron.hourly": "每小时",
544
+ "form.cron.every10min": "每 10 分钟",
545
+ "form.cron.weekly": "每周一 09:00",
546
+ "form.cron.next": "下次 {time}",
547
+ "form.field.isolation": "执行隔离",
548
+ "form.iso.locked": "任务已有执行记录,隔离方式已锁定",
549
+ "form.iso.lockedShort": "已锁定(执行开始后不可更改)",
550
+ "form.iso.nonGit": "当前项目非 git 仓库",
551
+ "form.iso.worktree": "🌿 Worktree 隔离",
552
+ "form.iso.worktreeTitle": "每次执行在独立 worktree 分支上进行",
553
+ "form.iso.worktreeHint": "独立分支 task/标题+ID,互不污染",
554
+ "form.iso.none": "📁 原目录执行",
555
+ "form.iso.noneTitle": "直接在项目目录执行(不使用 git)",
556
+ "form.iso.noneHintNonGit": "当前项目非 git 仓库,将在原目录执行",
557
+ "form.iso.noneHint": "不使用 git,直接在项目目录工作",
558
+ "form.iso.nonGitNote": "当前项目非 git 仓库,将在原目录执行(任务仍按默认配置创建,运行时自动降级)",
559
+ "form.field.checklist": "验收清单(DoD)",
560
+ "form.field.checklistOptional": "验收清单(DoD,可选)",
561
+ "form.check.itemPlaceholder": "验收项 {n}(完成标准)",
562
+ "form.check.removeTitle": "删除该验收项",
563
+ "form.check.add": "+ 添加验收项",
564
+ "form.check.checkedTitle": "勾选状态随保存保留(当前勾选人:{who})",
565
+ "form.check.notCheckedYet": "未勾选",
566
+ "form.check.hintCreate": "共 {n} 项,执行会话按清单干活并逐项勾选,未完成项验收时高亮",
567
+ "form.check.hintEdit": "已勾选 {checked}/{total}(保存将整体覆盖清单,勾选状态保留)",
568
+ "form.hint.needTitle": "请填写标题",
569
+ "form.hint.needProject": "请选择项目",
570
+ "form.hint.cronBad": "Cron 表达式无效(分 时 日 月 周)",
571
+ "form.hint.nextRun": "下次运行 {time}",
572
+ "form.hint.saveVersion": "保存后版本 v{v} → v{next}",
573
+ "form.hint.createClaim": "创建后项目内会话可认领执行",
574
+ "form.action.runBlockedTitle": "任务正在执行中,不能重复发起",
575
+ "form.action.runBusyTitle": "正在提交…",
576
+ "form.action.runTitle": "保存后立即发起执行(新会话)",
577
+ "form.action.run": "⚡ 立即执行",
578
+ "form.action.save": "保存修改",
579
+ "form.action.create": "创建任务",
580
+ "tpl.aria": "管理模板",
581
+ "tpl.title": "任务模板",
582
+ "tpl.subtitle": "新建任务 ▼ 下拉的模板:改名 / 删除 / 直接使用;任务详情页「存为模板」可新增",
583
+ "tpl.empty": "暂无模板 — 在任务详情页点「存为模板」把常用配置沉淀下来",
584
+ "tpl.name.aria": "模板名 {name}",
585
+ "tpl.builtin": "内置",
586
+ "tpl.custom": "自建",
587
+ "tpl.meta.checklist": " · 清单 {n} 项",
588
+ "tpl.rename.title": "保存改名",
589
+ "tpl.rename.button": "改名",
590
+ "tpl.use.title": "用此模板打开新建表单",
591
+ "tpl.use.button": "用此新建",
592
+ "tpl.delete.title": "删除该模板",
593
+ "tpl.renamed": "模板已改名",
594
+ "tpl.foot.hint": "模板随台账一同保存在 DSH 主目录,升级不丢",
595
+ "imp.aria": "导入台账",
596
+ "imp.title": "导入台账",
597
+ "imp.subtitle": "选择导出的 JSON 备份文件:先预览、再合并或整册替换",
598
+ "imp.parseError": "文件不是合法 JSON",
599
+ "imp.note": "⬇ JSON 导出的文件即为同格式备份,可直接导入恢复;导入文件的 schemaVersion 必须与当前版本一致。",
600
+ "imp.previewing": "预览中…",
601
+ "imp.stat.create": "新增",
602
+ "imp.stat.overwrite": "覆盖(同 id)",
603
+ "imp.stat.invalid": "无效(跳过)",
604
+ "imp.sec.create": "新增任务",
605
+ "imp.sec.overwrite": "覆盖任务(整卡替换,含执行历史与评论)",
606
+ "imp.sec.invalid": "无效条目(不会导入)",
607
+ "imp.noId": "(无 id)",
608
+ "imp.mode.merge": "⊕ 合并",
609
+ "imp.mode.mergeHint": "新增 + 按 id 覆盖,其余不动",
610
+ "imp.mode.replace": "💣 整册替换",
611
+ "imp.mode.replaceHint": "清空当前台账,以导入文件为准(先自动备份)",
612
+ "imp.result.replace": "整册替换完成:导入 {n} 张(原 {total} 张已整册备份)",
613
+ "imp.result.merge": "合并完成:新增 {n} 张、覆盖 {m} 张",
614
+ "imp.foot.replaceConfirm": "⚠ 再次点击确认执行整册替换(不可撤销,已自动备份)",
615
+ "imp.foot.replaceNeedConfirm": "整册替换需要二次确认",
616
+ "imp.foot.mergeHint": "合并只写入预览中列出的任务",
617
+ "imp.action.run": "执行导入",
618
+ "imp.action.confirmReplace": "确认整册替换",
619
+ "set.aria": "看板设置",
620
+ "set.title": "看板设置",
621
+ "set.subtitle": "新建任务与会话同步的全局默认值",
622
+ "set.iso.heading": "默认执行隔离",
623
+ "set.iso.noneHint": "不使用 git,直接在项目目录工作(出厂默认)",
624
+ "set.iso.worktreeHint": "每次执行在独立 worktree 分支上进行(task/标题+ID),互不污染",
625
+ "set.iso.current": "当前保存的默认:{current}。仅影响之后新建的任务;已有任务保持创建时的选择,非 git 项目运行时仍自动降级原目录。",
626
+ "set.sync.heading": "自动同步工作区会话",
627
+ "set.sync.off.name": "🚫 关闭同步",
628
+ "set.sync.off.title": "仅管理在任务看板中创建和触发的任务",
629
+ "set.sync.off.hint": "仅管理看板任务(出厂默认)",
630
+ "set.sync.on.name": "🔄 自动纳入会话",
631
+ "set.sync.on.title": "各工作区直接新建的会话也会在看板展示,运行中进入「进行中」,完成后自动进入「待验收」",
632
+ "set.sync.on.hint": "跟踪运行并在完成后进入待验收",
633
+ "set.sync.stateOn": "已开启:工作区直接新建并执行的会话将自动在看板生成任务卡片,并在完成后流转至「待验收」列。",
634
+ "set.sync.stateOff": "已关闭:仅在看板内部创建与触发执行的任务会出现在看板上。",
635
+ "set.foot.dirty": "有未保存的修改",
636
+ "set.foot.clean": "与看板当前设置一致",
637
+ "set.action.save": "保存设置",
638
+ "form.field.permission": "执行权限",
639
+ "form.perm.write": "可写入工作区",
640
+ "form.perm.writeHint": "可读写工作区及临时目录(推荐默认)",
641
+ "form.perm.readOnly": "仅可查看",
642
+ "form.perm.readOnlyHint": "只读查看与检索,禁止修改文件或执行外部命令",
643
+ "form.perm.fullAccess": "完全权限",
644
+ "form.perm.fullAccessHint": "完全无限制权限,可访问全盘及执行外部命令",
645
+ "form.perm.defaultTag": "(默认)",
646
+ "set.perm.heading": "默认执行权限",
647
+ "set.perm.writeName": "📁 可写入工作区(出厂默认)",
648
+ "set.perm.writeHint": "可读写工作区及临时目录,写操作无需二次确认",
649
+ "set.perm.readOnlyName": "🔒 仅可查看",
650
+ "set.perm.readOnlyHint": "仅允许只读查看与检索,禁止修改文件或破坏性命令",
651
+ "set.perm.fullName": "⚡ 完全权限",
652
+ "set.perm.fullHint": "完全无限制权限,可访问全盘及执行系统外部命令",
653
+ "set.perm.current": "当前保存的默认:{current}。新建任务时预设的执行权限。",
654
+ "card.badge.permReadOnly": "🔒 仅查看",
655
+ "card.badge.permReadOnlyTitle": "权限:仅可查看",
656
+ "card.badge.permFull": "⚡ 全权限",
657
+ "card.badge.permFullTitle": "权限:完全权限",
658
+ "detail.chip.permReadOnly": "仅可查看",
659
+ "detail.chip.permFull": "完全权限",
660
+ "detail.chip.permWrite": "可写入工作区",
661
+ "tpl.meta.permReadOnly": "仅查看",
662
+ "tpl.meta.permFull": "全权限",
663
+ "md.imageAlt": "图片 {n}",
664
+ "md.imageTitle": "点击查看大图 ({alt})",
665
+ "md.lightboxAlt": "大图预览",
666
+ "md.closePreview": "关闭预览",
667
+ "slash.aria": "快捷命令与技能",
668
+ "slash.title": "快捷命令与技能补全",
669
+ "slash.hint": "↑↓ 选择 · Enter / Tab 确认 · Esc 关闭",
670
+ "slash.badge.command": "⚡ 命令",
671
+ "slash.badge.skill": "🧩 技能",
672
+ "slash.tipA": "💡 输入",
673
+ "slash.tipB": "可快速补全 Slash 命令与 Agent 技能",
674
+ "slash.cmd.goal.desc": "自主完成长期目标任务,持续深度推进",
675
+ "slash.cmd.goal.hint": "<目标描述>",
676
+ "slash.cmd.schedule.desc": "设置一次性定时或周期性 Cron 调度",
677
+ "slash.cmd.schedule.hint": "<时间/表达式>",
678
+ "slash.cmd.plan.desc": "在行动前制定分步实施计划并由用户确认",
679
+ "slash.cmd.browser.desc": "启动网页浏览器交互与实时页面检索",
680
+ "slash.cmd.grill-me.desc": "通过多轮单题访谈深入对齐需求与设计意图",
681
+ "slash.cmd.teamwork-preview.desc": "多智能体协作与团队工作流预览",
682
+ "slash.cmd.learn.desc": "沉淀解决经验与新规则到知识库",
683
+ "slash.cmd.review.desc": "多维度代码审查(正确性、架构与安全)",
684
+ "slash.cmd.security.desc": "安全加固与代码漏洞扫描",
685
+ "slash.cmd.permission.desc": "切换当前会话权限级别 (read-only / workspace-write / full-access)",
686
+ "slash.cmd.permission.hint": "<preset>",
687
+ "slash.skill.frontend-ui-engineering": "构建生产级、可访问的高品质前端界面与组件",
688
+ "slash.skill.api-and-interface-design": "设计稳定契约、清晰边界的 REST / RPC 接口",
689
+ "slash.skill.test-driven-development": "测试驱动开发(TDD),编写单元与集成测试",
690
+ "slash.skill.debugging-and-error-recovery": "系统化定位 Bug 根因并恢复错误",
691
+ "slash.skill.performance-optimization": "前后端性能调优、减少渲染开销与查询优化",
692
+ "slash.skill.ci-cd-and-automation": "自动化构建、CI/CD 流水线与质量门禁",
693
+ "slash.skill.code-review-and-quality": "多轴向代码审查与重构指导",
694
+ "slash.skill.code-simplification": "精简复杂逻辑,提升可读性与可维护性",
695
+ "slash.skill.context-engineering": "优化上下文结构与提示词工程",
696
+ "slash.skill.doubt-driven-development": "以怀疑驱动的对抗式审查,确保核心逻辑正确",
697
+ "slash.skill.git-workflow-and-versioning": "Git 工作流、分支管理、语义化版本与变更日志",
698
+ "slash.skill.idea-refine": "通过发散与收敛思维细化方案与假设检验",
699
+ "slash.skill.incremental-implementation": "小步快跑、增量交付多文件变更",
700
+ "slash.skill.interview-me": "深度访谈挖掘真实意图",
701
+ "slash.skill.memory-leak-debugging": "排查诊断 JavaScript/Node.js 内存泄漏",
702
+ "slash.skill.observability-and-instrumentation": "添加日志、指标打点与链路追踪",
703
+ "slash.skill.planning-and-task-breakdown": "将复杂需求拆解为有序可执行任务",
704
+ "slash.skill.security-and-hardening": "防御安全漏洞、输入过滤与鉴权加固",
705
+ "slash.skill.shipping-and-launch": "生产发布前检查清单与回滚策略",
706
+ "slash.skill.source-driven-development": "基于权威官方文档与源码进行设计实现",
707
+ "slash.skill.spec-driven-development": "在编码前制定清晰的技术规范",
708
+ "slash.skill.using-agent-skills": "发现并动态调用智能体各项专业技能"
709
+ };
710
+
711
+ //#endregion
712
+ //#region src/client/i18n/en.ts
713
+ const en = {
714
+ "shared.close": "Close",
715
+ "shared.cancel": "Cancel",
716
+ "shared.loading": "Loading…",
717
+ "shared.blocked": "Blocked",
718
+ "shared.permission": "Permission",
719
+ "shared.confirmDelete": "Delete",
720
+ "shared.current": " (current: {name})",
721
+ "shared.entry.aria": "Agent task board",
722
+ "shared.entry.label": "Task board",
723
+ "shared.stats.title": "To do {todo} | In progress {doing} | In review {review} (todo | in progress | in review)",
724
+ "shared.duplicate.suffix": " (copy)",
725
+ "status.column.backlog": "Backlog",
726
+ "status.column.todo": "To do",
727
+ "status.column.in_progress": "In progress",
728
+ "status.column.in_review": "In review",
729
+ "status.column.done": "Done",
730
+ "status.column.canceled": "Canceled",
731
+ "status.column.archived": "Archived",
732
+ "status.pill.backlog": "Planned",
733
+ "status.pill.todo": "To do",
734
+ "status.pill.in_progress": "In progress",
735
+ "status.pill.in_review": "In review",
736
+ "status.pill.done": "Done",
737
+ "status.pill.canceled": "Canceled",
738
+ "status.pill.archived": "Archived",
739
+ "status.move.backlog": "Backlog",
740
+ "status.move.todo": "To do",
741
+ "status.move.in_progress": "In progress",
742
+ "status.move.in_review": "In review",
743
+ "status.move.done": "Done",
744
+ "status.move.canceled": "Canceled",
745
+ "status.move.archived": "Archived",
746
+ "urgency.urgent": "Urgent",
747
+ "urgency.normal": "Normal",
748
+ "urgency.relaxed": "Relaxed",
749
+ "outcome.running": "Running",
750
+ "outcome.succeeded": "Succeeded",
751
+ "outcome.failed": "Failed",
752
+ "outcome.cancelled": "Canceled",
753
+ "board.title": "Agent Task Board",
754
+ "board.count.tasks": "{n} tasks · rev {rev}",
755
+ "board.action.newTask": "+ New Task ▼",
756
+ "board.action.blankTask": "Blank task",
757
+ "board.action.manageTemplates": "⌗ Manage templates…",
758
+ "board.search.placeholder": "Search title / ID…",
759
+ "board.filter.allProjects": "All projects",
760
+ "board.sort.title": "Sort within columns",
761
+ "board.sort.default": "Default order",
762
+ "board.sort.updated": "Recently updated",
763
+ "board.sort.urgency": "By urgency",
764
+ "board.sort.created": "Creation time",
765
+ "board.sort.byTitle": "By title",
766
+ "board.action.backToBoard": "Back to board",
767
+ "board.action.otherTasks": "Other tasks",
768
+ "board.action.settingsTitle": "Board settings: default execution isolation for new tasks, etc.",
769
+ "board.action.settings": "🛠 Settings",
770
+ "board.action.diagTitle": "Health diagnostics: orphan worktrees, ledger basics",
771
+ "board.action.diag": "⚙ Diagnostics",
772
+ "board.action.importTitle": "Import the ledger from a JSON backup (preview, then merge or replace)",
773
+ "board.action.import": "⬆ Import",
774
+ "board.action.exportTitle": "Export the ledger: full JSON backup or task-list CSV",
775
+ "board.action.export": "⬇ Export ▼",
776
+ "board.export.jsonTitle": "Full ledger backup (execution history and board settings included); import it to restore",
777
+ "board.export.json": "Full ledger (JSON)",
778
+ "board.export.csvTitle": "Task-list spreadsheet (opens directly in Excel; BOM added for CJK text)",
779
+ "board.export.csv": "Task list (CSV)",
780
+ "board.drag.forbidden": "Cannot drag from \"{from}\" to \"{to}\"",
781
+ "board.empty": "No tasks",
782
+ "board.secondary.empty": "No canceled / archived / deleted tasks",
783
+ "board.group.trashed": "Deleted",
784
+ "diag.title": "Health diagnostics",
785
+ "diag.subtitle": "Ledger basics and orphan-worktree cleanup",
786
+ "diag.revision": "Ledger revision",
787
+ "diag.tasks": "Total tasks",
788
+ "diag.running": "Running",
789
+ "diag.orphans": "Orphan worktrees",
790
+ "diag.orphans.heading": "Orphan worktrees (no owning task, directory still present)",
791
+ "diag.orphans.none": "None — every project’s .dsh-worktrees directory is clean",
792
+ "diag.orphans.cleanup": "Clean up",
793
+ "diag.orphans.hint": "Note: orphan directories with uncommitted changes are refused; handle their contents manually first. Remove a live task’s worktree from its task detail pane.",
794
+ "diag.gitignore.heading": "gitignore suggestions",
795
+ "diag.gitignore.none": "Nothing to do — every git project already ignores .dsh-worktrees",
796
+ "diag.gitignore.suggestA": "suggests adding one line to .gitignore:",
797
+ "diag.gitignore.suggestB": "(no automatic edits)",
798
+ "card.drag.running": "This task is being executed by a session ({title}) and cannot be dragged",
799
+ "card.badge.stale": "⏱ Claim stale",
800
+ "card.badge.modelTitle": "Pinned model: {model}",
801
+ "card.badge.modelEffort": " · reasoning effort: {effort}",
802
+ "card.badge.checklistReview": "In review: checklist has unchecked items",
803
+ "card.badge.checklist": "Acceptance checklist progress",
804
+ "card.badge.pendingPurge": "Pending purge",
805
+ "card.session.jumpTitle": "Click to jump to the session: {id}",
806
+ "card.session.missing": "The session has been deleted ({id}) and cannot be opened",
807
+ "card.session.archived": "The session is archived ({id}) and hidden from the session list",
808
+ "card.session.unavailable": "Session navigation unavailable; session id: {id}",
809
+ "card.reject.placeholder": "Reason for sending back (optional; the agent reads it before starting)…",
810
+ "card.reject.confirm": "Send back to todo",
811
+ "card.action.doneTitle": "Accept and complete: move to Done",
812
+ "card.action.done": "✓ Done",
813
+ "card.action.rejectTitle": "Send back to todo, optionally with a reason",
814
+ "card.action.reject": "✗ Send back",
815
+ "alert.ok": "Got it",
816
+ "diff.commit": "Commit {hash}",
817
+ "diff.file": "File {path}",
818
+ "diff.truncated": "⚠ Content truncated (too long)",
819
+ "diff.failed": "Failed to fetch (see the error bar at the top of the board; the object may be gone with a removed worktree)",
820
+ "checklist.title": "Acceptance checklist (DoD)",
821
+ "checklist.unchecked": " · {n} unchecked",
822
+ "checklist.allDone": " · all done",
823
+ "checklist.byUser": "👤 User",
824
+ "checklist.uncheckedItem": "Unchecked",
825
+ "checklist.evidence": "Evidence: {note}",
826
+ "report.title": "Execution report",
827
+ "report.submitted": "Submitted by the execution session · {time}",
828
+ "report.changedFiles": "Changed files",
829
+ "report.checks": "Self-verification",
830
+ "report.artifacts": "Artifacts",
831
+ "report.risk": "Remaining risks",
832
+ "iso.title": "Execution isolation",
833
+ "iso.none": "📁 Run in the original directory",
834
+ "iso.worktreeTitle": "Execution isolation · Worktree",
835
+ "iso.branch": "🌿 Branch",
836
+ "iso.baseline": "Baseline {base} → {head}",
837
+ "iso.changed": "{n} files changed",
838
+ "iso.commit.openTitle": "Click to expand this commit’s diff",
839
+ "iso.commits.more": "… {n} commits in total",
840
+ "iso.nocommit": "This execution produced no commits (changes may be uncommitted — see the warning below)",
841
+ "iso.dirty.toggle": "⚠ {n} uncommitted changes (have the agent commit before merging, or handle them manually)",
842
+ "iso.dirty.expand": " ▼ view files",
843
+ "iso.dirty.collapse": " ▲",
844
+ "iso.dirty.openTitle": "Click to view the uncommitted diff of this file",
845
+ "iso.dirty.more": "… {n} in total (full list in the task ledger)",
846
+ "iso.hint.running": "Running — merge or clean up after it ends",
847
+ "iso.merge.confirm": "Merge the branch into the main worktree with --no-ff?",
848
+ "iso.merge.go": "Merge",
849
+ "iso.merge.title": "git merge --no-ff the task branch into the main worktree (requires a clean main worktree; conflicts are reported as-is)",
850
+ "iso.merge.button": "⇥ Merge into main worktree",
851
+ "iso.merge.failed": "Merge failed: {error}",
852
+ "iso.merge.noop": "The branch has no commits ahead of the main worktree — nothing to merge (send it back to continue running, or clean it up)",
853
+ "iso.remove.wt": "🗑 Remove worktree",
854
+ "iso.remove.wtTitle": "git worktree remove (refused when uncommitted changes exist)",
855
+ "iso.remove.wtb": "🗑 Remove worktree + branch",
856
+ "iso.remove.wtbTitle": "Remove the worktree and delete the task branch (refused when uncommitted changes exist)",
857
+ "iso.remove.confirmWt": "Remove the worktree directory?",
858
+ "iso.remove.confirmWtb": "Remove the worktree and delete the branch?",
859
+ "iso.remove.failed": "Removal failed: {error}",
860
+ "iso.remove.branchFailed": "Worktree removed, but branch deletion failed: {error}",
861
+ "iso.hint.keep": "Branch and worktree kept — send back to continue editing",
862
+ "detail.session.jumpTitle": "Jump to the corresponding session: {id}",
863
+ "detail.session.jump": "🤖 Jump to session ↗",
864
+ "detail.action.edit": "✎ Edit",
865
+ "detail.action.duplicateTitle": "Duplicate this task’s full configuration as a new card (todo column)",
866
+ "detail.action.duplicate": "⧉ Duplicate",
867
+ "detail.action.saveTplTitle": "Save this task’s configuration (checklist included) as a template for new tasks",
868
+ "detail.action.saveTplDone": "Saved as a template (available in the New Task ▼ menu; rename it in template management)",
869
+ "detail.action.saveTpl": "⌗ Save as template",
870
+ "detail.action.reuseTitle": "Reuse-run: keep the existing worktree and branch (last run’s changes and commits stay in place) and continue on top of them; the default \"Run\" resets to a fresh baseline",
871
+ "detail.action.reuse": "↻ Reuse-run",
872
+ "detail.action.runTitleModel": "Run in a new session ({model})",
873
+ "detail.action.runTitleDefault": "Run in a new session (default model)",
874
+ "detail.action.run": "▶ Run",
875
+ "detail.action.stopConfirm": "Stop this execution session?",
876
+ "detail.action.stop": "Stop",
877
+ "detail.action.stopTitle": "Stop execution session {id} (the task returns to todo)",
878
+ "detail.action.stopExec": "■ Stop execution",
879
+ "detail.chip.nextRun": "{cron} · next {time}",
880
+ "detail.chip.checklist": "Checklist {done}/{total}",
881
+ "detail.chip.isolated": "Worktree isolation",
882
+ "detail.chip.holderTitle": "Click to jump to the session: {id}",
883
+ "detail.chip.holderStale": "Claim stale · ",
884
+ "detail.chip.holderBy": "Held by ",
885
+ "detail.chip.holderSuffix": " ↗",
886
+ "detail.chip.trashed": "Deleted, pending purge",
887
+ "detail.sub.line": "Updated {time} · last change by {who}",
888
+ "detail.updatedBy.system": "⚙️ System",
889
+ "detail.updatedBy.user": "👤 User",
890
+ "detail.field.description": "Description",
891
+ "detail.field.prompt": "Execution prompt",
892
+ "detail.move.to": "Move to → {status}",
893
+ "detail.move.confirmDoneUnchecked": "{n} checklist items are still unchecked — confirm done?",
894
+ "detail.move.confirmDone": "Confirm done?",
895
+ "detail.move.confirm": "Confirm",
896
+ "detail.blocked.unmark": "✓ Unblock",
897
+ "detail.blocked.mark": "⛔ Mark blocked",
898
+ "detail.release.title": "Release {id}’s claim: the task returns to todo (the holding session may still be working — make sure it has stopped first)",
899
+ "detail.release.button": "🔓 Release claim",
900
+ "detail.comments.title": "Comments",
901
+ "detail.comments.empty": "No comments yet — the agent reports changes and verification here at handoff",
902
+ "detail.comments.user": "User",
903
+ "detail.composer.placeholder": "Leave a comment as the user (the agent reads it before starting)…",
904
+ "detail.composer.send": "Post",
905
+ "detail.exec.title": "Executions",
906
+ "detail.exec.prunedTitle": "{n} older execution records were pruned at the retention cap",
907
+ "detail.exec.pruned": "+{n} pruned",
908
+ "detail.exec.trigger.manual": "Manual",
909
+ "detail.exec.trigger.scheduled": "Scheduled",
910
+ "detail.exec.openTitle": "Click to open the execution session: {id}",
911
+ "detail.danger.delete": "🗑 Delete (mark for purge)",
912
+ "detail.danger.purgeConfirm": "Physical purge is irreversible",
913
+ "detail.danger.purgeGo": "Purge",
914
+ "detail.danger.purge": "🔥 Purge physically (confirm required)",
915
+ "form.title.create": "New task",
916
+ "form.title.edit": "Edit task",
917
+ "form.subtitle.create": "Push onto the board; sessions in the project can claim and run it",
918
+ "form.subtitle.edit": "Adjust the task content and execution configuration",
919
+ "form.field.title": "Title",
920
+ "form.field.titlePlaceholder": "One line: what should be done",
921
+ "form.field.project": "Project",
922
+ "form.field.model": "Model (default = session default model)",
923
+ "form.field.modelDefault": "Default model",
924
+ "form.model.option": "{name} ({provider})",
925
+ "form.field.effort": "Reasoning effort",
926
+ "form.field.effortTitle": "Set the model’s reasoning effort (e.g. low/medium/high); default = follow the model/provider default",
927
+ "form.effort.follow": "Follow model default",
928
+ "form.effort.low": "Low (low)",
929
+ "form.effort.medium": "Medium (medium)",
930
+ "form.effort.high": "High (high)",
931
+ "form.effort.none": "Off (none)",
932
+ "form.field.preset": "Execution preset",
933
+ "form.field.presetTitle": "The execution session is composed from this preset (tool set and persona); default = the deployment default preset",
934
+ "form.preset.follow": "Follow deployment default",
935
+ "form.preset.defaultTag": " (deployment default)",
936
+ "form.field.urgency": "Urgency",
937
+ "form.urgency.urgent": "Urgent",
938
+ "form.urgency.urgentHint": "Handle first",
939
+ "form.urgency.normal": "Normal",
940
+ "form.urgency.normalHint": "Normal scheduling",
941
+ "form.urgency.relaxed": "Relaxed",
942
+ "form.urgency.relaxedHint": "When there is time",
943
+ "form.field.description": "Description",
944
+ "form.field.descriptionOptional": "Description (optional)",
945
+ "form.desc.placeholder": "Requirement details, background, acceptance criteria…",
946
+ "form.field.prompt": "Execution prompt (the actual prompt = title + description + prompt)",
947
+ "form.field.promptOptional": "Execution prompt (optional; the actual prompt = title + description + prompt)",
948
+ "form.prompt.placeholder": "Extra instructions appended after \"title + task description\" and sent to the execution session. Template variables: {{lastExecution}} (last execution result), {{lastComments}} (latest 3 comments)",
949
+ "form.field.mode": "Execution mode",
950
+ "form.mode.claim": "🤝 Claim-based",
951
+ "form.mode.claimHint": "Sessions in the project claim it",
952
+ "form.mode.scheduled": "⏰ Scheduled",
953
+ "form.mode.scheduledHint": "Starts automatically on schedule",
954
+ "form.field.cron": "Cron expression",
955
+ "form.cron.placeholder": "min hour day month weekday",
956
+ "form.cron.daily": "Daily 09:00",
957
+ "form.cron.hourly": "Hourly",
958
+ "form.cron.every10min": "Every 10 minutes",
959
+ "form.cron.weekly": "Mondays 09:00",
960
+ "form.cron.next": "Next {time}",
961
+ "form.field.isolation": "Execution isolation",
962
+ "form.iso.locked": "The task has execution history; isolation is locked",
963
+ "form.iso.lockedShort": "Locked (cannot change once execution has started)",
964
+ "form.iso.nonGit": "This project is not a git repository",
965
+ "form.iso.worktree": "🌿 Worktree isolation",
966
+ "form.iso.worktreeTitle": "Each execution runs on its own worktree branch",
967
+ "form.iso.worktreeHint": "Isolated branch task/title+ID, no cross-contamination",
968
+ "form.iso.none": "📁 Run in the project directory",
969
+ "form.iso.noneTitle": "Run directly in the project directory (no git)",
970
+ "form.iso.noneHintNonGit": "Not a git repository; will run in the project directory",
971
+ "form.iso.noneHint": "No git; works directly in the project directory",
972
+ "form.iso.nonGitNote": "This project is not a git repository; the task will run in its directory (still created with the default configuration; the runtime degrades automatically)",
973
+ "form.field.checklist": "Acceptance checklist (DoD)",
974
+ "form.field.checklistOptional": "Acceptance checklist (DoD, optional)",
975
+ "form.check.itemPlaceholder": "Checklist item {n} (definition of done)",
976
+ "form.check.removeTitle": "Remove this checklist item",
977
+ "form.check.add": "+ Add checklist item",
978
+ "form.check.checkedTitle": "Checked state is preserved on save (currently checked by: {who})",
979
+ "form.check.notCheckedYet": "not checked yet",
980
+ "form.check.hintCreate": "{n} items; the execution session works through them and checks each off; unfinished items are highlighted at review",
981
+ "form.check.hintEdit": "{checked}/{total} checked (saving replaces the whole list; checked state is preserved)",
982
+ "form.hint.needTitle": "Enter a title",
983
+ "form.hint.needProject": "Select a project",
984
+ "form.hint.cronBad": "Invalid cron expression (min hour day month weekday)",
985
+ "form.hint.nextRun": "Next run {time}",
986
+ "form.hint.saveVersion": "On save: v{v} → v{next}",
987
+ "form.hint.createClaim": "After creation, sessions in the project can claim and run it",
988
+ "form.action.runBlockedTitle": "The task is running; cannot start another",
989
+ "form.action.runBusyTitle": "Submitting…",
990
+ "form.action.runTitle": "Save, then immediately start an execution (new session)",
991
+ "form.action.run": "⚡ Run now",
992
+ "form.action.save": "Save changes",
993
+ "form.action.create": "Create task",
994
+ "tpl.aria": "Manage templates",
995
+ "tpl.title": "Task templates",
996
+ "tpl.subtitle": "Templates from the New Task ▼ menu: rename / delete / use directly; \"Save as template\" in the task detail pane adds new ones",
997
+ "tpl.empty": "No templates yet — click \"Save as template\" in a task detail pane to capture a common configuration",
998
+ "tpl.name.aria": "Template name {name}",
999
+ "tpl.builtin": "Built-in",
1000
+ "tpl.custom": "Custom",
1001
+ "tpl.meta.checklist": " · {n} checklist items",
1002
+ "tpl.rename.title": "Save rename",
1003
+ "tpl.rename.button": "Rename",
1004
+ "tpl.use.title": "Open the new-task form with this template",
1005
+ "tpl.use.button": "Use",
1006
+ "tpl.delete.title": "Delete this template",
1007
+ "tpl.renamed": "Template renamed",
1008
+ "tpl.foot.hint": "Templates are stored with the ledger in the DSH home directory and survive upgrades",
1009
+ "imp.aria": "Import ledger",
1010
+ "imp.title": "Import ledger",
1011
+ "imp.subtitle": "Pick an exported JSON backup: preview first, then merge or replace everything",
1012
+ "imp.parseError": "The file is not valid JSON",
1013
+ "imp.note": "The ⬇ JSON export is a same-format backup and can be imported to restore; the file’s schemaVersion must match the current version.",
1014
+ "imp.previewing": "Previewing…",
1015
+ "imp.stat.create": "New",
1016
+ "imp.stat.overwrite": "Overwrite (same id)",
1017
+ "imp.stat.invalid": "Invalid (skipped)",
1018
+ "imp.sec.create": "New tasks",
1019
+ "imp.sec.overwrite": "Overwritten tasks (whole-card replacement, execution history and comments included)",
1020
+ "imp.sec.invalid": "Invalid entries (not imported)",
1021
+ "imp.noId": "(no id)",
1022
+ "imp.mode.merge": "⊕ Merge",
1023
+ "imp.mode.mergeHint": "Adds new + overwrites by id; everything else untouched",
1024
+ "imp.mode.replace": "💣 Replace all",
1025
+ "imp.mode.replaceHint": "Clears the current ledger and adopts the imported file (automatic backup first)",
1026
+ "imp.result.replace": "Replace complete: imported {n} tasks ({total} former tasks backed up)",
1027
+ "imp.result.merge": "Merge complete: {n} added, {m} overwritten",
1028
+ "imp.foot.replaceConfirm": "⚠ Click again to confirm the full replacement (irreversible; a backup has been taken)",
1029
+ "imp.foot.replaceNeedConfirm": "Full replacement requires a second confirmation",
1030
+ "imp.foot.mergeHint": "Merge writes only the tasks listed in the preview",
1031
+ "imp.action.run": "Import",
1032
+ "imp.action.confirmReplace": "Confirm replace",
1033
+ "set.aria": "Board settings",
1034
+ "set.title": "Board settings",
1035
+ "set.subtitle": "Global defaults for new tasks and session sync",
1036
+ "set.iso.heading": "Default execution isolation",
1037
+ "set.iso.noneHint": "No git; works directly in the project directory (factory default)",
1038
+ "set.iso.worktreeHint": "Each execution runs on its own worktree branch (task/title+ID), isolated from the others",
1039
+ "set.iso.current": "Currently saved default: {current}. Affects only tasks created hereafter; existing tasks keep their creation-time choice, and non-git projects still degrade to the project directory at run time.",
1040
+ "set.sync.heading": "Auto-sync workspace sessions",
1041
+ "set.sync.off.name": "🚫 Sync off",
1042
+ "set.sync.off.title": "Manage only tasks created and triggered in the task board",
1043
+ "set.sync.off.hint": "Board tasks only (factory default)",
1044
+ "set.sync.on.name": "🔄 Auto-include sessions",
1045
+ "set.sync.on.title": "Sessions created directly in workspaces also appear on the board: In progress while running, In review automatically when done",
1046
+ "set.sync.on.hint": "Tracks runs and enters review on completion",
1047
+ "set.sync.stateOn": "On: sessions created and run directly in workspaces automatically become board cards and flow to the \"In review\" column when done.",
1048
+ "set.sync.stateOff": "Off: only tasks created and triggered inside the board appear on the board.",
1049
+ "set.foot.dirty": "Unsaved changes",
1050
+ "set.foot.clean": "Matches the current board settings",
1051
+ "set.action.save": "Save settings",
1052
+ "form.field.permission": "Execution permission",
1053
+ "form.perm.write": "Workspace write",
1054
+ "form.perm.writeHint": "Read-write access to the workspace and temp directories (recommended default)",
1055
+ "form.perm.readOnly": "Read-only",
1056
+ "form.perm.readOnlyHint": "View and search only; no file changes or external commands",
1057
+ "form.perm.fullAccess": "Full access",
1058
+ "form.perm.fullAccessHint": "Unrestricted: whole-disk access and external commands",
1059
+ "form.perm.defaultTag": " (default)",
1060
+ "set.perm.heading": "Default execution permission",
1061
+ "set.perm.writeName": "📁 Workspace write (factory default)",
1062
+ "set.perm.writeHint": "Read-write access to the workspace and temp directories; writes need no extra confirmation",
1063
+ "set.perm.readOnlyName": "🔒 Read-only",
1064
+ "set.perm.readOnlyHint": "Read-only viewing and search only; no file changes or destructive commands",
1065
+ "set.perm.fullName": "⚡ Full access",
1066
+ "set.perm.fullHint": "Unrestricted: whole-disk access and external system commands",
1067
+ "set.perm.current": "Currently saved default: {current}. The execution permission preset applied to new tasks.",
1068
+ "card.badge.permReadOnly": "🔒 Read-only",
1069
+ "card.badge.permReadOnlyTitle": "Permission: read-only",
1070
+ "card.badge.permFull": "⚡ Full access",
1071
+ "card.badge.permFullTitle": "Permission: full access",
1072
+ "detail.chip.permReadOnly": "Read-only",
1073
+ "detail.chip.permFull": "Full access",
1074
+ "detail.chip.permWrite": "Workspace write",
1075
+ "tpl.meta.permReadOnly": "read-only",
1076
+ "tpl.meta.permFull": "full access",
1077
+ "md.imageAlt": "Image {n}",
1078
+ "md.imageTitle": "Click to view full size ({alt})",
1079
+ "md.lightboxAlt": "Full-size preview",
1080
+ "md.closePreview": "Close preview",
1081
+ "slash.aria": "Quick commands and skills",
1082
+ "slash.title": "Quick command and skill completion",
1083
+ "slash.hint": "↑↓ navigate · Enter / Tab pick · Esc close",
1084
+ "slash.badge.command": "⚡ command",
1085
+ "slash.badge.skill": "🧩 skill",
1086
+ "slash.tipA": "💡 Type",
1087
+ "slash.tipB": "to autocomplete slash commands and agent skills",
1088
+ "slash.cmd.goal.desc": "Autonomously drive long-horizon goals to completion",
1089
+ "slash.cmd.goal.hint": "<goal description>",
1090
+ "slash.cmd.schedule.desc": "Set one-off or recurring cron schedules",
1091
+ "slash.cmd.schedule.hint": "<time or expression>",
1092
+ "slash.cmd.plan.desc": "Draft a step-by-step plan and get user confirmation before acting",
1093
+ "slash.cmd.browser.desc": "Launch a browser for page interaction and live search",
1094
+ "slash.cmd.grill-me.desc": "Align requirements and design intent through one-question-at-a-time interviews",
1095
+ "slash.cmd.teamwork-preview.desc": "Multi-agent collaboration and team workflow preview",
1096
+ "slash.cmd.learn.desc": "Capture solutions and new rules into the knowledge base",
1097
+ "slash.cmd.review.desc": "Multi-axis code review (correctness, architecture, security)",
1098
+ "slash.cmd.security.desc": "Security hardening and vulnerability scanning",
1099
+ "slash.cmd.permission.desc": "Switch the session permission level (read-only / workspace-write / full-access)",
1100
+ "slash.cmd.permission.hint": "<preset>",
1101
+ "slash.skill.frontend-ui-engineering": "Build production-grade, accessible frontend interfaces and components",
1102
+ "slash.skill.api-and-interface-design": "Design stable, well-bounded REST / RPC interfaces",
1103
+ "slash.skill.test-driven-development": "Test-driven development (TDD): unit and integration tests",
1104
+ "slash.skill.debugging-and-error-recovery": "Systematically locate bug root causes and recover",
1105
+ "slash.skill.performance-optimization": "Frontend/backend performance tuning and query optimization",
1106
+ "slash.skill.ci-cd-and-automation": "Build automation, CI/CD pipelines and quality gates",
1107
+ "slash.skill.code-review-and-quality": "Multi-axis code review and refactoring guidance",
1108
+ "slash.skill.code-simplification": "Simplify complex logic for readability and maintainability",
1109
+ "slash.skill.context-engineering": "Optimize context structure and prompt engineering",
1110
+ "slash.skill.doubt-driven-development": "Adversarial, doubt-driven review of core logic",
1111
+ "slash.skill.git-workflow-and-versioning": "Git workflow, branching, semantic versioning and changelogs",
1112
+ "slash.skill.idea-refine": "Refine ideas and test hypotheses via divergent-convergent thinking",
1113
+ "slash.skill.incremental-implementation": "Deliver multi-file changes in small, incremental steps",
1114
+ "slash.skill.interview-me": "Deep interviews to surface true intent",
1115
+ "slash.skill.memory-leak-debugging": "Diagnose JavaScript/Node.js memory leaks",
1116
+ "slash.skill.observability-and-instrumentation": "Add logging, metrics and distributed tracing",
1117
+ "slash.skill.planning-and-task-breakdown": "Break complex requirements into ordered, executable tasks",
1118
+ "slash.skill.security-and-hardening": "Harden against vulnerabilities; input filtering and auth hardening",
1119
+ "slash.skill.shipping-and-launch": "Pre-launch checklists and rollback strategies",
1120
+ "slash.skill.source-driven-development": "Design from authoritative docs and source code",
1121
+ "slash.skill.spec-driven-development": "Write clear technical specs before coding",
1122
+ "slash.skill.using-agent-skills": "Discover and invoke agent skills dynamically"
1123
+ };
1124
+
1125
+ //#endregion
1126
+ //#region src/client/i18n/runtime.ts
1127
+ /**
1128
+ * Taskboard i18n runtime: the zh/en dictionary lookup plus the locale
1129
+ * SOURCE adapter.
1130
+ *
1131
+ * Locale source, in priority order:
1132
+ * 1. The DSH locale service (ctx.get('locale'), the same plugin that backs
1133
+ * 设置 → 通用设置 → 语言). Consumed through a NARROW structural face and
1134
+ * attached SOFTLY — the plugin must keep working on compositions where
1135
+ * the service is absent, so 'locale' is deliberately NOT in the client
1136
+ * inject list (a hard inject would wait forever there).
1137
+ * 2. A local fallback: <html lang> when the DSH locale plugin maintains it
1138
+ * (it points <html lang> at the active locale), else navigator.language,
1139
+ * else 'en' (matches DSH: zh only when something asked for Chinese).
1140
+ *
1141
+ * Preference WRITES are never made here: switching languages is the DSH
1142
+ * settings page's job; this module only reads.
1143
+ *
1144
+ * React re-render: useT() subscribes via useSyncExternalStore; the snapshot
1145
+ * object is replaced only on locale change, so renders are stable.
1146
+ *
1147
+ * @module dsh-taskboard/client/i18n/runtime
1148
+ */
1149
+ const DICTS = {
1150
+ zh,
1151
+ en
1152
+ };
1153
+ let unsubscribeService;
1154
+ let snapshot = {
1155
+ active: detectFallbackLocale(),
1156
+ revision: 0
1157
+ };
1158
+ const listeners = /* @__PURE__ */ new Set();
1159
+ function isLocaleId(value) {
1160
+ return value === "zh" || value === "en";
1161
+ }
1162
+ /** Derive the fallback locale without the DSH service (zh only when asked). */
1163
+ function detectFallbackLocale() {
1164
+ try {
1165
+ if (typeof document !== "undefined") {
1166
+ const lang = document.documentElement.lang;
1167
+ if (typeof lang === "string" && lang.length > 0) {
1168
+ const lower = lang.toLowerCase();
1169
+ if (lower.startsWith("zh")) return "zh";
1170
+ if (lower.startsWith("en")) return "en";
1171
+ }
1172
+ }
1173
+ } catch {}
1174
+ try {
1175
+ if (typeof navigator !== "undefined") return (navigator.language ?? "en").toLowerCase().startsWith("zh") ? "zh" : "en";
1176
+ } catch {}
1177
+ return "en";
1178
+ }
1179
+ function publish(next) {
1180
+ if (next.active === snapshot.active && next.revision === snapshot.revision) return;
1181
+ snapshot = next;
1182
+ for (const fn of listeners) fn();
1183
+ }
1184
+ /**
1185
+ * Attach the DSH locale service (call from the client entry's apply with
1186
+ * ctx.get('locale')). Absent/malformed services are ignored — the fallback
1187
+ * detection stays in charge.
1188
+ * @param localeService - the ctx 'locale' service, when provided.
1189
+ */
1190
+ function initI18n(localeService) {
1191
+ const face = localeService;
1192
+ if (face === null || face === void 0 || typeof face.getSnapshot !== "function" || typeof face.subscribe !== "function") {
1193
+ publish({
1194
+ active: detectFallbackLocale(),
1195
+ revision: 0
1196
+ });
1197
+ return;
1198
+ }
1199
+ const sync = () => {
1200
+ try {
1201
+ const s = face.getSnapshot();
1202
+ publish({
1203
+ active: isLocaleId(s.active) ? s.active : detectFallbackLocale(),
1204
+ revision: s.revision
1205
+ });
1206
+ } catch {}
1207
+ };
1208
+ sync();
1209
+ unsubscribeService = face.subscribe(sync);
1210
+ }
1211
+ /** Detach the service and return to fallback detection (tests, dispose). */
1212
+ function disposeI18n() {
1213
+ try {
1214
+ unsubscribeService?.();
1215
+ } catch {}
1216
+ unsubscribeService = void 0;
1217
+ publish({
1218
+ active: detectFallbackLocale(),
1219
+ revision: 0
1220
+ });
1221
+ }
1222
+ /** Subscribe-side store face for useSyncExternalStore. */
1223
+ const localeStore = {
1224
+ getSnapshot() {
1225
+ return snapshot;
1226
+ },
1227
+ subscribe(fn) {
1228
+ listeners.add(fn);
1229
+ return () => {
1230
+ listeners.delete(fn);
1231
+ };
1232
+ }
1233
+ };
1234
+ const PLACEHOLDER = /\{(\w+)\}/g;
1235
+ /** Translate through the active dictionary, {name} placeholders substituted. */
1236
+ const translate = (key, params) => {
1237
+ const template = DICTS[snapshot.active][key] ?? DICTS.en[key] ?? key;
1238
+ if (params === void 0) return template;
1239
+ return template.replace(PLACEHOLDER, (match, name) => {
1240
+ const value = params[name];
1241
+ return value === void 0 ? match : String(value);
1242
+ });
1243
+ };
1244
+ /**
1245
+ * React hook: subscribes the component to locale changes and returns the
1246
+ * translate function (stable identity — it reads the active locale at call
1247
+ * time, so any re-render triggered by the subscription renders fresh text).
1248
+ */
1249
+ function useT() {
1250
+ (0, react.useSyncExternalStore)(localeStore.subscribe, localeStore.getSnapshot);
1251
+ return translate;
1252
+ }
1253
+
265
1254
  //#endregion
266
1255
  //#region src/client/controller.ts
267
1256
  /** localStorage key for persisted view state (filters + sort). */
@@ -510,6 +1499,38 @@ window.__ModuleLoader__.load({
510
1499
  return this.catalogFaces.presets;
511
1500
  }
512
1501
  /**
1502
+ * Fetch model catalog: prefers installed runtime face, falls back to Taskboard client API (0.5.5).
1503
+ */
1504
+ async fetchModelCatalog() {
1505
+ if (this.catalogFaces.models !== void 0) try {
1506
+ const list = await this.catalogFaces.models();
1507
+ if (list !== void 0 && list.length > 0) return list;
1508
+ } catch {}
1509
+ try {
1510
+ return (await this.client.modelCatalog()).models ?? [];
1511
+ } catch {
1512
+ return [];
1513
+ }
1514
+ }
1515
+ /**
1516
+ * Fetch preset roster: prefers installed runtime face, falls back to Taskboard client API (0.5.5).
1517
+ */
1518
+ async fetchPresetCatalog() {
1519
+ if (this.catalogFaces.presets !== void 0) try {
1520
+ const roster = await this.catalogFaces.presets();
1521
+ if (roster !== void 0 && roster.presets !== void 0 && roster.presets.length > 0) return roster;
1522
+ } catch {}
1523
+ try {
1524
+ const res = await this.client.modelCatalog();
1525
+ return {
1526
+ presets: res.presets ?? [],
1527
+ ...res.defaultPresetId !== void 0 ? { defaultId: res.defaultPresetId } : {}
1528
+ };
1529
+ } catch {
1530
+ return { presets: [] };
1531
+ }
1532
+ }
1533
+ /**
513
1534
  * Jump to an execution's session (open it in the GUI). On success the board
514
1535
  * closes so the conversation shows; a deleted-or-archived session reports
515
1536
  * 'missing' for the caller to prompt about.
@@ -771,7 +1792,7 @@ window.__ModuleLoader__.load({
771
1792
  async duplicate(task) {
772
1793
  try {
773
1794
  await this.client.create({
774
- title: `${task.title.slice(0, 196)}(副本)`,
1795
+ title: task.title.slice(0, 196) + translate("shared.duplicate.suffix"),
775
1796
  workspaceId: task.workspaceId,
776
1797
  urgency: task.urgency,
777
1798
  description: task.description.length > 0 ? task.description : void 0,
@@ -783,6 +1804,7 @@ window.__ModuleLoader__.load({
783
1804
  model: task.model,
784
1805
  isolation: task.isolation,
785
1806
  ...task.presetId !== void 0 ? { presetId: task.presetId } : {},
1807
+ ...task.permission !== void 0 ? { permission: task.permission } : {},
786
1808
  ...task.checklist !== void 0 && task.checklist.length > 0 ? { checklist: task.checklist.map((i) => i.text) } : {}
787
1809
  });
788
1810
  await this.refresh();
@@ -853,10 +1875,19 @@ window.__ModuleLoader__.load({
853
1875
  model: task.model,
854
1876
  isolation: task.isolation,
855
1877
  ...task.presetId !== void 0 ? { presetId: task.presetId } : {},
1878
+ ...task.permission !== void 0 ? { permission: task.permission } : {},
856
1879
  ...task.checklist !== void 0 && task.checklist.length > 0 ? { checklist: task.checklist.map((i) => i.text) } : {}
857
1880
  }
858
1881
  });
859
1882
  }
1883
+ /** Load prompt completions (skills + slash commands) from host (0.5.5). */
1884
+ async fetchPromptCompletions() {
1885
+ try {
1886
+ return await this.client.promptCompletions();
1887
+ } catch {
1888
+ return;
1889
+ }
1890
+ }
860
1891
  /** Open the import modal. */
861
1892
  openImport() {
862
1893
  this.setState({ importOpen: true });
@@ -1399,6 +2430,10 @@ window.__ModuleLoader__.load({
1399
2430
  box-shadow: var(--dsw-shadow-lv3, 0 12px 32px rgba(0,0,0,.18));
1400
2431
  animation: dsh-atb-pop .16s ease;
1401
2432
  }
2433
+ .dsh-atb-taskform-modal {
2434
+ width: min(960px, calc(100vw - 40px));
2435
+ max-height: calc(100vh - 50px);
2436
+ }
1402
2437
  @keyframes dsh-atb-pop { from { opacity: 0; transform: translateY(8px) scale(.98); } }
1403
2438
  .dsh-atb-modal-head {
1404
2439
  display: flex; align-items: center; gap: 10px;
@@ -1422,6 +2457,59 @@ window.__ModuleLoader__.load({
1422
2457
  padding: 13px 16px; overflow-y: auto;
1423
2458
  display: grid; grid-template-columns: 1fr 1fr; gap: 11px 10px;
1424
2459
  }
2460
+ .dsh-atb-taskform-body {
2461
+ padding: 14px 18px;
2462
+ display: grid;
2463
+ grid-template-columns: 1.05fr 1.15fr;
2464
+ gap: 18px;
2465
+ }
2466
+ .dsh-atb-form-col {
2467
+ display: flex;
2468
+ flex-direction: column;
2469
+ gap: 11px;
2470
+ min-width: 0;
2471
+ }
2472
+ .dsh-atb-form-left {
2473
+ border-right: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.14));
2474
+ padding-right: 18px;
2475
+ }
2476
+ .dsh-atb-form-right {
2477
+ display: flex;
2478
+ flex-direction: column;
2479
+ gap: 14px;
2480
+ }
2481
+ .dsh-atb-form-right .dsh-atb-field {
2482
+ flex: 1;
2483
+ display: flex;
2484
+ flex-direction: column;
2485
+ }
2486
+ .dsh-atb-form-right .dsh-atb-prompt-wrap {
2487
+ flex: 1;
2488
+ display: flex;
2489
+ flex-direction: column;
2490
+ }
2491
+ .dsh-atb-form-right .dsh-atb-prompt-inner {
2492
+ flex: 1;
2493
+ display: flex;
2494
+ flex-direction: column;
2495
+ }
2496
+ .dsh-atb-form-right .dsh-atb-prompt-input {
2497
+ flex: 1;
2498
+ min-height: 130px;
2499
+ resize: vertical;
2500
+ }
2501
+ .dsh-atb-form-subgrid {
2502
+ display: grid;
2503
+ grid-template-columns: 1fr 1fr;
2504
+ gap: 10px;
2505
+ }
2506
+
2507
+ @media (max-width: 768px) {
2508
+ .dsh-atb-taskform-modal { width: calc(100vw - 20px); }
2509
+ .dsh-atb-taskform-body { grid-template-columns: 1fr; gap: 14px; padding: 12px 14px; }
2510
+ .dsh-atb-form-left { border-right: none; padding-right: 0; }
2511
+ .dsh-atb-form-right .dsh-atb-prompt-input { min-height: 90px; }
2512
+ }
1425
2513
  .dsh-atb-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
1426
2514
  .dsh-atb-field[data-span="full"] { grid-column: 1 / -1; }
1427
2515
  .dsh-atb-field-label {
@@ -1731,6 +2819,80 @@ window.__ModuleLoader__.load({
1731
2819
  .dsh-atb-set { max-width: 460px; width: min(460px, 92vw); }
1732
2820
  .dsh-atb-set .dsh-atb-mode-picker { margin-top: 8px; }
1733
2821
  .dsh-atb-set .dsh-atb-isolation-note { margin-top: 10px; }
2822
+
2823
+ /* ---------- 0.5.5 SlashPromptInput & Permission Picker ---------- */
2824
+ .dsh-atb-perm-picker { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; margin-top: 4px; }
2825
+ .dsh-atb-perm-opt {
2826
+ display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
2827
+ padding: 8px 10px; border-radius: 9px; cursor: pointer; text-align: left;
2828
+ border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.35));
2829
+ background: transparent; color: inherit;
2830
+ transition: border-color .12s ease, background .12s ease;
2831
+ }
2832
+ .dsh-atb-perm-name { display: flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600; }
2833
+ .dsh-atb-perm-hint { font-size: 10.5px; color: var(--dsw-alias-label-tertiary, gray); line-height: 1.35; }
2834
+ .dsh-atb-perm-opt:hover { border-color: var(--dsw-alias-label-tertiary, rgba(128,128,128,.6)); }
2835
+ .dsh-atb-perm-opt[data-on="true"] {
2836
+ border-color: var(--dsw-alias-brand-primary, #1f2328);
2837
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #1f2328) 9%, transparent);
2838
+ }
2839
+
2840
+ .dsh-atb-prompt-wrap {
2841
+ display: flex; flex-direction: column; gap: 6px; position: relative; width: 100%;
2842
+ border-radius: 9px; transition: border-color .12s ease;
2843
+ }
2844
+ .dsh-atb-prompt-wrap[data-drag-over="true"] {
2845
+ outline: 2px dashed var(--dsw-alias-brand-primary, #1f2328);
2846
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #1f2328) 6%, transparent);
2847
+ }
2848
+ .dsh-atb-prompt-inner { position: relative; width: 100%; }
2849
+ .dsh-atb-prompt-input {
2850
+ width: 100%; box-sizing: border-box; font: inherit; font-size: 13px; line-height: 1.5;
2851
+ padding: 7px 10px; border-radius: 8px; resize: vertical; min-height: 68px;
2852
+ border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.35));
2853
+ background: var(--dsw-specific-input-major, transparent); color: var(--dsw-alias-label-primary, inherit);
2854
+ }
2855
+ .dsh-atb-prompt-input:focus {
2856
+ outline: none; border-color: var(--dsw-alias-brand-primary, #1f2328);
2857
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary, #1f2328) 18%, transparent);
2858
+ }
2859
+
2860
+ /* Slash Autocomplete Popup */
2861
+ .dsh-atb-slash-popup {
2862
+ position: absolute; left: 0; bottom: calc(100% + 6px); width: 100%; max-height: 240px; z-index: 100;
2863
+ display: flex; flex-direction: column; overflow: hidden; border-radius: 10px;
2864
+ background: var(--dsw-alias-bg-overlay, #fff); color: var(--dsw-alias-label-primary, inherit);
2865
+ border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.28));
2866
+ box-shadow: var(--dsw-shadow-lv3, 0 10px 28px rgba(0,0,0,.22));
2867
+ }
2868
+ .dsh-atb-slash-head {
2869
+ display: flex; align-items: center; justify-content: space-between;
2870
+ padding: 6px 10px; border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.15));
2871
+ background: var(--dsw-alias-bg-elevated, rgba(128,128,128,.06));
2872
+ }
2873
+ .dsh-atb-slash-title { font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary, gray); }
2874
+ .dsh-atb-slash-hint { font-size: 10px; color: var(--dsw-alias-label-tertiary, gray); }
2875
+ .dsh-atb-slash-list { overflow-y: auto; max-height: 200px; display: flex; flex-direction: column; padding: 4px; }
2876
+ .dsh-atb-slash-item {
2877
+ display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px;
2878
+ cursor: pointer; font-size: 12px; transition: background .1s ease;
2879
+ }
2880
+ .dsh-atb-slash-item[data-active="true"], .dsh-atb-slash-item:hover {
2881
+ background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.14));
2882
+ }
2883
+ .dsh-atb-slash-badge {
2884
+ font-size: 10px; font-weight: 600; padding: 1px 5px; border-radius: 4px; flex-shrink: 0;
2885
+ }
2886
+ .dsh-atb-slash-badge[data-kind="command"] { background: rgba(217,130,43,.15); color: #d9822b; }
2887
+ .dsh-atb-slash-badge[data-kind="skill"] { background: rgba(142,78,198,.15); color: #a06ce0; }
2888
+ .dsh-atb-slash-name { font-weight: 600; font-family: monospace; font-size: 12.5px; }
2889
+ .dsh-atb-slash-param { font-size: 11px; color: var(--dsw-alias-label-tertiary, gray); font-family: monospace; }
2890
+ .dsh-atb-slash-desc { font-size: 11px; color: var(--dsw-alias-label-secondary, gray); margin-left: auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 45%; }
2891
+
2892
+ /* Prompt Foot Toolbar */
2893
+ .dsh-atb-prompt-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
2894
+ .dsh-atb-prompt-tip { font-size: 11px; color: var(--dsw-alias-label-tertiary, gray); }
2895
+ .dsh-atb-prompt-tip code { font-size: 10.5px; padding: 1px 4px; border-radius: 4px; background: rgba(128,128,128,.14); }
1734
2896
  `;
1735
2897
  /** Style element id (stable since 0.1.x: hook for tests and debugging). */
1736
2898
  const STYLE_ID = "dsh-taskboard-styles";
@@ -1816,8 +2978,8 @@ window.__ModuleLoader__.load({
1816
2978
  entry.type = "button";
1817
2979
  entry.dataset.dshAtbEntry = "";
1818
2980
  entry.className = "dsh-atb-entry";
1819
- entry.setAttribute("aria-label", "Agent 任务看板");
1820
- entry.innerHTML = `<span class="dsh-atb-entry-icon">${ICON}</span><span class="dsh-atb-entry-label">任务看板</span><span class="dsh-atb-entry-stats"></span>`;
2981
+ entry.setAttribute("aria-label", translate("shared.entry.aria"));
2982
+ entry.innerHTML = `<span class="dsh-atb-entry-icon">${ICON}</span><span class="dsh-atb-entry-label">${translate("shared.entry.label")}</span><span class="dsh-atb-entry-stats"></span>`;
1821
2983
  entry.addEventListener("click", () => {
1822
2984
  controller.toggleBoard();
1823
2985
  });
@@ -1915,7 +3077,11 @@ window.__ModuleLoader__.load({
1915
3077
  setRollValue(slots[0], todo);
1916
3078
  setRollValue(slots[1], inProgress);
1917
3079
  setRollValue(slots[2], inReview);
1918
- stats.title = `待办 ${todo} | 进行中 ${inProgress} | 待验收 ${inReview}(待办|进行中|待验收)`;
3080
+ stats.title = translate("shared.stats.title", {
3081
+ todo,
3082
+ doing: inProgress,
3083
+ review: inReview
3084
+ });
1919
3085
  };
1920
3086
  return update;
1921
3087
  }
@@ -1997,12 +3163,19 @@ window.__ModuleLoader__.load({
1997
3163
  syncStats();
1998
3164
  };
1999
3165
  const unsubscribe = controller.subscribe(syncActive);
3166
+ const unsubscribeLocale = localeStore.subscribe(() => {
3167
+ entry.setAttribute("aria-label", translate("shared.entry.aria"));
3168
+ const label = entry.querySelector(".dsh-atb-entry-label");
3169
+ if (label !== null) label.textContent = translate("shared.entry.label");
3170
+ syncStats();
3171
+ });
2000
3172
  syncActive();
2001
3173
  tryPlace();
2002
3174
  return () => {
2003
3175
  clearInterval(retry);
2004
3176
  waitObserver.disconnect();
2005
3177
  rootObserver.disconnect();
3178
+ unsubscribeLocale();
2006
3179
  unsubscribe();
2007
3180
  entry.remove();
2008
3181
  };
@@ -2017,53 +3190,53 @@ window.__ModuleLoader__.load({
2017
3190
  * @module dsh-taskboard/shared/version
2018
3191
  */
2019
3192
  /** The package version (must equal package.json "version"). */
2020
- const PLUGIN_VERSION = "0.5.4";
3193
+ const PLUGIN_VERSION = "0.6.0";
2021
3194
 
2022
3195
  //#endregion
2023
3196
  //#region src/client/board/labels.ts
2024
- /** Column headers on the five-column main board (+ secondary tab). */
2025
- const COLUMN_LABELS = {
2026
- backlog: "待规划",
2027
- todo: "待办",
2028
- in_progress: "进行中",
2029
- in_review: "待验收",
2030
- done: "已完成",
2031
- canceled: "已取消",
2032
- archived: "已归档"
3197
+ /** Column header keys on the five-column main board (+ secondary tab). */
3198
+ const COLUMN_KEYS = {
3199
+ backlog: "status.column.backlog",
3200
+ todo: "status.column.todo",
3201
+ in_progress: "status.column.in_progress",
3202
+ in_review: "status.column.in_review",
3203
+ done: "status.column.done",
3204
+ canceled: "status.column.canceled",
3205
+ archived: "status.column.archived"
2033
3206
  };
2034
- /** Status pill text (detail pane) — historical wording kept verbatim:
3207
+ /** Status pill keys (detail pane) — historical wording kept verbatim:
2035
3208
  * terminal states read short here, the column headers carry the full forms. */
2036
- const STATUS_LABEL = {
2037
- backlog: "待规划",
2038
- todo: "待办",
2039
- in_progress: "进行中",
2040
- in_review: "待验收",
2041
- done: "完成",
2042
- canceled: "取消",
2043
- archived: "归档"
3209
+ const STATUS_KEYS = {
3210
+ backlog: "status.pill.backlog",
3211
+ todo: "status.pill.todo",
3212
+ in_progress: "status.pill.in_progress",
3213
+ in_review: "status.pill.in_review",
3214
+ done: "status.pill.done",
3215
+ canceled: "status.pill.canceled",
3216
+ archived: "status.pill.archived"
2044
3217
  };
2045
- /** Move-button verbs (shorter than the pill text). */
2046
- const MOVE_LABEL = {
2047
- backlog: "待规划",
2048
- todo: "待办",
2049
- in_progress: "进行中",
2050
- in_review: "待验收",
2051
- done: "完成",
2052
- canceled: "取消",
2053
- archived: "归档"
3218
+ /** Move-button verb keys (shorter than the pill text). */
3219
+ const MOVE_KEYS = {
3220
+ backlog: "status.move.backlog",
3221
+ todo: "status.move.todo",
3222
+ in_progress: "status.move.in_progress",
3223
+ in_review: "status.move.in_review",
3224
+ done: "status.move.done",
3225
+ canceled: "status.move.canceled",
3226
+ archived: "status.move.archived"
2054
3227
  };
2055
- /** Urgency chip labels. */
2056
- const URGENCY_LABEL = {
2057
- urgent: "紧急",
2058
- normal: "一般",
2059
- relaxed: "不急"
3228
+ /** Urgency chip keys. */
3229
+ const URGENCY_KEYS = {
3230
+ urgent: "urgency.urgent",
3231
+ normal: "urgency.normal",
3232
+ relaxed: "urgency.relaxed"
2060
3233
  };
2061
- /** Execution outcome labels. */
2062
- const OUTCOME_LABEL = {
2063
- running: "执行中",
2064
- succeeded: "成功",
2065
- failed: "失败",
2066
- cancelled: "已取消"
3234
+ /** Execution outcome keys. */
3235
+ const OUTCOME_KEYS = {
3236
+ running: "outcome.running",
3237
+ succeeded: "outcome.succeeded",
3238
+ failed: "outcome.failed",
3239
+ cancelled: "outcome.cancelled"
2067
3240
  };
2068
3241
 
2069
3242
  //#endregion
@@ -2113,6 +3286,7 @@ window.__ModuleLoader__.load({
2113
3286
  * @param onAlert - show an alert message (replaces native alert).
2114
3287
  */
2115
3288
  function TaskCard({ task, controller, draggable = false, now, onAlert }) {
3289
+ const t = useT();
2116
3290
  const [rejectOpen, setRejectOpen] = (0, react.useState)(false);
2117
3291
  const [note, setNote] = (0, react.useState)("");
2118
3292
  const last = task.executions.length > 0 ? task.executions[task.executions.length - 1] : void 0;
@@ -2139,7 +3313,7 @@ window.__ModuleLoader__.load({
2139
3313
  onDragStart: (e) => {
2140
3314
  if (running !== void 0) {
2141
3315
  e.preventDefault();
2142
- const msg = `该任务正由会话执行中(${task.title}),不能拖动`;
3316
+ const msg = t("card.drag.running", { title: task.title });
2143
3317
  if (onAlert !== void 0) onAlert(msg);
2144
3318
  else alert(msg);
2145
3319
  return;
@@ -2169,17 +3343,17 @@ window.__ModuleLoader__.load({
2169
3343
  children: [
2170
3344
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2171
3345
  className: "dsh-atb-badge",
2172
- children: URGENCY_LABEL[task.urgency]
3346
+ children: t(URGENCY_KEYS[task.urgency])
2173
3347
  }),
2174
3348
  task.blocked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2175
3349
  className: "dsh-atb-badge",
2176
3350
  "data-kind": "blocked",
2177
- children: "受阻"
3351
+ children: t("shared.blocked")
2178
3352
  }),
2179
3353
  stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2180
3354
  className: "dsh-atb-badge",
2181
3355
  "data-kind": "stale",
2182
- children: "⏱ 认领超时"
3356
+ children: t("card.badge.stale")
2183
3357
  }),
2184
3358
  task.execution.mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2185
3359
  className: "dsh-atb-badge",
@@ -2188,13 +3362,25 @@ window.__ModuleLoader__.load({
2188
3362
  }),
2189
3363
  task.model !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2190
3364
  className: "dsh-atb-badge",
2191
- title: `固定模型: ${task.model.provider}/${task.model.model}${task.model.reasoningEffort !== void 0 ? ` · 思考强度: ${task.model.reasoningEffort}` : ""}`,
3365
+ title: t("card.badge.modelTitle", { model: task.model.provider + "/" + task.model.model }) + (task.model.reasoningEffort !== void 0 ? t("card.badge.modelEffort", { effort: task.model.reasoningEffort }) : ""),
2192
3366
  children: [task.model.model, task.model.reasoningEffort !== void 0 ? ` (${task.model.reasoningEffort})` : ""]
2193
3367
  }),
3368
+ task.permission === "read-only" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3369
+ className: "dsh-atb-badge",
3370
+ "data-kind": "blocked",
3371
+ title: t("card.badge.permReadOnlyTitle"),
3372
+ children: t("card.badge.permReadOnly")
3373
+ }),
3374
+ task.permission === "danger-full-access" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3375
+ className: "dsh-atb-badge",
3376
+ "data-kind": "urgent",
3377
+ title: t("card.badge.permFullTitle"),
3378
+ children: t("card.badge.permFull")
3379
+ }),
2194
3380
  task.checklist !== void 0 && task.checklist.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2195
3381
  className: "dsh-atb-badge",
2196
3382
  "data-kind": task.status === "in_review" && task.checklist.some((i) => !i.checked) ? "blocked" : "checklist",
2197
- title: task.status === "in_review" && task.checklist.some((i) => !i.checked) ? "待验收:清单未全部勾选" : "验收清单进度",
3383
+ title: task.status === "in_review" && task.checklist.some((i) => !i.checked) ? t("card.badge.checklistReview") : t("card.badge.checklist"),
2198
3384
  children: [
2199
3385
  "☑ ",
2200
3386
  task.checklist.filter((i) => i.checked).length,
@@ -2205,30 +3391,30 @@ window.__ModuleLoader__.load({
2205
3391
  task.status === "done" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2206
3392
  className: "dsh-atb-badge",
2207
3393
  "data-kind": "done",
2208
- children: "完成"
3394
+ children: t("status.pill.done")
2209
3395
  }),
2210
3396
  last !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2211
3397
  className: "dsh-atb-badge",
2212
3398
  "data-kind": last.outcome === "running" ? "running" : last.outcome,
2213
- children: OUTCOME_LABEL[last.outcome] ?? last.outcome
3399
+ children: t(OUTCOME_KEYS[last.outcome] ?? last.outcome)
2214
3400
  }),
2215
3401
  targetSessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2216
3402
  type: "button",
2217
3403
  className: "dsh-atb-card-session",
2218
- title: `点击一键跳转到会话:${targetSessionId}`,
3404
+ title: t("card.session.jumpTitle", { id: targetSessionId }),
2219
3405
  onClick: (e) => {
2220
3406
  e.stopPropagation();
2221
3407
  controller.openSession(targetSessionId).then((result) => {
2222
3408
  if (result === "missing") {
2223
- const msg = `该会话已被删除(${shortId$1(targetSessionId)}),无法打开`;
3409
+ const msg = t("card.session.missing", { id: shortId$1(targetSessionId) });
2224
3410
  if (onAlert !== void 0) onAlert(msg);
2225
3411
  else alert(msg);
2226
3412
  } else if (result === "archived") {
2227
- const msg = `该会话已归档(${shortId$1(targetSessionId)}),已从会话列表隐藏`;
3413
+ const msg = t("card.session.archived", { id: shortId$1(targetSessionId) });
2228
3414
  if (onAlert !== void 0) onAlert(msg);
2229
3415
  else alert(msg);
2230
3416
  } else if (result === "unavailable") {
2231
- const msg = `会话导航不可用,会话 ID:${targetSessionId}`;
3417
+ const msg = t("card.session.unavailable", { id: targetSessionId });
2232
3418
  if (onAlert !== void 0) onAlert(msg);
2233
3419
  else alert(msg);
2234
3420
  }
@@ -2244,7 +3430,7 @@ window.__ModuleLoader__.load({
2244
3430
  task.trashedAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2245
3431
  className: "dsh-atb-badge",
2246
3432
  "data-kind": "trashed",
2247
- children: "待清除"
3433
+ children: t("card.badge.pendingPurge")
2248
3434
  }),
2249
3435
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2250
3436
  style: { marginLeft: "auto" },
@@ -2259,7 +3445,7 @@ window.__ModuleLoader__.load({
2259
3445
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2260
3446
  className: "dsh-atb-input dsh-atb-quick-note",
2261
3447
  value: note,
2262
- placeholder: "退回原因(可选,agent 开工前会读)…",
3448
+ placeholder: t("card.reject.placeholder"),
2263
3449
  autoFocus: true,
2264
3450
  spellCheck: false,
2265
3451
  onChange: (e) => setNote(e.target.value),
@@ -2276,7 +3462,7 @@ window.__ModuleLoader__.load({
2276
3462
  className: "dsh-atb-quickbtn",
2277
3463
  "data-act": "reject-confirm",
2278
3464
  onClick: submitReject,
2279
- children: "退回待办"
3465
+ children: t("card.reject.confirm")
2280
3466
  }),
2281
3467
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2282
3468
  type: "button",
@@ -2286,7 +3472,7 @@ window.__ModuleLoader__.load({
2286
3472
  setRejectOpen(false);
2287
3473
  setNote("");
2288
3474
  },
2289
- children: "取消"
3475
+ children: t("shared.cancel")
2290
3476
  })
2291
3477
  ]
2292
3478
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2296,16 +3482,16 @@ window.__ModuleLoader__.load({
2296
3482
  type: "button",
2297
3483
  className: "dsh-atb-quickbtn",
2298
3484
  "data-act": "done",
2299
- title: "验收完成:移至已完成",
3485
+ title: t("card.action.doneTitle"),
2300
3486
  onClick: () => void controller.move(task.id, task.version, "done"),
2301
- children: "✓ 完成"
3487
+ children: t("card.action.done")
2302
3488
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2303
3489
  type: "button",
2304
3490
  className: "dsh-atb-quickbtn",
2305
3491
  "data-act": "reject",
2306
- title: "退回待办,可附退回原因",
3492
+ title: t("card.action.rejectTitle"),
2307
3493
  onClick: () => setRejectOpen(true),
2308
- children: "✗ 退回"
3494
+ children: t("card.action.reject")
2309
3495
  })]
2310
3496
  }))
2311
3497
  ]
@@ -2334,6 +3520,7 @@ window.__ModuleLoader__.load({
2334
3520
  };
2335
3521
  }
2336
3522
  function AlertModal({ message, onClose }) {
3523
+ const t = useT();
2337
3524
  (0, react.useEffect)(() => {
2338
3525
  const handler = (e) => {
2339
3526
  if (e.key === "Escape") onClose();
@@ -2361,7 +3548,7 @@ window.__ModuleLoader__.load({
2361
3548
  className: "dsh-atb-btn",
2362
3549
  "data-primary": "true",
2363
3550
  onClick: onClose,
2364
- children: "知道了"
3551
+ children: t("alert.ok")
2365
3552
  })
2366
3553
  ]
2367
3554
  })
@@ -2416,6 +3603,58 @@ window.__ModuleLoader__.load({
2416
3603
  }), children]
2417
3604
  });
2418
3605
  }
3606
+ /** Render markdown text with embedded clickable images and lightbox preview. */
3607
+ function MarkdownContent({ text }) {
3608
+ const t = useT();
3609
+ const [lightboxUrl, setLightboxUrl] = (0, react.useState)(null);
3610
+ const regex = /!\[(.*?)\]\(((?:data:image\/[^)]+)|(?:https?:\/\/[^)]+)|(?:[^)]+\.(?:png|jpg|jpeg|gif|webp|svg)))\)/gi;
3611
+ const parts = [];
3612
+ let lastIndex = 0;
3613
+ let match;
3614
+ let count = 0;
3615
+ while ((match = regex.exec(text)) !== null) {
3616
+ if (match.index > lastIndex) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text.slice(lastIndex, match.index) }, `txt-${lastIndex}`));
3617
+ const alt = match[1] || t("md.imageAlt", { n: ++count });
3618
+ const url = match[2] ?? "";
3619
+ parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3620
+ className: "dsh-atb-detail-img-wrap",
3621
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
3622
+ src: url,
3623
+ alt,
3624
+ className: "dsh-atb-detail-img",
3625
+ onClick: () => setLightboxUrl(url),
3626
+ title: t("md.imageTitle", { alt })
3627
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3628
+ className: "dsh-atb-detail-img-caption",
3629
+ children: alt
3630
+ })]
3631
+ }, `img-${match.index}`));
3632
+ lastIndex = regex.lastIndex;
3633
+ }
3634
+ if (lastIndex < text.length) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text.slice(lastIndex) }, `txt-${lastIndex}`));
3635
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3636
+ className: "dsh-atb-markdown-body",
3637
+ children: parts
3638
+ }), lightboxUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3639
+ className: "dsh-atb-lightbox-backdrop",
3640
+ onClick: () => setLightboxUrl(null),
3641
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3642
+ className: "dsh-atb-lightbox-content",
3643
+ onClick: (e) => e.stopPropagation(),
3644
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
3645
+ src: lightboxUrl,
3646
+ alt: t("md.lightboxAlt"),
3647
+ className: "dsh-atb-lightbox-img"
3648
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3649
+ type: "button",
3650
+ className: "dsh-atb-lightbox-close",
3651
+ title: t("md.closePreview"),
3652
+ onClick: () => setLightboxUrl(null),
3653
+ children: "✕"
3654
+ })]
3655
+ })
3656
+ })] });
3657
+ }
2419
3658
  /** The most recent execution carrying isolation facts, newest first. */
2420
3659
  function latestIsolated(task) {
2421
3660
  return [...task.executions].reverse().find((e) => e.isolation !== void 0 || e.worktreePath !== void 0 || e.isolationNote !== void 0);
@@ -2437,6 +3676,7 @@ window.__ModuleLoader__.load({
2437
3676
  * @param spec - what to show: one commit hash, or one changed path.
2438
3677
  */
2439
3678
  function DiffView({ controller, task, execution, commit, path }) {
3679
+ const t = useT();
2440
3680
  const [state, setState] = (0, react.useState)({ loading: true });
2441
3681
  (0, react.useEffect)(() => {
2442
3682
  let alive = true;
@@ -2473,20 +3713,20 @@ window.__ModuleLoader__.load({
2473
3713
  children: [
2474
3714
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2475
3715
  className: "dsh-atb-diffview-title",
2476
- children: commit !== void 0 ? `提交 ${shortHash(commit)}` : `文件 ${path}`
3716
+ children: commit !== void 0 ? t("diff.commit", { hash: shortHash(commit) }) : t("diff.file", { path: path ?? "" })
2477
3717
  }),
2478
3718
  state.loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2479
3719
  className: "dsh-atb-diffview-hint",
2480
- children: "读取中…"
3720
+ children: t("shared.loading")
2481
3721
  }),
2482
3722
  state.truncated === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2483
3723
  className: "dsh-atb-diffview-hint",
2484
- children: "⚠ 内容过长已截断"
3724
+ children: t("diff.truncated")
2485
3725
  })
2486
3726
  ]
2487
3727
  }), state.failed === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2488
3728
  className: "dsh-atb-diffview-error",
2489
- children: "获取失败(原因见看板顶部错误条;对象可能已随 worktree 删除丢失)"
3729
+ children: t("diff.failed")
2490
3730
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
2491
3731
  className: "dsh-atb-diffview-pre",
2492
3732
  children: state.diff ?? ""
@@ -2498,6 +3738,7 @@ window.__ModuleLoader__.load({
2498
3738
  * per row; unchecked items highlight while the task sits in in_review.
2499
3739
  */
2500
3740
  function ChecklistBlock({ task, controller }) {
3741
+ const t = useT();
2501
3742
  const items = task.checklist ?? [];
2502
3743
  if (items.length === 0) return null;
2503
3744
  const { done, total } = checklistProgress(task);
@@ -2508,7 +3749,7 @@ window.__ModuleLoader__.load({
2508
3749
  "data-kind": "checklist",
2509
3750
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2510
3751
  className: "dsh-atb-fieldcard-label",
2511
- children: ["验收清单(DoD)", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3752
+ children: [t("checklist.title"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2512
3753
  className: "dsh-atb-cl-progress",
2513
3754
  "data-tone": reviewing && unchecked > 0 ? "bad" : void 0,
2514
3755
  children: [
@@ -2516,7 +3757,7 @@ window.__ModuleLoader__.load({
2516
3757
  done,
2517
3758
  "/",
2518
3759
  total,
2519
- reviewing && unchecked > 0 ? ` · ${unchecked} 项未完成` : done === total ? " · 全部完成" : ""
3760
+ reviewing && unchecked > 0 ? t("checklist.unchecked", { n: unchecked }) : done === total ? t("checklist.allDone") : ""
2520
3761
  ]
2521
3762
  })]
2522
3763
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -2537,10 +3778,10 @@ window.__ModuleLoader__.load({
2537
3778
  }),
2538
3779
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2539
3780
  className: "dsh-atb-cl-meta",
2540
- children: [item.checked ? `${item.checkedBy === "user" ? "👤 用户" : `🤖 ${shortId(item.checkedBy)}`} · ${fmtTime(item.checkedAt)}` : "未完成", item.note !== void 0 && item.note.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3781
+ children: [item.checked ? `${item.checkedBy === "user" ? t("checklist.byUser") : `🤖 ${shortId(item.checkedBy)}`} · ${fmtTime(item.checkedAt)}` : t("checklist.uncheckedItem"), item.note !== void 0 && item.note.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2541
3782
  className: "dsh-atb-cl-note",
2542
3783
  title: item.note,
2543
- children: ["证据:", item.note]
3784
+ children: t("checklist.evidence", { note: item.note })
2544
3785
  })]
2545
3786
  })
2546
3787
  ]
@@ -2553,6 +3794,7 @@ window.__ModuleLoader__.load({
2553
3794
  * carries one, rendered section by section for the reviewer.
2554
3795
  */
2555
3796
  function ReportBlock({ task }) {
3797
+ const t = useT();
2556
3798
  const execution = [...task.executions].reverse().find((e) => e.report !== void 0);
2557
3799
  const report = execution?.report;
2558
3800
  if (execution === void 0 || report === void 0) return null;
@@ -2572,23 +3814,23 @@ window.__ModuleLoader__.load({
2572
3814
  children: [
2573
3815
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2574
3816
  className: "dsh-atb-fieldcard-label",
2575
- children: ["执行报告", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3817
+ children: [t("report.title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2576
3818
  className: "dsh-atb-cl-progress",
2577
- children: ["由执行会话提交 · ", fmtTime(execution.endedAt ?? execution.startedAt)]
3819
+ children: t("report.submitted", { time: fmtTime(execution.endedAt ?? execution.startedAt) })
2578
3820
  })]
2579
3821
  }),
2580
3822
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2581
3823
  className: "dsh-atb-rpt-summary",
2582
3824
  children: report.summary
2583
3825
  }),
2584
- section("改动文件", report.changedFiles),
2585
- section("自验情况", report.checks),
2586
- section("产物", report.artifacts),
3826
+ section(t("report.changedFiles"), report.changedFiles),
3827
+ section(t("report.checks"), report.checks),
3828
+ section(t("report.artifacts"), report.artifacts),
2587
3829
  report.risk.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2588
3830
  className: "dsh-atb-rpt-sec",
2589
3831
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2590
3832
  className: "dsh-atb-rpt-label",
2591
- children: "剩余风险"
3833
+ children: t("report.risk")
2592
3834
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2593
3835
  className: "dsh-atb-rpt-risk",
2594
3836
  children: report.risk
@@ -2603,6 +3845,7 @@ window.__ModuleLoader__.load({
2603
3845
  * remove worktree — plan §3.3).
2604
3846
  */
2605
3847
  function IsolationBlock({ task, controller }) {
3848
+ const t = useT();
2606
3849
  const { alert: showAlert, el: alertEl } = useAlert();
2607
3850
  const [confirmMerge, setConfirmMerge] = (0, react.useState)(false);
2608
3851
  const [confirmRemove, setConfirmRemove] = (0, react.useState)(null);
@@ -2617,8 +3860,8 @@ window.__ModuleLoader__.load({
2617
3860
  controller.mergeBranch(task.id).then((result) => {
2618
3861
  setBusy(false);
2619
3862
  setConfirmMerge(false);
2620
- if (!result.ok) showAlert(`合并失败:${result.error}`);
2621
- else if (result.noop === true) showAlert("该分支没有领先主工作区的新提交,无需合并(可退回续跑或直接清理)");
3863
+ if (!result.ok) showAlert(t("iso.merge.failed", { error: result.error }));
3864
+ else if (result.noop === true) showAlert(t("iso.merge.noop"));
2622
3865
  });
2623
3866
  };
2624
3867
  const doRemove = (deleteBranch) => {
@@ -2626,8 +3869,8 @@ window.__ModuleLoader__.load({
2626
3869
  controller.removeWorktree(task.id, deleteBranch).then((result) => {
2627
3870
  setBusy(false);
2628
3871
  setConfirmRemove(null);
2629
- if (!result.ok) showAlert(`删除失败:${result.error}`);
2630
- else if (result.branchError !== void 0) showAlert(`worktree 已删除,但分支删除失败:${result.branchError}`);
3872
+ if (!result.ok) showAlert(t("iso.remove.failed", { error: result.error }));
3873
+ else if (result.branchError !== void 0) showAlert(t("iso.remove.branchFailed", { error: result.branchError }));
2631
3874
  });
2632
3875
  };
2633
3876
  if (execution.isolation !== "worktree" || execution.worktreePath === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2636,11 +3879,11 @@ window.__ModuleLoader__.load({
2636
3879
  children: [
2637
3880
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2638
3881
  className: "dsh-atb-fieldcard-label",
2639
- children: "执行隔离"
3882
+ children: t("iso.title")
2640
3883
  }),
2641
3884
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2642
3885
  className: "dsh-atb-iso-none",
2643
- children: ["📁 原目录执行", execution.isolationNote !== void 0 ? ` · ${execution.isolationNote}` : ""]
3886
+ children: [t("iso.none"), execution.isolationNote !== void 0 ? ` · ${execution.isolationNote}` : ""]
2644
3887
  }),
2645
3888
  alertEl
2646
3889
  ]
@@ -2655,7 +3898,7 @@ window.__ModuleLoader__.load({
2655
3898
  children: [
2656
3899
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2657
3900
  className: "dsh-atb-fieldcard-label",
2658
- children: "执行隔离 · Worktree"
3901
+ children: t("iso.worktreeTitle")
2659
3902
  }),
2660
3903
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2661
3904
  className: "dsh-atb-iso-facts",
@@ -2663,24 +3906,22 @@ window.__ModuleLoader__.load({
2663
3906
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2664
3907
  className: "dsh-atb-iso-fact",
2665
3908
  title: execution.worktreePath,
2666
- children: ["🌿 分支 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: execution.branch ?? task.branch })]
2667
- }),
2668
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2669
- className: "dsh-atb-iso-fact",
2670
3909
  children: [
2671
- "基线 ",
2672
- shortHash(execution.baseCommit),
2673
- " ",
2674
- shortHash(execution.headCommit)
3910
+ t("iso.branch"),
3911
+ " ",
3912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: execution.branch ?? task.branch })
2675
3913
  ]
2676
3914
  }),
2677
- execution.changedFiles !== void 0 && execution.changedFiles > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2678
3916
  className: "dsh-atb-iso-fact",
2679
- children: [
2680
- "改动 ",
2681
- execution.changedFiles,
2682
- " 个文件"
2683
- ]
3917
+ children: t("iso.baseline", {
3918
+ base: shortHash(execution.baseCommit),
3919
+ head: shortHash(execution.headCommit)
3920
+ })
3921
+ }),
3922
+ execution.changedFiles !== void 0 && execution.changedFiles > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3923
+ className: "dsh-atb-iso-fact",
3924
+ children: t("iso.changed", { n: execution.changedFiles })
2684
3925
  }),
2685
3926
  execution.diffStat !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2686
3927
  className: "dsh-atb-iso-fact",
@@ -2697,7 +3938,7 @@ window.__ModuleLoader__.load({
2697
3938
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2698
3939
  type: "button",
2699
3940
  className: "dsh-atb-iso-commit-btn",
2700
- title: "点击展开该提交的 diff",
3941
+ title: t("iso.commit.openTitle"),
2701
3942
  onClick: () => setOpenDiff(openDiff?.commit === c.hash ? null : { commit: c.hash }),
2702
3943
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: shortHash(c.hash) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: c.subject })]
2703
3944
  }), openDiff?.commit === c.hash && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiffView, {
@@ -2706,17 +3947,13 @@ window.__ModuleLoader__.load({
2706
3947
  execution,
2707
3948
  commit: c.hash
2708
3949
  })]
2709
- }, c.hash)), commitTotal > 10 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3950
+ }, c.hash)), commitTotal > 10 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2710
3951
  className: "dsh-atb-iso-more",
2711
- children: [
2712
- "… 共 ",
2713
- commitTotal,
2714
- " 个提交"
2715
- ]
3952
+ children: t("iso.commits.more", { n: commitTotal })
2716
3953
  })]
2717
3954
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2718
3955
  className: "dsh-atb-iso-nocommit",
2719
- children: "该次执行没有产生提交(改动可能未提交,见下方警告)"
3956
+ children: t("iso.nocommit")
2720
3957
  }),
2721
3958
  dirtyTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2722
3959
  className: "dsh-atb-iso-dirty",
@@ -2725,12 +3962,7 @@ window.__ModuleLoader__.load({
2725
3962
  type: "button",
2726
3963
  className: "dsh-atb-iso-dirty-toggle",
2727
3964
  onClick: () => setDirtyOpen(!dirtyOpen),
2728
- children: [
2729
- "⚠ 有 ",
2730
- dirtyTotal,
2731
- " 处未提交修改(合并前请让 agent 提交,或手动处理)",
2732
- dirtyOpen ? " ▲" : " ▼ 查看文件"
2733
- ]
3965
+ children: [t("iso.dirty.toggle", { n: dirtyTotal }), dirtyOpen ? t("iso.dirty.collapse") : t("iso.dirty.expand")]
2734
3966
  }),
2735
3967
  dirtyOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2736
3968
  className: "dsh-atb-iso-dirty-files",
@@ -2739,7 +3971,7 @@ window.__ModuleLoader__.load({
2739
3971
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2740
3972
  type: "button",
2741
3973
  className: "dsh-atb-iso-dirty-file",
2742
- title: "点击查看该文件的未提交 diff",
3974
+ title: t("iso.dirty.openTitle"),
2743
3975
  onClick: () => setOpenDiff(openDiff?.path === filePath ? null : { path: filePath }),
2744
3976
  children: [
2745
3977
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: line.slice(0, 2) }),
@@ -2747,13 +3979,9 @@ window.__ModuleLoader__.load({
2747
3979
  filePath
2748
3980
  ]
2749
3981
  }, `${line}-${index}`);
2750
- }), dirtyTotal > 30 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3982
+ }), dirtyTotal > 30 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2751
3983
  className: "dsh-atb-iso-more",
2752
- children: [
2753
- "… 共 ",
2754
- dirtyTotal,
2755
- " 处(完整列表见任务台账)"
2756
- ]
3984
+ children: t("iso.dirty.more", { n: dirtyTotal })
2757
3985
  })]
2758
3986
  }),
2759
3987
  openDiff?.path !== void 0 && dirtyOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiffView, {
@@ -2769,13 +3997,13 @@ window.__ModuleLoader__.load({
2769
3997
  children: [
2770
3998
  running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2771
3999
  className: "dsh-atb-iso-hint",
2772
- children: "执行中 — 结束后可合并或清理"
4000
+ children: t("iso.hint.running")
2773
4001
  }) : confirmMerge ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2774
4002
  className: "dsh-atb-confirm",
2775
4003
  children: [
2776
4004
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2777
4005
  className: "dsh-atb-confirm-label",
2778
- children: "将分支以 --no-ff 合并到主工作区?"
4006
+ children: t("iso.merge.confirm")
2779
4007
  }),
2780
4008
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2781
4009
  type: "button",
@@ -2783,45 +4011,45 @@ window.__ModuleLoader__.load({
2783
4011
  "data-primary": "true",
2784
4012
  disabled: busy,
2785
4013
  onClick: doMerge,
2786
- children: "确认合并"
4014
+ children: t("iso.merge.go")
2787
4015
  }),
2788
4016
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2789
4017
  type: "button",
2790
4018
  className: "dsh-atb-btn",
2791
4019
  onClick: () => setConfirmMerge(false),
2792
- children: "取消"
4020
+ children: t("shared.cancel")
2793
4021
  })
2794
4022
  ]
2795
4023
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2796
4024
  type: "button",
2797
4025
  className: "dsh-atb-btn",
2798
4026
  disabled: busy,
2799
- title: "在主工作区 git merge --no-ff 该任务分支(要求主区干净;冲突会原样报告)",
4027
+ title: t("iso.merge.title"),
2800
4028
  onClick: () => setConfirmMerge(true),
2801
- children: "⇥ 合并到主工作区"
4029
+ children: t("iso.merge.button")
2802
4030
  }),
2803
4031
  !running && (confirmRemove === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2804
4032
  type: "button",
2805
4033
  className: "dsh-atb-btn",
2806
4034
  "data-danger": "true",
2807
4035
  disabled: busy,
2808
- title: "git worktree remove(有未提交修改时拒绝)",
4036
+ title: t("iso.remove.wtTitle"),
2809
4037
  onClick: () => setConfirmRemove("wt"),
2810
- children: "🗑 删除 worktree"
4038
+ children: t("iso.remove.wt")
2811
4039
  }), task.branch !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2812
4040
  type: "button",
2813
4041
  className: "dsh-atb-btn",
2814
4042
  "data-danger": "true",
2815
4043
  disabled: busy,
2816
- title: "删除 worktree 并删除任务分支(有未提交修改时拒绝)",
4044
+ title: t("iso.remove.wtbTitle"),
2817
4045
  onClick: () => setConfirmRemove("wtb"),
2818
- children: "🗑 删 worktree + 分支"
4046
+ children: t("iso.remove.wtb")
2819
4047
  })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2820
4048
  className: "dsh-atb-confirm",
2821
4049
  children: [
2822
4050
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2823
4051
  className: "dsh-atb-confirm-label",
2824
- children: confirmRemove === "wtb" ? "删除 worktree 并删除分支?" : "删除 worktree 目录?"
4052
+ children: confirmRemove === "wtb" ? t("iso.remove.confirmWtb") : t("iso.remove.confirmWt")
2825
4053
  }),
2826
4054
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2827
4055
  type: "button",
@@ -2829,19 +4057,19 @@ window.__ModuleLoader__.load({
2829
4057
  "data-danger": "true",
2830
4058
  disabled: busy,
2831
4059
  onClick: () => doRemove(confirmRemove === "wtb"),
2832
- children: "确认删除"
4060
+ children: t("shared.confirmDelete")
2833
4061
  }),
2834
4062
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2835
4063
  type: "button",
2836
4064
  className: "dsh-atb-btn",
2837
4065
  onClick: () => setConfirmRemove(null),
2838
- children: "取消"
4066
+ children: t("shared.cancel")
2839
4067
  })
2840
4068
  ]
2841
4069
  })),
2842
4070
  !running && confirmRemove === null && !confirmMerge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2843
4071
  className: "dsh-atb-iso-hint",
2844
- children: "分支与 worktree 保留中 — 可退回继续修改"
4072
+ children: t("iso.hint.keep")
2845
4073
  })
2846
4074
  ]
2847
4075
  }),
@@ -2856,6 +4084,7 @@ window.__ModuleLoader__.load({
2856
4084
  * @param now - current epoch ms (stale-claim highlight).
2857
4085
  */
2858
4086
  function TaskDetail({ task, controller, now }) {
4087
+ const t = useT();
2859
4088
  const [comment, setComment] = (0, react.useState)("");
2860
4089
  const [confirmDone, setConfirmDone] = (0, react.useState)(false);
2861
4090
  const [confirmPurge, setConfirmPurge] = (0, react.useState)(false);
@@ -2879,9 +4108,9 @@ window.__ModuleLoader__.load({
2879
4108
  /** Jump to an execution's session; prompt precisely when it cannot open. */
2880
4109
  const jumpToSession = (sessionId) => {
2881
4110
  controller.openSession(sessionId).then((result) => {
2882
- if (result === "missing") showAlert(`该会话已被删除(${shortId(sessionId)}),无法打开`);
2883
- else if (result === "archived") showAlert(`该会话已归档(${shortId(sessionId)}),已从会话列表隐藏`);
2884
- else if (result === "unavailable") showAlert(`会话导航不可用,会话 ID:${sessionId}`);
4111
+ if (result === "missing") showAlert(t("card.session.missing", { id: shortId(sessionId) }));
4112
+ else if (result === "archived") showAlert(t("card.session.archived", { id: shortId(sessionId) }));
4113
+ else if (result === "unavailable") showAlert(t("card.session.unavailable", { id: sessionId }));
2885
4114
  });
2886
4115
  };
2887
4116
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2898,7 +4127,7 @@ window.__ModuleLoader__.load({
2898
4127
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: task.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2899
4128
  className: "dsh-atb-statuspill",
2900
4129
  "data-status": task.status,
2901
- children: STATUS_LABEL[task.status] ?? task.status
4130
+ children: t(STATUS_KEYS[task.status] ?? task.status)
2902
4131
  })]
2903
4132
  }),
2904
4133
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2906,7 +4135,7 @@ window.__ModuleLoader__.load({
2906
4135
  children: [
2907
4136
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
2908
4137
  tone: task.urgency,
2909
- children: ["● ", URGENCY_LABEL[task.urgency] ?? task.urgency]
4138
+ children: ["● ", t(URGENCY_KEYS[task.urgency] ?? task.urgency)]
2910
4139
  }),
2911
4140
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
2912
4141
  icon: "📁",
@@ -2914,35 +4143,32 @@ window.__ModuleLoader__.load({
2914
4143
  }),
2915
4144
  task.model !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
2916
4145
  icon: "✦",
2917
- title: `固定模型: ${task.model.provider}/${task.model.model}${task.model.reasoningEffort !== void 0 ? ` · 思考强度: ${task.model.reasoningEffort}` : ""}`,
4146
+ title: t("card.badge.modelTitle", { model: task.model.provider + "/" + task.model.model }) + (task.model.reasoningEffort !== void 0 ? t("card.badge.modelEffort", { effort: task.model.reasoningEffort }) : ""),
2918
4147
  children: [task.model.model, task.model.reasoningEffort !== void 0 ? ` · ${task.model.reasoningEffort}` : ""]
2919
4148
  }),
2920
4149
  task.presetId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
2921
4150
  icon: "🎛",
2922
4151
  children: task.presetId
2923
4152
  }),
2924
- task.execution.mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
4153
+ task.execution.mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
2925
4154
  icon: "⏰",
2926
- children: [
2927
- task.execution.cron,
2928
- " · 下次 ",
2929
- fmtTime(task.execution.nextRunAt)
2930
- ]
4155
+ children: t("detail.chip.nextRun", {
4156
+ cron: task.execution.cron ?? "",
4157
+ time: fmtTime(task.execution.nextRunAt)
4158
+ })
2931
4159
  }),
2932
4160
  task.blocked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
2933
4161
  icon: "⛔",
2934
4162
  tone: "urgent",
2935
- children: "受阻"
4163
+ children: t("shared.blocked")
2936
4164
  }),
2937
- task.checklist !== void 0 && task.checklist.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
4165
+ task.checklist !== void 0 && task.checklist.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
2938
4166
  icon: "☑",
2939
4167
  tone: task.status === "in_review" && task.checklist.some((i) => !i.checked) ? "urgent" : void 0,
2940
- children: [
2941
- "清单 ",
2942
- checklistProgress(task).done,
2943
- "/",
2944
- task.checklist.length
2945
- ]
4168
+ children: t("detail.chip.checklist", {
4169
+ done: checklistProgress(task).done,
4170
+ total: task.checklist.length
4171
+ })
2946
4172
  }),
2947
4173
  task.branch !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, {
2948
4174
  icon: "🌿",
@@ -2951,40 +4177,52 @@ window.__ModuleLoader__.load({
2951
4177
  }),
2952
4178
  (task.isolation === void 0 || task.isolation === "worktree") && task.branch === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
2953
4179
  icon: "🌿",
2954
- children: "Worktree 隔离"
4180
+ children: t("detail.chip.isolated")
4181
+ }),
4182
+ task.permission === "read-only" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
4183
+ icon: "🔒",
4184
+ tone: "urgent",
4185
+ children: t("detail.chip.permReadOnly")
4186
+ }),
4187
+ task.permission === "danger-full-access" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
4188
+ icon: "⚡",
4189
+ tone: "urgent",
4190
+ children: t("detail.chip.permFull")
4191
+ }),
4192
+ (task.permission === "workspace-write" || task.permission === void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
4193
+ icon: "📁",
4194
+ children: t("detail.chip.permWrite")
2955
4195
  }),
2956
4196
  holder !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2957
4197
  type: "button",
2958
4198
  className: "dsh-atb-chip2 dsh-atb-chip-btn",
2959
4199
  "data-tone": stale ? "urgent" : void 0,
2960
- title: `点击跳转至该会话:${holder}`,
4200
+ title: t("detail.chip.holderTitle", { id: holder }),
2961
4201
  onClick: () => jumpToSession(holder),
2962
4202
  children: [
2963
4203
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2964
4204
  className: "dsh-atb-chip2-icon",
2965
4205
  children: stale ? "⏱" : "🤖"
2966
4206
  }),
2967
- stale ? "认领超时 · " : "",
4207
+ stale ? t("detail.chip.holderStale") : t("detail.chip.holderBy"),
2968
4208
  shortId(holder),
2969
- " 持有 ↗"
4209
+ t("detail.chip.holderSuffix")
2970
4210
  ]
2971
4211
  }),
2972
4212
  task.trashedAt !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chip, {
2973
4213
  icon: "🗑",
2974
4214
  tone: "urgent",
2975
- children: "已删除待清除"
4215
+ children: t("detail.chip.trashed")
2976
4216
  }),
2977
4217
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Chip, { children: ["v", task.version] })
2978
4218
  ]
2979
4219
  }),
2980
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2981
4221
  className: "dsh-atb-detail-sub",
2982
- children: [
2983
- "更新 ",
2984
- fmtTime(task.updatedAt),
2985
- " · 最近操作 ",
2986
- task.updatedBy.kind === "agent" ? `🤖 ${shortId(task.updatedBy.sessionId)}` : task.updatedBy.kind === "system" ? "⚙️ 系统" : "👤 用户"
2987
- ]
4222
+ children: t("detail.sub.line", {
4223
+ time: fmtTime(task.updatedAt),
4224
+ who: task.updatedBy.kind === "agent" ? `🤖 ${shortId(task.updatedBy.sessionId)}` : task.updatedBy.kind === "system" ? t("detail.updatedBy.system") : t("detail.updatedBy.user")
4225
+ })
2988
4226
  })
2989
4227
  ]
2990
4228
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2993,56 +4231,56 @@ window.__ModuleLoader__.load({
2993
4231
  targetSessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2994
4232
  type: "button",
2995
4233
  className: "dsh-atb-detail-session",
2996
- title: `一键跳转到对应会话:${targetSessionId}`,
4234
+ title: t("detail.session.jumpTitle", { id: targetSessionId }),
2997
4235
  onClick: () => jumpToSession(targetSessionId),
2998
- children: "🤖 跳转会话 ↗"
4236
+ children: t("detail.session.jump")
2999
4237
  }),
3000
4238
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3001
4239
  type: "button",
3002
4240
  className: "dsh-atb-detail-edit",
3003
4241
  onClick: () => controller.openEditor(task.id),
3004
- children: "✎ 编辑"
4242
+ children: t("detail.action.edit")
3005
4243
  }),
3006
4244
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3007
4245
  type: "button",
3008
4246
  className: "dsh-atb-detail-edit",
3009
- title: "复制此任务的全部配置为一张新卡(待办列)",
4247
+ title: t("detail.action.duplicateTitle"),
3010
4248
  disabled: actionBusy,
3011
4249
  onClick: () => runAction(() => controller.duplicate(task)),
3012
- children: "⧉ 复制"
4250
+ children: t("detail.action.duplicate")
3013
4251
  }),
3014
4252
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3015
4253
  type: "button",
3016
4254
  className: "dsh-atb-detail-edit",
3017
- title: "把此任务的配置(含清单)保存为模板,新建任务时可用",
4255
+ title: t("detail.action.saveTplTitle"),
3018
4256
  disabled: actionBusy,
3019
4257
  onClick: () => runAction(async () => {
3020
- if (await controller.saveAsTemplate(task)) showAlert("已存为模板(新建任务 ▼ 下拉可用,可在模板管理中改名)");
4258
+ if (await controller.saveAsTemplate(task)) showAlert(t("detail.action.saveTplDone"));
3021
4259
  }),
3022
- children: "⌗ 存为模板"
4260
+ children: t("detail.action.saveTpl")
3023
4261
  }),
3024
4262
  canRun && task.branch !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3025
4263
  type: "button",
3026
4264
  className: "dsh-atb-detail-run",
3027
- title: "续跑:保留现有 worktree 与分支(上次的改动和提交都在原处),在其上继续执行;默认「立即执行」会重置为全新基线",
4265
+ title: t("detail.action.reuseTitle"),
3028
4266
  disabled: actionBusy,
3029
4267
  onClick: () => runAction(() => controller.run(task.id, true)),
3030
- children: "↻ 续跑"
4268
+ children: t("detail.action.reuse")
3031
4269
  }),
3032
4270
  canRun && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3033
4271
  type: "button",
3034
4272
  className: "dsh-atb-detail-run",
3035
- title: task.model !== void 0 ? `新会话执行(${task.model.model})` : "新会话执行(默认模型)",
4273
+ title: task.model !== void 0 ? t("detail.action.runTitleModel", { model: task.model.model }) : t("detail.action.runTitleDefault"),
3036
4274
  disabled: actionBusy,
3037
4275
  onClick: () => runAction(() => controller.run(task.id)),
3038
- children: "▶ 立即执行"
4276
+ children: t("detail.action.run")
3039
4277
  }),
3040
4278
  runningExecution !== void 0 && (confirmCancel ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3041
4279
  className: "dsh-atb-confirm",
3042
4280
  children: [
3043
4281
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3044
4282
  className: "dsh-atb-confirm-label",
3045
- children: "停止该执行会话?"
4283
+ children: t("detail.action.stopConfirm")
3046
4284
  }),
3047
4285
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3048
4286
  type: "button",
@@ -3052,27 +4290,27 @@ window.__ModuleLoader__.load({
3052
4290
  controller.cancel(task.id);
3053
4291
  setConfirmCancel(false);
3054
4292
  },
3055
- children: "停止"
4293
+ children: t("detail.action.stop")
3056
4294
  }),
3057
4295
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3058
4296
  type: "button",
3059
4297
  className: "dsh-atb-btn",
3060
4298
  onClick: () => setConfirmCancel(false),
3061
- children: "取消"
4299
+ children: t("shared.cancel")
3062
4300
  })
3063
4301
  ]
3064
4302
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3065
4303
  type: "button",
3066
4304
  className: "dsh-atb-detail-run",
3067
4305
  "data-danger": "true",
3068
- title: `停止执行会话 ${runningExecution.sessionId ?? ""}(任务回到待办)`,
4306
+ title: t("detail.action.stopTitle", { id: runningExecution.sessionId ?? "" }),
3069
4307
  onClick: () => setConfirmCancel(true),
3070
- children: "■ 停止执行"
4308
+ children: t("detail.action.stopExec")
3071
4309
  })),
3072
4310
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3073
4311
  type: "button",
3074
4312
  className: "dsh-atb-detail-close",
3075
- "aria-label": "关闭",
4313
+ "aria-label": t("shared.close"),
3076
4314
  onClick: () => controller.select(void 0),
3077
4315
  children: "✕"
3078
4316
  })
@@ -3083,10 +4321,10 @@ window.__ModuleLoader__.load({
3083
4321
  className: "dsh-atb-fieldcard",
3084
4322
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3085
4323
  className: "dsh-atb-fieldcard-label",
3086
- children: "描述"
4324
+ children: t("detail.field.description")
3087
4325
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3088
4326
  className: "dsh-atb-desc",
3089
- children: task.description
4327
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkdownContent, { text: task.description })
3090
4328
  })]
3091
4329
  }),
3092
4330
  task.prompt.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3094,10 +4332,10 @@ window.__ModuleLoader__.load({
3094
4332
  "data-kind": "prompt",
3095
4333
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3096
4334
  className: "dsh-atb-fieldcard-label",
3097
- children: "执行 Prompt"
4335
+ children: t("detail.field.prompt")
3098
4336
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3099
4337
  className: "dsh-atb-promptbox",
3100
- children: task.prompt
4338
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkdownContent, { text: task.prompt })
3101
4339
  })]
3102
4340
  }),
3103
4341
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IsolationBlock, {
@@ -3120,7 +4358,7 @@ window.__ModuleLoader__.load({
3120
4358
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3121
4359
  className: "dsh-atb-confirm-label",
3122
4360
  "data-tone": unchecked > 0 ? "bad" : void 0,
3123
- children: unchecked > 0 ? `仍有 ${unchecked} 项清单未勾选,确认完成?` : "确认完成?"
4361
+ children: unchecked > 0 ? t("detail.move.confirmDoneUnchecked", { n: unchecked }) : t("detail.move.confirmDone")
3124
4362
  }),
3125
4363
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3126
4364
  type: "button",
@@ -3130,42 +4368,42 @@ window.__ModuleLoader__.load({
3130
4368
  controller.move(task.id, task.version, "done");
3131
4369
  setConfirmDone(false);
3132
4370
  },
3133
- children: "确认"
4371
+ children: t("detail.move.confirm")
3134
4372
  }),
3135
4373
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3136
4374
  type: "button",
3137
4375
  className: "dsh-atb-btn",
3138
4376
  onClick: () => setConfirmDone(false),
3139
- children: "取消"
4377
+ children: t("shared.cancel")
3140
4378
  })
3141
4379
  ]
3142
- }, to) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4380
+ }, to) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3143
4381
  type: "button",
3144
4382
  className: "dsh-atb-movebtn",
3145
4383
  "data-to": to,
3146
4384
  onClick: () => setConfirmDone(true),
3147
- children: ["移至→", MOVE_LABEL[to]]
3148
- }, to) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4385
+ children: t("detail.move.to", { status: t(MOVE_KEYS[to]) })
4386
+ }, to) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3149
4387
  type: "button",
3150
4388
  className: "dsh-atb-movebtn",
3151
4389
  "data-to": to,
3152
4390
  onClick: () => void controller.move(task.id, task.version, to),
3153
- children: ["移至→", MOVE_LABEL[to]]
4391
+ children: t("detail.move.to", { status: t(MOVE_KEYS[to]) })
3154
4392
  }, to)),
3155
4393
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3156
4394
  type: "button",
3157
4395
  className: "dsh-atb-movebtn",
3158
4396
  "data-to": "blocked",
3159
4397
  onClick: () => void controller.toggleBlocked(task),
3160
- children: task.blocked ? "✓ 解除受阻" : "⛔ 标记受阻"
4398
+ children: task.blocked ? t("detail.blocked.unmark") : t("detail.blocked.mark")
3161
4399
  }),
3162
4400
  holder !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3163
4401
  type: "button",
3164
4402
  className: "dsh-atb-movebtn",
3165
4403
  "data-to": "release",
3166
- title: `释放 ${holder} 的认领:任务回到待办(持有会话可能仍在工作,确认它已停止后再释放)`,
4404
+ title: t("detail.release.title", { id: holder }),
3167
4405
  onClick: () => void controller.move(task.id, task.version, "todo"),
3168
- children: "🔓 释放认领"
4406
+ children: t("detail.release.button")
3169
4407
  })
3170
4408
  ]
3171
4409
  })
@@ -3173,13 +4411,13 @@ window.__ModuleLoader__.load({
3173
4411
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3174
4412
  className: "dsh-atb-section",
3175
4413
  children: [
3176
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", { children: ["评论", task.comments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4414
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", { children: [t("detail.comments.title"), task.comments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3177
4415
  className: "dsh-atb-count2",
3178
4416
  children: task.comments.length
3179
4417
  })] }),
3180
4418
  task.comments.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3181
4419
  className: "dsh-atb-empty2",
3182
- children: "暂无评论 — agent 交接时会在这里汇报改动与验证结果"
4420
+ children: t("detail.comments.empty")
3183
4421
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3184
4422
  className: "dsh-atb-commentlist",
3185
4423
  children: task.comments.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3192,7 +4430,7 @@ window.__ModuleLoader__.load({
3192
4430
  className: "dsh-atb-bubble-main",
3193
4431
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3194
4432
  className: "dsh-atb-bubble-meta",
3195
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: c.threadId !== void 0 ? `agent ${shortId(c.threadId)}` : "用户" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fmtTime(c.createdAt) })]
4433
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: c.threadId !== void 0 ? `agent ${shortId(c.threadId)}` : t("detail.comments.user") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: fmtTime(c.createdAt) })]
3196
4434
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3197
4435
  className: "dsh-atb-bubble-body",
3198
4436
  children: c.body
@@ -3205,7 +4443,7 @@ window.__ModuleLoader__.load({
3205
4443
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3206
4444
  className: "dsh-atb-composer-input",
3207
4445
  value: comment,
3208
- placeholder: "以用户身份留言(agent 开工前会读)…",
4446
+ placeholder: t("detail.composer.placeholder"),
3209
4447
  onChange: (e) => setComment(e.target.value),
3210
4448
  onKeyDown: (e) => {
3211
4449
  if ((e.ctrlKey || e.metaKey) && e.key === "Enter" && comment.trim().length > 0) controller.comment(task.id, comment).then((ok) => {
@@ -3221,7 +4459,7 @@ window.__ModuleLoader__.load({
3221
4459
  if (ok) setComment("");
3222
4460
  });
3223
4461
  },
3224
- children: "发表"
4462
+ children: t("detail.composer.send")
3225
4463
  })]
3226
4464
  })
3227
4465
  ]
@@ -3229,19 +4467,15 @@ window.__ModuleLoader__.load({
3229
4467
  task.executions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3230
4468
  className: "dsh-atb-section",
3231
4469
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", { children: [
3232
- "执行记录",
4470
+ t("detail.exec.title"),
3233
4471
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3234
4472
  className: "dsh-atb-count2",
3235
4473
  children: task.executions.length
3236
4474
  }),
3237
- task.executionsPruned !== void 0 && task.executionsPruned > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4475
+ task.executionsPruned !== void 0 && task.executionsPruned > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3238
4476
  className: "dsh-atb-count2",
3239
- title: `更早的 ${task.executionsPruned} 条执行记录已按保留上限清理`,
3240
- children: [
3241
- "+",
3242
- task.executionsPruned,
3243
- " 已清理"
3244
- ]
4477
+ title: t("detail.exec.prunedTitle", { n: task.executionsPruned }),
4478
+ children: t("detail.exec.pruned", { n: task.executionsPruned })
3245
4479
  })
3246
4480
  ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3247
4481
  className: "dsh-atb-execlist",
@@ -3254,12 +4488,12 @@ window.__ModuleLoader__.load({
3254
4488
  }),
3255
4489
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3256
4490
  className: "dsh-atb-exec-trigger",
3257
- children: e.trigger === "manual" ? "手动" : "定时"
4491
+ children: e.trigger === "manual" ? t("detail.exec.trigger.manual") : t("detail.exec.trigger.scheduled")
3258
4492
  }),
3259
4493
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3260
4494
  className: "dsh-atb-exec-outcome",
3261
4495
  "data-outcome": e.outcome,
3262
- children: OUTCOME_LABEL[e.outcome] ?? e.outcome
4496
+ children: t(OUTCOME_KEYS[e.outcome] ?? e.outcome)
3263
4497
  }),
3264
4498
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3265
4499
  className: "dsh-atb-exec-time",
@@ -3268,7 +4502,7 @@ window.__ModuleLoader__.load({
3268
4502
  e.sessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3269
4503
  type: "button",
3270
4504
  className: "dsh-atb-exec-session",
3271
- title: `点击打开该执行会话:${e.sessionId}`,
4505
+ title: t("detail.exec.openTitle", { id: e.sessionId }),
3272
4506
  onClick: () => jumpToSession(e.sessionId),
3273
4507
  children: [
3274
4508
  "🤖 ",
@@ -3292,13 +4526,13 @@ window.__ModuleLoader__.load({
3292
4526
  className: "dsh-atb-btn",
3293
4527
  "data-danger": "true",
3294
4528
  onClick: () => void controller.remove(task.id, task.version, false),
3295
- children: "🗑 删除(标记待清除)"
4529
+ children: t("detail.danger.delete")
3296
4530
  }) : confirmPurge ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3297
4531
  className: "dsh-atb-confirm",
3298
4532
  children: [
3299
4533
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3300
4534
  className: "dsh-atb-confirm-label",
3301
- children: "物理清除不可恢复"
4535
+ children: t("detail.danger.purgeConfirm")
3302
4536
  }),
3303
4537
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3304
4538
  type: "button",
@@ -3308,13 +4542,13 @@ window.__ModuleLoader__.load({
3308
4542
  controller.remove(task.id, task.version, true);
3309
4543
  setConfirmPurge(false);
3310
4544
  },
3311
- children: "确认清除"
4545
+ children: t("detail.danger.purgeGo")
3312
4546
  }),
3313
4547
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3314
4548
  type: "button",
3315
4549
  className: "dsh-atb-btn",
3316
4550
  onClick: () => setConfirmPurge(false),
3317
- children: "取消"
4551
+ children: t("shared.cancel")
3318
4552
  })
3319
4553
  ]
3320
4554
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -3322,7 +4556,7 @@ window.__ModuleLoader__.load({
3322
4556
  className: "dsh-atb-btn",
3323
4557
  "data-danger": "true",
3324
4558
  onClick: () => setConfirmPurge(true),
3325
- children: "🔥 物理清除(需确认)"
4559
+ children: t("detail.danger.purge")
3326
4560
  })
3327
4561
  }),
3328
4562
  alertEl
@@ -3330,6 +4564,384 @@ window.__ModuleLoader__.load({
3330
4564
  });
3331
4565
  }
3332
4566
 
4567
+ //#endregion
4568
+ //#region src/client/board/SlashPromptInput.tsx
4569
+ /**
4570
+ * SlashPromptInput: Rich text input component for task description & execution prompt.
4571
+ * Features:
4572
+ * - Slash autocomplete popup for commands and skills with keyboard navigation.
4573
+ * - Clean text editing without image base64 pollution.
4574
+ *
4575
+ * @module dsh-taskboard/client/board/SlashPromptInput
4576
+ */
4577
+ /** Default built-in slash commands (descriptions resolve through t at render,
4578
+ * so they follow the GUI language live; host-provided items override by name). */
4579
+ const defaultCommands = (t) => [
4580
+ {
4581
+ name: "goal",
4582
+ kind: "command",
4583
+ description: t("slash.cmd.goal.desc"),
4584
+ hint: t("slash.cmd.goal.hint")
4585
+ },
4586
+ {
4587
+ name: "schedule",
4588
+ kind: "command",
4589
+ description: t("slash.cmd.schedule.desc"),
4590
+ hint: t("slash.cmd.schedule.hint")
4591
+ },
4592
+ {
4593
+ name: "plan",
4594
+ kind: "command",
4595
+ description: t("slash.cmd.plan.desc")
4596
+ },
4597
+ {
4598
+ name: "browser",
4599
+ kind: "command",
4600
+ description: t("slash.cmd.browser.desc")
4601
+ },
4602
+ {
4603
+ name: "grill-me",
4604
+ kind: "command",
4605
+ description: t("slash.cmd.grill-me.desc")
4606
+ },
4607
+ {
4608
+ name: "teamwork-preview",
4609
+ kind: "command",
4610
+ description: t("slash.cmd.teamwork-preview.desc")
4611
+ },
4612
+ {
4613
+ name: "learn",
4614
+ kind: "command",
4615
+ description: t("slash.cmd.learn.desc")
4616
+ },
4617
+ {
4618
+ name: "review",
4619
+ kind: "command",
4620
+ description: t("slash.cmd.review.desc")
4621
+ },
4622
+ {
4623
+ name: "security",
4624
+ kind: "command",
4625
+ description: t("slash.cmd.security.desc")
4626
+ },
4627
+ {
4628
+ name: "permission",
4629
+ kind: "command",
4630
+ description: t("slash.cmd.permission.desc"),
4631
+ hint: t("slash.cmd.permission.hint")
4632
+ }
4633
+ ];
4634
+ /** Default built-in skills (descriptions resolve through t at render). */
4635
+ const defaultSkills = (t) => [
4636
+ {
4637
+ name: "frontend-ui-engineering",
4638
+ kind: "skill",
4639
+ description: t("slash.skill.frontend-ui-engineering")
4640
+ },
4641
+ {
4642
+ name: "api-and-interface-design",
4643
+ kind: "skill",
4644
+ description: t("slash.skill.api-and-interface-design")
4645
+ },
4646
+ {
4647
+ name: "test-driven-development",
4648
+ kind: "skill",
4649
+ description: t("slash.skill.test-driven-development")
4650
+ },
4651
+ {
4652
+ name: "debugging-and-error-recovery",
4653
+ kind: "skill",
4654
+ description: t("slash.skill.debugging-and-error-recovery")
4655
+ },
4656
+ {
4657
+ name: "performance-optimization",
4658
+ kind: "skill",
4659
+ description: t("slash.skill.performance-optimization")
4660
+ },
4661
+ {
4662
+ name: "ci-cd-and-automation",
4663
+ kind: "skill",
4664
+ description: t("slash.skill.ci-cd-and-automation")
4665
+ },
4666
+ {
4667
+ name: "code-review-and-quality",
4668
+ kind: "skill",
4669
+ description: t("slash.skill.code-review-and-quality")
4670
+ },
4671
+ {
4672
+ name: "code-simplification",
4673
+ kind: "skill",
4674
+ description: t("slash.skill.code-simplification")
4675
+ },
4676
+ {
4677
+ name: "context-engineering",
4678
+ kind: "skill",
4679
+ description: t("slash.skill.context-engineering")
4680
+ },
4681
+ {
4682
+ name: "doubt-driven-development",
4683
+ kind: "skill",
4684
+ description: t("slash.skill.doubt-driven-development")
4685
+ },
4686
+ {
4687
+ name: "git-workflow-and-versioning",
4688
+ kind: "skill",
4689
+ description: t("slash.skill.git-workflow-and-versioning")
4690
+ },
4691
+ {
4692
+ name: "idea-refine",
4693
+ kind: "skill",
4694
+ description: t("slash.skill.idea-refine")
4695
+ },
4696
+ {
4697
+ name: "incremental-implementation",
4698
+ kind: "skill",
4699
+ description: t("slash.skill.incremental-implementation")
4700
+ },
4701
+ {
4702
+ name: "interview-me",
4703
+ kind: "skill",
4704
+ description: t("slash.skill.interview-me")
4705
+ },
4706
+ {
4707
+ name: "memory-leak-debugging",
4708
+ kind: "skill",
4709
+ description: t("slash.skill.memory-leak-debugging")
4710
+ },
4711
+ {
4712
+ name: "observability-and-instrumentation",
4713
+ kind: "skill",
4714
+ description: t("slash.skill.observability-and-instrumentation")
4715
+ },
4716
+ {
4717
+ name: "planning-and-task-breakdown",
4718
+ kind: "skill",
4719
+ description: t("slash.skill.planning-and-task-breakdown")
4720
+ },
4721
+ {
4722
+ name: "security-and-hardening",
4723
+ kind: "skill",
4724
+ description: t("slash.skill.security-and-hardening")
4725
+ },
4726
+ {
4727
+ name: "shipping-and-launch",
4728
+ kind: "skill",
4729
+ description: t("slash.skill.shipping-and-launch")
4730
+ },
4731
+ {
4732
+ name: "source-driven-development",
4733
+ kind: "skill",
4734
+ description: t("slash.skill.source-driven-development")
4735
+ },
4736
+ {
4737
+ name: "spec-driven-development",
4738
+ kind: "skill",
4739
+ description: t("slash.skill.spec-driven-development")
4740
+ },
4741
+ {
4742
+ name: "using-agent-skills",
4743
+ kind: "skill",
4744
+ description: t("slash.skill.using-agent-skills")
4745
+ }
4746
+ ];
4747
+ /**
4748
+ * Rich prompt textarea with / autocomplete for slash commands & skills.
4749
+ */
4750
+ function SlashPromptInput({ value, onChange, controller, placeholder, rows = 4, maxLength = 8e3, disabled = false, autoFocus = false, className, ariaLabel }) {
4751
+ const t = useT();
4752
+ const textareaRef = (0, react.useRef)(null);
4753
+ const [hostCompletions, setHostCompletions] = (0, react.useState)(void 0);
4754
+ const completions = (0, react.useMemo)(() => {
4755
+ const merge = (defaults, host) => {
4756
+ const map = /* @__PURE__ */ new Map();
4757
+ for (const d of defaults) map.set(d.name, d);
4758
+ for (const h of host ?? []) map.set(h.name, h);
4759
+ return Array.from(map.values());
4760
+ };
4761
+ return {
4762
+ commands: merge(defaultCommands(t), hostCompletions?.commands),
4763
+ skills: merge(defaultSkills(t), hostCompletions?.skills)
4764
+ };
4765
+ }, [t, hostCompletions]);
4766
+ const [popupOpen, setPopupOpen] = (0, react.useState)(false);
4767
+ const [slashQuery, setSlashQuery] = (0, react.useState)("");
4768
+ const [slashStart, setSlashStart] = (0, react.useState)(-1);
4769
+ const [selectedIndex, setSelectedIndex] = (0, react.useState)(0);
4770
+ (0, react.useEffect)(() => {
4771
+ if (controller === void 0) return;
4772
+ let alive = true;
4773
+ controller.fetchPromptCompletions().then((res) => {
4774
+ if (!alive || res === void 0) return;
4775
+ setHostCompletions({
4776
+ commands: res.commands.map((c) => ({
4777
+ ...c,
4778
+ kind: "command"
4779
+ })),
4780
+ skills: res.skills.map((s) => ({
4781
+ ...s,
4782
+ kind: "skill"
4783
+ }))
4784
+ });
4785
+ });
4786
+ return () => {
4787
+ alive = false;
4788
+ };
4789
+ }, [controller]);
4790
+ const filteredItems = (0, react.useMemo)(() => {
4791
+ const q = slashQuery.toLowerCase().trim();
4792
+ const all = [...completions.commands, ...completions.skills];
4793
+ if (q.length === 0) return all;
4794
+ return all.filter((item) => item.name.toLowerCase().includes(q) || item.description !== void 0 && item.description.toLowerCase().includes(q));
4795
+ }, [completions, slashQuery]);
4796
+ (0, react.useEffect)(() => {
4797
+ if (selectedIndex >= filteredItems.length) setSelectedIndex(Math.max(0, filteredItems.length - 1));
4798
+ }, [filteredItems.length, selectedIndex]);
4799
+ const checkSlashTrigger = () => {
4800
+ const el = textareaRef.current;
4801
+ if (el === null) return;
4802
+ const pos = el.selectionStart;
4803
+ const currentText = el.value.slice(0, pos);
4804
+ const lastSlash = currentText.lastIndexOf("/");
4805
+ if (lastSlash >= 0) {
4806
+ const charBefore = lastSlash > 0 ? currentText[lastSlash - 1] ?? "\n" : "\n";
4807
+ const isWordStart = /\s/.test(charBefore) || lastSlash === 0;
4808
+ const queryPart = currentText.slice(lastSlash + 1);
4809
+ const noWhitespaceInQuery = !/\s/.test(queryPart);
4810
+ if (isWordStart && noWhitespaceInQuery) {
4811
+ setSlashStart(lastSlash);
4812
+ setSlashQuery(queryPart);
4813
+ setPopupOpen(true);
4814
+ return;
4815
+ }
4816
+ }
4817
+ setPopupOpen(false);
4818
+ };
4819
+ const applyCompletion = (item) => {
4820
+ const el = textareaRef.current;
4821
+ if (el === null || slashStart < 0) return;
4822
+ const pos = el.selectionStart;
4823
+ const before = value.slice(0, slashStart);
4824
+ const after = value.slice(pos);
4825
+ const inserted = `/${item.name} `;
4826
+ onChange(before + inserted + after);
4827
+ setPopupOpen(false);
4828
+ setTimeout(() => {
4829
+ if (textareaRef.current !== null) {
4830
+ const nextPos = slashStart + inserted.length;
4831
+ textareaRef.current.focus();
4832
+ textareaRef.current.setSelectionRange(nextPos, nextPos);
4833
+ }
4834
+ }, 0);
4835
+ };
4836
+ const handleKeyDown = (e) => {
4837
+ if (popupOpen && filteredItems.length > 0) {
4838
+ if (e.key === "ArrowDown") {
4839
+ e.preventDefault();
4840
+ setSelectedIndex((prev) => (prev + 1) % filteredItems.length);
4841
+ return;
4842
+ }
4843
+ if (e.key === "ArrowUp") {
4844
+ e.preventDefault();
4845
+ setSelectedIndex((prev) => (prev - 1 + filteredItems.length) % filteredItems.length);
4846
+ return;
4847
+ }
4848
+ if (e.key === "Enter" || e.key === "Tab") {
4849
+ const picked = filteredItems[selectedIndex];
4850
+ if (picked !== void 0) {
4851
+ e.preventDefault();
4852
+ applyCompletion(picked);
4853
+ return;
4854
+ }
4855
+ }
4856
+ if (e.key === "Escape") {
4857
+ e.preventDefault();
4858
+ setPopupOpen(false);
4859
+ return;
4860
+ }
4861
+ }
4862
+ };
4863
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4864
+ className: `dsh-atb-prompt-wrap ${className ?? ""}`,
4865
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4866
+ className: "dsh-atb-prompt-inner",
4867
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
4868
+ ref: textareaRef,
4869
+ className: "dsh-atb-prompt-input",
4870
+ value,
4871
+ rows,
4872
+ maxLength,
4873
+ disabled,
4874
+ autoFocus,
4875
+ placeholder,
4876
+ "aria-label": ariaLabel,
4877
+ onChange: (e) => {
4878
+ onChange(e.target.value);
4879
+ checkSlashTrigger();
4880
+ },
4881
+ onKeyUp: checkSlashTrigger,
4882
+ onClick: checkSlashTrigger,
4883
+ onKeyDown: handleKeyDown
4884
+ }), popupOpen && filteredItems.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4885
+ className: "dsh-atb-slash-popup",
4886
+ role: "listbox",
4887
+ "aria-label": t("slash.aria"),
4888
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4889
+ className: "dsh-atb-slash-head",
4890
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4891
+ className: "dsh-atb-slash-title",
4892
+ children: t("slash.title")
4893
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4894
+ className: "dsh-atb-slash-hint",
4895
+ children: t("slash.hint")
4896
+ })]
4897
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4898
+ className: "dsh-atb-slash-list",
4899
+ children: filteredItems.map((item, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4900
+ role: "option",
4901
+ "aria-selected": idx === selectedIndex,
4902
+ className: "dsh-atb-slash-item",
4903
+ "data-active": idx === selectedIndex ? "true" : void 0,
4904
+ "data-kind": item.kind,
4905
+ onClick: () => applyCompletion(item),
4906
+ onMouseEnter: () => setSelectedIndex(idx),
4907
+ children: [
4908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4909
+ className: "dsh-atb-slash-badge",
4910
+ "data-kind": item.kind,
4911
+ children: item.kind === "command" ? t("slash.badge.command") : t("slash.badge.skill")
4912
+ }),
4913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4914
+ className: "dsh-atb-slash-name",
4915
+ children: ["/", item.name]
4916
+ }),
4917
+ item.hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4918
+ className: "dsh-atb-slash-param",
4919
+ children: item.hint
4920
+ }),
4921
+ item.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4922
+ className: "dsh-atb-slash-desc",
4923
+ children: item.description
4924
+ })
4925
+ ]
4926
+ }, `${item.kind}-${item.name}`))
4927
+ })]
4928
+ })]
4929
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4930
+ className: "dsh-atb-prompt-foot",
4931
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4932
+ className: "dsh-atb-prompt-tip",
4933
+ children: [
4934
+ t("slash.tipA"),
4935
+ " ",
4936
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: "/" }),
4937
+ " ",
4938
+ t("slash.tipB")
4939
+ ]
4940
+ })
4941
+ })]
4942
+ });
4943
+ }
4944
+
3333
4945
  //#endregion
3334
4946
  //#region src/client/board/TaskFormModal.tsx
3335
4947
  /**
@@ -3370,43 +4982,64 @@ window.__ModuleLoader__.load({
3370
4982
  else localStorage.setItem(LAST_MODEL_KEY, JSON.stringify(model));
3371
4983
  } catch {}
3372
4984
  }
3373
- /** Urgency segmented options with a one-line hint each. */
3374
- const URGENCY_OPTIONS = [
4985
+ /** Urgency segmented options with a one-line hint each (translated per render). */
4986
+ const urgencyOptions = (t) => [
3375
4987
  {
3376
4988
  value: "urgent",
3377
- label: "紧急",
3378
- hint: "优先处理"
4989
+ label: t("form.urgency.urgent"),
4990
+ hint: t("form.urgency.urgentHint")
3379
4991
  },
3380
4992
  {
3381
4993
  value: "normal",
3382
- label: "一般",
3383
- hint: "正常排期"
4994
+ label: t("form.urgency.normal"),
4995
+ hint: t("form.urgency.normalHint")
3384
4996
  },
3385
4997
  {
3386
4998
  value: "relaxed",
3387
- label: "不急",
3388
- hint: "有空再做"
4999
+ label: t("form.urgency.relaxed"),
5000
+ hint: t("form.urgency.relaxedHint")
3389
5001
  }
3390
5002
  ];
3391
- /** Cron presets offered in the scheduled mode. */
3392
- const CRON_PRESETS = [
5003
+ /** Cron presets offered in the scheduled mode (translated per render). */
5004
+ const cronPresets = (t) => [
3393
5005
  {
3394
- label: "每天 09:00",
5006
+ label: t("form.cron.daily"),
3395
5007
  cron: "0 9 * * *"
3396
5008
  },
3397
5009
  {
3398
- label: "每小时",
5010
+ label: t("form.cron.hourly"),
3399
5011
  cron: "0 * * * *"
3400
5012
  },
3401
5013
  {
3402
- label: "每 10 分钟",
5014
+ label: t("form.cron.every10min"),
3403
5015
  cron: "*/10 * * * *"
3404
5016
  },
3405
5017
  {
3406
- label: "每周一 09:00",
5018
+ label: t("form.cron.weekly"),
3407
5019
  cron: "0 9 * * 1"
3408
5020
  }
3409
5021
  ];
5022
+ /** Permission presets aligned with DSH (translated per render). */
5023
+ const permissionOptions = (t) => [
5024
+ {
5025
+ value: "workspace-write",
5026
+ label: t("form.perm.write"),
5027
+ hint: t("form.perm.writeHint"),
5028
+ icon: "📁"
5029
+ },
5030
+ {
5031
+ value: "read-only",
5032
+ label: t("form.perm.readOnly"),
5033
+ hint: t("form.perm.readOnlyHint"),
5034
+ icon: "🔒"
5035
+ },
5036
+ {
5037
+ value: "danger-full-access",
5038
+ label: t("form.perm.fullAccess"),
5039
+ hint: t("form.perm.fullAccessHint"),
5040
+ icon: "⚡"
5041
+ }
5042
+ ];
3410
5043
  /** Field shell: label + control, optionally spanning the full grid row. */
3411
5044
  function Field({ label, required = false, full = false, children }) {
3412
5045
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
@@ -3427,6 +5060,7 @@ window.__ModuleLoader__.load({
3427
5060
  * replaces the whole list on save).
3428
5061
  */
3429
5062
  function ChecklistEditor({ rows, onChange, editing }) {
5063
+ const t = useT();
3430
5064
  const setRow = (index, patch) => {
3431
5065
  onChange(rows.map((row, i) => i === index ? {
3432
5066
  ...row,
@@ -3444,21 +5078,21 @@ window.__ModuleLoader__.load({
3444
5078
  type: "checkbox",
3445
5079
  className: "dsh-atb-cke-box",
3446
5080
  checked: row.checked,
3447
- title: `勾选状态随保存保留(当前勾选人:${row.checkedBy ?? "未勾选"})`,
5081
+ title: t("form.check.checkedTitle", { who: row.checkedBy ?? t("form.check.notCheckedYet") }),
3448
5082
  onChange: (e) => setRow(index, { checked: e.target.checked })
3449
5083
  }),
3450
5084
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3451
5085
  className: "dsh-atb-cke-text",
3452
5086
  value: row.text,
3453
5087
  maxLength: 200,
3454
- placeholder: `验收项 ${index + 1}(完成标准)`,
5088
+ placeholder: t("form.check.itemPlaceholder", { n: index + 1 }),
3455
5089
  spellCheck: false,
3456
5090
  onChange: (e) => setRow(index, { text: e.target.value })
3457
5091
  }),
3458
5092
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3459
5093
  type: "button",
3460
5094
  className: "dsh-atb-cke-del",
3461
- title: "删除该验收项",
5095
+ title: t("form.check.removeTitle"),
3462
5096
  onClick: () => onChange(rows.filter((_, i) => i !== index)),
3463
5097
  children: "✕"
3464
5098
  })
@@ -3471,11 +5105,14 @@ window.__ModuleLoader__.load({
3471
5105
  text: "",
3472
5106
  checked: false
3473
5107
  }]),
3474
- children: "+ 添加验收项"
5108
+ children: t("form.check.add")
3475
5109
  }),
3476
5110
  rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3477
5111
  className: "dsh-atb-cke-hint",
3478
- children: editing ? `已勾选 ${checked}/${rows.length}(保存将整体覆盖清单,勾选状态保留)` : `共 ${rows.length} 项,执行会话按清单干活并逐项勾选,未完成项验收时高亮`
5112
+ children: editing ? t("form.check.hintEdit", {
5113
+ checked,
5114
+ total: rows.length
5115
+ }) : t("form.check.hintCreate", { n: rows.length })
3479
5116
  })
3480
5117
  ]
3481
5118
  });
@@ -3489,6 +5126,7 @@ window.__ModuleLoader__.load({
3489
5126
  * @param task - the task being edited (create mode when absent).
3490
5127
  */
3491
5128
  function TaskFormModal({ controller, task }) {
5129
+ const t = useT();
3492
5130
  const state = controller.getSnapshot();
3493
5131
  const prefill = state.templatePrefill;
3494
5132
  const editing = task !== void 0;
@@ -3510,6 +5148,7 @@ window.__ModuleLoader__.load({
3510
5148
  const [presetId, setPresetId] = (0, react.useState)(initialPreset);
3511
5149
  const [presets, setPresets] = (0, react.useState)([]);
3512
5150
  const [presetDefault, setPresetDefault] = (0, react.useState)(void 0);
5151
+ const [permission, setPermission] = (0, react.useState)(task?.permission ?? (prefill?.permission ? asPermission(prefill.permission) : defaultPermissionOf(state.ledger.settings)));
3513
5152
  const [isolation, setIsolation] = (0, react.useState)(task?.isolation ?? (prefill?.isolation === "none" ? "none" : prefill?.isolation === "worktree" ? "worktree" : defaultIsolationOf(state.ledger.settings)));
3514
5153
  const [checkRows, setCheckRows] = (0, react.useState)(task?.checklist !== void 0 && task.checklist.length > 0 ? task.checklist.map((i) => ({ ...i })) : (prefill?.checklist ?? []).map((text) => ({
3515
5154
  text,
@@ -3526,14 +5165,10 @@ window.__ModuleLoader__.load({
3526
5165
  return () => document.removeEventListener("keydown", onKey);
3527
5166
  }, [controller]);
3528
5167
  (0, react.useEffect)(() => {
3529
- const face = controller.modelCatalog;
3530
- if (face === void 0) return;
3531
- face().then(setCatalog).catch(() => setCatalog([]));
5168
+ controller.fetchModelCatalog().then(setCatalog).catch(() => setCatalog([]));
3532
5169
  }, [controller]);
3533
5170
  (0, react.useEffect)(() => {
3534
- const face = controller.presetCatalog;
3535
- if (face === void 0) return;
3536
- face().then((roster) => {
5171
+ controller.fetchPresetCatalog().then((roster) => {
3537
5172
  setPresets(roster.presets);
3538
5173
  setPresetDefault(roster.defaultId);
3539
5174
  if (!editing && initialPreset === "" && roster.defaultId !== void 0) setPresetId(roster.defaultId);
@@ -3599,6 +5234,7 @@ window.__ModuleLoader__.load({
3599
5234
  model: picked ?? null,
3600
5235
  ...isolationOut !== void 0 && !isolationLocked ? { isolation: isolationOut } : {},
3601
5236
  presetId: presetOut ?? null,
5237
+ permission,
3602
5238
  checklist: rows.length > 0 ? rows : null
3603
5239
  }) : controller.create({
3604
5240
  title,
@@ -3613,6 +5249,7 @@ window.__ModuleLoader__.load({
3613
5249
  model: picked,
3614
5250
  ...isolationOut !== void 0 ? { isolation: isolationOut } : {},
3615
5251
  ...presetOut !== void 0 ? { presetId: presetOut } : {},
5252
+ permission,
3616
5253
  ...rows.length > 0 ? { checklist: rows.map((r) => r.text) } : {}
3617
5254
  })).catch(() => void 0).finally(() => setBusy(false));
3618
5255
  };
@@ -3640,6 +5277,7 @@ window.__ModuleLoader__.load({
3640
5277
  model: picked ?? null,
3641
5278
  ...isolationOut !== void 0 && !isolationLocked ? { isolation: isolationOut } : {},
3642
5279
  presetId: presetOut ?? null,
5280
+ permission,
3643
5281
  checklist: rows.length > 0 ? rows : null
3644
5282
  })) await controller.run(task.id);
3645
5283
  } else {
@@ -3656,24 +5294,28 @@ window.__ModuleLoader__.load({
3656
5294
  model: picked,
3657
5295
  ...isolationOut !== void 0 ? { isolation: isolationOut } : {},
3658
5296
  ...presetOut !== void 0 ? { presetId: presetOut } : {},
5297
+ permission,
3659
5298
  ...rows.length > 0 ? { checklist: rows.map((r) => r.text) } : {}
3660
5299
  });
3661
5300
  if (id !== void 0) await controller.run(id);
3662
5301
  }
3663
5302
  })().catch(() => void 0).finally(() => setBusy(false));
3664
5303
  };
3665
- const hint = !valid ? title.trim().length === 0 ? "请填写标题" : workspaceId === "" ? "请选择项目" : "Cron 表达式无效(分 时 日 月 周)" : mode === "scheduled" && nextRun !== null ? `下次运行 ${fmtTime(nextRun)}` : editing ? `保存后版本 v${task.version} → v${task.version + 1}` : "创建后项目内会话可认领执行";
5304
+ const hint = !valid ? title.trim().length === 0 ? t("form.hint.needTitle") : workspaceId === "" ? t("form.hint.needProject") : t("form.hint.cronBad") : mode === "scheduled" && nextRun !== null ? t("form.hint.nextRun", { time: fmtTime(nextRun) }) : editing ? t("form.hint.saveVersion", {
5305
+ v: task.version,
5306
+ next: task.version + 1
5307
+ }) : t("form.hint.createClaim");
3666
5308
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3667
5309
  className: "dsh-atb-modal-backdrop",
3668
5310
  onClick: (e) => {
3669
5311
  if (e.target === e.currentTarget) controller.closeForm();
3670
5312
  },
3671
5313
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3672
- className: "dsh-atb-modal",
5314
+ className: "dsh-atb-modal dsh-atb-taskform-modal",
3673
5315
  "data-mode": editing ? "edit" : "create",
3674
5316
  role: "dialog",
3675
5317
  "aria-modal": "true",
3676
- "aria-label": editing ? "编辑任务" : "新建任务",
5318
+ "aria-label": editing ? t("form.title.edit") : t("form.title.create"),
3677
5319
  children: [
3678
5320
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3679
5321
  className: "dsh-atb-modal-head",
@@ -3684,271 +5326,307 @@ window.__ModuleLoader__.load({
3684
5326
  }),
3685
5327
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3686
5328
  className: "dsh-atb-modal-headtext",
3687
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: editing ? "编辑任务" : "新建任务" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: editing ? "调整任务内容与执行配置" : "推入看板,项目内会话可认领执行" })]
5329
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: editing ? t("form.title.edit") : t("form.title.create") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: editing ? t("form.subtitle.edit") : t("form.subtitle.create") })]
3688
5330
  }),
3689
5331
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3690
5332
  type: "button",
3691
5333
  className: "dsh-atb-modal-close",
3692
- "aria-label": "关闭",
5334
+ "aria-label": t("shared.close"),
3693
5335
  onClick: () => controller.closeForm(),
3694
5336
  children: "✕"
3695
5337
  })
3696
5338
  ]
3697
5339
  }),
3698
5340
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3699
- className: "dsh-atb-modal-body",
3700
- children: [
3701
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3702
- label: "标题",
3703
- required: true,
3704
- full: true,
3705
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3706
- ref: titleRef,
3707
- value: title,
3708
- onChange: (e) => setTitle(e.target.value),
3709
- placeholder: "一句话说清要做什么",
3710
- maxLength: 200
3711
- })
3712
- }),
3713
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3714
- label: "项目",
3715
- required: true,
3716
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
3717
- value: workspaceId,
3718
- onChange: (e) => setWorkspaceId(e.target.value),
3719
- children: state.workspaces.map((ws) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3720
- value: ws.id,
3721
- children: ws.title || ws.path
3722
- }, ws.id))
3723
- })
3724
- }),
3725
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3726
- label: "模型(默认 = 会话默认模型)",
3727
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3728
- value: model,
3729
- onChange: (e) => {
3730
- const val = e.target.value;
3731
- setModel(val);
3732
- if (val === "") setReasoningEffort("");
3733
- else {
3734
- const pm = JSON.parse(val);
3735
- const cm = catalog.find((m) => m.provider === pm.provider && m.model === pm.model);
3736
- if (cm?.reasoning?.defaultEffort !== void 0) setReasoningEffort(cm.reasoning.defaultEffort);
3737
- else setReasoningEffort("");
3738
- }
3739
- },
3740
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3741
- value: "",
3742
- children: "默认模型"
3743
- }), catalog.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3744
- value: JSON.stringify({
3745
- provider: m.provider,
3746
- model: m.model
3747
- }),
3748
- children: [
3749
- m.name ?? m.model,
3750
- "(",
3751
- m.provider,
3752
- ")"
3753
- ]
3754
- }, `${m.provider}/${m.model}`))]
3755
- })
3756
- }),
3757
- parsedModel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3758
- label: "思考强度(Reasoning Effort)",
3759
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3760
- value: reasoningEffort,
3761
- onChange: (e) => setReasoningEffort(e.target.value),
3762
- title: "设置模型的思考强度(如 low/medium/high);默认 = 跟随模型/提供商默认",
3763
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3764
- value: "",
3765
- children: ["跟随模型默认", modelReasoning?.defaultEffort !== void 0 ? `(当前:${modelReasoning.efforts.find((ef) => ef.id === modelReasoning.defaultEffort)?.name ?? modelReasoning.defaultEffort})` : ""]
3766
- }), modelReasoning !== void 0 && modelReasoning.efforts.length > 0 ? modelReasoning.efforts.map((eff) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3767
- value: eff.id,
3768
- children: [eff.name, eff.description ? ` (${eff.description})` : ""]
3769
- }, eff.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3770
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3771
- value: "low",
3772
- children: "低 (low)"
5341
+ className: "dsh-atb-modal-body dsh-atb-taskform-body",
5342
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5343
+ className: "dsh-atb-form-col dsh-atb-form-left",
5344
+ children: [
5345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5346
+ label: t("form.field.title"),
5347
+ required: true,
5348
+ full: true,
5349
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5350
+ ref: titleRef,
5351
+ value: title,
5352
+ onChange: (e) => setTitle(e.target.value),
5353
+ placeholder: t("form.field.titlePlaceholder"),
5354
+ maxLength: 200
5355
+ })
5356
+ }),
5357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5358
+ className: "dsh-atb-form-subgrid",
5359
+ children: [
5360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5361
+ label: t("form.field.project"),
5362
+ required: true,
5363
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
5364
+ value: workspaceId,
5365
+ onChange: (e) => setWorkspaceId(e.target.value),
5366
+ children: state.workspaces.map((ws) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5367
+ value: ws.id,
5368
+ children: ws.title || ws.path
5369
+ }, ws.id))
5370
+ })
3773
5371
  }),
3774
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3775
- value: "medium",
3776
- children: "中 (medium)"
5372
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5373
+ label: t("form.field.model"),
5374
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5375
+ value: model,
5376
+ onChange: (e) => {
5377
+ const val = e.target.value;
5378
+ setModel(val);
5379
+ if (val === "") setReasoningEffort("");
5380
+ else {
5381
+ const pm = JSON.parse(val);
5382
+ const cm = catalog.find((m) => m.provider === pm.provider && m.model === pm.model);
5383
+ if (cm?.reasoning?.defaultEffort !== void 0) setReasoningEffort(cm.reasoning.defaultEffort);
5384
+ else setReasoningEffort("");
5385
+ }
5386
+ },
5387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5388
+ value: "",
5389
+ children: t("form.field.modelDefault")
5390
+ }), catalog.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5391
+ value: JSON.stringify({
5392
+ provider: m.provider,
5393
+ model: m.model
5394
+ }),
5395
+ children: t("form.model.option", {
5396
+ name: m.name ?? m.model,
5397
+ provider: m.provider
5398
+ })
5399
+ }, `${m.provider}/${m.model}`))]
5400
+ })
3777
5401
  }),
3778
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3779
- value: "high",
3780
- children: "高 (high)"
5402
+ parsedModel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5403
+ label: t("form.field.effort"),
5404
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5405
+ value: reasoningEffort,
5406
+ onChange: (e) => setReasoningEffort(e.target.value),
5407
+ title: t("form.field.effortTitle"),
5408
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
5409
+ value: "",
5410
+ children: [t("form.effort.follow"), modelReasoning?.defaultEffort !== void 0 ? t("shared.current", { name: modelReasoning.efforts.find((ef) => ef.id === modelReasoning.defaultEffort)?.name ?? modelReasoning.defaultEffort }) : ""]
5411
+ }), modelReasoning !== void 0 && modelReasoning.efforts.length > 0 ? modelReasoning.efforts.map((eff) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
5412
+ value: eff.id,
5413
+ children: [eff.name, eff.description ? ` (${eff.description})` : ""]
5414
+ }, eff.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5415
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5416
+ value: "low",
5417
+ children: t("form.effort.low")
5418
+ }),
5419
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5420
+ value: "medium",
5421
+ children: t("form.effort.medium")
5422
+ }),
5423
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5424
+ value: "high",
5425
+ children: t("form.effort.high")
5426
+ }),
5427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5428
+ value: "none",
5429
+ children: t("form.effort.none")
5430
+ })
5431
+ ] })]
5432
+ })
3781
5433
  }),
3782
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3783
- value: "none",
3784
- children: "关闭思考 (none)"
5434
+ presets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5435
+ label: t("form.field.preset"),
5436
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5437
+ value: presetId,
5438
+ onChange: (e) => setPresetId(e.target.value),
5439
+ title: t("form.field.presetTitle"),
5440
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
5441
+ value: "",
5442
+ children: [t("form.preset.follow"), presetDefault !== void 0 ? t("shared.current", { name: presets.find((p) => p.id === presetDefault)?.name ?? presetDefault }) : ""]
5443
+ }), presets.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
5444
+ value: p.id,
5445
+ children: [p.name ?? p.id, p.id === presetDefault ? t("form.preset.defaultTag") : ""]
5446
+ }, p.id))]
5447
+ })
3785
5448
  })
3786
- ] })]
3787
- })
3788
- }),
3789
- presets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3790
- label: "执行模式(preset)",
3791
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3792
- value: presetId,
3793
- onChange: (e) => setPresetId(e.target.value),
3794
- title: "执行会话按该 preset 组合(决定工具集与人设);默认 = 部署默认 preset",
3795
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3796
- value: "",
3797
- children: ["跟随部署默认", presetDefault !== void 0 ? `(当前:${presets.find((p) => p.id === presetDefault)?.name ?? presetDefault})` : ""]
3798
- }), presets.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
3799
- value: p.id,
3800
- children: [p.name ?? p.id, p.id === presetDefault ? "(部署默认)" : ""]
3801
- }, p.id))]
3802
- })
3803
- }),
3804
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3805
- label: "紧急度",
3806
- full: true,
3807
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3808
- className: "dsh-atb-urgency-picker",
3809
- children: URGENCY_OPTIONS.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3810
- type: "button",
3811
- className: "dsh-atb-urgency-opt",
3812
- "data-urgency": o.value,
3813
- "data-on": urgency === o.value,
3814
- onClick: () => setUrgency(o.value),
3815
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3816
- className: "dsh-atb-urgency-name",
5449
+ ]
5450
+ }),
5451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5452
+ label: t("form.field.urgency"),
5453
+ full: true,
5454
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5455
+ className: "dsh-atb-urgency-picker",
5456
+ children: urgencyOptions(t).map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5457
+ type: "button",
5458
+ className: "dsh-atb-urgency-opt",
5459
+ "data-urgency": o.value,
5460
+ "data-on": urgency === o.value,
5461
+ onClick: () => setUrgency(o.value),
5462
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5463
+ className: "dsh-atb-urgency-name",
5464
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5465
+ className: "dsh-atb-dot",
5466
+ "data-urgency": o.value
5467
+ }), o.label]
5468
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5469
+ className: "dsh-atb-urgency-hint",
5470
+ children: o.hint
5471
+ })]
5472
+ }, o.value))
5473
+ })
5474
+ }),
5475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5476
+ label: t("form.field.permission"),
5477
+ full: true,
5478
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5479
+ className: "dsh-atb-perm-picker",
5480
+ children: permissionOptions(t).map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5481
+ type: "button",
5482
+ className: "dsh-atb-perm-opt",
5483
+ "data-on": permission === opt.value,
5484
+ onClick: () => setPermission(opt.value),
5485
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5486
+ className: "dsh-atb-perm-name",
5487
+ children: [
5488
+ opt.icon,
5489
+ " ",
5490
+ opt.label,
5491
+ opt.value === "workspace-write" ? t("form.perm.defaultTag") : ""
5492
+ ]
5493
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5494
+ className: "dsh-atb-perm-hint",
5495
+ children: opt.hint
5496
+ })]
5497
+ }, opt.value))
5498
+ })
5499
+ }),
5500
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5501
+ label: t("form.field.mode"),
5502
+ full: true,
5503
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5504
+ className: "dsh-atb-mode-picker",
5505
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5506
+ type: "button",
5507
+ className: "dsh-atb-mode-opt",
5508
+ "data-on": mode === "claim",
5509
+ onClick: () => setMode("claim"),
3817
5510
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3818
- className: "dsh-atb-dot",
3819
- "data-urgency": o.value
3820
- }), o.label]
3821
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3822
- className: "dsh-atb-urgency-hint",
3823
- children: o.hint
5511
+ className: "dsh-atb-mode-name",
5512
+ children: t("form.mode.claim")
5513
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5514
+ className: "dsh-atb-mode-hint",
5515
+ children: t("form.mode.claimHint")
5516
+ })]
5517
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5518
+ type: "button",
5519
+ className: "dsh-atb-mode-opt",
5520
+ "data-on": mode === "scheduled",
5521
+ onClick: () => setMode("scheduled"),
5522
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5523
+ className: "dsh-atb-mode-name",
5524
+ children: t("form.mode.scheduled")
5525
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5526
+ className: "dsh-atb-mode-hint",
5527
+ children: t("form.mode.scheduledHint")
5528
+ })]
3824
5529
  })]
3825
- }, o.value))
3826
- })
3827
- }),
3828
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3829
- label: editing ? "描述" : "描述(可选)",
3830
- full: true,
3831
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3832
- value: description,
3833
- onChange: (e) => setDescription(e.target.value),
3834
- placeholder: "需求细节、验收标准…"
3835
- })
3836
- }),
3837
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3838
- label: editing ? "执行 Prompt(实际 Prompt = 标题+任务描述+Prompt)" : "执行 Prompt(可选;实际 Prompt = 标题+任务描述+Prompt)",
3839
- full: true,
3840
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3841
- value: prompt,
3842
- onChange: (e) => setPrompt(e.target.value),
3843
- placeholder: "追加在「标题+任务描述」之后发给执行会话的补充指令。支持模板变量:{{lastExecution}}(上次执行结果)、{{lastComments}}(最近 3 条评论)"
3844
- })
3845
- }),
3846
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3847
- label: "执行方式",
3848
- full: true,
3849
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3850
- className: "dsh-atb-mode-picker",
3851
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3852
- type: "button",
3853
- className: "dsh-atb-mode-opt",
3854
- "data-on": mode === "claim",
3855
- onClick: () => setMode("claim"),
3856
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3857
- className: "dsh-atb-mode-name",
3858
- children: "🤝 认领制"
3859
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3860
- className: "dsh-atb-mode-hint",
3861
- children: "项目内会话认领"
5530
+ })
5531
+ }),
5532
+ mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
5533
+ label: t("form.field.cron"),
5534
+ required: true,
5535
+ full: true,
5536
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5537
+ className: cronBad ? "dsh-atb-input-bad" : void 0,
5538
+ value: cron,
5539
+ onChange: (e) => setCron(e.target.value),
5540
+ placeholder: t("form.cron.placeholder"),
5541
+ spellCheck: false
5542
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5543
+ className: "dsh-atb-cron-presets",
5544
+ children: [cronPresets(t).map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5545
+ type: "button",
5546
+ className: "dsh-atb-cron-preset",
5547
+ "data-on": cron.trim() === p.cron,
5548
+ onClick: () => setCron(p.cron),
5549
+ children: p.label
5550
+ }, p.cron)), !cronBad && nextRun !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5551
+ className: "dsh-atb-cron-next",
5552
+ children: t("form.cron.next", { time: fmtTime(nextRun) })
3862
5553
  })]
3863
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3864
- type: "button",
3865
- className: "dsh-atb-mode-opt",
3866
- "data-on": mode === "scheduled",
3867
- onClick: () => setMode("scheduled"),
3868
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3869
- className: "dsh-atb-mode-name",
3870
- children: " 定时执行"
3871
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3872
- className: "dsh-atb-mode-hint",
3873
- children: "到点自动开跑"
5554
+ })]
5555
+ }),
5556
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
5557
+ label: t("form.field.isolation"),
5558
+ full: true,
5559
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5560
+ className: "dsh-atb-mode-picker",
5561
+ "data-disabled": isolationDisabled ? "true" : void 0,
5562
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5563
+ type: "button",
5564
+ className: "dsh-atb-mode-opt",
5565
+ "data-on": isolation === "worktree",
5566
+ disabled: isolationDisabled,
5567
+ title: isolationLocked ? t("form.iso.locked") : !gitOk ? t("form.iso.nonGit") : t("form.iso.worktreeTitle"),
5568
+ onClick: () => setIsolation("worktree"),
5569
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5570
+ className: "dsh-atb-mode-name",
5571
+ children: t("form.iso.worktree")
5572
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5573
+ className: "dsh-atb-mode-hint",
5574
+ children: isolationLocked ? t("form.iso.lockedShort") : !gitOk ? t("form.iso.nonGit") : t("form.iso.worktreeHint")
5575
+ })]
5576
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5577
+ type: "button",
5578
+ className: "dsh-atb-mode-opt",
5579
+ "data-on": isolation === "none",
5580
+ disabled: isolationDisabled,
5581
+ title: isolationLocked ? t("form.iso.locked") : t("form.iso.noneTitle"),
5582
+ onClick: () => setIsolation("none"),
5583
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5584
+ className: "dsh-atb-mode-name",
5585
+ children: t("form.iso.none")
5586
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5587
+ className: "dsh-atb-mode-hint",
5588
+ children: isolationLocked ? t("form.iso.lockedShort") : !gitOk ? t("form.iso.noneHintNonGit") : t("form.iso.noneHint")
5589
+ })]
3874
5590
  })]
5591
+ }), !gitOk && !isolationLocked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5592
+ className: "dsh-atb-isolation-note",
5593
+ children: t("form.iso.nonGitNote")
3875
5594
  })]
5595
+ }),
5596
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5597
+ label: editing ? t("form.field.checklist") : t("form.field.checklistOptional"),
5598
+ full: true,
5599
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChecklistEditor, {
5600
+ rows: checkRows,
5601
+ onChange: setCheckRows,
5602
+ editing
5603
+ })
3876
5604
  })
3877
- }),
3878
- mode === "scheduled" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
3879
- label: "Cron 表达式",
3880
- required: true,
3881
- full: true,
3882
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3883
- className: cronBad ? "dsh-atb-input-bad" : void 0,
3884
- value: cron,
3885
- onChange: (e) => setCron(e.target.value),
3886
- placeholder: "分 时 日 月 周",
3887
- spellCheck: false
3888
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3889
- className: "dsh-atb-cron-presets",
3890
- children: [CRON_PRESETS.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3891
- type: "button",
3892
- className: "dsh-atb-cron-preset",
3893
- "data-on": cron.trim() === p.cron,
3894
- onClick: () => setCron(p.cron),
3895
- children: p.label
3896
- }, p.cron)), !cronBad && nextRun !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3897
- className: "dsh-atb-cron-next",
3898
- children: ["下次 ", fmtTime(nextRun)]
3899
- })]
3900
- })]
3901
- }),
3902
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
3903
- label: "执行隔离",
5605
+ ]
5606
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5607
+ className: "dsh-atb-form-col dsh-atb-form-right",
5608
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5609
+ label: editing ? t("form.field.description") : t("form.field.descriptionOptional"),
3904
5610
  full: true,
3905
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3906
- className: "dsh-atb-mode-picker",
3907
- "data-disabled": isolationDisabled ? "true" : void 0,
3908
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3909
- type: "button",
3910
- className: "dsh-atb-mode-opt",
3911
- "data-on": isolation === "worktree",
3912
- disabled: isolationDisabled,
3913
- title: isolationLocked ? "任务已有执行记录,隔离方式已锁定" : !gitOk ? "当前项目非 git 仓库" : "每次执行在独立 worktree 分支上进行",
3914
- onClick: () => setIsolation("worktree"),
3915
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3916
- className: "dsh-atb-mode-name",
3917
- children: "🌿 Worktree 隔离"
3918
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3919
- className: "dsh-atb-mode-hint",
3920
- children: isolationLocked ? "已锁定(执行开始后不可更改)" : !gitOk ? "当前项目非 git 仓库" : "独立分支 task/标题+ID,互不污染"
3921
- })]
3922
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3923
- type: "button",
3924
- className: "dsh-atb-mode-opt",
3925
- "data-on": isolation === "none",
3926
- disabled: isolationDisabled,
3927
- title: isolationLocked ? "任务已有执行记录,隔离方式已锁定" : "直接在项目目录执行(不使用 git)",
3928
- onClick: () => setIsolation("none"),
3929
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3930
- className: "dsh-atb-mode-name",
3931
- children: "📁 原目录执行"
3932
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3933
- className: "dsh-atb-mode-hint",
3934
- children: isolationLocked ? "已锁定(执行开始后不可更改)" : !gitOk ? "当前项目非 git 仓库,将在原目录执行" : "不使用 git,直接在项目目录工作"
3935
- })]
3936
- })]
3937
- }), !gitOk && !isolationLocked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3938
- className: "dsh-atb-isolation-note",
3939
- children: "当前项目非 git 仓库,将在原目录执行(任务仍按默认配置创建,运行时自动降级)"
3940
- })]
3941
- }),
3942
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3943
- label: editing ? "验收清单(DoD)" : "验收清单(DoD,可选)",
5611
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlashPromptInput, {
5612
+ value: description,
5613
+ onChange: setDescription,
5614
+ controller,
5615
+ rows: 7,
5616
+ placeholder: t("form.desc.placeholder")
5617
+ })
5618
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
5619
+ label: editing ? t("form.field.prompt") : t("form.field.promptOptional"),
3944
5620
  full: true,
3945
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChecklistEditor, {
3946
- rows: checkRows,
3947
- onChange: setCheckRows,
3948
- editing
5621
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlashPromptInput, {
5622
+ value: prompt,
5623
+ onChange: setPrompt,
5624
+ controller,
5625
+ rows: 7,
5626
+ placeholder: t("form.prompt.placeholder")
3949
5627
  })
3950
- })
3951
- ]
5628
+ })]
5629
+ })]
3952
5630
  }),
3953
5631
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3954
5632
  className: "dsh-atb-modal-foot",
@@ -3963,15 +5641,15 @@ window.__ModuleLoader__.load({
3963
5641
  type: "button",
3964
5642
  className: "dsh-atb-btn",
3965
5643
  onClick: () => controller.closeForm(),
3966
- children: "取消"
5644
+ children: t("shared.cancel")
3967
5645
  }),
3968
5646
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3969
5647
  type: "button",
3970
5648
  className: "dsh-atb-btn",
3971
5649
  disabled: !valid || runBlocked || busy,
3972
- title: runBlocked ? "任务正在执行中,不能重复发起" : busy ? "正在提交…" : "保存后立即发起执行(新会话)",
5650
+ title: runBlocked ? t("form.action.runBlockedTitle") : busy ? t("form.action.runBusyTitle") : t("form.action.runTitle"),
3973
5651
  onClick: submitAndRun,
3974
- children: "⚡ 立即执行"
5652
+ children: t("form.action.run")
3975
5653
  }),
3976
5654
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3977
5655
  type: "button",
@@ -3979,7 +5657,7 @@ window.__ModuleLoader__.load({
3979
5657
  "data-primary": "true",
3980
5658
  disabled: !valid || busy,
3981
5659
  onClick: submit,
3982
- children: editing ? "保存修改" : "创建任务"
5660
+ children: editing ? t("form.action.save") : t("form.action.create")
3983
5661
  })
3984
5662
  ]
3985
5663
  })]
@@ -4000,15 +5678,15 @@ window.__ModuleLoader__.load({
4000
5678
  *
4001
5679
  * @module dsh-taskboard/client/board/SettingsModal
4002
5680
  */
4003
- /** The isolation options with one-line hints (mirrors the task form). */
4004
- const ISOLATION_OPTIONS = [{
5681
+ /** The isolation options with one-line hints (mirrors the task form; translated per render). */
5682
+ const isolationOptions = (t) => [{
4005
5683
  value: "none",
4006
- name: "📁 原目录执行",
4007
- hint: "不使用 git,直接在项目目录工作(出厂默认)"
5684
+ name: t("form.iso.none"),
5685
+ hint: t("set.iso.noneHint")
4008
5686
  }, {
4009
5687
  value: "worktree",
4010
- name: "🌿 Worktree 隔离",
4011
- hint: "每次执行在独立 worktree 分支上进行(task/标题+ID),互不污染"
5688
+ name: t("form.iso.worktree"),
5689
+ hint: t("set.iso.worktreeHint")
4012
5690
  }];
4013
5691
  /**
4014
5692
  * The 看板设置 modal: reads the live ledger settings, stages a local draft,
@@ -4016,11 +5694,21 @@ window.__ModuleLoader__.load({
4016
5694
  * @param controller - the board controller.
4017
5695
  */
4018
5696
  function SettingsModal({ controller }) {
4019
- const current = controller.getSnapshot().ledger.settings?.defaultIsolation ?? "none";
4020
- const [draft, setDraft] = (0, react.useState)(current);
4021
- const dirty = draft !== current;
5697
+ const t = useT();
5698
+ const state = controller.getSnapshot();
5699
+ const currentIso = state.ledger.settings?.defaultIsolation ?? "none";
5700
+ const currentSync = defaultSyncExternalSessionsOf(state.ledger.settings);
5701
+ const currentPerm = defaultPermissionOf(state.ledger.settings);
5702
+ const [draftIso, setDraftIso] = (0, react.useState)(currentIso);
5703
+ const [draftSync, setDraftSync] = (0, react.useState)(currentSync);
5704
+ const [draftPerm, setDraftPerm] = (0, react.useState)(currentPerm);
5705
+ const dirty = draftIso !== currentIso || draftSync !== currentSync || draftPerm !== currentPerm;
4022
5706
  const save = () => {
4023
- controller.updateSettings({ defaultIsolation: draft }).then((ok) => {
5707
+ controller.updateSettings({
5708
+ defaultIsolation: draftIso,
5709
+ syncExternalSessions: draftSync,
5710
+ defaultPermission: draftPerm
5711
+ }).then((ok) => {
4024
5712
  if (ok) controller.closeSettings();
4025
5713
  });
4026
5714
  };
@@ -4033,7 +5721,7 @@ window.__ModuleLoader__.load({
4033
5721
  className: "dsh-atb-modal dsh-atb-set",
4034
5722
  role: "dialog",
4035
5723
  "aria-modal": "true",
4036
- "aria-label": "看板设置",
5724
+ "aria-label": t("set.aria"),
4037
5725
  children: [
4038
5726
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4039
5727
  className: "dsh-atb-modal-head",
@@ -4044,70 +5732,162 @@ window.__ModuleLoader__.load({
4044
5732
  }),
4045
5733
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4046
5734
  className: "dsh-atb-modal-headtext",
4047
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "看板设置" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "新建任务时应用的默认值(不影响已有任务)" })]
5735
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("set.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("set.subtitle") })]
4048
5736
  }),
4049
5737
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4050
5738
  type: "button",
4051
5739
  className: "dsh-atb-modal-close",
4052
- "aria-label": "关闭",
5740
+ "aria-label": t("shared.close"),
4053
5741
  onClick: () => controller.closeSettings(),
4054
5742
  children: "✕"
4055
5743
  })
4056
5744
  ]
4057
5745
  }),
4058
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5746
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4059
5747
  className: "dsh-atb-modal-body",
4060
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4061
- className: "dsh-atb-diag-sec",
4062
- children: [
4063
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "默认执行隔离" }),
4064
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4065
- className: "dsh-atb-mode-picker",
4066
- children: ISOLATION_OPTIONS.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4067
- type: "button",
4068
- className: "dsh-atb-mode-opt",
4069
- "data-on": draft === o.value,
4070
- title: o.hint,
4071
- onClick: () => setDraft(o.value),
4072
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4073
- className: "dsh-atb-mode-name",
4074
- children: o.name
4075
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4076
- className: "dsh-atb-mode-hint",
4077
- children: o.hint
5748
+ children: [
5749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5750
+ className: "dsh-atb-diag-sec",
5751
+ children: [
5752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("set.iso.heading") }),
5753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5754
+ className: "dsh-atb-mode-picker",
5755
+ children: isolationOptions(t).map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5756
+ type: "button",
5757
+ className: "dsh-atb-mode-opt",
5758
+ "data-on": draftIso === o.value,
5759
+ title: o.hint,
5760
+ onClick: () => setDraftIso(o.value),
5761
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5762
+ className: "dsh-atb-mode-name",
5763
+ children: o.name
5764
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5765
+ className: "dsh-atb-mode-hint",
5766
+ children: o.hint
5767
+ })]
5768
+ }, o.value))
5769
+ }),
5770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5771
+ className: "dsh-atb-isolation-note",
5772
+ children: t("set.iso.current", { current: currentIso === "worktree" ? t("form.iso.worktree") : t("form.iso.none") })
5773
+ })
5774
+ ]
5775
+ }),
5776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5777
+ className: "dsh-atb-diag-sec",
5778
+ children: [
5779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("set.sync.heading") }),
5780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5781
+ className: "dsh-atb-mode-picker",
5782
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5783
+ type: "button",
5784
+ className: "dsh-atb-mode-opt",
5785
+ "data-on": !draftSync,
5786
+ title: t("set.sync.off.title"),
5787
+ onClick: () => setDraftSync(false),
5788
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5789
+ className: "dsh-atb-mode-name",
5790
+ children: t("set.sync.off.name")
5791
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5792
+ className: "dsh-atb-mode-hint",
5793
+ children: t("set.sync.off.hint")
5794
+ })]
5795
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5796
+ type: "button",
5797
+ className: "dsh-atb-mode-opt",
5798
+ "data-on": draftSync,
5799
+ title: t("set.sync.on.title"),
5800
+ onClick: () => setDraftSync(true),
5801
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5802
+ className: "dsh-atb-mode-name",
5803
+ children: t("set.sync.on.name")
5804
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5805
+ className: "dsh-atb-mode-hint",
5806
+ children: t("set.sync.on.hint")
5807
+ })]
4078
5808
  })]
4079
- }, o.value))
4080
- }),
4081
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4082
- className: "dsh-atb-isolation-note",
4083
- children: [
4084
- "当前保存的默认:",
4085
- current === "worktree" ? "🌿 Worktree 隔离" : "📁 原目录执行",
4086
- "。 仅影响之后新建的任务;已有任务保持创建时的选择,非 git 项目运行时仍自动降级原目录。"
4087
- ]
4088
- })
4089
- ]
4090
- })
5809
+ }),
5810
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5811
+ className: "dsh-atb-isolation-note",
5812
+ children: currentSync ? t("set.sync.stateOn") : t("set.sync.stateOff")
5813
+ })
5814
+ ]
5815
+ }),
5816
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5817
+ className: "dsh-atb-diag-sec",
5818
+ children: [
5819
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("set.perm.heading") }),
5820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5821
+ className: "dsh-atb-perm-picker",
5822
+ children: [
5823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5824
+ type: "button",
5825
+ className: "dsh-atb-perm-opt",
5826
+ "data-on": draftPerm === "workspace-write",
5827
+ onClick: () => setDraftPerm("workspace-write"),
5828
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5829
+ className: "dsh-atb-perm-name",
5830
+ children: t("set.perm.writeName")
5831
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5832
+ className: "dsh-atb-perm-hint",
5833
+ children: t("set.perm.writeHint")
5834
+ })]
5835
+ }),
5836
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5837
+ type: "button",
5838
+ className: "dsh-atb-perm-opt",
5839
+ "data-on": draftPerm === "read-only",
5840
+ onClick: () => setDraftPerm("read-only"),
5841
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5842
+ className: "dsh-atb-perm-name",
5843
+ children: t("set.perm.readOnlyName")
5844
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5845
+ className: "dsh-atb-perm-hint",
5846
+ children: t("set.perm.readOnlyHint")
5847
+ })]
5848
+ }),
5849
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5850
+ type: "button",
5851
+ className: "dsh-atb-perm-opt",
5852
+ "data-on": draftPerm === "danger-full-access",
5853
+ onClick: () => setDraftPerm("danger-full-access"),
5854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5855
+ className: "dsh-atb-perm-name",
5856
+ children: t("set.perm.fullName")
5857
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5858
+ className: "dsh-atb-perm-hint",
5859
+ children: t("set.perm.fullHint")
5860
+ })]
5861
+ })
5862
+ ]
5863
+ }),
5864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5865
+ className: "dsh-atb-isolation-note",
5866
+ children: t("set.perm.current", { current: currentPerm === "read-only" ? t("set.perm.readOnlyName") : currentPerm === "danger-full-access" ? t("set.perm.fullName") : t("set.perm.writeName") })
5867
+ })
5868
+ ]
5869
+ })
5870
+ ]
4091
5871
  }),
4092
5872
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4093
5873
  className: "dsh-atb-modal-foot",
4094
5874
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4095
5875
  className: "dsh-atb-modal-hint",
4096
- children: dirty ? "有未保存的修改" : "与看板当前设置一致"
5876
+ children: dirty ? t("set.foot.dirty") : t("set.foot.clean")
4097
5877
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4098
5878
  className: "dsh-atb-modal-footbtns",
4099
5879
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4100
5880
  type: "button",
4101
5881
  className: "dsh-atb-btn",
4102
5882
  onClick: () => controller.closeSettings(),
4103
- children: "取消"
5883
+ children: t("shared.cancel")
4104
5884
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4105
5885
  type: "button",
4106
5886
  className: "dsh-atb-btn",
4107
5887
  "data-primary": "true",
4108
5888
  disabled: !dirty,
4109
5889
  onClick: save,
4110
- children: "保存设置"
5890
+ children: t("set.action.save")
4111
5891
  })]
4112
5892
  })]
4113
5893
  })
@@ -4145,6 +5925,7 @@ window.__ModuleLoader__.load({
4145
5925
  * @param controller - the controller.
4146
5926
  */
4147
5927
  function ImportModal({ controller }) {
5928
+ const t = useT();
4148
5929
  const [fileName, setFileName] = (0, react.useState)("");
4149
5930
  const [parsed, setParsed] = (0, react.useState)(null);
4150
5931
  const [parseError, setParseError] = (0, react.useState)(void 0);
@@ -4173,7 +5954,7 @@ window.__ModuleLoader__.load({
4173
5954
  if (p !== void 0) setPlan(p);
4174
5955
  });
4175
5956
  } catch {
4176
- setParseError("文件不是合法 JSON");
5957
+ setParseError(t("imp.parseError"));
4177
5958
  }
4178
5959
  });
4179
5960
  };
@@ -4189,7 +5970,13 @@ window.__ModuleLoader__.load({
4189
5970
  setBusy(false);
4190
5971
  setConfirmReplace(false);
4191
5972
  if (r === void 0) return;
4192
- setResult(r.mode === "replace" ? `整册替换完成:导入 ${r.created + r.overwritten} 张(原 ${r.replacedTotal} 张已整册备份)` : `合并完成:新增 ${r.created} 张、覆盖 ${r.overwritten} 张`);
5973
+ setResult(r.mode === "replace" ? t("imp.result.replace", {
5974
+ n: r.created + r.overwritten,
5975
+ total: r.replacedTotal ?? 0
5976
+ }) : t("imp.result.merge", {
5977
+ n: r.created,
5978
+ m: r.overwritten
5979
+ }));
4193
5980
  });
4194
5981
  };
4195
5982
  const close = () => controller.closeImport();
@@ -4202,7 +5989,7 @@ window.__ModuleLoader__.load({
4202
5989
  className: "dsh-atb-modal dsh-atb-imp",
4203
5990
  role: "dialog",
4204
5991
  "aria-modal": "true",
4205
- "aria-label": "导入台账",
5992
+ "aria-label": t("imp.aria"),
4206
5993
  children: [
4207
5994
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4208
5995
  className: "dsh-atb-modal-head",
@@ -4213,12 +6000,12 @@ window.__ModuleLoader__.load({
4213
6000
  }),
4214
6001
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4215
6002
  className: "dsh-atb-modal-headtext",
4216
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "导入台账" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "选择导出的 JSON 备份文件:先预览、再合并或整册替换" })]
6003
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("imp.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("imp.subtitle") })]
4217
6004
  }),
4218
6005
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4219
6006
  type: "button",
4220
6007
  className: "dsh-atb-modal-close",
4221
- "aria-label": "关闭",
6008
+ "aria-label": t("shared.close"),
4222
6009
  onClick: close,
4223
6010
  children: "✕"
4224
6011
  })
@@ -4241,7 +6028,7 @@ window.__ModuleLoader__.load({
4241
6028
  }),
4242
6029
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4243
6030
  className: "dsh-atb-imp-note",
4244
- children: "⬇ JSON 导出的文件即为同格式备份,可直接导入恢复;导入文件的 schemaVersion 必须与当前版本一致。"
6031
+ children: t("imp.note")
4245
6032
  }),
4246
6033
  parseError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4247
6034
  className: "dsh-atb-imp-error",
@@ -4249,7 +6036,7 @@ window.__ModuleLoader__.load({
4249
6036
  }),
4250
6037
  plan === void 0 && parseError === void 0 && fileName.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4251
6038
  className: "dsh-atb-empty2",
4252
- children: "预览中…"
6039
+ children: t("imp.previewing")
4253
6040
  }),
4254
6041
  plan !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4255
6042
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4258,37 +6045,37 @@ window.__ModuleLoader__.load({
4258
6045
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4259
6046
  className: "dsh-atb-imp-stat",
4260
6047
  "data-tone": "ok",
4261
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: plan.create.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "新增" })]
6048
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: plan.create.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imp.stat.create") })]
4262
6049
  }),
4263
6050
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4264
6051
  className: "dsh-atb-imp-stat",
4265
6052
  "data-tone": "warn",
4266
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: plan.overwrite.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "覆盖(同 id)" })]
6053
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: plan.overwrite.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imp.stat.overwrite") })]
4267
6054
  }),
4268
6055
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4269
6056
  className: "dsh-atb-imp-stat",
4270
6057
  "data-tone": plan.invalid.length > 0 ? "bad" : void 0,
4271
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: plan.invalid.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "无效(跳过)" })]
6058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: plan.invalid.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imp.stat.invalid") })]
4272
6059
  })
4273
6060
  ]
4274
6061
  }),
4275
6062
  plan.create.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4276
6063
  className: "dsh-atb-imp-sec",
4277
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "新增任务" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6064
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("imp.sec.create") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4278
6065
  className: "dsh-atb-imp-list",
4279
6066
  children: plan.create.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlanRow, { row: r }, r.id))
4280
6067
  })]
4281
6068
  }),
4282
6069
  plan.overwrite.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4283
6070
  className: "dsh-atb-imp-sec",
4284
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "覆盖任务(整卡替换,含执行历史与评论)" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("imp.sec.overwrite") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4285
6072
  className: "dsh-atb-imp-list",
4286
6073
  children: plan.overwrite.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlanRow, { row: r }, r.id))
4287
6074
  })]
4288
6075
  }),
4289
6076
  plan.invalid.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4290
6077
  className: "dsh-atb-imp-sec",
4291
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "无效条目(不会导入)" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6078
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("imp.sec.invalid") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4292
6079
  className: "dsh-atb-imp-list",
4293
6080
  children: plan.invalid.map((r, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4294
6081
  className: "dsh-atb-imp-row",
@@ -4296,7 +6083,7 @@ window.__ModuleLoader__.load({
4296
6083
  title: r.id ?? "",
4297
6084
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4298
6085
  className: "dsh-atb-imp-row-title",
4299
- children: r.id ?? "(无 id)"
6086
+ children: r.id ?? t("imp.noId")
4300
6087
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4301
6088
  className: "dsh-atb-imp-row-status",
4302
6089
  children: r.reason
@@ -4316,10 +6103,10 @@ window.__ModuleLoader__.load({
4316
6103
  },
4317
6104
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4318
6105
  className: "dsh-atb-mode-name",
4319
- children: "⊕ 合并"
6106
+ children: t("imp.mode.merge")
4320
6107
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4321
6108
  className: "dsh-atb-mode-hint",
4322
- children: "新增 + 按 id 覆盖,其余不动"
6109
+ children: t("imp.mode.mergeHint")
4323
6110
  })]
4324
6111
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4325
6112
  type: "button",
@@ -4328,10 +6115,10 @@ window.__ModuleLoader__.load({
4328
6115
  onClick: () => setMode("replace"),
4329
6116
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4330
6117
  className: "dsh-atb-mode-name",
4331
- children: "💣 整册替换"
6118
+ children: t("imp.mode.replace")
4332
6119
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4333
6120
  className: "dsh-atb-mode-hint",
4334
- children: "清空当前台账,以导入文件为准(先自动备份)"
6121
+ children: t("imp.mode.replaceHint")
4335
6122
  })]
4336
6123
  })]
4337
6124
  }),
@@ -4346,14 +6133,14 @@ window.__ModuleLoader__.load({
4346
6133
  className: "dsh-atb-modal-foot",
4347
6134
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4348
6135
  className: "dsh-atb-modal-hint",
4349
- children: mode === "replace" ? confirmReplace ? "⚠ 再次点击确认执行整册替换(不可撤销,已自动备份)" : "整册替换需要二次确认" : "合并只写入预览中列出的任务"
6136
+ children: mode === "replace" ? confirmReplace ? t("imp.foot.replaceConfirm") : t("imp.foot.replaceNeedConfirm") : t("imp.foot.mergeHint")
4350
6137
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4351
6138
  className: "dsh-atb-modal-footbtns",
4352
6139
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4353
6140
  type: "button",
4354
6141
  className: "dsh-atb-btn",
4355
6142
  onClick: close,
4356
- children: result !== void 0 ? "关闭" : "取消"
6143
+ children: result !== void 0 ? t("shared.close") : t("shared.cancel")
4357
6144
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4358
6145
  type: "button",
4359
6146
  className: "dsh-atb-btn",
@@ -4361,7 +6148,7 @@ window.__ModuleLoader__.load({
4361
6148
  "data-danger": mode === "replace" && confirmReplace ? "true" : void 0,
4362
6149
  disabled: plan === void 0 || busy,
4363
6150
  onClick: commit,
4364
- children: mode === "replace" && confirmReplace ? "确认整册替换" : "执行导入"
6151
+ children: mode === "replace" && confirmReplace ? t("imp.action.confirmReplace") : t("imp.action.run")
4365
6152
  })]
4366
6153
  })]
4367
6154
  })
@@ -4384,6 +6171,7 @@ window.__ModuleLoader__.load({
4384
6171
  * @param controller - the controller.
4385
6172
  */
4386
6173
  function TemplateManager({ controller }) {
6174
+ const t = useT();
4387
6175
  const state = controller.getSnapshot();
4388
6176
  const [edits, setEdits] = (0, react.useState)({});
4389
6177
  const [confirmId, setConfirmId] = (0, react.useState)(void 0);
@@ -4405,7 +6193,7 @@ window.__ModuleLoader__.load({
4405
6193
  delete next[id];
4406
6194
  return next;
4407
6195
  });
4408
- showAlert("模板已改名");
6196
+ showAlert(t("tpl.renamed"));
4409
6197
  }
4410
6198
  });
4411
6199
  };
@@ -4418,7 +6206,7 @@ window.__ModuleLoader__.load({
4418
6206
  className: "dsh-atb-modal dsh-atb-tplm",
4419
6207
  role: "dialog",
4420
6208
  "aria-modal": "true",
4421
- "aria-label": "管理模板",
6209
+ "aria-label": t("tpl.aria"),
4422
6210
  children: [
4423
6211
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4424
6212
  className: "dsh-atb-modal-head",
@@ -4429,12 +6217,12 @@ window.__ModuleLoader__.load({
4429
6217
  }),
4430
6218
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4431
6219
  className: "dsh-atb-modal-headtext",
4432
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "任务模板" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "新建任务 ▼ 下拉的模板:改名 / 删除 / 直接使用;任务详情页「存为模板」可新增" })]
6220
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("tpl.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("tpl.subtitle") })]
4433
6221
  }),
4434
6222
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4435
6223
  type: "button",
4436
6224
  className: "dsh-atb-modal-close",
4437
- "aria-label": "关闭",
6225
+ "aria-label": t("shared.close"),
4438
6226
  onClick: close,
4439
6227
  children: "✕"
4440
6228
  })
@@ -4444,33 +6232,34 @@ window.__ModuleLoader__.load({
4444
6232
  className: "dsh-atb-modal-body",
4445
6233
  children: state.templates.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4446
6234
  className: "dsh-atb-empty2",
4447
- children: "暂无模板 — 在任务详情页点「存为模板」把常用配置沉淀下来"
6235
+ children: t("tpl.empty")
4448
6236
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4449
6237
  className: "dsh-atb-tplm-list",
4450
- children: state.templates.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6238
+ children: state.templates.map((tpl) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4451
6239
  className: "dsh-atb-tplm-row",
4452
6240
  children: [
4453
6241
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4454
6242
  className: "dsh-atb-tplm-name",
4455
- value: nameOf(t.id, t.name),
6243
+ value: nameOf(tpl.id, tpl.name),
4456
6244
  maxLength: 60,
4457
6245
  spellCheck: false,
4458
- "aria-label": `模板名 ${t.name}`,
6246
+ "aria-label": t("tpl.name.aria", { name: tpl.name }),
4459
6247
  onChange: (e) => setEdits((prev) => ({
4460
6248
  ...prev,
4461
- [t.id]: e.target.value
6249
+ [tpl.id]: e.target.value
4462
6250
  })),
4463
6251
  onKeyDown: (e) => {
4464
- if (e.key === "Enter") save(t.id, nameOf(t.id, t.name));
6252
+ if (e.key === "Enter") save(tpl.id, nameOf(tpl.id, tpl.name));
4465
6253
  }
4466
6254
  }),
4467
6255
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4468
6256
  className: "dsh-atb-tplm-meta",
4469
- title: `${t.builtin === true ? "内置" : "自建"}${t.task.checklist !== void 0 && t.task.checklist.length > 0 ? ` · 清单 ${t.task.checklist.length} 项` : ""}${t.task.urgency !== void 0 ? ` · ${t.task.urgency}` : ""}`,
6257
+ title: `${tpl.builtin === true ? t("tpl.builtin") : t("tpl.custom")}${tpl.task.checklist !== void 0 && tpl.task.checklist.length > 0 ? t("tpl.meta.checklist", { n: tpl.task.checklist.length }) : ""}${tpl.task.urgency !== void 0 ? ` · ${tpl.task.urgency}` : ""}${tpl.task.permission !== void 0 ? ` · ${t("shared.permission")}: ${tpl.task.permission}` : ""}`,
4470
6258
  children: [
4471
- t.builtin === true ? "内置" : "自建",
4472
- t.task.checklist !== void 0 && t.task.checklist.length > 0 ? ` · 清单 ${t.task.checklist.length} 项` : "",
4473
- t.task.urgency !== void 0 ? ` · ${t.task.urgency}` : ""
6259
+ tpl.builtin === true ? t("tpl.builtin") : t("tpl.custom"),
6260
+ tpl.task.checklist !== void 0 && tpl.task.checklist.length > 0 ? t("tpl.meta.checklist", { n: tpl.task.checklist.length }) : "",
6261
+ tpl.task.urgency !== void 0 ? ` · ${tpl.task.urgency}` : "",
6262
+ tpl.task.permission !== void 0 && tpl.task.permission !== "workspace-write" ? ` · ${tpl.task.permission === "read-only" ? t("tpl.meta.permReadOnly") : t("tpl.meta.permFull")}` : ""
4474
6263
  ]
4475
6264
  }),
4476
6265
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -4479,61 +6268,61 @@ window.__ModuleLoader__.load({
4479
6268
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4480
6269
  type: "button",
4481
6270
  className: "dsh-atb-btn",
4482
- disabled: nameOf(t.id, t.name) === t.name || nameOf(t.id, t.name).trim().length === 0,
4483
- title: "保存改名",
4484
- onClick: () => save(t.id, nameOf(t.id, t.name)),
4485
- children: "改名"
6271
+ disabled: nameOf(tpl.id, tpl.name) === tpl.name || nameOf(tpl.id, tpl.name).trim().length === 0,
6272
+ title: t("tpl.rename.title"),
6273
+ onClick: () => save(tpl.id, nameOf(tpl.id, tpl.name)),
6274
+ children: t("tpl.rename.button")
4486
6275
  }),
4487
6276
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4488
6277
  type: "button",
4489
6278
  className: "dsh-atb-btn",
4490
- title: "用此模板打开新建表单",
6279
+ title: t("tpl.use.title"),
4491
6280
  onClick: () => {
4492
6281
  close();
4493
- controller.newFromTemplate(t.task);
6282
+ controller.newFromTemplate(tpl.task);
4494
6283
  },
4495
- children: "用此新建"
6284
+ children: t("tpl.use.button")
4496
6285
  }),
4497
- confirmId === t.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6286
+ confirmId === tpl.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4498
6287
  type: "button",
4499
6288
  className: "dsh-atb-btn",
4500
6289
  "data-danger": "true",
4501
6290
  onClick: () => {
4502
- controller.deleteTemplate(t.id);
6291
+ controller.deleteTemplate(tpl.id);
4503
6292
  setConfirmId(void 0);
4504
6293
  },
4505
- children: "确认删除"
6294
+ children: t("shared.confirmDelete")
4506
6295
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4507
6296
  type: "button",
4508
6297
  className: "dsh-atb-btn",
4509
6298
  onClick: () => setConfirmId(void 0),
4510
- children: "取消"
6299
+ children: t("shared.cancel")
4511
6300
  })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4512
6301
  type: "button",
4513
6302
  className: "dsh-atb-btn",
4514
6303
  "data-danger": "true",
4515
- title: "删除该模板",
4516
- onClick: () => setConfirmId(t.id),
6304
+ title: t("tpl.delete.title"),
6305
+ onClick: () => setConfirmId(tpl.id),
4517
6306
  children: "🗑"
4518
6307
  })
4519
6308
  ]
4520
6309
  })
4521
6310
  ]
4522
- }, t.id))
6311
+ }, tpl.id))
4523
6312
  })
4524
6313
  }),
4525
6314
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4526
6315
  className: "dsh-atb-modal-foot",
4527
6316
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4528
6317
  className: "dsh-atb-modal-hint",
4529
- children: "模板随台账一同保存在 DSH 主目录,升级不丢"
6318
+ children: t("tpl.foot.hint")
4530
6319
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4531
6320
  className: "dsh-atb-modal-footbtns",
4532
6321
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4533
6322
  type: "button",
4534
6323
  className: "dsh-atb-btn",
4535
6324
  onClick: close,
4536
- children: "关闭"
6325
+ children: t("shared.close")
4537
6326
  })
4538
6327
  })]
4539
6328
  })
@@ -4573,6 +6362,7 @@ window.__ModuleLoader__.load({
4573
6362
  * @param controller - the controller.
4574
6363
  */
4575
6364
  function TaskBoard({ controller }) {
6365
+ const t = useT();
4576
6366
  const state = (0, react.useSyncExternalStore)((cb) => controller.subscribe(cb), () => controller.getSnapshot());
4577
6367
  const [now, setNow] = (0, react.useState)(() => Date.now());
4578
6368
  (0, react.useEffect)(() => {
@@ -4594,15 +6384,14 @@ window.__ModuleLoader__.load({
4594
6384
  children: [
4595
6385
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
4596
6386
  className: "dsh-atb-title",
4597
- children: "Agent 任务看板"
6387
+ children: t("board.title")
4598
6388
  }),
4599
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6389
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4600
6390
  className: "dsh-atb-count",
4601
- children: [
4602
- live.length,
4603
- " 任务 · rev ",
4604
- state.ledger.revision
4605
- ]
6391
+ children: t("board.count.tasks", {
6392
+ n: live.length,
6393
+ rev: state.ledger.revision
6394
+ })
4606
6395
  }),
4607
6396
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4608
6397
  className: "dsh-atb-newmenu",
@@ -4615,7 +6404,7 @@ window.__ModuleLoader__.load({
4615
6404
  setNewMenuOpen(next);
4616
6405
  if (next) controller.prepareTemplateMenu();
4617
6406
  },
4618
- children: "+ 新建任务 ▼"
6407
+ children: t("board.action.newTask")
4619
6408
  }), newMenuOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4620
6409
  className: "dsh-atb-newmenu-backdrop",
4621
6410
  onClick: closeMenu
@@ -4629,7 +6418,7 @@ window.__ModuleLoader__.load({
4629
6418
  closeMenu();
4630
6419
  controller.setComposer(true);
4631
6420
  },
4632
- children: "空白任务"
6421
+ children: t("board.action.blankTask")
4633
6422
  }),
4634
6423
  state.templates.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4635
6424
  type: "button",
@@ -4649,7 +6438,7 @@ window.__ModuleLoader__.load({
4649
6438
  closeMenu();
4650
6439
  controller.openTemplateManager();
4651
6440
  },
4652
- children: "⌗ 管理模板…"
6441
+ children: t("board.action.manageTemplates")
4653
6442
  })
4654
6443
  ]
4655
6444
  })] })]
@@ -4658,7 +6447,7 @@ window.__ModuleLoader__.load({
4658
6447
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4659
6448
  className: "dsh-atb-input dsh-atb-search",
4660
6449
  value: state.search,
4661
- placeholder: "搜索标题 / ID…",
6450
+ placeholder: t("board.search.placeholder"),
4662
6451
  spellCheck: false,
4663
6452
  onChange: (e) => controller.setSearch(e.target.value)
4664
6453
  }),
@@ -4668,7 +6457,7 @@ window.__ModuleLoader__.load({
4668
6457
  onChange: (e) => controller.setWorkspaceFilter(e.target.value === "" ? void 0 : e.target.value),
4669
6458
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4670
6459
  value: "",
4671
- children: "全部项目"
6460
+ children: t("board.filter.allProjects")
4672
6461
  }), state.workspaces.map((ws) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4673
6462
  value: ws.id,
4674
6463
  children: ws.title || ws.path
@@ -4677,28 +6466,28 @@ window.__ModuleLoader__.load({
4677
6466
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
4678
6467
  className: "dsh-atb-select",
4679
6468
  value: state.sortBy,
4680
- title: "列内排序",
6469
+ title: t("board.sort.title"),
4681
6470
  onChange: (e) => controller.setSortBy(e.target.value),
4682
6471
  children: [
4683
6472
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4684
6473
  value: "default",
4685
- children: "默认排序"
6474
+ children: t("board.sort.default")
4686
6475
  }),
4687
6476
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4688
6477
  value: "updated",
4689
- children: "最近更新"
6478
+ children: t("board.sort.updated")
4690
6479
  }),
4691
6480
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4692
6481
  value: "urgency",
4693
- children: "按紧急度"
6482
+ children: t("board.sort.urgency")
4694
6483
  }),
4695
6484
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4696
6485
  value: "created",
4697
- children: "创建时间"
6486
+ children: t("board.sort.created")
4698
6487
  }),
4699
6488
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4700
6489
  value: "title",
4701
- children: "按标题"
6490
+ children: t("board.sort.byTitle")
4702
6491
  })
4703
6492
  ]
4704
6493
  }),
@@ -4715,43 +6504,43 @@ window.__ModuleLoader__.load({
4715
6504
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4716
6505
  className: "dsh-atb-dot",
4717
6506
  "data-urgency": u
4718
- }), URGENCY_LABEL[u]]
6507
+ }), t(URGENCY_KEYS[u])]
4719
6508
  }, u)),
4720
6509
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4721
6510
  type: "button",
4722
6511
  className: "dsh-atb-btn",
4723
6512
  onClick: () => controller.toggleSecondary(),
4724
- children: state.secondaryOpen ? "返回看板" : "其它任务"
6513
+ children: state.secondaryOpen ? t("board.action.backToBoard") : t("board.action.otherTasks")
4725
6514
  }),
4726
6515
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4727
6516
  type: "button",
4728
6517
  className: "dsh-atb-btn",
4729
- title: "看板设置:新建任务的默认执行隔离等",
6518
+ title: t("board.action.settingsTitle"),
4730
6519
  onClick: () => controller.openSettings(),
4731
- children: "🛠 设置"
6520
+ children: t("board.action.settings")
4732
6521
  }),
4733
6522
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4734
6523
  type: "button",
4735
6524
  className: "dsh-atb-btn",
4736
- title: "健康诊断:遗留 worktree、台账基本项",
6525
+ title: t("board.action.diagTitle"),
4737
6526
  onClick: () => controller.openDiagnostics(),
4738
- children: "⚙ 诊断"
6527
+ children: t("board.action.diag")
4739
6528
  }),
4740
6529
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4741
6530
  type: "button",
4742
6531
  className: "dsh-atb-btn",
4743
- title: "从 JSON 备份文件导入台账(预览后合并或整册替换)",
6532
+ title: t("board.action.importTitle"),
4744
6533
  onClick: () => controller.openImport(),
4745
- children: "⬆ 导入"
6534
+ children: t("board.action.import")
4746
6535
  }),
4747
6536
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4748
6537
  className: "dsh-atb-newmenu",
4749
6538
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4750
6539
  type: "button",
4751
6540
  className: "dsh-atb-btn",
4752
- title: "导出台账:完整 JSON 备份或任务清单 CSV",
6541
+ title: t("board.action.exportTitle"),
4753
6542
  onClick: () => setExportOpen(!exportOpen),
4754
- children: "⬇ 导出 ▼"
6543
+ children: t("board.action.export")
4755
6544
  }), exportOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4756
6545
  className: "dsh-atb-newmenu-backdrop",
4757
6546
  onClick: closeExport
@@ -4760,21 +6549,21 @@ window.__ModuleLoader__.load({
4760
6549
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4761
6550
  type: "button",
4762
6551
  className: "dsh-atb-newmenu-opt",
4763
- title: "完整台账备份(含执行历史与看板设置),可用于导入恢复",
6552
+ title: t("board.export.jsonTitle"),
4764
6553
  onClick: () => {
4765
6554
  closeExport();
4766
6555
  controller.exportJson();
4767
6556
  },
4768
- children: "完整台账(JSON)"
6557
+ children: t("board.export.json")
4769
6558
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4770
6559
  type: "button",
4771
6560
  className: "dsh-atb-newmenu-opt",
4772
- title: "任务清单表格(Excel 可直接打开,中文已加 BOM)",
6561
+ title: t("board.export.csvTitle"),
4773
6562
  onClick: () => {
4774
6563
  closeExport();
4775
6564
  controller.exportCsv();
4776
6565
  },
4777
- children: "任务清单(CSV)"
6566
+ children: t("board.export.csv")
4778
6567
  })]
4779
6568
  })] })]
4780
6569
  }),
@@ -4818,7 +6607,10 @@ window.__ModuleLoader__.load({
4818
6607
  const task = state.ledger.tasks.find((t) => t.id === id);
4819
6608
  if (task === void 0 || task.status === status) return;
4820
6609
  if (!canTransition(task.status, status)) {
4821
- showAlert(`无法从「${COLUMN_LABELS[task.status]}」拖至「${COLUMN_LABELS[status]}」`);
6610
+ showAlert(t("board.drag.forbidden", {
6611
+ from: t(COLUMN_KEYS[task.status]),
6612
+ to: t(COLUMN_KEYS[status])
6613
+ }));
4822
6614
  return;
4823
6615
  }
4824
6616
  controller.move(id, task.version, status);
@@ -4830,7 +6622,7 @@ window.__ModuleLoader__.load({
4830
6622
  className: "dsh-atb-dot",
4831
6623
  "data-status": status
4832
6624
  }),
4833
- COLUMN_LABELS[status],
6625
+ t(COLUMN_KEYS[status]),
4834
6626
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4835
6627
  className: "dsh-atb-colcount",
4836
6628
  children: columnTasks.length
@@ -4846,7 +6638,7 @@ window.__ModuleLoader__.load({
4846
6638
  onAlert: showAlert
4847
6639
  }, task.id)), columnTasks.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4848
6640
  className: "dsh-atb-empty",
4849
- children: "无任务"
6641
+ children: t("board.empty")
4850
6642
  })]
4851
6643
  })]
4852
6644
  }, status);
@@ -4874,6 +6666,7 @@ window.__ModuleLoader__.load({
4874
6666
  }
4875
6667
  /** ⚙ Health-diagnostics panel (plan §3.6): ledger basics + orphan worktrees + one-click cleanup. */
4876
6668
  function DiagnosticsPanel({ controller }) {
6669
+ const t = useT();
4877
6670
  const state = controller.getSnapshot();
4878
6671
  const diag = state.diagnostics;
4879
6672
  const wsName = (id) => {
@@ -4889,7 +6682,7 @@ window.__ModuleLoader__.load({
4889
6682
  className: "dsh-atb-modal dsh-atb-diag",
4890
6683
  role: "dialog",
4891
6684
  "aria-modal": "true",
4892
- "aria-label": "健康诊断",
6685
+ "aria-label": t("diag.title"),
4893
6686
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4894
6687
  className: "dsh-atb-modal-head",
4895
6688
  children: [
@@ -4899,12 +6692,12 @@ window.__ModuleLoader__.load({
4899
6692
  }),
4900
6693
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4901
6694
  className: "dsh-atb-modal-headtext",
4902
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "健康诊断" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "台账基本项与 worktree 遗留清理" })]
6695
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("diag.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("diag.subtitle") })]
4903
6696
  }),
4904
6697
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4905
6698
  type: "button",
4906
6699
  className: "dsh-atb-modal-close",
4907
- "aria-label": "关闭",
6700
+ "aria-label": t("shared.close"),
4908
6701
  onClick: () => controller.closeDiagnostics(),
4909
6702
  children: "✕"
4910
6703
  })
@@ -4913,38 +6706,38 @@ window.__ModuleLoader__.load({
4913
6706
  className: "dsh-atb-modal-body",
4914
6707
  children: diag === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4915
6708
  className: "dsh-atb-empty2",
4916
- children: "读取中…"
6709
+ children: t("shared.loading")
4917
6710
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4918
6711
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4919
6712
  className: "dsh-atb-diag-grid",
4920
6713
  children: [
4921
6714
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4922
6715
  className: "dsh-atb-diag-item",
4923
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.revision }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "台账修订号" })]
6716
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.revision }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("diag.revision") })]
4924
6717
  }),
4925
6718
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4926
6719
  className: "dsh-atb-diag-item",
4927
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.tasks }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "任务总数" })]
6720
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.tasks }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("diag.tasks") })]
4928
6721
  }),
4929
6722
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4930
6723
  className: "dsh-atb-diag-item",
4931
6724
  "data-bad": diag.staleRunning > 0 ? "true" : void 0,
4932
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.staleRunning }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "执行中" })]
6725
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.staleRunning }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("diag.running") })]
4933
6726
  }),
4934
6727
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4935
6728
  className: "dsh-atb-diag-item",
4936
6729
  "data-bad": diag.orphanWorktrees.length > 0 ? "true" : void 0,
4937
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.orphanWorktrees.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "遗留 worktree" })]
6730
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: diag.orphanWorktrees.length }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("diag.orphans") })]
4938
6731
  })
4939
6732
  ]
4940
6733
  }),
4941
6734
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4942
6735
  className: "dsh-atb-diag-sec",
4943
6736
  children: [
4944
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "遗留 worktree(台账无主但目录存在)" }),
6737
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("diag.orphans.heading") }),
4945
6738
  diag.orphanWorktrees.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4946
6739
  className: "dsh-atb-empty2",
4947
- children: "无遗留 — 各项目 .dsh-worktrees 目录干净"
6740
+ children: t("diag.orphans.none")
4948
6741
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4949
6742
  className: "dsh-atb-diag-orphans",
4950
6743
  children: diag.orphanWorktrees.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4962,21 +6755,21 @@ window.__ModuleLoader__.load({
4962
6755
  className: "dsh-atb-btn",
4963
6756
  "data-danger": "true",
4964
6757
  onClick: () => void controller.cleanupOrphan(o.workspaceId, o.taskId),
4965
- children: "清理"
6758
+ children: t("diag.orphans.cleanup")
4966
6759
  })]
4967
6760
  }, o.path))
4968
6761
  }),
4969
6762
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4970
6763
  className: "dsh-atb-empty2",
4971
- children: "提示:有未提交修改的遗留目录会被拒绝清理,请先手动处理其内容。live 任务的 worktree 请在任务详情页删除。"
6764
+ children: t("diag.orphans.hint")
4972
6765
  })
4973
6766
  ]
4974
6767
  }),
4975
6768
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4976
6769
  className: "dsh-atb-diag-sec",
4977
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "gitignore 建议" }), (diag.gitIgnoreSuggestions ?? []).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6770
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t("diag.gitignore.heading") }), (diag.gitIgnoreSuggestions ?? []).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4978
6771
  className: "dsh-atb-empty2",
4979
- children: "无待办 — 各 git 项目已忽略 .dsh-worktrees 目录"
6772
+ children: t("diag.gitignore.none")
4980
6773
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4981
6774
  className: "dsh-atb-diag-orphans",
4982
6775
  children: diag.gitIgnoreSuggestions.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4986,9 +6779,11 @@ window.__ModuleLoader__.load({
4986
6779
  title: s.workspacePath,
4987
6780
  children: [
4988
6781
  wsName(s.workspaceId),
4989
- " · 建议在 .gitignore 加入一行 ",
6782
+ " · ",
6783
+ t("diag.gitignore.suggestA"),
6784
+ " ",
4990
6785
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: ".dsh-worktrees/" }),
4991
- "(不会自动修改)"
6786
+ t("diag.gitignore.suggestB")
4992
6787
  ]
4993
6788
  })
4994
6789
  }, s.workspaceId))
@@ -5001,22 +6796,23 @@ window.__ModuleLoader__.load({
5001
6796
  }
5002
6797
  /** Secondary tab: tasks grouped into canceled / archived / trashed columns. */
5003
6798
  function SecondaryTab({ controller, tasks }) {
6799
+ const t = useT();
5004
6800
  const trashed = tasks.filter((t) => t.trashedAt !== void 0);
5005
6801
  const archived = tasks.filter((t) => t.trashedAt === void 0 && t.status === "archived");
5006
6802
  const canceled = tasks.filter((t) => t.trashedAt === void 0 && t.status === "canceled");
5007
6803
  const groups = [
5008
6804
  {
5009
- label: "已取消",
6805
+ label: t("status.column.canceled"),
5010
6806
  dot: "canceled",
5011
6807
  rows: canceled
5012
6808
  },
5013
6809
  {
5014
- label: "已归档",
6810
+ label: t("status.column.archived"),
5015
6811
  dot: "archived",
5016
6812
  rows: archived
5017
6813
  },
5018
6814
  {
5019
- label: "已删除",
6815
+ label: t("board.group.trashed"),
5020
6816
  dot: "trashed",
5021
6817
  rows: trashed
5022
6818
  }
@@ -5025,7 +6821,7 @@ window.__ModuleLoader__.load({
5025
6821
  className: "dsh-atb-secondary",
5026
6822
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5027
6823
  className: "dsh-atb-empty",
5028
- children: "无已取消 / 已归档 / 已删除任务"
6824
+ children: t("board.secondary.empty")
5029
6825
  })
5030
6826
  });
5031
6827
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -5052,7 +6848,7 @@ window.__ModuleLoader__.load({
5052
6848
  controller
5053
6849
  }, task.id)), group.rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5054
6850
  className: "dsh-atb-empty",
5055
- children: "无任务"
6851
+ children: t("board.empty")
5056
6852
  })]
5057
6853
  })]
5058
6854
  }, group.label))
@@ -5208,37 +7004,126 @@ window.__ModuleLoader__.load({
5208
7004
  function apply(ctx) {
5209
7005
  try {
5210
7006
  injectStyles();
5211
- const controller = new BoardController(createClient());
5212
- const connection = ctx.get?.("connection");
5213
- if (connection !== void 0) {
5214
- controller.installModelCatalog(async () => {
5215
- const response = await connection.api.llm.models({});
5216
- if (!response.result.ok) return [];
5217
- const out = [];
5218
- for (const group of response.result.value.groups) for (const model of group.models) out.push({
5219
- provider: group.id,
5220
- model: model.id,
5221
- name: model.name,
5222
- ...model.reasoning !== void 0 ? { reasoning: model.reasoning } : {}
5223
- });
5224
- return out;
5225
- });
5226
- controller.installPresetRoster(async () => {
5227
- const list = connection.api.agentPresets;
5228
- if (list === void 0) return { presets: [] };
5229
- const response = await list.list({});
5230
- if (!response.result.ok) return { presets: [] };
5231
- const presets = response.result.value.presets.map((p) => ({
5232
- id: p.id,
5233
- name: p.name
5234
- }));
5235
- const def = response.result.value.presets.find((p) => p.isDefault);
5236
- return {
5237
- presets,
5238
- ...def !== void 0 ? { defaultId: def.id } : {}
7007
+ initI18n(ctx.get?.("locale"));
7008
+ const client = createClient();
7009
+ const controller = new BoardController(client);
7010
+ controller.installModelCatalog(async () => {
7011
+ try {
7012
+ const modelDirs = ctx.get?.("modelDirectories") ?? ctx.modelDirectories;
7013
+ if (modelDirs?.catalog?.load !== void 0) {
7014
+ const res = await modelDirs.catalog.load();
7015
+ if (res?.groups !== void 0 && res.groups.length > 0) {
7016
+ const out = [];
7017
+ for (const group of res.groups) for (const model of group.models) out.push({
7018
+ provider: group.id,
7019
+ model: model.id,
7020
+ name: model.name,
7021
+ ...model.description !== void 0 ? { description: model.description } : {},
7022
+ ...model.reasoning !== void 0 ? { reasoning: model.reasoning } : {}
7023
+ });
7024
+ if (out.length > 0) return out;
7025
+ }
7026
+ }
7027
+ } catch {}
7028
+ try {
7029
+ const remote = ctx.get?.("remote") ?? ctx.remote;
7030
+ if (remote?.session?.modelCatalog !== void 0) {
7031
+ const res = await remote.session.modelCatalog();
7032
+ if (res.ok && res.value?.groups !== void 0 && res.value.groups.length > 0) {
7033
+ const out = [];
7034
+ for (const group of res.value.groups) for (const model of group.models) out.push({
7035
+ provider: group.id,
7036
+ model: model.id,
7037
+ name: model.name,
7038
+ ...model.description !== void 0 ? { description: model.description } : {},
7039
+ ...model.reasoning !== void 0 ? { reasoning: model.reasoning } : {}
7040
+ });
7041
+ if (out.length > 0) return out;
7042
+ }
7043
+ }
7044
+ if (remote?.llm?.models !== void 0) {
7045
+ const res = await remote.llm.models({});
7046
+ if (res.result.ok && res.result.value?.groups !== void 0 && res.result.value.groups.length > 0) {
7047
+ const out = [];
7048
+ for (const group of res.result.value.groups) for (const model of group.models) out.push({
7049
+ provider: group.id,
7050
+ model: model.id,
7051
+ name: model.name,
7052
+ ...model.description !== void 0 ? { description: model.description } : {},
7053
+ ...model.reasoning !== void 0 ? { reasoning: model.reasoning } : {}
7054
+ });
7055
+ if (out.length > 0) return out;
7056
+ }
7057
+ }
7058
+ } catch {}
7059
+ try {
7060
+ const connection = ctx.get?.("connection") ?? ctx.connection;
7061
+ if (connection?.api?.llm?.models !== void 0) {
7062
+ const res = await connection.api.llm.models({});
7063
+ if (res.result.ok && res.result.value?.groups !== void 0 && res.result.value.groups.length > 0) {
7064
+ const out = [];
7065
+ for (const group of res.result.value.groups) for (const model of group.models) out.push({
7066
+ provider: group.id,
7067
+ model: model.id,
7068
+ name: model.name,
7069
+ ...model.description !== void 0 ? { description: model.description } : {},
7070
+ ...model.reasoning !== void 0 ? { reasoning: model.reasoning } : {}
7071
+ });
7072
+ if (out.length > 0) return out;
7073
+ }
7074
+ }
7075
+ } catch {}
7076
+ try {
7077
+ const res = await client.modelCatalog();
7078
+ if (res.models !== void 0 && res.models.length > 0) return res.models;
7079
+ } catch {}
7080
+ return [];
7081
+ });
7082
+ controller.installPresetRoster(async () => {
7083
+ try {
7084
+ const remote = ctx.get?.("remote") ?? ctx.remote;
7085
+ if (remote?.agentPresets?.list !== void 0) {
7086
+ const res = await remote.agentPresets.list();
7087
+ const rawPresets = res.ok === true ? res.value.presets : res.result?.ok === true ? res.result.value.presets : void 0;
7088
+ if (rawPresets !== void 0 && rawPresets.length > 0) {
7089
+ const presets = rawPresets.map((p) => ({
7090
+ id: p.id,
7091
+ name: p.name
7092
+ }));
7093
+ const def = rawPresets.find((p) => p.isDefault);
7094
+ return {
7095
+ presets,
7096
+ ...def !== void 0 ? { defaultId: def.id } : {}
7097
+ };
7098
+ }
7099
+ }
7100
+ } catch {}
7101
+ try {
7102
+ const connection = ctx.get?.("connection") ?? ctx.connection;
7103
+ if (connection?.api?.agentPresets?.list !== void 0) {
7104
+ const res = await connection.api.agentPresets.list({});
7105
+ if (res.result.ok && res.result.value?.presets !== void 0 && res.result.value.presets.length > 0) {
7106
+ const presets = res.result.value.presets.map((p) => ({
7107
+ id: p.id,
7108
+ name: p.name
7109
+ }));
7110
+ const def = res.result.value.presets.find((p) => p.isDefault);
7111
+ return {
7112
+ presets,
7113
+ ...def !== void 0 ? { defaultId: def.id } : {}
7114
+ };
7115
+ }
7116
+ }
7117
+ } catch {}
7118
+ try {
7119
+ const res = await client.modelCatalog();
7120
+ if (res.presets !== void 0 && res.presets.length > 0) return {
7121
+ presets: res.presets,
7122
+ ...res.defaultPresetId !== void 0 ? { defaultId: res.defaultPresetId } : {}
5239
7123
  };
5240
- });
5241
- }
7124
+ } catch {}
7125
+ return { presets: [] };
7126
+ });
5242
7127
  controller.installSessionJumper(createSessionJumper({
5243
7128
  getSessions: () => ctx.get?.("sessions"),
5244
7129
  getWorkspaces: () => ctx.get?.("workspaces")
@@ -5254,6 +7139,7 @@ window.__ModuleLoader__.load({
5254
7139
  ctx.effect?.(() => () => {
5255
7140
  for (const d of disposers.splice(0)) d();
5256
7141
  controller.dispose();
7142
+ disposeI18n();
5257
7143
  }, "dsh-taskboard: client mount");
5258
7144
  } catch (error) {
5259
7145
  console.error("[dsh-taskboard] client half failed to start:", error);