dsh-plugin-t-expert 0.2.15 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/lib/client.js +578 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,18 @@ DeepSeek Harness 插件:**22 个分类 / 316 位专家**的名册(中文名
|
|
|
15
15
|
设置页一共**三个标签**:**专家**(启停 / 看提示词 / 新建自建专家)、**分类**(自建分类的增删改)、
|
|
16
16
|
**队伍**(小队模板 + 运行中的团队)。
|
|
17
17
|
|
|
18
|
+
真实的后台任务列表不在设置页。右上角有一个**常驻的小徽章**(与团队徽章同一层、同一视觉体系),
|
|
19
|
+
它是随时点开查看的入口;只有真的还有东西在跑时才亮起来并给出运行中数量(安静时只是一个灰点,不显示数字),
|
|
20
|
+
点开就是任务面板:当前会话及其子代理的后台任务,只读(状态、退出码与耗时,
|
|
21
|
+
不含输出与终止)。面板可拖动,位置记在本机。
|
|
22
|
+
|
|
23
|
+
徽章只在"从无到有"时冒出,**面板绝不自动展开**——展开与否完全由你决定,关掉之后同一批任务不会再打扰你。
|
|
24
|
+
子代理区显示的是**子代理的真名**(取自语宿主的子代理目录 `label`,而不是从提示词生成的会话标题),
|
|
25
|
+
点一行即可跳到那个子代理的会话(与官方任务页同一个 `openSubagent` 接口)。
|
|
26
|
+
每行副标题显示该子代理的**请求文本前一段**(会话标题就是从它生成的),并标注运行中/已结束;
|
|
27
|
+
列表顺序沿用宿主子代理目录的原生次序——与官方任务页一致(快照里没有创建时间字段,不自造一种序)。
|
|
28
|
+
输入区 **T专家** 按钮弹开的浮层里也有一个**任务**页(与团队 tab 同层),随时手动查看。
|
|
29
|
+
|
|
18
30
|
---
|
|
19
31
|
|
|
20
32
|
## 一、安装
|
|
@@ -183,7 +195,7 @@ Host/Origin 校验与浏览器认证,静态资源另有白名单,非白名
|
|
|
183
195
|
```bash
|
|
184
196
|
npm install # 装开发期依赖(.npmrc 里开了 legacy-peer-deps,原因见该文件)
|
|
185
197
|
npm run typecheck # 类型检查(tsconfig.json 只覆盖自研 Host 文件,不碰并入的引擎)
|
|
186
|
-
npm run verify # 自检套件(现
|
|
198
|
+
npm run verify # 自检套件(现 521 项断言):插件契约、工具、remote、客户端产物、播种与快照,
|
|
187
199
|
# 外加「发布包自洽」(真打一份 tgz、解开、再用插件解析器读一遍)
|
|
188
200
|
npm run invariants # 引擎不变量门禁:真 cordis + 真 lib/teams + 桩 Agent,只报「违反集 D」
|
|
189
201
|
npm run build # 构建客户端产物(lib/client.js)
|
|
@@ -209,6 +221,19 @@ npm run sync-data # 把运行时数据同步进包内 data/
|
|
|
209
221
|
| `INV-6` | `review` 必须带 `reviewedTaskId`;`needs_revision` 走 `failed` 且自动生成 `repair`;`verdict=pass` 才算完成 |
|
|
210
222
|
| `INV-7` | 质量类建单契约:`implementation` 必须带 `objective`/`acceptance`/`inScope`/`verify` |
|
|
211
223
|
| `INV-8` | 调度器契约:`approve` 后 pending 任务会被自动派给空闲成员(不是停在池里等人 claim) |
|
|
224
|
+
| `INV-9` | 宿主路径契约:`agents` 未就绪时快照不得抛错(用**真 cordis 受门控 ctx** 钉住;旧写法会抛 `without inject`,那一瞬间所有团队会从面板消失) |
|
|
225
|
+
|
|
226
|
+
**聚合入口**:`npm run invariants -- --all` 会把引擎不变量与既有的三道门禁一起跑,统一输出违反集 `D`:
|
|
227
|
+
|
|
228
|
+
| 聚合项 | 说明 |
|
|
229
|
+
| --- | --- |
|
|
230
|
+
| 引擎不变量 | 本文件的 9 条(真 cordis + 真 `lib/teams` + 桩 Agent) |
|
|
231
|
+
| 插件契约 | 以**黑盒**调用 `tools/verify.mjs`(521 项),只取退出码与摘要行 |
|
|
232
|
+
| 宿主 skill 漂移 | `tools/skill-drift.mjs`(它现在也能独立跑:有漂移即退出码 1) |
|
|
233
|
+
| 数据快照一致 | `tools/sync-data.mjs --check`(**源目录不存在时显式记 SKIP**,不算违反) |
|
|
234
|
+
|
|
235
|
+
约定:门禁报告「已跳过」不算违反(与 `verify.mjs` 的 skip 规矩一致)。CI 里仍分别跑 `verify` 与
|
|
236
|
+
`invariants`(不重复跑一遍 `--all`)。
|
|
212
237
|
|
|
213
238
|
探针基座的三条忠告写在 `tools/invariants.mjs` 的文件头(成员 id 要 approve 后才出现、团队 id
|
|
214
239
|
会被 slug 化、工具执行上下文必须带 `signal` 且成员 agent 要有 `status`/`whenIdle`)——它们都是
|
package/lib/client.js
CHANGED
|
@@ -19888,6 +19888,31 @@ var zh = {
|
|
|
19888
19888
|
"tab.squads": "\u5C0F\u961F",
|
|
19889
19889
|
"tab.teams": "\u56E2\u961F",
|
|
19890
19890
|
"tab.roster": "\u961F\u4F0D",
|
|
19891
|
+
"tab.tasks": "\u4EFB\u52A1",
|
|
19892
|
+
"tasks.hint": "\u5F53\u524D\u4F1A\u8BDD\u53CA\u5176\u5B50\u4EE3\u7406\u7684\u540E\u53F0\u4EFB\u52A1\uFF1A\u7531\u5BBF\u4E3B\u7684 session/jobs \u63A8\u9001\u5B9E\u65F6\u5237\u65B0\uFF0C\u53EA\u8BFB\uFF0C\u4E0D\u542B\u8F93\u51FA\u4E0E\u7EC8\u6B62\u3002",
|
|
19893
|
+
"tasks.empty": "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u540E\u53F0\u4EFB\u52A1\u3002",
|
|
19894
|
+
"tasks.count": "{total} \u9879",
|
|
19895
|
+
"tasks.countRunning": "{running} \u9879\u8FD0\u884C\u4E2D \xB7 {total} \u9879",
|
|
19896
|
+
"tasks.owner": "\u5F52\u5C5E\uFF1A{name}",
|
|
19897
|
+
"tasks.status.running": "\u8FD0\u884C\u4E2D",
|
|
19898
|
+
"tasks.status.stopping": "\u505C\u6B62\u4E2D",
|
|
19899
|
+
"tasks.status.completed": "\u5DF2\u5B8C\u6210",
|
|
19900
|
+
"tasks.status.killed": "\u5DF2\u7EC8\u6B62",
|
|
19901
|
+
"tasks.status.failed": "\u5931\u8D25",
|
|
19902
|
+
"tasks.duration.seconds": "{seconds} \u79D2",
|
|
19903
|
+
"tasks.duration.minutes": "{minutes} \u5206 {seconds} \u79D2",
|
|
19904
|
+
"tasks.badgeTitle": "\u540E\u53F0\u4EFB\u52A1\uFF1A\u70B9\u5F00\u770B\u5217\u8868\uFF08\u53EF\u62D6\u52A8\u9762\u677F\uFF09",
|
|
19905
|
+
"tasks.subagents": "\u5B50\u4EE3\u7406",
|
|
19906
|
+
"tasks.subagentRunningCount": "{running} \u4E2A\u5B50\u4EE3\u7406\u8FD0\u884C\u4E2D",
|
|
19907
|
+
"tasks.subagentCount": "{total} \u4E2A\u5B50\u4EE3\u7406",
|
|
19908
|
+
"tasks.jobRunningCount": "{running} \u9879\u4EFB\u52A1\u8FD0\u884C\u4E2D",
|
|
19909
|
+
"tasks.jobCount": "{total} \u9879\u4EFB\u52A1",
|
|
19910
|
+
"tasks.noSubagents": "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5B50\u4EE3\u7406\u3002",
|
|
19911
|
+
"tasks.stateRunning": "\u8FD0\u884C\u4E2D",
|
|
19912
|
+
"tasks.modeOneShot": "\u4E00\u6B21\u6027",
|
|
19913
|
+
"tasks.modeContinuable": "\u53EF\u7EE7\u7EED",
|
|
19914
|
+
"tasks.stateInactive": "\u5DF2\u7ED3\u675F",
|
|
19915
|
+
"tasks.subagentOpen": "\u70B9\u51FB\u6253\u5F00\u8BE5\u5B50\u4EE3\u7406\u4F1A\u8BDD",
|
|
19891
19916
|
"roster.squads": "\u5C0F\u961F\u6A21\u677F\uFF08\u53EF\u590D\u7528\uFF09",
|
|
19892
19917
|
"roster.squadsHint": "\u9884\u5148\u6311\u597D\u7684\u73ED\u5E95\uFF1A\u6210\u5458\u3001\u522B\u540D\u3001\u542F\u505C\u3002\u7528\u5B83\u5C31\u8BF4 /t <\u5C0F\u961F> <\u76EE\u6807>\uFF0C\u6216\u5728\u8F93\u5165\u6846\u5F39\u7A97\u91CC\u70B9\u300C\u586B\u5165\u300D\uFF1B\u6539\u5B8C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\u3001\u65E0\u9700\u91CD\u542F\uFF08/t \u5217\u8868\u4F1A\u7ACB\u5373\u5237\u65B0\uFF0C\u5F15\u64CE\u4F1A\u5C31\u5730\u70ED\u91CD\u8F7D\uFF09\uFF1B\u4FDD\u5B58\u5931\u8D25\u4F1A\u56DE\u6EDA\uFF0C\u76D8\u4E0A\u4ECD\u662F\u65E7\u914D\u7F6E\u3002",
|
|
19893
19918
|
"roster.teams": "\u8FD0\u884C\u4E2D\u7684\u56E2\u961F",
|
|
@@ -20033,6 +20058,31 @@ var en = {
|
|
|
20033
20058
|
"tab.squads": "Squads",
|
|
20034
20059
|
"tab.teams": "Teams",
|
|
20035
20060
|
"tab.roster": "Roster",
|
|
20061
|
+
"tab.tasks": "Tasks",
|
|
20062
|
+
"tasks.hint": "Background jobs of the current conversation and its subagents, refreshed live from the host session/jobs push \u2014 read-only, no output view, no kill.",
|
|
20063
|
+
"tasks.empty": "No background jobs in this conversation yet.",
|
|
20064
|
+
"tasks.count": "{total} total",
|
|
20065
|
+
"tasks.countRunning": "{running} running \xB7 {total} total",
|
|
20066
|
+
"tasks.owner": "owner: {name}",
|
|
20067
|
+
"tasks.status.running": "running",
|
|
20068
|
+
"tasks.status.stopping": "stopping",
|
|
20069
|
+
"tasks.status.completed": "completed",
|
|
20070
|
+
"tasks.status.killed": "killed",
|
|
20071
|
+
"tasks.status.failed": "failed",
|
|
20072
|
+
"tasks.duration.seconds": "{seconds}s",
|
|
20073
|
+
"tasks.duration.minutes": "{minutes}m {seconds}s",
|
|
20074
|
+
"tasks.badgeTitle": "Background jobs \u2014 click to open the list (draggable)",
|
|
20075
|
+
"tasks.subagents": "Subagents",
|
|
20076
|
+
"tasks.subagentRunningCount": "{running} subagents running",
|
|
20077
|
+
"tasks.subagentCount": "{total} subagents",
|
|
20078
|
+
"tasks.jobRunningCount": "{running} jobs running",
|
|
20079
|
+
"tasks.jobCount": "{total} jobs",
|
|
20080
|
+
"tasks.noSubagents": "No subagents in this conversation.",
|
|
20081
|
+
"tasks.stateRunning": "running",
|
|
20082
|
+
"tasks.modeOneShot": "one-shot",
|
|
20083
|
+
"tasks.modeContinuable": "continuable",
|
|
20084
|
+
"tasks.stateInactive": "finished",
|
|
20085
|
+
"tasks.subagentOpen": "Open this subagent session",
|
|
20036
20086
|
"roster.squads": "Squad templates (reusable)",
|
|
20037
20087
|
"roster.squadsHint": "A pre-picked roster: members, aliases, enable/disable. Run it with /t <squad> <goal>, or tap Fill in the composer popup. Saving takes effect immediately, with no restart (the /t list refreshes at once and the engine hot-reloads in place); a failed save rolls back, so the old config stays on disk.",
|
|
20038
20088
|
"roster.teams": "Running teams",
|
|
@@ -20179,6 +20229,17 @@ var CSS = `
|
|
|
20179
20229
|
.t-team-picker-row:hover{background:var(--dsw-alias-bg-layer-3)}
|
|
20180
20230
|
.t-team-picker-slug{color:var(--dsw-alias-label-caption);font-family:var(--dsw-font-markdown-code-block-small);font-size:11px}
|
|
20181
20231
|
.t-team-selected-row{display:flex;align-items:center;gap:6px;font-size:12px;padding:3px 4px;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
20232
|
+
.t-team-jobs{display:flex;flex-direction:column;gap:4px}
|
|
20233
|
+
.t-team-job-row{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
|
|
20234
|
+
.t-team-job-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--dsw-alias-label-caption)}
|
|
20235
|
+
.t-team-job-dot[data-state="running"]{background:var(--dsw-alias-state-success-primary)}
|
|
20236
|
+
.t-team-job-dot[data-state="stopping"]{background:var(--dsw-alias-state-warn-primary)}
|
|
20237
|
+
.t-team-job-dot[data-state="completed"]{background:var(--dsw-alias-label-secondary)}
|
|
20238
|
+
.t-team-job-dot[data-state="killed"]{background:var(--dsw-alias-state-warn-primary)}
|
|
20239
|
+
.t-team-job-dot[data-state="failed"]{background:var(--dsw-alias-state-error-primary)}
|
|
20240
|
+
.t-team-job-main{flex:1 1 auto;min-width:0;width:100%;display:flex;flex-direction:column;gap:2px}
|
|
20241
|
+
.t-team-job-label{font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
20242
|
+
.t-team-job-meta{font-size:12px;color:var(--dsw-alias-label-caption);display:flex;flex-direction:column;gap:1px;min-width:0}
|
|
20182
20243
|
.t-team-teams{display:flex;flex-direction:column;gap:8px}
|
|
20183
20244
|
.t-team-team{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);padding:10px 12px;display:flex;flex-direction:column;gap:6px}
|
|
20184
20245
|
.t-team-team-head{display:flex;align-items:center;gap:8px}
|
|
@@ -20258,6 +20319,40 @@ var CSS = `
|
|
|
20258
20319
|
.t-team-pop .t-team-squad{padding:8px;gap:6px}
|
|
20259
20320
|
.t-team-pop .t-team-squad-desc{font-size:12px}
|
|
20260
20321
|
.t-team-pop .t-team-team{padding:8px}
|
|
20322
|
+
.t-team-pop .t-team-job-row{padding:5px 7px}
|
|
20323
|
+
.t-team-job-row[data-kind="subagent"] .t-team-job-label{font-weight:500}
|
|
20324
|
+
.t-team-job-desc{min-width:0;width:100%;display:block;font-size:11.5px;line-height:1.35;color:var(--dsw-alias-label-caption);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
20325
|
+
.t-team-job-row[data-clickable="true"]{cursor:pointer}
|
|
20326
|
+
.t-team-job-row[data-clickable="true"]:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
20327
|
+
/* shell.overlay \u91CC\u7684\u4EFB\u52A1\u6D6E\u5C42\uFF1A\u5FBD\u7AE0\u4E0E\u56E2\u961F\u5FBD\u7AE0\u540C\u4E00\u89C6\u89C9\u4F53\u7CFB\uFF08\u6BDB\u73BB\u7483+\u8109\u51B2\uFF09 */
|
|
20328
|
+
.t-team-job-badge{position:fixed;z-index:55;cursor:grab;touch-action:none;box-sizing:border-box;display:inline-flex;align-items:center;gap:7px;height:34px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-3));backdrop-filter:blur(16px);box-shadow:0 8px 28px color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;font-weight:600;line-height:20px;cursor:pointer;transition:border-color .15s,transform .12s}
|
|
20329
|
+
.t-team-job-badge:hover{border-color:var(--dsw-alias-border-l3);transform:translateY(-1px)}
|
|
20330
|
+
.t-team-job-badge:active{cursor:grabbing}
|
|
20331
|
+
.t-team-job-badge:active{transform:translateY(0) scale(.98)}
|
|
20332
|
+
.t-team-job-badge-dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--dsw-alias-label-tertiary)}
|
|
20333
|
+
.t-team-job-badge-dot[data-live="true"]{background:var(--dsw-alias-state-success-primary);animation:t-team-job-pulse 1.25s ease-in-out infinite}
|
|
20334
|
+
.t-team-job-badge-count{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}
|
|
20335
|
+
@keyframes t-team-job-pulse{0%,to{opacity:.42}50%{opacity:1}}
|
|
20336
|
+
.t-team-job-panel{position:fixed;z-index:60;display:flex;flex-direction:column;width:380px;max-height:min(460px,72vh);box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-3));backdrop-filter:blur(20px) saturate(1.08);box-shadow:0 12px 32px color-mix(in srgb, var(--dsw-alias-label-primary) 12%, transparent), 0 32px 72px color-mix(in srgb, var(--dsw-alias-label-primary) 16%, transparent);color:var(--dsw-alias-label-primary);animation:t-team-job-panel-in .16s ease-out}
|
|
20337
|
+
@keyframes t-team-job-panel-in{0%{opacity:0}to{opacity:1}}
|
|
20338
|
+
.t-team-job-panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:44px;padding:0 10px 0 16px;border-bottom:1px solid var(--dsw-alias-border-l2);cursor:grab;touch-action:none;user-select:none}
|
|
20339
|
+
.t-team-job-panel-head:active{cursor:grabbing}
|
|
20340
|
+
.t-team-job-panel-title{font-size:14px;font-weight:600}
|
|
20341
|
+
.t-team-job-panel-close{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;cursor:pointer}
|
|
20342
|
+
.t-team-job-panel-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
20343
|
+
.t-team-job-panel-tabs{display:flex;gap:4px;padding:6px 6px 0;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
20344
|
+
.t-team-job-panel-tabs .t-team-tab{font-size:12px;padding:3px 9px}
|
|
20345
|
+
.t-team-job-tab-count{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;margin-left:2px}
|
|
20346
|
+
.t-team-job-panel-body{display:flex;flex-direction:column;gap:6px;padding:6px;overflow:auto;min-height:0;scrollbar-width:thin;scrollbar-color:color-mix(in srgb, var(--dsw-alias-label-tertiary) 28%, transparent) transparent}
|
|
20347
|
+
.t-team-job-panel-body::-webkit-scrollbar{width:6px}
|
|
20348
|
+
.t-team-job-panel-body::-webkit-scrollbar-track{background:transparent}
|
|
20349
|
+
.t-team-job-panel-body::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--dsw-alias-label-tertiary) 28%, transparent);background-clip:padding-box;border:2px solid transparent;border-radius:999px}
|
|
20350
|
+
.t-team-job-panel-body:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb, var(--dsw-alias-label-tertiary) 44%, transparent);background-clip:padding-box}
|
|
20351
|
+
.t-team-job-panel-body .t-team-jobs{gap:2px}
|
|
20352
|
+
.t-team-job-panel-body .t-team-job-row{padding:3px 6px;gap:6px;border-radius:6px;background:color-mix(in srgb, var(--dsw-alias-label-primary) 5%, transparent)}
|
|
20353
|
+
.t-team-job-panel-body .t-team-job-meta{gap:2px}
|
|
20354
|
+
.t-team-job-panel-body .t-team-section-head{margin-top:2px}
|
|
20355
|
+
.t-team-pop[data-tab="tasks"]{width:420px;max-height:520px}
|
|
20261
20356
|
.t-team-pop .t-team-picker-list{max-height:150px}
|
|
20262
20357
|
.t-team-pop-list{display:flex;flex-direction:column;gap:6px;overflow:auto;min-height:0}
|
|
20263
20358
|
.t-team-pop-item{display:flex;gap:8px;align-items:flex-start;text-align:left;padding:6px 8px;border-radius:8px;border:1px solid transparent;background:transparent;color:inherit;font:inherit;cursor:pointer}
|
|
@@ -20410,7 +20505,7 @@ var SectionBoundary = class extends import_react.default.Component {
|
|
|
20410
20505
|
return this.props.children;
|
|
20411
20506
|
}
|
|
20412
20507
|
};
|
|
20413
|
-
function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squadsPreview, teamsPreview, draftPreview }) {
|
|
20508
|
+
function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab, squadsPreview, teamsPreview, draftPreview }) {
|
|
20414
20509
|
const [snapshot, setSnapshot] = import_react.default.useState(() => snapshotOf(remote) ?? null);
|
|
20415
20510
|
const [query, setQuery] = import_react.default.useState("");
|
|
20416
20511
|
const [division, setDivision] = import_react.default.useState("");
|
|
@@ -20824,6 +20919,472 @@ function SettingsPanel({ t, remote, getActive, refreshCatalog, initialTab, squad
|
|
|
20824
20919
|
)
|
|
20825
20920
|
));
|
|
20826
20921
|
}
|
|
20922
|
+
function isJobRowLive(job) {
|
|
20923
|
+
return job.status === "running" || job.status === "stopping";
|
|
20924
|
+
}
|
|
20925
|
+
function inJobSessionTree(byId, sessionId, rootId) {
|
|
20926
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20927
|
+
let cursor = sessionId;
|
|
20928
|
+
while (typeof cursor === "string" && cursor !== "" && !seen.has(cursor)) {
|
|
20929
|
+
if (cursor === rootId) return true;
|
|
20930
|
+
seen.add(cursor);
|
|
20931
|
+
const parent = byId?.[cursor]?.parentId;
|
|
20932
|
+
cursor = typeof parent === "string" ? parent : void 0;
|
|
20933
|
+
}
|
|
20934
|
+
return false;
|
|
20935
|
+
}
|
|
20936
|
+
function collectJobRows(byId, jobsBySession, rootId) {
|
|
20937
|
+
const rows = [];
|
|
20938
|
+
if (jobsBySession === void 0 || rootId === void 0) return rows;
|
|
20939
|
+
for (const [sessionId, jobs] of Object.entries(jobsBySession)) {
|
|
20940
|
+
if (!Array.isArray(jobs) || jobs.length === 0) continue;
|
|
20941
|
+
if (!inJobSessionTree(byId, sessionId, rootId)) continue;
|
|
20942
|
+
const ownerTitle = byId?.[sessionId]?.displayTitle ?? sessionId;
|
|
20943
|
+
for (const job of jobs) rows.push({ ownerSessionId: sessionId, ownerTitle, job });
|
|
20944
|
+
}
|
|
20945
|
+
return rows;
|
|
20946
|
+
}
|
|
20947
|
+
function orderJobRows(rows) {
|
|
20948
|
+
return [...rows].sort((left, right) => {
|
|
20949
|
+
const liveLeft = isJobRowLive(left.job);
|
|
20950
|
+
if (liveLeft !== isJobRowLive(right.job)) return liveLeft ? -1 : 1;
|
|
20951
|
+
if (liveLeft) return left.job.startedAt - right.job.startedAt;
|
|
20952
|
+
const settled = (right.job.finishedAt ?? right.job.startedAt) - (left.job.finishedAt ?? left.job.startedAt);
|
|
20953
|
+
return settled !== 0 ? settled : left.job.startedAt - right.job.startedAt;
|
|
20954
|
+
});
|
|
20955
|
+
}
|
|
20956
|
+
function jobRowDotState(status) {
|
|
20957
|
+
switch (status) {
|
|
20958
|
+
case "running":
|
|
20959
|
+
return "running";
|
|
20960
|
+
case "stopping":
|
|
20961
|
+
return "stopping";
|
|
20962
|
+
case "completed":
|
|
20963
|
+
return "completed";
|
|
20964
|
+
case "killed":
|
|
20965
|
+
return "killed";
|
|
20966
|
+
case "failed":
|
|
20967
|
+
return "failed";
|
|
20968
|
+
default:
|
|
20969
|
+
return "unknown";
|
|
20970
|
+
}
|
|
20971
|
+
}
|
|
20972
|
+
function jobRowStatusKey(status) {
|
|
20973
|
+
switch (status) {
|
|
20974
|
+
case "running":
|
|
20975
|
+
return "tasks.status.running";
|
|
20976
|
+
case "stopping":
|
|
20977
|
+
return "tasks.status.stopping";
|
|
20978
|
+
case "completed":
|
|
20979
|
+
return "tasks.status.completed";
|
|
20980
|
+
case "killed":
|
|
20981
|
+
return "tasks.status.killed";
|
|
20982
|
+
case "failed":
|
|
20983
|
+
return "tasks.status.failed";
|
|
20984
|
+
default:
|
|
20985
|
+
return void 0;
|
|
20986
|
+
}
|
|
20987
|
+
}
|
|
20988
|
+
function jobRowDetail(job) {
|
|
20989
|
+
const detail = typeof job.detail === "string" ? job.detail.trim() : "";
|
|
20990
|
+
if (detail === "") return void 0;
|
|
20991
|
+
if (job.status === "completed" && /^exit code:\s*0$/iu.test(detail)) return void 0;
|
|
20992
|
+
return detail;
|
|
20993
|
+
}
|
|
20994
|
+
function formatJobRowDuration(ms, t) {
|
|
20995
|
+
if (typeof ms !== "number" || !Number.isFinite(ms) || ms < 0) return "";
|
|
20996
|
+
const total = Math.floor(ms / 1e3);
|
|
20997
|
+
const minutes = Math.floor(total / 60);
|
|
20998
|
+
const seconds = total % 60;
|
|
20999
|
+
return minutes > 0 ? t("tasks.duration.minutes", { minutes, seconds }) : t("tasks.duration.seconds", { seconds });
|
|
21000
|
+
}
|
|
21001
|
+
function collectTreeSubagents(byId, subagentsByParent, rootId) {
|
|
21002
|
+
const out = [];
|
|
21003
|
+
if (rootId === void 0) return out;
|
|
21004
|
+
const now = Date.now();
|
|
21005
|
+
for (const summary of Object.values(byId ?? {})) {
|
|
21006
|
+
if (summary?.id === void 0 || summary.id === rootId) continue;
|
|
21007
|
+
if (!inJobSessionTree(byId, summary.id, rootId)) continue;
|
|
21008
|
+
const catalog = subagentsByParent?.[summary.parentId ?? ""];
|
|
21009
|
+
const entry = (catalog?.entries ?? []).find((row) => row?.kind === "child" && row.id === summary.id);
|
|
21010
|
+
const label = typeof entry?.label === "string" && entry.label !== "" ? entry.label : void 0;
|
|
21011
|
+
out.push({
|
|
21012
|
+
id: summary.id,
|
|
21013
|
+
parentId: summary.parentId,
|
|
21014
|
+
title: label ?? summary.displayTitle ?? summary.id,
|
|
21015
|
+
named: label !== void 0,
|
|
21016
|
+
// 请求文本前一段:会话标题就是从它生成的,官方也用同一份来当副标题
|
|
21017
|
+
request: summary.displayTitle ?? "",
|
|
21018
|
+
running: entry === void 0 ? summary.running === true : entry.activity === "running",
|
|
21019
|
+
mode: entry?.mode,
|
|
21020
|
+
direct: summary.parentId === rootId,
|
|
21021
|
+
// 目录数组里靠后的视为更新(排序时按倒序用),未收录的以 -1 沉底
|
|
21022
|
+
order: entry === void 0 ? -1 : (catalog?.entries ?? []).indexOf(entry),
|
|
21023
|
+
now
|
|
21024
|
+
});
|
|
21025
|
+
}
|
|
21026
|
+
return out.sort((left, right) => {
|
|
21027
|
+
if (left.order !== right.order) return right.order - left.order;
|
|
21028
|
+
return String(left.title).localeCompare(String(right.title));
|
|
21029
|
+
});
|
|
21030
|
+
}
|
|
21031
|
+
function renderSubagentRows({ subagents, t, onOpen }) {
|
|
21032
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-jobs" }, subagents.map((item) => /* @__PURE__ */ import_react.default.createElement(
|
|
21033
|
+
"div",
|
|
21034
|
+
{
|
|
21035
|
+
className: "t-team-job-row",
|
|
21036
|
+
"data-kind": "subagent",
|
|
21037
|
+
"data-clickable": onOpen === void 0 ? void 0 : "true",
|
|
21038
|
+
key: `subagent:${item.id}`,
|
|
21039
|
+
role: onOpen === void 0 ? void 0 : "button",
|
|
21040
|
+
tabIndex: onOpen === void 0 ? void 0 : 0,
|
|
21041
|
+
title: onOpen === void 0 ? item.id : `${item.id} \xB7 ${t("tasks.subagentOpen")}`,
|
|
21042
|
+
onClick: onOpen === void 0 ? void 0 : () => onOpen(item),
|
|
21043
|
+
onKeyDown: onOpen === void 0 ? void 0 : (event) => {
|
|
21044
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
21045
|
+
event.preventDefault();
|
|
21046
|
+
onOpen(item);
|
|
21047
|
+
}
|
|
21048
|
+
}
|
|
21049
|
+
},
|
|
21050
|
+
/* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-dot", "data-state": item.running ? "running" : "completed", "aria-hidden": "true" }),
|
|
21051
|
+
/* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-main" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-label", title: `${item.id} \xB7 ${item.running ? t("tasks.stateRunning") : t("tasks.stateInactive")}` }, item.title), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-desc" }, [
|
|
21052
|
+
item.request !== "" ? item.request : void 0,
|
|
21053
|
+
// 目录没给 mode 就整段跳过,不要冒出一个孤零零的"可继续"
|
|
21054
|
+
item.mode === "one-shot" ? t("tasks.modeOneShot") : item.mode === void 0 ? void 0 : t("tasks.modeContinuable"),
|
|
21055
|
+
item.running ? t("tasks.stateRunning") : t("tasks.stateInactive")
|
|
21056
|
+
].filter(Boolean).join(" \xB7 ")))
|
|
21057
|
+
)));
|
|
21058
|
+
}
|
|
21059
|
+
function useJobRows(ctx) {
|
|
21060
|
+
const list = ctx?.sessions?.list;
|
|
21061
|
+
const snapshot = import_react.default.useSyncExternalStore(
|
|
21062
|
+
import_react.default.useCallback((onChange) => typeof list?.subscribe === "function" ? list.subscribe(onChange) : () => {
|
|
21063
|
+
}, [list]),
|
|
21064
|
+
import_react.default.useCallback(() => typeof list?.getSnapshot === "function" ? list.getSnapshot() : void 0, [list]),
|
|
21065
|
+
// 首次渲染(含 SSR)也要立刻拿到数据,否则要先空一帧、等订阅推一次才出现。
|
|
21066
|
+
import_react.default.useCallback(() => typeof list?.getSnapshot === "function" ? list.getSnapshot() : void 0, [list])
|
|
21067
|
+
);
|
|
21068
|
+
const rootId = snapshot?.current;
|
|
21069
|
+
const byId = snapshot?.byId ?? {};
|
|
21070
|
+
const rows = import_react.default.useMemo(
|
|
21071
|
+
() => orderJobRows(collectJobRows(byId, snapshot?.jobsBySession, rootId)),
|
|
21072
|
+
[byId, snapshot?.jobsBySession, rootId]
|
|
21073
|
+
);
|
|
21074
|
+
const liveCount = rows.reduce((count, row) => count + (isJobRowLive(row.job) ? 1 : 0), 0);
|
|
21075
|
+
const [now, setNow] = import_react.default.useState(() => Date.now());
|
|
21076
|
+
import_react.default.useEffect(() => {
|
|
21077
|
+
if (liveCount === 0) return void 0;
|
|
21078
|
+
const timer = window.setInterval(() => setNow(Date.now()), 1e3);
|
|
21079
|
+
return () => window.clearInterval(timer);
|
|
21080
|
+
}, [liveCount]);
|
|
21081
|
+
const subagents = import_react.default.useMemo(
|
|
21082
|
+
() => collectTreeSubagents(byId, snapshot?.subagentsByParent, rootId),
|
|
21083
|
+
[byId, snapshot?.subagentsByParent, rootId]
|
|
21084
|
+
);
|
|
21085
|
+
const subagentsRunning = subagents.reduce((count, item) => count + (item.running ? 1 : 0), 0);
|
|
21086
|
+
return { rows, liveCount, subagents, subagentsRunning, now };
|
|
21087
|
+
}
|
|
21088
|
+
function renderJobRows({ rows, now, multiOwner, t }) {
|
|
21089
|
+
if (rows.length === 0) return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("tasks.empty"));
|
|
21090
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-jobs" }, rows.map((row) => {
|
|
21091
|
+
const job = row.job ?? {};
|
|
21092
|
+
const live = isJobRowLive(job);
|
|
21093
|
+
const elapsed = live ? now - job.startedAt : (job.finishedAt ?? job.startedAt) - job.startedAt;
|
|
21094
|
+
const statusKey = jobRowStatusKey(job.status);
|
|
21095
|
+
const statusText = statusKey === void 0 ? String(job.status ?? "") : t(statusKey);
|
|
21096
|
+
const meta3 = [
|
|
21097
|
+
live ? void 0 : statusText,
|
|
21098
|
+
jobRowDetail(job),
|
|
21099
|
+
formatJobRowDuration(elapsed, t),
|
|
21100
|
+
multiOwner ? t("tasks.owner", { name: row.ownerTitle }) : void 0
|
|
21101
|
+
].filter(Boolean);
|
|
21102
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-job-row", key: `${row.ownerSessionId}:${job.id}` }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-dot", "data-state": jobRowDotState(job.status), "aria-hidden": "true" }), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-main" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-label", title: `${job.label ?? ""} \xB7 ${job.id ?? ""}` }, live ? `${statusText} \xB7 ${job.label ?? ""}` : job.label ?? ""), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-meta" }, meta3.map((part, index) => /* @__PURE__ */ import_react.default.createElement("span", { key: index }, part)))));
|
|
21103
|
+
}));
|
|
21104
|
+
}
|
|
21105
|
+
function JobsTab({ t, ctx, compact }) {
|
|
21106
|
+
const { rows, liveCount, subagents, now } = useJobRows(ctx);
|
|
21107
|
+
const multiOwner = new Set(rows.map((row) => row.ownerSessionId)).size > 1;
|
|
21108
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabbody" }, compact !== true && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-note" }, t("tasks.hint")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-team-meta" }, liveCount > 0 ? t("tasks.countRunning", { running: liveCount, total: rows.length }) : t("tasks.count", { total: rows.length })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("tasks.subagents")), subagents.length === 0 ? /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("tasks.noSubagents")) : renderSubagentRows({ subagents, t, onOpen: (item) => openSubagentSession(ctx, item) }), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-section-head" }, t("tab.tasks")), renderJobRows({ rows, now, multiOwner, t }));
|
|
21109
|
+
}
|
|
21110
|
+
function openSubagentSession(ctx, item) {
|
|
21111
|
+
const sessions = ctx?.sessions;
|
|
21112
|
+
if (sessions === void 0 || item?.id === void 0) return;
|
|
21113
|
+
const resolved = sessions.subagentAddress?.(item.id);
|
|
21114
|
+
const address = resolved ?? (item.parentId === void 0 ? void 0 : { parentSessionId: item.parentId, childSessionId: item.id, mode: item.mode ?? "continuable" });
|
|
21115
|
+
if (address === void 0) return;
|
|
21116
|
+
try {
|
|
21117
|
+
sessions.openSubagent?.(address);
|
|
21118
|
+
} catch (error62) {
|
|
21119
|
+
console.error("[t-team] \u6253\u5F00\u5B50\u4EE3\u7406\u4F1A\u8BDD\u5931\u8D25\uFF1A", error62);
|
|
21120
|
+
}
|
|
21121
|
+
}
|
|
21122
|
+
var JOB_DOCK_POS_KEY = "t-team:jobs-dock:v1";
|
|
21123
|
+
var JOB_BADGE_POS_KEY = "t-team:jobs-badge:v1";
|
|
21124
|
+
var JOB_BADGE_DEFAULT = { left: 0, top: 108 };
|
|
21125
|
+
var JOB_BADGE_ESTIMATE_WIDTH = 180;
|
|
21126
|
+
var JOB_DOCK_DEFAULT = { left: 18, top: 112 };
|
|
21127
|
+
var JOB_DOCK_MARGIN = 8;
|
|
21128
|
+
var JOB_DOCK_PANEL_WIDTH = 380;
|
|
21129
|
+
var JOB_DOCK_FALLBACK_WIDTH = 320;
|
|
21130
|
+
var JOB_DOCK_FALLBACK_HEIGHT = 420;
|
|
21131
|
+
var JOB_DOCK_DRAG_THRESHOLD = 4;
|
|
21132
|
+
function nonNegative(value, fallback) {
|
|
21133
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback();
|
|
21134
|
+
}
|
|
21135
|
+
function readBadgePos() {
|
|
21136
|
+
const fallback = { left: JOB_DOCK_MARGIN, top: JOB_BADGE_DEFAULT.top };
|
|
21137
|
+
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
|
|
21138
|
+
try {
|
|
21139
|
+
const raw = window.localStorage.getItem(JOB_BADGE_POS_KEY);
|
|
21140
|
+
if (raw !== null) {
|
|
21141
|
+
const parsed = JSON.parse(raw);
|
|
21142
|
+
if (typeof parsed?.left === "number" && typeof parsed?.top === "number") {
|
|
21143
|
+
return clampJobDockPos(parsed, { offsetWidth: JOB_BADGE_ESTIMATE_WIDTH });
|
|
21144
|
+
}
|
|
21145
|
+
}
|
|
21146
|
+
} catch {
|
|
21147
|
+
}
|
|
21148
|
+
}
|
|
21149
|
+
const computed = clampJobDockPos({
|
|
21150
|
+
left: (typeof window === "undefined" ? JOB_DOCK_MARGIN : window.innerWidth) - JOB_BADGE_ESTIMATE_WIDTH,
|
|
21151
|
+
top: JOB_BADGE_DEFAULT.top
|
|
21152
|
+
}, { offsetWidth: JOB_BADGE_ESTIMATE_WIDTH });
|
|
21153
|
+
return Number.isFinite(computed.left) && Number.isFinite(computed.top) ? computed : fallback;
|
|
21154
|
+
}
|
|
21155
|
+
function clampJobDockPos(pos, panel) {
|
|
21156
|
+
const viewport = typeof document === "undefined" ? void 0 : document.documentElement;
|
|
21157
|
+
const width = nonNegative(viewport?.clientWidth, () => nonNegative(
|
|
21158
|
+
typeof window === "undefined" ? void 0 : window.innerWidth,
|
|
21159
|
+
() => JOB_DOCK_PANEL_WIDTH
|
|
21160
|
+
));
|
|
21161
|
+
const height = nonNegative(viewport?.clientHeight, () => nonNegative(
|
|
21162
|
+
typeof window === "undefined" ? void 0 : window.innerHeight,
|
|
21163
|
+
() => JOB_DOCK_PANEL_WIDTH
|
|
21164
|
+
));
|
|
21165
|
+
const measured = panel?.offsetWidth ?? 0;
|
|
21166
|
+
const panelWidth = measured > 0 ? measured : JOB_DOCK_FALLBACK_WIDTH;
|
|
21167
|
+
const measuredHeight = panel?.offsetHeight ?? 0;
|
|
21168
|
+
const panelHeight = measuredHeight > 0 ? measuredHeight : JOB_DOCK_FALLBACK_HEIGHT;
|
|
21169
|
+
const maxLeft = Math.max(JOB_DOCK_MARGIN, width - panelWidth - JOB_DOCK_MARGIN);
|
|
21170
|
+
const maxTop = Math.max(JOB_DOCK_MARGIN, height - panelHeight - JOB_DOCK_MARGIN);
|
|
21171
|
+
return {
|
|
21172
|
+
left: Math.max(JOB_DOCK_MARGIN, Math.min(pos.left, maxLeft)),
|
|
21173
|
+
top: Math.max(JOB_DOCK_MARGIN, Math.min(pos.top, maxTop))
|
|
21174
|
+
};
|
|
21175
|
+
}
|
|
21176
|
+
function readJobDockPos() {
|
|
21177
|
+
if (typeof window === "undefined") return JOB_DOCK_DEFAULT;
|
|
21178
|
+
try {
|
|
21179
|
+
const raw = window.localStorage.getItem(JOB_DOCK_POS_KEY);
|
|
21180
|
+
if (raw === null) return JOB_DOCK_DEFAULT;
|
|
21181
|
+
const parsed = JSON.parse(raw);
|
|
21182
|
+
if (typeof parsed?.left !== "number" || typeof parsed?.top !== "number") return JOB_DOCK_DEFAULT;
|
|
21183
|
+
return { left: parsed.left, top: parsed.top };
|
|
21184
|
+
} catch {
|
|
21185
|
+
return JOB_DOCK_DEFAULT;
|
|
21186
|
+
}
|
|
21187
|
+
}
|
|
21188
|
+
function JobsDock({ t, ctx, preview }) {
|
|
21189
|
+
const { rows, liveCount, subagents, subagentsRunning, now } = useJobRows(ctx);
|
|
21190
|
+
const [open2, setOpen] = import_react.default.useState(preview?.open === true);
|
|
21191
|
+
const [panelTab, setPanelTab] = import_react.default.useState(preview?.tab ?? "subagents");
|
|
21192
|
+
const [pos, setPos] = import_react.default.useState(() => clampJobDockPos(readJobDockPos(), { offsetWidth: JOB_DOCK_PANEL_WIDTH }));
|
|
21193
|
+
const dragRef = import_react.default.useRef(void 0);
|
|
21194
|
+
const panelRef = import_react.default.useRef(null);
|
|
21195
|
+
const badgeRef = import_react.default.useRef(null);
|
|
21196
|
+
const badgeDragRef = import_react.default.useRef(void 0);
|
|
21197
|
+
const badgeMovedRef = import_react.default.useRef(false);
|
|
21198
|
+
const [badgePos, setBadgePos] = import_react.default.useState(readBadgePos);
|
|
21199
|
+
const closedByUserRef = import_react.default.useRef(false);
|
|
21200
|
+
const wasEmptyRef = import_react.default.useRef(true);
|
|
21201
|
+
import_react.default.useEffect(() => {
|
|
21202
|
+
const empty = rows.length === 0;
|
|
21203
|
+
const wasEmpty = wasEmptyRef.current;
|
|
21204
|
+
wasEmptyRef.current = empty;
|
|
21205
|
+
if (empty) {
|
|
21206
|
+
closedByUserRef.current = false;
|
|
21207
|
+
return;
|
|
21208
|
+
}
|
|
21209
|
+
if (wasEmpty && !closedByUserRef.current) setOpen(true);
|
|
21210
|
+
}, [rows.length]);
|
|
21211
|
+
const badgePlacedRef = import_react.default.useRef(false);
|
|
21212
|
+
import_react.default.useEffect(() => {
|
|
21213
|
+
if (badgePlacedRef.current) return;
|
|
21214
|
+
badgePlacedRef.current = true;
|
|
21215
|
+
const el = badgeRef.current;
|
|
21216
|
+
let saved;
|
|
21217
|
+
try {
|
|
21218
|
+
saved = window.localStorage.getItem(JOB_BADGE_POS_KEY);
|
|
21219
|
+
} catch {
|
|
21220
|
+
saved = null;
|
|
21221
|
+
}
|
|
21222
|
+
if (saved !== null) {
|
|
21223
|
+
setBadgePos((current) => clampJobDockPos(current, el));
|
|
21224
|
+
return;
|
|
21225
|
+
}
|
|
21226
|
+
const width = el?.offsetWidth ?? JOB_BADGE_ESTIMATE_WIDTH;
|
|
21227
|
+
setBadgePos(clampJobDockPos({
|
|
21228
|
+
left: window.innerWidth - width - JOB_DOCK_MARGIN,
|
|
21229
|
+
top: JOB_BADGE_DEFAULT.top
|
|
21230
|
+
}, el));
|
|
21231
|
+
}, []);
|
|
21232
|
+
import_react.default.useEffect(() => {
|
|
21233
|
+
if (!open2) return void 0;
|
|
21234
|
+
const sessions = ctx?.sessions;
|
|
21235
|
+
const list = sessions?.list?.getSnapshot?.();
|
|
21236
|
+
const rootId = list?.current;
|
|
21237
|
+
if (rootId === void 0) return void 0;
|
|
21238
|
+
try {
|
|
21239
|
+
sessions.setSubagentCatalogOpen?.(rootId, true);
|
|
21240
|
+
void sessions.refreshSubagents?.(rootId);
|
|
21241
|
+
} catch {
|
|
21242
|
+
}
|
|
21243
|
+
return () => {
|
|
21244
|
+
try {
|
|
21245
|
+
sessions.setSubagentCatalogOpen?.(rootId, false);
|
|
21246
|
+
} catch {
|
|
21247
|
+
}
|
|
21248
|
+
};
|
|
21249
|
+
}, [open2, ctx]);
|
|
21250
|
+
import_react.default.useEffect(() => {
|
|
21251
|
+
if (!open2) return void 0;
|
|
21252
|
+
const onResize = () => setPos((current) => clampJobDockPos(current, panelRef.current));
|
|
21253
|
+
window.addEventListener("resize", onResize);
|
|
21254
|
+
return () => window.removeEventListener("resize", onResize);
|
|
21255
|
+
}, [open2]);
|
|
21256
|
+
const multiOwner = new Set(rows.map((row) => row.ownerSessionId)).size > 1;
|
|
21257
|
+
if (rows.length === 0 && subagents.length === 0) return null;
|
|
21258
|
+
const anyLive = liveCount > 0 || subagentsRunning > 0;
|
|
21259
|
+
const badgeText = [
|
|
21260
|
+
subagents.length > 0 ? subagentsRunning > 0 ? t("tasks.subagentRunningCount", { running: subagentsRunning }) : t("tasks.subagentCount", { total: subagents.length }) : void 0,
|
|
21261
|
+
rows.length > 0 ? liveCount > 0 ? t("tasks.jobRunningCount", { running: liveCount }) : t("tasks.jobCount", { total: rows.length }) : void 0
|
|
21262
|
+
].filter(Boolean).join(" \xB7 ");
|
|
21263
|
+
function startBadgeDrag(event) {
|
|
21264
|
+
if (event.button !== void 0 && event.button !== 0) return;
|
|
21265
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
21266
|
+
badgeMovedRef.current = false;
|
|
21267
|
+
badgeDragRef.current = {
|
|
21268
|
+
pointerId: event.pointerId,
|
|
21269
|
+
startX: event.clientX,
|
|
21270
|
+
startY: event.clientY,
|
|
21271
|
+
originLeft: badgePos.left,
|
|
21272
|
+
originTop: badgePos.top
|
|
21273
|
+
};
|
|
21274
|
+
}
|
|
21275
|
+
function moveBadgeDrag(event) {
|
|
21276
|
+
const drag = badgeDragRef.current;
|
|
21277
|
+
if (drag === void 0 || drag.pointerId !== event.pointerId) return;
|
|
21278
|
+
const dx = event.clientX - drag.startX;
|
|
21279
|
+
const dy = event.clientY - drag.startY;
|
|
21280
|
+
if (Math.abs(dx) + Math.abs(dy) >= JOB_DOCK_DRAG_THRESHOLD) badgeMovedRef.current = true;
|
|
21281
|
+
if (!badgeMovedRef.current) return;
|
|
21282
|
+
setBadgePos(clampJobDockPos({ left: drag.originLeft + dx, top: drag.originTop + dy }, badgeRef.current));
|
|
21283
|
+
}
|
|
21284
|
+
function endBadgeDrag(event) {
|
|
21285
|
+
const drag = badgeDragRef.current;
|
|
21286
|
+
badgeDragRef.current = void 0;
|
|
21287
|
+
if (drag === void 0 || drag.pointerId !== event.pointerId) return;
|
|
21288
|
+
if (!badgeMovedRef.current) return;
|
|
21289
|
+
try {
|
|
21290
|
+
window.localStorage.setItem(JOB_BADGE_POS_KEY, JSON.stringify(badgePos));
|
|
21291
|
+
} catch {
|
|
21292
|
+
}
|
|
21293
|
+
}
|
|
21294
|
+
function startDrag(event) {
|
|
21295
|
+
if (event.button !== void 0 && event.button !== 0) return;
|
|
21296
|
+
event.preventDefault();
|
|
21297
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
21298
|
+
dragRef.current = {
|
|
21299
|
+
pointerId: event.pointerId,
|
|
21300
|
+
startX: event.clientX,
|
|
21301
|
+
startY: event.clientY,
|
|
21302
|
+
originLeft: pos.left,
|
|
21303
|
+
originTop: pos.top,
|
|
21304
|
+
moved: false
|
|
21305
|
+
};
|
|
21306
|
+
}
|
|
21307
|
+
function moveDrag(event) {
|
|
21308
|
+
const drag = dragRef.current;
|
|
21309
|
+
if (drag === void 0 || drag.pointerId !== event.pointerId) return;
|
|
21310
|
+
const dx = event.clientX - drag.startX;
|
|
21311
|
+
const dy = event.clientY - drag.startY;
|
|
21312
|
+
if (!drag.moved && Math.abs(dx) + Math.abs(dy) < JOB_DOCK_DRAG_THRESHOLD) return;
|
|
21313
|
+
drag.moved = true;
|
|
21314
|
+
setPos(clampJobDockPos(
|
|
21315
|
+
{ left: drag.originLeft + dx, top: drag.originTop + dy },
|
|
21316
|
+
panelRef.current
|
|
21317
|
+
));
|
|
21318
|
+
}
|
|
21319
|
+
function endDrag(event) {
|
|
21320
|
+
const drag = dragRef.current;
|
|
21321
|
+
dragRef.current = void 0;
|
|
21322
|
+
if (drag === void 0 || drag.pointerId !== event.pointerId) return;
|
|
21323
|
+
if (!drag.moved) return;
|
|
21324
|
+
try {
|
|
21325
|
+
window.localStorage.setItem(JOB_DOCK_POS_KEY, JSON.stringify(pos));
|
|
21326
|
+
} catch {
|
|
21327
|
+
}
|
|
21328
|
+
}
|
|
21329
|
+
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
|
|
21330
|
+
"button",
|
|
21331
|
+
{
|
|
21332
|
+
className: "t-team-job-badge",
|
|
21333
|
+
type: "button",
|
|
21334
|
+
ref: badgeRef,
|
|
21335
|
+
"data-testid": "t-team-job-badge",
|
|
21336
|
+
"data-live": anyLive,
|
|
21337
|
+
"data-dragging": badgeDragRef.current === void 0 ? void 0 : "true",
|
|
21338
|
+
style: { left: badgePos.left, top: badgePos.top },
|
|
21339
|
+
"aria-expanded": open2,
|
|
21340
|
+
title: t("tasks.badgeTitle"),
|
|
21341
|
+
onPointerDown: startBadgeDrag,
|
|
21342
|
+
onPointerMove: moveBadgeDrag,
|
|
21343
|
+
onPointerUp: endBadgeDrag,
|
|
21344
|
+
onPointerCancel: endBadgeDrag,
|
|
21345
|
+
onClick: () => {
|
|
21346
|
+
if (badgeMovedRef.current) {
|
|
21347
|
+
badgeMovedRef.current = false;
|
|
21348
|
+
return;
|
|
21349
|
+
}
|
|
21350
|
+
setOpen((value) => !value);
|
|
21351
|
+
}
|
|
21352
|
+
},
|
|
21353
|
+
/* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-badge-dot", "data-live": anyLive, "aria-hidden": "true" }),
|
|
21354
|
+
/* @__PURE__ */ import_react.default.createElement("span", null, t("tab.tasks")),
|
|
21355
|
+
/* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-badge-count" }, badgeText)
|
|
21356
|
+
), open2 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-job-panel", ref: panelRef, style: { left: pos.left, top: pos.top } }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-job-panel-head", onPointerDown: startDrag, onPointerMove: moveDrag, onPointerUp: endDrag, onPointerCancel: endDrag }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-panel-title" }, t("tab.tasks")), /* @__PURE__ */ import_react.default.createElement(
|
|
21357
|
+
"button",
|
|
21358
|
+
{
|
|
21359
|
+
className: "t-team-job-panel-close",
|
|
21360
|
+
type: "button",
|
|
21361
|
+
title: t("button.close"),
|
|
21362
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
21363
|
+
onClick: () => {
|
|
21364
|
+
closedByUserRef.current = true;
|
|
21365
|
+
setOpen(false);
|
|
21366
|
+
}
|
|
21367
|
+
},
|
|
21368
|
+
"\u2715"
|
|
21369
|
+
)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-job-panel-tabs", role: "tablist" }, [
|
|
21370
|
+
["subagents", "tasks.subagents", subagentsRunning > 0 ? `${subagents.length} \xB7 ${subagentsRunning}` : String(subagents.length)],
|
|
21371
|
+
["jobs", "tab.tasks", liveCount > 0 ? `${rows.length} \xB7 ${liveCount}` : String(rows.length)]
|
|
21372
|
+
].map(([key, labelKey, count]) => /* @__PURE__ */ import_react.default.createElement(
|
|
21373
|
+
"button",
|
|
21374
|
+
{
|
|
21375
|
+
className: "t-team-tab",
|
|
21376
|
+
type: "button",
|
|
21377
|
+
role: "tab",
|
|
21378
|
+
key,
|
|
21379
|
+
"data-active": panelTab === key,
|
|
21380
|
+
"aria-selected": panelTab === key,
|
|
21381
|
+
onClick: () => setPanelTab(key)
|
|
21382
|
+
},
|
|
21383
|
+
t(labelKey),
|
|
21384
|
+
" ",
|
|
21385
|
+
/* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-job-tab-count" }, count)
|
|
21386
|
+
))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-job-panel-body" }, panelTab === "subagents" ? subagents.length === 0 ? /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("tasks.noSubagents")) : renderSubagentRows({ subagents, t, onOpen: (item) => openSubagentSession(ctx, item) }) : renderJobRows({ rows, now, multiOwner, t }))));
|
|
21387
|
+
}
|
|
20827
21388
|
function squadToInput(squad) {
|
|
20828
21389
|
return {
|
|
20829
21390
|
key: String(squad.key ?? "").trim(),
|
|
@@ -21209,9 +21770,9 @@ function insertAtTarget(target, reference) {
|
|
|
21209
21770
|
const offset = referencePrefixEnd(snapshot);
|
|
21210
21771
|
return target.insertReference(reference, { start: offset, end: offset, draftRev: snapshot.draftRev }) === true;
|
|
21211
21772
|
}
|
|
21212
|
-
function SummonButton({ t, remote, getActive, insertReference, fillSquadCommand, sessionId, preview }) {
|
|
21773
|
+
function SummonButton({ t, ctx, remote, getActive, insertReference, fillSquadCommand, sessionId, preview }) {
|
|
21213
21774
|
const [open2, setOpen] = import_react.default.useState(preview?.open === true);
|
|
21214
|
-
const [popTab, setPopTab] = import_react.default.useState(preview?.tab ?? "
|
|
21775
|
+
const [popTab, setPopTab] = import_react.default.useState(preview?.tab ?? "tasks");
|
|
21215
21776
|
const [squads, setSquads] = import_react.default.useState(preview?.squads ?? null);
|
|
21216
21777
|
const [teams, setTeams] = import_react.default.useState(preview?.teams ?? null);
|
|
21217
21778
|
const [squadQuery, setSquadQuery] = import_react.default.useState("");
|
|
@@ -21267,9 +21828,9 @@ function SummonButton({ t, remote, getActive, insertReference, fillSquadCommand,
|
|
|
21267
21828
|
}
|
|
21268
21829
|
const active = getActive();
|
|
21269
21830
|
const teamCount = teams?.length ?? 0;
|
|
21270
|
-
const popTabs = [["experts", "tab.experts"], ["squads", "tab.squads"], ...teamCount > 0 ? [["teams", "tab.teams"]] : []];
|
|
21831
|
+
const popTabs = [["tasks", "tab.tasks"], ["experts", "tab.experts"], ["squads", "tab.squads"], ...teamCount > 0 ? [["teams", "tab.teams"]] : []];
|
|
21271
21832
|
import_react.default.useEffect(() => {
|
|
21272
|
-
if (popTab === "teams" && teamCount === 0) setPopTab("
|
|
21833
|
+
if (popTab === "teams" && teamCount === 0) setPopTab("tasks");
|
|
21273
21834
|
}, [popTab, teamCount]);
|
|
21274
21835
|
const squadNeedle = squadQuery.trim().toLowerCase();
|
|
21275
21836
|
const visibleSquads = (squads?.squads ?? []).filter((squad) => {
|
|
@@ -21349,7 +21910,7 @@ function SummonButton({ t, remote, getActive, insertReference, fillSquadCommand,
|
|
|
21349
21910
|
onClick: () => void fillSquad(squad)
|
|
21350
21911
|
},
|
|
21351
21912
|
starting === squad.key ? t("pop.picking") : t("pop.fill")
|
|
21352
|
-
)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, squad.aliases.slice(0, 6).map((alias) => /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-member-chip", key: alias }, alias)), squad.aliases.length > 6 && /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-note" }, "+", squad.aliases.length - 6)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, squad.members.map((member) => /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-member-chip", "data-unknown": member.known === false, key: member.slug }, member.nameZh))))), squads !== null && visibleSquads.length === 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-pop-empty" }, squads.squads.length === 0 ? t("pop.emptySquads") : t("pop.squadNoMatch")))), popTab === "teams" && /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, compact: true, initialTeams: preview?.teams }), popTab === "experts" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
|
|
21913
|
+
)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, squad.aliases.slice(0, 6).map((alias) => /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-member-chip", key: alias }, alias)), squad.aliases.length > 6 && /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-note" }, "+", squad.aliases.length - 6)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, squad.members.map((member) => /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-member-chip", "data-unknown": member.known === false, key: member.slug }, member.nameZh))))), squads !== null && visibleSquads.length === 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-pop-empty" }, squads.squads.length === 0 ? t("pop.emptySquads") : t("pop.squadNoMatch")))), popTab === "teams" && /* @__PURE__ */ import_react.default.createElement(TeamsTab, { t, remote, compact: true, initialTeams: preview?.teams }), popTab === "tasks" && /* @__PURE__ */ import_react.default.createElement(JobsTab, { t, ctx, compact: true }), popTab === "experts" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
|
|
21353
21914
|
"input",
|
|
21354
21915
|
{
|
|
21355
21916
|
className: "t-team-input",
|
|
@@ -21432,6 +21993,17 @@ async function apply(ctx) {
|
|
|
21432
21993
|
locale: NS,
|
|
21433
21994
|
icon: "expert"
|
|
21434
21995
|
}, (props) => import_react.default.createElement(SettingsPanel, { ...props, t: ctx.locale.bind(NS), remote, getActive, refreshCatalog })));
|
|
21996
|
+
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
21997
|
+
name: "shell.overlay",
|
|
21998
|
+
id: `${NS}-jobs`,
|
|
21999
|
+
order: 81,
|
|
22000
|
+
label: () => ctx.locale.bind(NS)("tab.tasks"),
|
|
22001
|
+
locale: NS
|
|
22002
|
+
}, (props) => import_react.default.createElement(JobsDock, {
|
|
22003
|
+
...props,
|
|
22004
|
+
t: ctx.locale.bind(NS),
|
|
22005
|
+
ctx: props?.ctx ?? ctx
|
|
22006
|
+
})));
|
|
21435
22007
|
ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
|
|
21436
22008
|
name: "conversation.input.left",
|
|
21437
22009
|
id: NS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-t-expert",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "T Expert — a 316-expert, 22-division roster with full Chinese translations, as a standalone DeepSeek Harness plugin, with a built-in multi-agent team engine (T Team).",
|
|
6
6
|
"license": "MIT",
|