dsh-plugin-t-expert 0.3.6 → 0.3.25

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 (60) hide show
  1. package/README.md +101 -276
  2. package/data/.t-team-snapshot.json +10 -0
  3. package/data/experts/engineering/engineering-security-engineer.md +303 -0
  4. package/data/experts/engineering/engineering-threat-detection-engineer.md +553 -0
  5. package/data/experts/finance/finance-financial-forecaster.md +213 -0
  6. package/data/experts/hr/hr-recruiter.md +232 -0
  7. package/data/experts/marketing/marketing-search-growth-orchestrator.md +372 -0
  8. package/data/experts/specialized/specialized-pricing-optimizer.md +254 -0
  9. package/data/experts/support/support-recruitment-specialist.md +508 -0
  10. package/data/source.json +2 -2
  11. package/data/t-team.config.json +826 -1126
  12. package/data/teams.json +486 -672
  13. package/data/teams.resolved.json +743 -1109
  14. package/data/zh/COVERAGE.json +4 -4
  15. package/data/zh/company/chief-of-staff.md +278 -0
  16. package/data/zh/descriptions.json +21 -14
  17. package/data/zh/divisions.json +3 -3
  18. package/data/zh/engineering/engineering-security-engineer.md +303 -0
  19. package/data/zh/engineering/engineering-threat-detection-engineer.md +553 -0
  20. package/data/zh/finance/finance-financial-forecaster.md +213 -0
  21. package/data/zh/hr/hr-recruiter.md +232 -0
  22. package/data/zh/manual.json +4 -4
  23. package/data/zh/marketing/marketing-aeo-foundations.md +489 -219
  24. package/data/zh/marketing/marketing-ai-citation-strategist.md +692 -140
  25. package/data/zh/marketing/marketing-bilibili-strategist.md +194 -0
  26. package/data/zh/marketing/marketing-search-growth-orchestrator.md +372 -0
  27. package/data/zh/marketing/marketing-seo-specialist.md +427 -245
  28. package/data/zh/names.json +21 -14
  29. package/data/zh/specialized/recruitment-specialist.md +490 -31
  30. package/data/zh/specialized/specialized-french-consulting-market.md +173 -28
  31. package/data/zh/specialized/specialized-korean-business-navigator.md +198 -29
  32. package/data/zh/specialized/specialized-pricing-optimizer.md +254 -0
  33. package/data/zh/support/support-recruitment-specialist.md +508 -0
  34. package/lib/catalog.js +3 -3
  35. package/lib/client.js +450 -93
  36. package/lib/index.js +198 -14
  37. package/lib/remote-schemas.js +26 -1
  38. package/lib/remote.js +65 -5
  39. package/lib/schedule.js +14 -1
  40. package/lib/skill.js +21 -5
  41. package/lib/teams/state.js +9 -0
  42. package/package.json +4 -3
  43. package/vendor/third-party-licenses/README.md +2 -2
  44. package/data/zh/marketing/marketing-bilibili-content-strategist.md +0 -40
  45. package/data/zh/specialized/specialized-chief-of-staff.md +0 -40
  46. package/skills/t-expert-manager/SKILL.md +0 -125
  47. package/skills/t-expert-manager/references/ops-reference.md +0 -100
  48. /package/data/experts/{specialized/specialized-chief-of-staff.md → company/chief-of-staff.md} +0 -0
  49. /package/data/experts/engineering/{engineering-china-network-engineer.md → engineering-network-engineer-china.md} +0 -0
  50. /package/data/experts/marketing/{marketing-bilibili-content-strategist.md → marketing-bilibili-strategist.md} +0 -0
  51. /package/data/experts/marketing/{marketing-china-ecommerce-operator.md → marketing-ecommerce-operator.md} +0 -0
  52. /package/data/experts/marketing/{marketing-wechat-official-account.md → marketing-wechat-operator.md} +0 -0
  53. /package/data/experts/marketing/{marketing-xiaohongshu-specialist.md → marketing-xiaohongshu-operator.md} +0 -0
  54. /package/data/experts/{engineering/engineering-prompt-engineer.md → specialized/prompt-engineer.md} +0 -0
  55. /package/data/zh/engineering/{engineering-china-network-engineer.md → engineering-network-engineer-china.md} +0 -0
  56. /package/data/zh/manual-bodies/engineering/{engineering-china-network-engineer.md → engineering-network-engineer-china.md} +0 -0
  57. /package/data/zh/marketing/{marketing-china-ecommerce-operator.md → marketing-ecommerce-operator.md} +0 -0
  58. /package/data/zh/marketing/{marketing-wechat-official-account.md → marketing-wechat-operator.md} +0 -0
  59. /package/data/zh/marketing/{marketing-xiaohongshu-specialist.md → marketing-xiaohongshu-operator.md} +0 -0
  60. /package/data/zh/{engineering/engineering-prompt-engineer.md → specialized/prompt-engineer.md} +0 -0
package/lib/client.js CHANGED
@@ -19925,6 +19925,14 @@ var teamSchema = external_exports.object({
19925
19925
  teamId: external_exports.string(),
19926
19926
  name: external_exports.string(),
19927
19927
  workspace: external_exports.string(),
19928
+ // 2026-09-15:teamId 是团队名 slug 化的结果,**跨工作区会重名**,客户端把 workspacePath
19929
+ // 回传给 stop/deleteTeam 才能精确定位(strict schema 不声明就会被剥掉)。
19930
+ // 两个字段都 `.optional()` 是**兼容闸门**:客户端产物热重载,host 可能是旧的(不返回它们)——
19931
+ // 那时必需字段会让 `teamsSnapshotSchema.parse` 直接失败、列表**整片空白**。可选之后退化为
19932
+ // 「按 halted 判定按钮 + 按 teamId 全量遍历」的旧行为,而不是白屏。
19933
+ workspacePath: external_exports.string().optional(),
19934
+ /** 队长会话此刻是否在运行:客户端据此决定按钮叫「停止」还是「归档」。 */
19935
+ captainActive: external_exports.boolean().optional(),
19928
19936
  captainSessionId: external_exports.string(),
19929
19937
  phase: external_exports.string(),
19930
19938
  halted: external_exports.boolean(),
@@ -19940,7 +19948,16 @@ var startResultSchema = external_exports.object({
19940
19948
  var stopResultSchema = external_exports.object({
19941
19949
  teamName: external_exports.string(),
19942
19950
  cancelledTasks: external_exports.number(),
19943
- alreadyHalted: external_exports.boolean()
19951
+ alreadyHalted: external_exports.boolean(),
19952
+ // 2026-09-15 起,「停止」在队长会话已结束(或团队早已 halted)时改为**归档**:
19953
+ // 这个字段让面板知道该报「已停止」还是「已归档」。
19954
+ archived: external_exports.boolean()
19955
+ });
19956
+ var deleteTeamResultSchema = external_exports.object({
19957
+ teamName: external_exports.string(),
19958
+ deleted: external_exports.boolean(),
19959
+ // 删掉的是归档里那份(而不是还挂在「运行中」的那份)
19960
+ wasArchived: external_exports.boolean()
19944
19961
  });
19945
19962
  var squadInputSchema = external_exports.object({
19946
19963
  key: external_exports.string().min(1).max(64),
@@ -19978,7 +19995,8 @@ var scheduleSnapshotSchema = external_exports.object({
19978
19995
  /** 正在执行中的事项 id(面板据此显示「执行中…」)。 */
19979
19996
  running: external_exports.array(external_exports.string()),
19980
19997
  capabilities: external_exports.object({ agents: external_exports.boolean(), presets: external_exports.boolean() }),
19981
- workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
19998
+ /** 可选工作区(下拉选项);path 是目录,面板显示给用户看落地位置。 */
19999
+ workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string(), path: external_exports.string().optional() }))
19982
20000
  });
19983
20001
  var scheduleInputSchema = external_exports.object({
19984
20002
  title: external_exports.string().min(1).max(200),
@@ -19991,10 +20009,13 @@ var scheduleInputSchema = external_exports.object({
19991
20009
  var schedulePatchSchema = scheduleInputSchema.partial();
19992
20010
  var schedulePreviewSchema = external_exports.object({ fires: external_exports.array(external_exports.string()) });
19993
20011
  var scheduleArmedSchema = external_exports.object({ armed: external_exports.boolean() });
20012
+ var scheduleWorkspaceSchema = external_exports.object({
20013
+ workspace: external_exports.object({ id: external_exports.string(), title: external_exports.string(), path: external_exports.string() })
20014
+ });
19994
20015
 
19995
20016
  // src/client.jsx
19996
20017
  var NS = "t-team";
19997
- var CLIENT_BUILD_VERSION = "0.3.6";
20018
+ var CLIENT_BUILD_VERSION = "0.3.25";
19998
20019
  var uiPrefs = { showTaskBadge: true, showScheduleButton: true };
19999
20020
  var uiPrefsListeners = /* @__PURE__ */ new Set();
20000
20021
  function setUiPrefs(next) {
@@ -20187,6 +20208,16 @@ var zh = {
20187
20208
  "teams.stopConfirm": "\u505C\u6B62\u56E2\u961F\u300C{name}\u300D\uFF1F\u672A\u5B8C\u6210\u7684\u4EFB\u52A1\u4F1A\u88AB\u53D6\u6D88\uFF0C\u5DF2\u4EA4\u4ED8\u7ED3\u679C\u4FDD\u7559\u3002",
20188
20209
  "teams.stopping": "\u505C\u6B62\u4E2D\u2026",
20189
20210
  "teams.stopped": "\u5DF2\u505C\u6B62 {name}\uFF08\u53D6\u6D88 {count} \u9879\u4EFB\u52A1\uFF09",
20211
+ // 已停止、或队长会话已结束的团队:按钮改为「归档」——它本来就已经不干活了,
20212
+ // 唯一还想要的动作是把它从「运行中」列表里清掉(任务记录进「看归档」)。
20213
+ "teams.archive": "\u5F52\u6863",
20214
+ "teams.archiveConfirm": "\u5F52\u6863\u56E2\u961F\u300C{name}\u300D\uFF1F\u5B83\u4F1A\u4ECE\u300C\u8FD0\u884C\u4E2D\u300D\u79FB\u5230\u300C\u770B\u5F52\u6863\u300D\uFF0C\u4EFB\u52A1\u8BB0\u5F55\u4FDD\u7559\u3002",
20215
+ "teams.archiving": "\u5F52\u6863\u4E2D\u2026",
20216
+ "teams.archivedNote": "\u5DF2\u5F52\u6863 {name}",
20217
+ "teams.remove": "\u5220\u9664",
20218
+ "teams.removeConfirm": "\u5220\u9664\u56E2\u961F\u300C{name}\u300D\uFF1F\u56E2\u961F\u72B6\u6001\u4E0E\u4EFB\u52A1\u8BB0\u5F55\u4F1A\u88AB\u6C38\u4E45\u5220\u9664\uFF0C\u65E0\u6CD5\u6062\u590D\u3002",
20219
+ "teams.removing": "\u5220\u9664\u4E2D\u2026",
20220
+ "teams.removed": "\u5DF2\u5220\u9664 {name}",
20190
20221
  "teams.refresh": "\u5237\u65B0",
20191
20222
  "teams.tasks": "{done}/{total} \u4EFB\u52A1",
20192
20223
  "teams.members": "{count} \u540D\u6210\u5458",
@@ -20238,6 +20269,11 @@ var zh = {
20238
20269
  "sched.workspace": "\u5DE5\u4F5C\u533A",
20239
20270
  "sched.workspaceNone": "\u4E0D\u7ED1\u5B9A\u5DE5\u4F5C\u533A\uFF08\u9ED8\u8BA4\u76EE\u5F55\uFF09",
20240
20271
  "sched.workspaceHint": "\u6267\u884C\u65F6\u4F1A\u5728\u8FD9\u4E2A\u5DE5\u4F5C\u533A\u4E0B\u65B0\u5EFA\u4F1A\u8BDD\uFF0C\u5E76\u663E\u793A\u5728\u5DE5\u4F5C\u533A\u5206\u7EC4\u91CC\u3002",
20272
+ "sched.workspacePath": "\u76EE\u5F55\uFF1A{path}",
20273
+ "sched.workspaceCustom": "\uFF0B \u81EA\u5B9A\u4E49\u76EE\u5F55\u2026",
20274
+ "sched.workspaceCustomPlaceholder": "\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 /Users/\u4F60/tasks",
20275
+ "sched.workspaceCustomHint": "\u76EE\u5F55\u4E0D\u5B58\u5728\u65F6\u4F1A\u81EA\u52A8\u521B\u5EFA\uFF0C\u5E76\u6CE8\u518C\u6210\u4E00\u4E2A\u5DE5\u4F5C\u533A\u3002",
20276
+ "sched.workspaceCustomEmpty": "\u8BF7\u5148\u586B\u5199\u81EA\u5B9A\u4E49\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
20241
20277
  "sched.cronLabel": "cron \u5B9A\u65F6\u5668",
20242
20278
  "sched.cronHint": "\u4E94\u6BB5 croner \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u4F8B\u5982 \u201C0 9 * * *\u201D \u8868\u793A\u6BCF\u5929 09:00\u3002",
20243
20279
  "sched.cronHourly": "\u6BCF\u5C0F\u65F6",
@@ -20443,6 +20479,14 @@ var en = {
20443
20479
  "teams.stopConfirm": 'Stop team "{name}"? Unfinished tasks are cancelled; delivered results are kept.',
20444
20480
  "teams.stopping": "Stopping\u2026",
20445
20481
  "teams.stopped": "Stopped {name} ({count} tasks cancelled)",
20482
+ "teams.archive": "Archive",
20483
+ "teams.archiveConfirm": 'Archive team "{name}"? It moves from active to archived; task records are kept.',
20484
+ "teams.archiving": "Archiving\u2026",
20485
+ "teams.archivedNote": "Archived {name}",
20486
+ "teams.remove": "Delete",
20487
+ "teams.removeConfirm": 'Delete team "{name}"? Its state and task records are permanently removed; this cannot be undone.',
20488
+ "teams.removing": "Deleting\u2026",
20489
+ "teams.removed": "Deleted {name}",
20446
20490
  "teams.refresh": "Refresh",
20447
20491
  "teams.tasks": "{done}/{total} tasks",
20448
20492
  "teams.members": "{count} members",
@@ -20494,6 +20538,11 @@ var en = {
20494
20538
  "sched.workspace": "Workspace",
20495
20539
  "sched.workspaceNone": "None (default directory)",
20496
20540
  "sched.workspaceHint": "Each run starts a new session in this workspace; sessions appear grouped under it in the sidebar.",
20541
+ "sched.workspacePath": "Directory: {path}",
20542
+ "sched.workspaceCustom": "+ Custom directory\u2026",
20543
+ "sched.workspaceCustomPlaceholder": "Absolute path, e.g. /Users/you/tasks",
20544
+ "sched.workspaceCustomHint": "The directory is created when missing, then registered as a workspace.",
20545
+ "sched.workspaceCustomEmpty": "Enter an absolute path for the custom directory first.",
20497
20546
  "sched.cronLabel": "Cron schedule",
20498
20547
  "sched.cronHint": 'Five-field cron expression (minute hour day month weekday), e.g. "0 9 * * *" runs daily at 09:00.',
20499
20548
  "sched.cronHourly": "Hourly",
@@ -20631,6 +20680,9 @@ var CSS = `
20631
20680
  .t-team-chip:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
20632
20681
  .t-team-chip:disabled{opacity:.5;cursor:default}
20633
20682
  .t-team-chip[data-active="true"]{background:var(--dsw-alias-button-ghost-active-fill);border-color:var(--dsw-alias-button-ghost-active-border);color:var(--dsw-alias-label-primary);font-weight:600}
20683
+ /* \u7834\u574F\u6027\u52A8\u4F5C\uFF08\u5220\u9664\u56E2\u961F\uFF09\uFF1A\u5E38\u6001\u5C31\u5E26\u4E0A\u9519\u8BEF\u8272\uFF0C\u522B\u7B49 hover \u624D\u63D0\u793A\u3002 */
20684
+ .t-team-chip[data-tone="error"]{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 40%,transparent)}
20685
+ .t-team-chip[data-tone="error"]:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}
20634
20686
  .t-team-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:10px 12px}
20635
20687
  .t-team-group{margin-top:6px}
20636
20688
  .t-team-division{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);margin:2px 0 6px}
@@ -20808,18 +20860,23 @@ button:has(.t-team-schedule-glyph):hover{background:var(--dsw-alias-button-float
20808
20860
  .t-team-sched-nextList{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}
20809
20861
  .t-team-sched-nextList li{font-size:12px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:2px 8px;font-variant-numeric:tabular-nums}
20810
20862
  /* \u63D0\u793A\u8BCD\u8F93\u5165\u533A\uFF1A\u6846\u5185\u7B2C\u4E00\u884C\u662F\u4E13\u5BB6\u82AF\u7247\uFF08\u6807\u7B7E\u89C2\u611F\uFF0C\u4E0E\u5BF9\u8BDD\u8F93\u5165\u6846\u4E00\u81F4\uFF09\uFF0C\u4E0B\u9762\u662F\u6B63\u6587 */
20811
- .t-team-sched-promptbox{box-sizing:border-box;display:flex;flex-direction:column;gap:6px;padding:8px 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);transition:border-color .16s}
20863
+ .t-team-sched-promptbox{box-sizing:border-box;display:flex;flex-direction:column;padding:8px 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);transition:border-color .16s}
20812
20864
  .t-team-sched-promptbox:focus-within{border-color:var(--dsw-alias-brand-primary, var(--dsw-alias-label-primary))}
20813
- .t-team-sched-chips{display:flex;flex-wrap:wrap;gap:6px}
20814
- .t-team-sched-chip{display:inline-flex;align-items:center;gap:4px;max-width:100%;padding:2px 4px 2px 8px;border-radius:999px;background:color-mix(in srgb, var(--dsw-alias-brand-primary, #1677ff) 12%, transparent);color:var(--dsw-alias-brand-primary, #1677ff);font-size:12px;line-height:18px}
20865
+ /* \u82AF\u7247\u89C2\u611F\u5BF9\u9F50\u5BF9\u8BDD\u8F93\u5165\u6846\u91CC\u7684\u5F15\u7528\u82AF\u7247\uFF1A\u6D45\u7070\u5E95\u3001\u5706\u89D2\u77E9\u5F62\uFF08\u4E0D\u662F\u5168\u80F6\u56CA\uFF09\u3001\u84DD\u8272\u6587\u5B57\u3001
20866
+ * \u5DE6\u4FA7\u4E00\u4E2A\u5706\u5F62\u7EBF\u6761\u5C0F\u56FE\u6807\uFF1B\xD7 \u5E73\u65F6\u4E0D\u663E\u793A\uFF0C\u60AC\u505C\u624D\u6D6E\u51FA\u6765\uFF08\u622A\u56FE\u91CC\u5C31\u662F\u6CA1\u6709 \xD7 \u7684\u6837\u5B50\uFF09\u3002 */
20867
+ .t-team-sched-chip{box-sizing:border-box;display:inline-flex;align-items:center;gap:3px;max-width:100%;margin:0 2px;padding:0 4px;border:1px solid transparent;border-radius:6px;background:color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent);color:var(--dsw-alias-brand-primary, #1677ff);font-size:12px;line-height:18px;vertical-align:middle;user-select:none}
20868
+ /* \u60AC\u505C\u7ED9\u4E00\u5708\u6DE1\u63CF\u8FB9\uFF1A\u8BA9"\u9F20\u6807\u5728\u8FD9\u679A\u82AF\u7247\u4E0A"\u8FD9\u4EF6\u4E8B\u6709\u786E\u5B9A\u7684\u89C6\u89C9\u53CD\u9988\uFF08\u522B\u9760\u6D4F\u89C8\u5668\u9ED8\u8BA4\uFF09\u3002 */
20869
+ .t-team-sched-chip:hover{border-color:color-mix(in srgb, var(--dsw-alias-brand-primary, #1677ff) 35%, transparent)}
20870
+ .t-team-sched-chip-icon{flex:none;display:block}
20815
20871
  .t-team-sched-chip-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
20816
- .t-team-sched-chip-remove{padding:0 3px;border:0;background:transparent;color:inherit;font:inherit;font-size:13px;line-height:1;cursor:pointer;opacity:.7}
20817
- .t-team-sched-chip-remove:hover:not(:disabled){opacity:1}
20818
- .t-team-sched-chip-remove:disabled{opacity:.35;cursor:default}
20819
- /* \u26A0\uFE0F \u5FC5\u987B\u5E26 .t-team-sched-field \u524D\u7F00\uFF1A\u5426\u5219\u7279\u5F02\u6027\u4F4E\u4E8E\u901A\u7528\u7684 ".t-team-sched-field textarea" \u89C4\u5219\uFF0C
20820
- * \u6B63\u6587\u4F1A\u5E26\u7740\u81EA\u5DF1\u90A3\u5708\u8FB9\u6846\uFF0C\u89C6\u89C9\u4E0A\u53D8\u6210\u300C\u6846\u91CC\u5957\u6846\u300D\u3002 */
20821
- .t-team-sched-field .t-team-sched-prompt-input{box-sizing:border-box;width:100%;min-height:72px;padding:0;border:0;border-radius:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;resize:vertical}
20872
+ .t-team-sched-chip-remove{padding:0 1px;border:0;background:transparent;color:inherit;font:inherit;font-size:12px;line-height:1;cursor:pointer;opacity:.6;transition:opacity .12s}
20873
+ .t-team-sched-chip-remove:hover{opacity:1}
20874
+ /* \u63D0\u793A\u8BCD\u7F16\u8F91\u533A\uFF1Acontenteditable \u7684\u5BCC\u6587\u672C\u6D41\uFF08\u7EAF\u6587\u5B57 + \u82AF\u7247\uFF09\uFF0C\u82AF\u7247\u56E0\u6B64\u80FD\u63D2\u5728\u6B63\u6587\u4EFB\u610F\u4F4D\u7F6E\u3002
20875
+ * \u26A0\uFE0F \u7C7B\u540D\u5FC5\u987B\u5E26 .t-team-sched-field \u524D\u7F00\uFF1A\u5426\u5219\u7279\u5F02\u6027\u4F4E\u4E8E\u901A\u7528\u7684 ".t-team-sched-field textarea"
20876
+ * \u90A3\u7C7B\u89C4\u5219\uFF0C\u7F16\u8F91\u533A\u4F1A\u81EA\u5E26\u4E00\u5708\u8FB9\u6846\uFF08\u8E29\u8FC7\uFF1A\u6846\u91CC\u5957\u6846\uFF09\u3002 */
20877
+ .t-team-sched-field .t-team-sched-prompt-input{box-sizing:border-box;width:100%;min-height:72px;max-height:320px;overflow-y:auto;padding:0;border:0;border-radius:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:22px;white-space:pre-wrap;word-break:break-word;cursor:text;resize:none}
20822
20878
  .t-team-sched-field .t-team-sched-prompt-input:focus{outline:none;border:0}
20879
+ .t-team-sched-field .t-team-sched-prompt-input[data-empty="true"]::before{content:attr(data-placeholder);color:var(--dsw-alias-label-tertiary);pointer-events:none}
20823
20880
  /* \u63D0\u793A\u8BCD\u6846\u4E0B\u65B9\u7684\u300CT\u4E13\u5BB6\u300D\u6311\u9009\u5668\uFF1A\u6309\u94AE + \u53EA\u542B\u4E13\u5BB6\u5217\u8868\u7684\u6D6E\u5C42\uFF08\u7C7B\u540D\u72EC\u7ACB\uFF0C\u907F\u514D\u4E0E\u65E2\u6709\u7C7B\u4E92\u76F8\u8986\u76D6\uFF09 */
20824
20881
  .t-team-expert-pick{position:relative;display:flex;align-items:center;gap:8px;margin-top:2px}
20825
20882
  .t-team-expert-pick-hint{font-size:12px;color:var(--dsw-alias-label-secondary)}
@@ -20878,6 +20935,9 @@ var TYPERT_REMOTE = {
20878
20935
  direct("runScheduleItem", [
20879
20936
  { name: "id", wire: "id", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } }
20880
20937
  ], "TTeamSchedule", scheduleSnapshotSchema),
20938
+ direct("ensureScheduleWorkspace", [
20939
+ { name: "path", wire: "path", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(1024) } }
20940
+ ], "TTeamScheduleWorkspace", scheduleWorkspaceSchema),
20881
20941
  direct("previewSchedule", [
20882
20942
  { name: "cron", wire: "cron", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(120) } }
20883
20943
  ], "TTeamSchedulePreview", schedulePreviewSchema),
@@ -20893,9 +20953,16 @@ var TYPERT_REMOTE = {
20893
20953
  direct("getTeams", [
20894
20954
  { name: "archived", wire: "archived", source: "json", codec: { mode: "strict", typeSymbol: "boolean", schema: external_exports.boolean() } }
20895
20955
  ], "TTeamTeams", teamsSnapshotSchema),
20956
+ // workspacePath 标 acceptsUndefined:host 若是旧版(快照里没这个字段),这里就是 undefined,
20957
+ // 请求里不带该键,宿主退回全量遍历;不带标记会在 gateway 侧被判 arguments-invalid。
20896
20958
  direct("stopTeam", [
20897
- { name: "teamId", wire: "teamId", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } }
20959
+ { name: "teamId", wire: "teamId", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
20960
+ { name: "workspacePath", wire: "workspacePath", source: "json", acceptsUndefined: true, codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(1024) } }
20898
20961
  ], "TTeamStopResult", stopResultSchema),
20962
+ direct("deleteTeam", [
20963
+ { name: "teamId", wire: "teamId", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128) } },
20964
+ { name: "workspacePath", wire: "workspacePath", source: "json", acceptsUndefined: true, codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().max(1024) } }
20965
+ ], "TTeamDeleteResult", deleteTeamResultSchema),
20899
20966
  direct("createExpert", [
20900
20967
  { name: "expert", wire: "expert", source: "json", codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema } }
20901
20968
  ], "TTeamCatalog", catalogSnapshotSchema),
@@ -20941,11 +21008,7 @@ function onCatalogLoaded(listener) {
20941
21008
  catalogListeners.add(listener);
20942
21009
  return () => catalogListeners.delete(listener);
20943
21010
  }
20944
- async function refresh(remote, force = false) {
20945
- const cached2 = catalogCache.get(remote);
20946
- if (cached2 !== void 0 && !force) return cached2;
20947
- const next = unwrap(await remote.getCatalog(), "getCatalog");
20948
- next.enabledSet = new Set(next.enabled);
21011
+ function publishCatalog(remote, next) {
20949
21012
  catalogCache.set(remote, next);
20950
21013
  for (const listener of catalogListeners) {
20951
21014
  try {
@@ -20956,6 +21019,13 @@ async function refresh(remote, force = false) {
20956
21019
  }
20957
21020
  return next;
20958
21021
  }
21022
+ async function refresh(remote, force = false) {
21023
+ const cached2 = catalogCache.get(remote);
21024
+ if (cached2 !== void 0 && !force) return cached2;
21025
+ const next = unwrap(await remote.getCatalog(), "getCatalog");
21026
+ next.enabledSet = new Set(next.enabled);
21027
+ return publishCatalog(remote, next);
21028
+ }
20959
21029
  function displayName(expert, active) {
20960
21030
  return active === "en" ? expert.nameEn || expert.name : expert.name || expert.nameEn;
20961
21031
  }
@@ -20965,25 +21035,50 @@ function displayDescription(expert, active) {
20965
21035
  function divisionLabel(expert, active) {
20966
21036
  return active === "en" ? expert.divisionEn || expert.division : expert.divisionZh || expert.division;
20967
21037
  }
20968
- function splitExpertMentions(text, experts, active) {
20969
- let rest = String(text ?? "");
20970
- const mentions = [];
20971
- for (let step = 0; step < 32; step += 1) {
20972
- let matched;
20973
- for (const expert of experts ?? []) {
20974
- const names = [displayName(expert, active), expert?.name, expert?.nameEn, expert?.slug].filter((name) => typeof name === "string" && name !== "");
20975
- for (const name of names) {
20976
- if (!rest.startsWith(`@${name} `)) continue;
20977
- if (matched === void 0 || name.length > matched.name.length) {
20978
- matched = { name, display: displayName(expert, active) };
20979
- }
21038
+ function matchExpertAtHead(text, experts, active) {
21039
+ if (!text.startsWith("@")) return void 0;
21040
+ let matched;
21041
+ for (const expert of experts ?? []) {
21042
+ const names = [displayName(expert, active), expert?.name, expert?.nameEn, expert?.slug].filter((name) => typeof name === "string" && name !== "");
21043
+ for (const name of names) {
21044
+ if (!text.startsWith(`@${name}`)) continue;
21045
+ if (matched === void 0 || name.length > matched.name.length) {
21046
+ matched = { name, display: displayName(expert, active) };
20980
21047
  }
20981
21048
  }
20982
- if (matched === void 0) break;
20983
- rest = rest.slice(matched.name.length + 2);
20984
- if (!mentions.includes(matched.display)) mentions.push(matched.display);
20985
21049
  }
20986
- return { mentions, body: rest };
21050
+ return matched;
21051
+ }
21052
+ function tokenizePrompt(text, experts, active) {
21053
+ const raw = String(text ?? "");
21054
+ const tokens = [];
21055
+ let buffer = "";
21056
+ let rest = raw;
21057
+ const flush = () => {
21058
+ if (buffer === "") return;
21059
+ tokens.push({ type: "text", text: buffer });
21060
+ buffer = "";
21061
+ };
21062
+ while (rest !== "") {
21063
+ const hit = rest.startsWith("@") ? matchExpertAtHead(rest, experts, active) : void 0;
21064
+ if (hit === void 0) {
21065
+ buffer += rest[0];
21066
+ rest = rest.slice(1);
21067
+ continue;
21068
+ }
21069
+ flush();
21070
+ tokens.push({ type: "expert", name: hit.display });
21071
+ rest = rest.slice(hit.name.length + 1);
21072
+ }
21073
+ flush();
21074
+ return tokens;
21075
+ }
21076
+ function collectExpertMentions(text, experts, active) {
21077
+ const names = [];
21078
+ for (const token of tokenizePrompt(text, experts, active)) {
21079
+ if (token.type === "expert" && !names.includes(token.name)) names.push(token.name);
21080
+ }
21081
+ return names;
20987
21082
  }
20988
21083
  function matchExpert(expert, active, query) {
20989
21084
  if (query === "") return true;
@@ -21021,6 +21116,7 @@ var SectionBoundary = class extends import_react.default.Component {
21021
21116
  return this.props.children;
21022
21117
  }
21023
21118
  };
21119
+ var CUSTOM_WORKSPACE = "__custom__";
21024
21120
  var SCHEDULE_PANEL_KEY = `${NS}-schedule`;
21025
21121
  var SCHEDULE_PRESET_LABELS = {
21026
21122
  hourly: "sched.presetHourly",
@@ -21187,13 +21283,220 @@ function CronPicker({ t, remote, value, onChange, disabled }) {
21187
21283
  ), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-sum" }, hint === "" ? summary : hint), fires.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-next" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-sched-nextLabel" }, t("sched.nextRuns", { n: fires.length })), /* @__PURE__ */ import_react.default.createElement("ul", { className: "t-team-sched-nextList" }, fires.map((iso, index) => /* @__PURE__ */ import_react.default.createElement("li", { key: index }, new Date(iso).toLocaleString(void 0, { weekday: "short", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit" }))))));
21188
21284
  }
21189
21285
  function emptyScheduleForm() {
21190
- return { editingId: null, title: "", prompt: "", cron: "", enabled: true, workspaceId: "" };
21286
+ return { editingId: null, title: "", prompt: "", cron: "", enabled: true, workspaceId: "", customDir: "" };
21191
21287
  }
21192
21288
  function scheduleLastRun(t, item) {
21193
21289
  if (!item.lastRunAt) return t("sched.neverRun");
21194
21290
  const time3 = new Date(item.lastRunAt).toLocaleString();
21195
21291
  return item.lastRunError === void 0 ? time3 : `${time3}\uFF08${t("sched.failed")}: ${item.lastRunError}\uFF09`;
21196
21292
  }
21293
+ function PromptEditor({ editorRef, t, active, experts, value, disabled, onChange }) {
21294
+ const boxRef = import_react.default.useRef(null);
21295
+ const lastWritten = import_react.default.useRef(null);
21296
+ const savedRange = import_react.default.useRef(null);
21297
+ const buildChipIcon = () => {
21298
+ const NS2 = "http://www.w3.org/2000/svg";
21299
+ const svg = document.createElementNS(NS2, "svg");
21300
+ svg.setAttribute("class", "t-team-sched-chip-icon");
21301
+ svg.setAttribute("viewBox", "0 0 16 16");
21302
+ svg.setAttribute("width", "13");
21303
+ svg.setAttribute("height", "13");
21304
+ svg.setAttribute("fill", "none");
21305
+ svg.setAttribute("stroke", "currentColor");
21306
+ svg.setAttribute("stroke-width", "1.4");
21307
+ svg.setAttribute("stroke-linecap", "round");
21308
+ svg.setAttribute("aria-hidden", "true");
21309
+ const circle = document.createElementNS(NS2, "circle");
21310
+ circle.setAttribute("cx", "8");
21311
+ circle.setAttribute("cy", "8");
21312
+ circle.setAttribute("r", "6");
21313
+ const line1 = document.createElementNS(NS2, "line");
21314
+ line1.setAttribute("x1", "5.4");
21315
+ line1.setAttribute("y1", "6.6");
21316
+ line1.setAttribute("x2", "10.6");
21317
+ line1.setAttribute("y2", "6.6");
21318
+ const line2 = document.createElementNS(NS2, "line");
21319
+ line2.setAttribute("x1", "5.4");
21320
+ line2.setAttribute("y1", "9.4");
21321
+ line2.setAttribute("x2", "9");
21322
+ line2.setAttribute("y2", "9.4");
21323
+ svg.append(circle, line1, line2);
21324
+ return svg;
21325
+ };
21326
+ const buildChip = (name) => {
21327
+ const chip = document.createElement("span");
21328
+ chip.className = "t-team-sched-chip";
21329
+ chip.contentEditable = "false";
21330
+ chip.dataset.expert = name;
21331
+ const label = document.createElement("span");
21332
+ label.className = "t-team-sched-chip-name";
21333
+ label.textContent = name;
21334
+ const remove = document.createElement("button");
21335
+ remove.type = "button";
21336
+ remove.className = "t-team-sched-chip-remove";
21337
+ remove.dataset.remove = name;
21338
+ remove.textContent = "\xD7";
21339
+ remove.title = t("sched.expertRemove", { name });
21340
+ chip.append(buildChipIcon(), label, remove);
21341
+ return chip;
21342
+ };
21343
+ const serialize = () => {
21344
+ const box = boxRef.current;
21345
+ if (box === null) return "";
21346
+ let out = "";
21347
+ for (const node2 of box.childNodes) {
21348
+ if (node2.nodeType === 3) {
21349
+ out += node2.textContent ?? "";
21350
+ continue;
21351
+ }
21352
+ if (node2.nodeType !== 1) continue;
21353
+ const el = (
21354
+ /** @type {HTMLElement} */
21355
+ node2
21356
+ );
21357
+ if (el.dataset?.expert !== void 0) {
21358
+ out += `@${el.dataset.expert}`;
21359
+ continue;
21360
+ }
21361
+ if (el.tagName === "BR") {
21362
+ out += "\n";
21363
+ continue;
21364
+ }
21365
+ out += el.textContent ?? "";
21366
+ }
21367
+ return out;
21368
+ };
21369
+ const refreshEmpty = () => {
21370
+ const box = boxRef.current;
21371
+ if (box !== null) box.dataset.empty = box.textContent === "" ? "true" : "false";
21372
+ };
21373
+ const pushOut = () => {
21374
+ const text = serialize();
21375
+ lastWritten.current = text;
21376
+ refreshEmpty();
21377
+ onChange(text);
21378
+ };
21379
+ const rebuild = () => {
21380
+ const box = boxRef.current;
21381
+ if (box === null) return;
21382
+ box.textContent = "";
21383
+ for (const token of tokenizePrompt(value, experts, active)) {
21384
+ box.append(token.type === "text" ? document.createTextNode(token.text ?? "") : buildChip(token.name ?? ""));
21385
+ }
21386
+ refreshEmpty();
21387
+ };
21388
+ const removeExpert = (name) => {
21389
+ const box = boxRef.current;
21390
+ if (box === null) return;
21391
+ for (const el of box.querySelectorAll("[data-expert]")) {
21392
+ if (
21393
+ /** @type {HTMLElement} */
21394
+ el.dataset.expert === name
21395
+ ) {
21396
+ el.remove();
21397
+ break;
21398
+ }
21399
+ }
21400
+ pushOut();
21401
+ };
21402
+ const insertExpert = (name) => {
21403
+ const box = boxRef.current;
21404
+ if (box === null || disabled) return;
21405
+ const chip = buildChip(name);
21406
+ const space = document.createTextNode(" ");
21407
+ box.focus();
21408
+ const selection = window.getSelection();
21409
+ if (selection !== null && savedRange.current !== null && box.contains(savedRange.current.commonAncestorContainer)) {
21410
+ selection.removeAllRanges();
21411
+ selection.addRange(savedRange.current);
21412
+ }
21413
+ const inside = selection !== null && selection.rangeCount > 0 && box.contains(selection.anchorNode);
21414
+ if (inside) {
21415
+ const range = selection.getRangeAt(0);
21416
+ range.deleteContents();
21417
+ range.insertNode(chip);
21418
+ chip.after(space);
21419
+ range.setStartAfter(space);
21420
+ range.collapse(true);
21421
+ selection.removeAllRanges();
21422
+ selection.addRange(range);
21423
+ savedRange.current = range.cloneRange();
21424
+ } else {
21425
+ box.append(chip, space);
21426
+ }
21427
+ pushOut();
21428
+ };
21429
+ import_react.default.useEffect(() => {
21430
+ if (editorRef === void 0) return void 0;
21431
+ editorRef.current = { insertExpert, removeExpert, focus: () => boxRef.current?.focus() };
21432
+ return () => {
21433
+ editorRef.current = null;
21434
+ };
21435
+ });
21436
+ import_react.default.useEffect(() => {
21437
+ if (lastWritten.current === value) return;
21438
+ rebuild();
21439
+ }, [value, experts, active]);
21440
+ import_react.default.useEffect(() => {
21441
+ const onSelectionChange = () => {
21442
+ const box = boxRef.current;
21443
+ if (box === null) return;
21444
+ const selection = window.getSelection();
21445
+ if (selection === null || selection.rangeCount === 0) return;
21446
+ const range = selection.getRangeAt(0);
21447
+ if (box.contains(range.commonAncestorContainer)) savedRange.current = range.cloneRange();
21448
+ };
21449
+ document.addEventListener("selectionchange", onSelectionChange);
21450
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
21451
+ }, []);
21452
+ const onPaste = (event) => {
21453
+ const text = event.clipboardData?.getData("text/plain") ?? "";
21454
+ if (text === "") return;
21455
+ event.preventDefault();
21456
+ const selection = window.getSelection();
21457
+ if (selection === null || selection.rangeCount === 0) return;
21458
+ const range = selection.getRangeAt(0);
21459
+ range.deleteContents();
21460
+ const node2 = document.createTextNode(text);
21461
+ range.insertNode(node2);
21462
+ range.setStartAfter(node2);
21463
+ range.collapse(true);
21464
+ selection.removeAllRanges();
21465
+ selection.addRange(range);
21466
+ pushOut();
21467
+ };
21468
+ return /* @__PURE__ */ import_react.default.createElement(
21469
+ "div",
21470
+ {
21471
+ ref: boxRef,
21472
+ className: "t-team-sched-prompt-input",
21473
+ contentEditable: !disabled,
21474
+ suppressContentEditableWarning: true,
21475
+ role: "textbox",
21476
+ "aria-multiline": "true",
21477
+ "aria-label": t("sched.promptLabel"),
21478
+ "data-placeholder": t("sched.promptPlaceholder"),
21479
+ "data-empty": "true",
21480
+ onInput: () => {
21481
+ if (!disabled) pushOut();
21482
+ },
21483
+ onPaste,
21484
+ onBlur: () => {
21485
+ if (!disabled) pushOut();
21486
+ },
21487
+ onMouseDownCapture: (event) => {
21488
+ const target = (
21489
+ /** @type {HTMLElement} */
21490
+ event.target
21491
+ );
21492
+ const name = target?.dataset?.remove;
21493
+ if (name === void 0 || disabled) return;
21494
+ event.preventDefault();
21495
+ removeExpert(name);
21496
+ }
21497
+ }
21498
+ );
21499
+ }
21197
21500
  function ExpertPicker({ t, active, experts, disabled, selected, onPick }) {
21198
21501
  const [open2, setOpen] = import_react.default.useState(false);
21199
21502
  const [query, setQuery] = import_react.default.useState("");
@@ -21277,6 +21580,7 @@ function ExpertPicker({ t, active, experts, disabled, selected, onPick }) {
21277
21580
  "data-picked": picked.includes(displayName(expert, active)),
21278
21581
  className: "t-team-expert-pick-item",
21279
21582
  onMouseEnter: () => setIndex(position),
21583
+ onMouseDown: (event) => event.preventDefault(),
21280
21584
  onClick: () => pick2(expert)
21281
21585
  },
21282
21586
  /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-expert-pick-name" }, picked.includes(displayName(expert, active)) ? "\u2713 " : "", expert.emoji, " ", displayName(expert, active)),
@@ -21293,6 +21597,8 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21293
21597
  const [historyId, setHistoryId] = import_react.default.useState(null);
21294
21598
  const [runningId, setRunningId] = import_react.default.useState(null);
21295
21599
  const [experts, setExperts] = import_react.default.useState([]);
21600
+ const promptEditorRef = import_react.default.useRef(null);
21601
+ const [extraWorkspaces, setExtraWorkspaces] = import_react.default.useState([]);
21296
21602
  const layout = () => hostCtx?.get?.("layout") ?? hostCtx?.layout;
21297
21603
  const backToConversation = () => layout()?.selectPanel?.(null);
21298
21604
  const load = import_react.default.useCallback(async () => {
@@ -21311,17 +21617,23 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21311
21617
  }, [load]);
21312
21618
  import_react.default.useEffect(() => {
21313
21619
  let alive = true;
21620
+ const sync = () => {
21621
+ const current = snapshotOf(remote);
21622
+ if (current === void 0 || !alive) return;
21623
+ setExperts(current.experts.filter((expert) => expert.conflict !== true && current.enabledSet.has(expert.slug)));
21624
+ };
21625
+ const stop = onCatalogLoaded(sync);
21314
21626
  void (async () => {
21315
21627
  try {
21316
- const catalogSnapshot = await refresh(remote);
21317
- if (!alive) return;
21318
- setExperts(catalogSnapshot.experts.filter((expert) => expert.conflict !== true && catalogSnapshot.enabledSet.has(expert.slug)));
21628
+ await refresh(remote);
21629
+ sync();
21319
21630
  } catch (cause) {
21320
21631
  console.warn("[t-team] \u5B9A\u65F6\u4EFB\u52A1\uFF1A\u8BFB\u53D6\u4E13\u5BB6\u540D\u518C\u5931\u8D25\uFF0C\u300CT\u4E13\u5BB6\u300D\u6311\u9009\u5668\u6682\u4E0D\u53EF\u7528\uFF1A", cause);
21321
21632
  }
21322
21633
  })();
21323
21634
  return () => {
21324
21635
  alive = false;
21636
+ stop();
21325
21637
  };
21326
21638
  }, [remote]);
21327
21639
  const active = getActive();
@@ -21334,13 +21646,28 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21334
21646
  setError("");
21335
21647
  };
21336
21648
  function fillExpert(expert) {
21649
+ const editor = promptEditorRef.current;
21650
+ if (editor === null) return;
21337
21651
  const name = displayName(expert, active);
21338
- setForm((current) => {
21339
- if (current === null) return current;
21340
- const { mentions, body } = splitExpertMentions(current.prompt, experts, active);
21341
- const next = mentions.includes(name) ? mentions.filter((item) => item !== name) : [...mentions, name];
21342
- return { ...current, prompt: `${next.map((item) => `@${item} `).join("")}${body}` };
21343
- });
21652
+ if (mentioned.includes(name)) editor.removeExpert(name);
21653
+ else editor.insertExpert(name);
21654
+ }
21655
+ function rememberWorkspace(workspace) {
21656
+ if (workspace === void 0 || workspace === null) return "";
21657
+ setExtraWorkspaces((current) => current.some((item) => item.id === workspace.id) ? current : [...current, workspace]);
21658
+ return workspace.id;
21659
+ }
21660
+ async function openNewForm() {
21661
+ setBusy(true);
21662
+ let workspaceId = "";
21663
+ try {
21664
+ workspaceId = rememberWorkspace(unwrap(await remote.ensureScheduleWorkspace(""), "ensureScheduleWorkspace")?.workspace);
21665
+ } catch (cause) {
21666
+ setError(errorText(cause));
21667
+ } finally {
21668
+ setBusy(false);
21669
+ }
21670
+ setForm({ ...emptyScheduleForm(), workspaceId });
21344
21671
  }
21345
21672
  async function submitForm() {
21346
21673
  if (form === null || busy) return;
@@ -21350,13 +21677,22 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21350
21677
  }
21351
21678
  setBusy(true);
21352
21679
  try {
21680
+ let workspaceId = form.workspaceId;
21681
+ if (workspaceId === CUSTOM_WORKSPACE) {
21682
+ const dir = form.customDir.trim();
21683
+ if (dir === "") {
21684
+ window.alert(t("sched.workspaceCustomEmpty"));
21685
+ return;
21686
+ }
21687
+ workspaceId = rememberWorkspace(unwrap(await remote.ensureScheduleWorkspace(dir), "ensureScheduleWorkspace")?.workspace);
21688
+ }
21353
21689
  const payload = {
21354
21690
  title: form.title.trim(),
21355
21691
  prompt: form.prompt,
21356
21692
  cron: form.cron.trim(),
21357
21693
  enabled: form.enabled,
21358
21694
  // 空串 = 不绑定工作区(显式解绑也走这个值,宿主认得)。
21359
- workspaceId: form.workspaceId === "" ? "" : form.workspaceId
21695
+ workspaceId: workspaceId === void 0 || workspaceId === null ? "" : workspaceId
21360
21696
  };
21361
21697
  const next = form.editingId === null ? unwrap(await remote.createScheduleItem(payload), "createScheduleItem") : unwrap(await remote.updateScheduleItem(form.editingId, payload), "updateScheduleItem");
21362
21698
  applySnapshot(next);
@@ -21390,7 +21726,11 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21390
21726
  }
21391
21727
  const items = snapshot?.items ?? [];
21392
21728
  const workspaces = snapshot?.workspaces ?? [];
21393
- const workspaceTitle = (id) => workspaces.find((workspace) => workspace.id === id)?.title ?? id;
21729
+ const allWorkspaces = [...workspaces];
21730
+ for (const workspace of extraWorkspaces) {
21731
+ if (!allWorkspaces.some((item) => item.id === workspace.id)) allWorkspaces.push(workspace);
21732
+ }
21733
+ const workspaceTitle = (id) => allWorkspaces.find((workspace) => workspace.id === id)?.title ?? id;
21394
21734
  const rowMeta = (item) => {
21395
21735
  const parts = [];
21396
21736
  if (item.workspaceId !== void 0) parts.push(`${t("sched.workspace")}: ${workspaceTitle(item.workspaceId)}`);
@@ -21399,23 +21739,8 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21399
21739
  return parts.join(" \xB7 ");
21400
21740
  };
21401
21741
  const disabled = busy || loading || !showButton;
21402
- const parsedPrompt = form === null ? { mentions: [], body: "" } : splitExpertMentions(form.prompt, experts, active);
21403
- const mentioned = parsedPrompt.mentions;
21404
- const promptBody = parsedPrompt.body;
21405
- function setPromptBody(nextBody) {
21406
- setForm((current) => {
21407
- if (current === null) return current;
21408
- const { mentions } = splitExpertMentions(current.prompt, experts, active);
21409
- return { ...current, prompt: `${mentions.map((item) => `@${item} `).join("")}${nextBody}` };
21410
- });
21411
- }
21412
- function removeExpert(name) {
21413
- setForm((current) => {
21414
- if (current === null) return current;
21415
- const { mentions, body } = splitExpertMentions(current.prompt, experts, active);
21416
- return { ...current, prompt: `${mentions.filter((item) => item !== name).map((item) => `@${item} `).join("")}${body}` };
21417
- });
21418
- }
21742
+ const mentioned = form === null ? [] : collectExpertMentions(form.prompt, experts, active);
21743
+ const selectedWorkspacePath = form === null ? "" : allWorkspaces.find((workspace) => workspace.id === form.workspaceId)?.path ?? "";
21419
21744
  return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-head" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("h2", { className: "t-team-sched-title" }, t("sched.title")), /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-sched-intro" }, t("sched.intro"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-actions" }, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "t-team-sched-btn", disabled: loading, onClick: () => void load() }, t("sched.refresh")), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "t-team-sched-btn", onClick: backToConversation }, t("sched.back")))), !showButton && /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-sched-note" }, t("sched.off")), snapshot !== null && snapshot.capabilities.agents !== true && /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-sched-note" }, t("sched.noAgents")), error62 !== "" && /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-sched-error", role: "alert" }, t("sched.error"), " ", error62, /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "t-team-sched-btn", onClick: () => void load() }, t("sched.retry"))), loading && /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-sched-intro" }, t("sched.loading")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-body" }, !loading && items.length === 0 && error62 === "" && /* @__PURE__ */ import_react.default.createElement("p", { className: "t-team-sched-intro" }, t("sched.empty")), /* @__PURE__ */ import_react.default.createElement("ul", { className: "t-team-sched-list" }, items.map((item) => /* @__PURE__ */ import_react.default.createElement("li", { key: item.id, className: "t-team-sched-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-rowMain" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-sched-rowTitle" }, item.title), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-sched-rowCron" }, item.cron), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-sched-rowMeta" }, rowMeta(item)), item.runs.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
21420
21745
  "button",
21421
21746
  {
@@ -21446,11 +21771,12 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21446
21771
  prompt: item.prompt,
21447
21772
  cron: item.cron,
21448
21773
  enabled: item.enabled === true,
21449
- workspaceId: item.workspaceId ?? ""
21774
+ workspaceId: item.workspaceId ?? "",
21775
+ customDir: ""
21450
21776
  })
21451
21777
  },
21452
21778
  t("sched.edit")
21453
- ), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "t-team-sched-btn t-team-sched-btn-danger", disabled, onClick: () => void removeItem(item.id) }, t("sched.delete")))))), form === null ? /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "t-team-sched-btn t-team-sched-btn-primary", disabled, onClick: () => setForm(emptyScheduleForm()) }, t("sched.new")) : /* @__PURE__ */ import_react.default.createElement(
21779
+ ), /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "t-team-sched-btn t-team-sched-btn-danger", disabled, onClick: () => void removeItem(item.id) }, t("sched.delete")))))), form === null ? /* @__PURE__ */ import_react.default.createElement("button", { type: "button", className: "t-team-sched-btn t-team-sched-btn-primary", disabled, onClick: () => void openNewForm() }, t("sched.new")) : /* @__PURE__ */ import_react.default.createElement(
21454
21780
  "form",
21455
21781
  {
21456
21782
  className: "t-team-sched-form",
@@ -21469,26 +21795,16 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21469
21795
  onChange: (event) => setForm({ ...form, title: event.target.value })
21470
21796
  }
21471
21797
  )),
21472
- /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("sched.promptLabel")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-promptbox" }, mentioned.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-chips" }, mentioned.map((name) => /* @__PURE__ */ import_react.default.createElement("span", { key: name, className: "t-team-sched-chip" }, /* @__PURE__ */ import_react.default.createElement("span", { "aria-hidden": "true" }, "\u{1F9E9}"), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-sched-chip-name" }, name), /* @__PURE__ */ import_react.default.createElement(
21473
- "button",
21798
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("sched.promptLabel")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-promptbox" }, /* @__PURE__ */ import_react.default.createElement(
21799
+ PromptEditor,
21474
21800
  {
21475
- type: "button",
21476
- className: "t-team-sched-chip-remove",
21477
- disabled: busy,
21478
- title: t("sched.expertRemove", { name }),
21479
- "aria-label": t("sched.expertRemove", { name }),
21480
- onClick: () => removeExpert(name)
21481
- },
21482
- "\xD7"
21483
- )))), /* @__PURE__ */ import_react.default.createElement(
21484
- "textarea",
21485
- {
21486
- className: "t-team-sched-prompt-input",
21487
- value: promptBody,
21488
- rows: 4,
21801
+ editorRef: promptEditorRef,
21802
+ t,
21803
+ active,
21804
+ experts,
21805
+ value: form.prompt,
21489
21806
  disabled: busy,
21490
- placeholder: t("sched.promptPlaceholder"),
21491
- onChange: (event) => setPromptBody(event.target.value)
21807
+ onChange: (prompt) => setForm((current) => current === null ? current : { ...current, prompt })
21492
21808
  }
21493
21809
  )), /* @__PURE__ */ import_react.default.createElement(
21494
21810
  ExpertPicker,
@@ -21502,7 +21818,7 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21502
21818
  }
21503
21819
  ), mentioned.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-expert-pick-hint" }, mentioned.length === 1 ? t("sched.expertInvokeHint", { name: mentioned[0] }) : t("sched.expertInvokeManyHint", { names: mentioned.join(t("sched.listSep")) }))),
21504
21820
  /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("sched.cronLabel")), /* @__PURE__ */ import_react.default.createElement(CronPicker, { t, remote, value: form.cron, disabled: busy, onChange: (cron) => setForm((current) => current === null ? current : { ...current, cron }) }), /* @__PURE__ */ import_react.default.createElement("small", { className: "t-team-sched-hint" }, t("sched.cronHint"))),
21505
- workspaces.length > 0 && /* @__PURE__ */ import_react.default.createElement("label", { className: "t-team-sched-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("sched.workspace")), /* @__PURE__ */ import_react.default.createElement(
21821
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-sched-field" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("sched.workspace")), /* @__PURE__ */ import_react.default.createElement(
21506
21822
  "select",
21507
21823
  {
21508
21824
  value: form.workspaceId,
@@ -21510,8 +21826,18 @@ function SchedulePanel({ t, ctx: hostCtx, remote, getActive }) {
21510
21826
  onChange: (event) => setForm({ ...form, workspaceId: event.target.value })
21511
21827
  },
21512
21828
  /* @__PURE__ */ import_react.default.createElement("option", { value: "" }, t("sched.workspaceNone")),
21513
- workspaces.map((workspace) => /* @__PURE__ */ import_react.default.createElement("option", { key: workspace.id, value: workspace.id }, workspace.title))
21514
- ), /* @__PURE__ */ import_react.default.createElement("small", { className: "t-team-sched-hint" }, t("sched.workspaceHint"))),
21829
+ allWorkspaces.map((workspace) => /* @__PURE__ */ import_react.default.createElement("option", { key: workspace.id, value: workspace.id }, workspace.title)),
21830
+ form.workspaceId !== "" && form.workspaceId !== CUSTOM_WORKSPACE && !allWorkspaces.some((workspace) => workspace.id === form.workspaceId) && /* @__PURE__ */ import_react.default.createElement("option", { value: form.workspaceId }, form.workspaceId),
21831
+ /* @__PURE__ */ import_react.default.createElement("option", { value: CUSTOM_WORKSPACE }, t("sched.workspaceCustom"))
21832
+ ), form.workspaceId === CUSTOM_WORKSPACE ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
21833
+ "input",
21834
+ {
21835
+ value: form.customDir,
21836
+ disabled: busy,
21837
+ placeholder: t("sched.workspaceCustomPlaceholder"),
21838
+ onChange: (event) => setForm({ ...form, customDir: event.target.value })
21839
+ }
21840
+ ), /* @__PURE__ */ import_react.default.createElement("small", { className: "t-team-sched-hint" }, t("sched.workspaceCustomHint"))) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("small", { className: "t-team-sched-hint" }, t("sched.workspaceHint")), selectedWorkspacePath !== "" && /* @__PURE__ */ import_react.default.createElement("small", { className: "t-team-sched-hint" }, t("sched.workspacePath", { path: selectedWorkspacePath })))),
21515
21841
  /* @__PURE__ */ import_react.default.createElement("label", { className: "t-team-sched-checkbox" }, /* @__PURE__ */ import_react.default.createElement(
21516
21842
  "input",
21517
21843
  {
@@ -21741,7 +22067,7 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
21741
22067
  setSnapshot({ ...snapshot, enabled: list });
21742
22068
  try {
21743
22069
  const state = unwrap(await remote.setEnabled(list, snapshot.revision), "setEnabled");
21744
- catalogCache.set(remote, { ...snapshot, enabled: state.enabled, enabledSet: new Set(state.enabled), revision: state.revision });
22070
+ publishCatalog(remote, { ...snapshot, enabled: state.enabled, enabledSet: new Set(state.enabled), revision: state.revision });
21745
22071
  setSnapshot({ ...snapshot, enabled: state.enabled, revision: state.revision });
21746
22072
  setError("");
21747
22073
  } catch (cause) {
@@ -21828,7 +22154,7 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
21828
22154
  onClick: () => void setShowScheduleButton(!showScheduleButton)
21829
22155
  },
21830
22156
  /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-switch-knob" })
21831
- )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showScheduleButtonHint"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["categories", "tab.categories"], ["roster", "tab.roster"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
22157
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showScheduleButtonHint"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["categories", "tab.categories"], ["roster", "tab.roster"], ["teams", "tab.teams"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
21832
22158
  "button",
21833
22159
  {
21834
22160
  className: "t-team-tab",
@@ -21840,7 +22166,7 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
21840
22166
  onClick: () => setTab(key)
21841
22167
  },
21842
22168
  t(label)
21843
- ))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "categories" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement(CategoriesTab, { t, remote, categories, onChanged: () => reload(true) }))), tab === "roster" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.squads")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.squadsHint"))), /* @__PURE__ */ import_react.default.createElement(SquadsTab, { t, remote, initialData: squadsPreview, initialDraft: draftPreview }), /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.teams")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.teamsHint"))), /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, initialTeams: teamsPreview }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "experts" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.expertsHint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-bar" }, /* @__PURE__ */ import_react.default.createElement(
22169
+ ))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "categories" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement(CategoriesTab, { t, remote, categories, onChanged: () => reload(true) }))), tab === "roster" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.squads")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.squadsHint"))), /* @__PURE__ */ import_react.default.createElement(SquadsTab, { t, remote, initialData: squadsPreview, initialDraft: draftPreview }))), tab === "teams" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("roster.teams")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.teamsHint"))), /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, initialTeams: teamsPreview }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody", hidden: tab !== "experts" }, /* @__PURE__ */ import_react.default.createElement(SectionBoundary, { t, reloadKey: tab }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("roster.expertsHint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-bar" }, /* @__PURE__ */ import_react.default.createElement(
21844
22170
  "input",
21845
22171
  {
21846
22172
  className: "t-team-input",
@@ -22747,6 +23073,7 @@ function TeamsTab({ t, remote, initialTeams, compact }) {
22747
23073
  const [state, setState] = import_react.default.useState({ teams: initialTeams ?? null, error: "" });
22748
23074
  const [archived, setArchived] = import_react.default.useState(false);
22749
23075
  const [busyId, setBusyId] = import_react.default.useState("");
23076
+ const [removingId, setRemovingId] = import_react.default.useState("");
22750
23077
  const [note, setNote] = import_react.default.useState("");
22751
23078
  const load = import_react.default.useCallback(async (wantArchived) => {
22752
23079
  setState((current) => ({ teams: current.teams, error: "" }));
@@ -22769,12 +23096,13 @@ function TeamsTab({ t, remote, initialTeams, compact }) {
22769
23096
  }));
22770
23097
  }
22771
23098
  async function stop(team) {
22772
- if (!window.confirm(t("teams.stopConfirm", { name: team.name }))) return;
23099
+ const archiving = team.halted === true || team.captainActive === false;
23100
+ if (!window.confirm(t(archiving ? "teams.archiveConfirm" : "teams.stopConfirm", { name: team.name }))) return;
22773
23101
  setBusyId(team.teamId);
22774
23102
  setNote("");
22775
23103
  try {
22776
- const result = unwrap(await remote.stopTeam(team.teamId), "stopTeam");
22777
- setNote(t("teams.stopped", { name: result?.teamName ?? team.name, count: result?.cancelledTasks ?? 0 }));
23104
+ const result = unwrap(await remote.stopTeam(team.teamId, team.workspacePath), "stopTeam");
23105
+ setNote(result?.archived === true ? t("teams.archivedNote", { name: result?.teamName ?? team.name }) : t("teams.stopped", { name: result?.teamName ?? team.name, count: result?.cancelledTasks ?? 0 }));
22778
23106
  await load(archived);
22779
23107
  } catch (cause) {
22780
23108
  setState({ teams: state.teams, error: cause instanceof Error ? cause.message : String(cause) });
@@ -22782,14 +23110,43 @@ function TeamsTab({ t, remote, initialTeams, compact }) {
22782
23110
  setBusyId("");
22783
23111
  }
22784
23112
  }
23113
+ async function remove(team) {
23114
+ if (!window.confirm(t("teams.removeConfirm", { name: team.name }))) return;
23115
+ setRemovingId(team.teamId);
23116
+ setNote("");
23117
+ try {
23118
+ const result = unwrap(await remote.deleteTeam(team.teamId, team.workspacePath), "deleteTeam");
23119
+ setNote(t("teams.removed", { name: result?.teamName ?? team.name }));
23120
+ await load(archived);
23121
+ } catch (cause) {
23122
+ setState({ teams: state.teams, error: cause instanceof Error ? cause.message : String(cause) });
23123
+ } finally {
23124
+ setRemovingId("");
23125
+ }
23126
+ }
22785
23127
  const teams = state.teams;
22786
23128
  return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, compact !== true && /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => setArchived((value) => !value) }, archived ? t("teams.showActive") : t("teams.showArchived")), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => void load(archived) }, t("teams.refresh")), /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => openPanel((teams ?? [])[0]) }, t("teams.openPanel"))), note !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-ok" }, note), state.error !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, state.error), teams === null && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("teams.loading")), teams !== null && teams.length === 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, archived ? t("teams.emptyArchived") : t("teams.empty")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-teams" }, (teams ?? []).map((team) => {
22787
23129
  const taskList = Array.isArray(team?.tasks) ? team.tasks : [];
22788
23130
  const memberList = Array.isArray(team?.members) ? team.members : [];
22789
23131
  const done = taskList.filter((task) => task?.status === "completed").length;
22790
23132
  const working = memberList.filter((member) => member?.activity === "working").length;
23133
+ const archivable = team.halted === true || team.captainActive === false;
22791
23134
  const phase = team.halted === true ? t("teams.halted") : archived ? t("teams.archived") : team.phase === "staged" ? t("teams.staged") : t("teams.running");
22792
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team", key: team.teamId }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team-head" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-team-name", title: `${team.name} \xB7 ${team.teamId}` }, team.name), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-badge", "data-tone": team.halted === true ? "error" : team.phase === "staged" ? "warn" : void 0 }, phase)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("teams.members", { count: memberList.length })), /* @__PURE__ */ import_react.default.createElement("span", null, working > 0 ? `${t("teams.working", { count: working })} \xB7 ` : "", t("teams.tasks", { done, total: taskList.length })), /* @__PURE__ */ import_react.default.createElement("span", null, team.workspace)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, memberList.slice(0, 8).map((member) => /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-member-chip", key: member.name, "data-unknown": member.activity === "unknown" }, member.name))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => openPanel(team) }, t("teams.openPanel")), !archived && /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: busyId === team.teamId, onClick: () => void stop(team) }, busyId === team.teamId ? t("teams.stopping") : t("teams.stop"))));
23135
+ return (
23136
+ // key 必须带上工作区:teamId 跨工作区会重名,只用 teamId 会让两行共用同一个 key
23137
+ // (同列表内渲染状态互相串)。
23138
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team", key: `${team.workspacePath ?? ""}:${team.teamId}` }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team-head" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-team-name", title: `${team.name} \xB7 ${team.teamId}` }, team.name), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-badge", "data-tone": team.halted === true ? "error" : team.phase === "staged" ? "warn" : void 0 }, phase)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team-meta" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("teams.members", { count: memberList.length })), /* @__PURE__ */ import_react.default.createElement("span", null, working > 0 ? `${t("teams.working", { count: working })} \xB7 ` : "", t("teams.tasks", { done, total: taskList.length })), /* @__PURE__ */ import_react.default.createElement("span", null, team.workspace)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, memberList.slice(0, 8).map((member) => /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-member-chip", key: member.name, "data-unknown": member.activity === "unknown" }, member.name))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => openPanel(team) }, t("teams.openPanel")), !archived && /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", disabled: busyId === team.teamId || removingId === team.teamId, onClick: () => void stop(team) }, busyId === team.teamId ? archivable ? t("teams.archiving") : t("teams.stopping") : archivable ? t("teams.archive") : t("teams.stop")), /* @__PURE__ */ import_react.default.createElement(
23139
+ "button",
23140
+ {
23141
+ className: "t-team-chip",
23142
+ type: "button",
23143
+ "data-tone": "error",
23144
+ disabled: removingId === team.teamId || busyId === team.teamId,
23145
+ onClick: () => void remove(team)
23146
+ },
23147
+ removingId === team.teamId ? t("teams.removing") : t("teams.remove")
23148
+ )))
23149
+ );
22793
23150
  })));
22794
23151
  }
22795
23152
  function resolveInputTarget(ctx, sessionId) {