dsh-plugin-teamflow 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +252 -185
- package/README.en.md +57 -62
- package/README.md +50 -76
- package/lib/client.js +207 -38
- package/lib/descriptors.mjs +5 -1
- package/lib/host.mjs +2277 -241
- package/lib/store.mjs +22 -0
- package/package.json +134 -134
package/lib/client.js
CHANGED
|
@@ -29,11 +29,15 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
let react = require("react");
|
|
30
30
|
react = __toESM(react, 1);
|
|
31
31
|
//#region descriptors.ts
|
|
32
|
+
/** 恒等 parse:接受任意 JSON 值,原样返回。 */
|
|
33
|
+
const JSON_SCHEMA = { parse: (value) => value };
|
|
32
34
|
/** 统一 strict codec(本插件所有参数/结果均为自由 JSON)。 */
|
|
33
35
|
const strict = {
|
|
34
36
|
mode: "strict",
|
|
35
37
|
typeSymbol: "dsh-plugin-teamflow/types#Json",
|
|
36
|
-
schema:
|
|
38
|
+
schema: JSON_SCHEMA,
|
|
39
|
+
/** 首次跨边界使用时物化 schema(宿主按需调用,只调一次并缓存)。 */
|
|
40
|
+
create: () => JSON_SCHEMA
|
|
37
41
|
};
|
|
38
42
|
const p = (name) => ({
|
|
39
43
|
name,
|
|
@@ -334,6 +338,14 @@ window.__ModuleLoader__.load({
|
|
|
334
338
|
}
|
|
335
339
|
const stText = (s) => vocab("status", s);
|
|
336
340
|
const runStatusText = (s) => vocab("runStatus", s);
|
|
341
|
+
/**
|
|
342
|
+
* **阶段**状态文案:只有 `cancelled` 与 backlog 词表分道。
|
|
343
|
+
*
|
|
344
|
+
* `stText` 是 backlog 卡片词表(`status.cancelled`/`closed`/`verified` 都译「已关闭」),阶段渲染若直接复用它,
|
|
345
|
+
* 被中断的阶段会显示成「已关闭」(en:Closed)——2026-09-16 中断功能实测截图里,同一屏 run 行写「已取消」、
|
|
346
|
+
* 阶段节点写「已关闭」。阶段是被**中止**而不是被关闭,故单独取词;其余阶段状态仍走同一张表(不开两套词表)。
|
|
347
|
+
*/
|
|
348
|
+
const stageStatusText = (s) => s === "cancelled" ? t("stageStatus.cancelled") : stText(s);
|
|
337
349
|
const kindTitle = (k) => vocab("kind", k);
|
|
338
350
|
const roleName = (r) => vocab("role", r);
|
|
339
351
|
/** 角色 chip(带图标;未知角色回退「⚙️ <raw>」)。 */
|
|
@@ -413,6 +425,7 @@ window.__ModuleLoader__.load({
|
|
|
413
425
|
function stageLabelOf(s) {
|
|
414
426
|
const raw = String(s && s.label || "");
|
|
415
427
|
if (s && s.taskKey) return raw || String(s.taskKey);
|
|
428
|
+
if (s && phaseKeyOf(s.phase) === "dev" && raw) return raw;
|
|
416
429
|
return (s && s.phase ? phaseNameOf(s.phase) : "") || raw;
|
|
417
430
|
}
|
|
418
431
|
const COLUMNS = {
|
|
@@ -527,6 +540,70 @@ window.__ModuleLoader__.load({
|
|
|
527
540
|
}
|
|
528
541
|
}, `${t("common.expandFull")}${more}`));
|
|
529
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* 中断运行按钮(**两段式内联确认**):首次点击进入待确认态,3 秒内再点一次才真正执行。
|
|
545
|
+
*
|
|
546
|
+
* 为什么不自造对话框:客户端全目录 grep `confirm|dialog` 0 命中——目前没有宿主确认能力依赖,
|
|
547
|
+
* 内联两段式不引新依赖、也不改宿主能力面,且天然贴合「误点一个跑了几十分钟的 run」的防护诉求。
|
|
548
|
+
*
|
|
549
|
+
* 数据面由调用方给(`onConfirm(runId)`):会话内工作台与全局面板/右栏共用同一个 `teamflow/cancel`
|
|
550
|
+
* 方法,本件只负责待确认窗口、忙碌态与**点击不冒泡**(面板 run 行整行可点开详情)。
|
|
551
|
+
* `onConfirm` **必须自行消化错误**(它由调用方 try/catch 并落到可见的 err 提示);本件兜底只记 console,
|
|
552
|
+
* 不让点击处理里出现未处理的 rejection。
|
|
553
|
+
*/
|
|
554
|
+
function CancelButton({ runId, label, title, onConfirm, style }) {
|
|
555
|
+
const [arm, setArm] = react.default.useState(false);
|
|
556
|
+
const [busy, setBusy] = react.default.useState(false);
|
|
557
|
+
react.default.useEffect(() => {
|
|
558
|
+
if (!arm) return void 0;
|
|
559
|
+
const timer = setTimeout(() => setArm(false), 3e3);
|
|
560
|
+
return () => clearTimeout(timer);
|
|
561
|
+
}, [arm]);
|
|
562
|
+
const onClick = async (e) => {
|
|
563
|
+
if (e && typeof e.stopPropagation === "function") e.stopPropagation();
|
|
564
|
+
if (busy) return;
|
|
565
|
+
if (!arm) {
|
|
566
|
+
setArm(true);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
setArm(false);
|
|
570
|
+
setBusy(true);
|
|
571
|
+
try {
|
|
572
|
+
await onConfirm(runId);
|
|
573
|
+
} catch (err) {
|
|
574
|
+
console.warn("[teamflow] cancel failed", err);
|
|
575
|
+
} finally {
|
|
576
|
+
setBusy(false);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
const tone = busy ? {
|
|
580
|
+
opacity: .6,
|
|
581
|
+
cursor: "default"
|
|
582
|
+
} : arm ? {
|
|
583
|
+
background: T.error,
|
|
584
|
+
color: "#fff",
|
|
585
|
+
borderColor: T.error
|
|
586
|
+
} : null;
|
|
587
|
+
return h("button", {
|
|
588
|
+
onClick,
|
|
589
|
+
disabled: busy,
|
|
590
|
+
title: title || void 0,
|
|
591
|
+
style: {
|
|
592
|
+
font: "inherit",
|
|
593
|
+
fontSize: 12,
|
|
594
|
+
padding: "4px 12px",
|
|
595
|
+
borderRadius: 8,
|
|
596
|
+
cursor: "pointer",
|
|
597
|
+
border: `1px solid ${T.error}`,
|
|
598
|
+
background: "transparent",
|
|
599
|
+
color: T.error,
|
|
600
|
+
fontWeight: 600,
|
|
601
|
+
transition: "background .12s ease, color .12s ease",
|
|
602
|
+
...style || {},
|
|
603
|
+
...tone || {}
|
|
604
|
+
}
|
|
605
|
+
}, busy ? t("cancel.busy") : arm ? t("cancel.arm") : label || t("cancel.btn"));
|
|
606
|
+
}
|
|
530
607
|
function fmtTime(tm) {
|
|
531
608
|
if (!tm) return "—";
|
|
532
609
|
const d = new Date(tm);
|
|
@@ -690,6 +767,7 @@ window.__ModuleLoader__.load({
|
|
|
690
767
|
"runStatus.cancelled": "已取消",
|
|
691
768
|
"runStatus.interrupted": "已中断",
|
|
692
769
|
"runStatus.superseded": "已取代",
|
|
770
|
+
"stageStatus.cancelled": "已中止",
|
|
693
771
|
"phase.prd": "PRD 产品需求",
|
|
694
772
|
"phase.design": "UI/UX 设计",
|
|
695
773
|
"phase.scaffold": "架构规划",
|
|
@@ -770,6 +848,13 @@ window.__ModuleLoader__.load({
|
|
|
770
848
|
"workbench.history": "历史",
|
|
771
849
|
"workbench.openRightBarTip": "在右侧栏打开该 run 详情(与任务夹产物并排看)",
|
|
772
850
|
"workbench.openRightBarBtn": "⇥ 右栏打开",
|
|
851
|
+
"cancel.btn": "⏹ 中断",
|
|
852
|
+
"cancel.btnWithId": "⏹ 中断 #{id}",
|
|
853
|
+
"cancel.arm": "确认中断?",
|
|
854
|
+
"cancel.busy": "中断中…",
|
|
855
|
+
"cancel.sent": "已请求中断,等待当前阶段收尾…",
|
|
856
|
+
"cancel.tip": "中断 {id}\n立即中止当前阶段子代理;已完成阶段保留,之后可用「从断点重跑」继续;取消的 run 不产生提交",
|
|
857
|
+
"cancel.failed": "中断未生效:该流水线已不在运行中(可能刚结束)",
|
|
773
858
|
"board.empty": "backlog 为空(还没有流水线运行过)",
|
|
774
859
|
"board.dragReason": "看板拖拽流转",
|
|
775
860
|
"board.cardTip": "{id} · {status}{summary}(点击查看详情)",
|
|
@@ -951,6 +1036,7 @@ window.__ModuleLoader__.load({
|
|
|
951
1036
|
"runStatus.cancelled": "Cancelled",
|
|
952
1037
|
"runStatus.interrupted": "Interrupted",
|
|
953
1038
|
"runStatus.superseded": "Superseded",
|
|
1039
|
+
"stageStatus.cancelled": "Stopped",
|
|
954
1040
|
"phase.prd": "PRD",
|
|
955
1041
|
"phase.design": "UI/UX design",
|
|
956
1042
|
"phase.scaffold": "Architecture",
|
|
@@ -1031,6 +1117,13 @@ window.__ModuleLoader__.load({
|
|
|
1031
1117
|
"workbench.history": "History",
|
|
1032
1118
|
"workbench.openRightBarTip": "Open this run's detail in the right sidebar (side by side with task-folder artifacts)",
|
|
1033
1119
|
"workbench.openRightBarBtn": "⇥ Open in right bar",
|
|
1120
|
+
"cancel.btn": "⏹ Stop",
|
|
1121
|
+
"cancel.btnWithId": "⏹ Stop #{id}",
|
|
1122
|
+
"cancel.arm": "Confirm stop?",
|
|
1123
|
+
"cancel.busy": "Stopping…",
|
|
1124
|
+
"cancel.sent": "Stop requested — waiting for the current stage to wind down…",
|
|
1125
|
+
"cancel.tip": "Stop {id}\nAborts the subagent of the current stage immediately; finished stages are kept and can be resumed from the checkpoint later; a cancelled run never commits",
|
|
1126
|
+
"cancel.failed": "Stop had no effect: the pipeline is no longer running (it may have just finished)",
|
|
1034
1127
|
"board.empty": "Backlog is empty (no pipeline has run yet)",
|
|
1035
1128
|
"board.dragReason": "kanban drag transition",
|
|
1036
1129
|
"board.cardTip": "{id} · {status}{summary} (click for details)",
|
|
@@ -1400,7 +1493,7 @@ window.__ModuleLoader__.load({
|
|
|
1400
1493
|
} }, p.lastRequirement) : null, p.lastVerdict ? h("div", { style: { marginTop: 4 } }, chip(t("panel.verdict", { v: p.lastVerdict }), stColor(p.lastVerdict === "accepted" ? "accepted" : p.lastVerdict))) : null);
|
|
1401
1494
|
})));
|
|
1402
1495
|
}
|
|
1403
|
-
function RunList({ runs, activeRunId, onOpenRun, onInlineRun }) {
|
|
1496
|
+
function RunList({ runs, activeRunId, onOpenRun, onInlineRun, onCancel }) {
|
|
1404
1497
|
if (!runs.length) return muted(t("runList.empty"), { padding: "2px 2px 8px" });
|
|
1405
1498
|
return h("div", { style: {
|
|
1406
1499
|
display: "flex",
|
|
@@ -1453,7 +1546,15 @@ window.__ModuleLoader__.load({
|
|
|
1453
1546
|
} }, h("span", null, t("runList.stageProgress", {
|
|
1454
1547
|
done: r.doneStages,
|
|
1455
1548
|
total: r.stageCount
|
|
1456
|
-
})), h("span", null, runUsageText(r.usage)), h("span", null, `${fmtTime(r.startedAt)}${r.endedAt ? ` → ${fmtTime(r.endedAt)}` : ""} ${fmtDur(r.startedAt, r.endedAt)}`))), h(
|
|
1549
|
+
})), h("span", null, runUsageText(r.usage)), h("span", null, `${fmtTime(r.startedAt)}${r.endedAt ? ` → ${fmtTime(r.endedAt)}` : ""} ${fmtDur(r.startedAt, r.endedAt)}`))), r.status === "running" && onCancel ? h(CancelButton, {
|
|
1550
|
+
runId: r.id,
|
|
1551
|
+
title: t("cancel.tip", { id: r.id }),
|
|
1552
|
+
onConfirm: onCancel,
|
|
1553
|
+
style: {
|
|
1554
|
+
fontSize: 11,
|
|
1555
|
+
padding: "2px 9px"
|
|
1556
|
+
}
|
|
1557
|
+
}) : null, h("button", {
|
|
1457
1558
|
style: brandBtn,
|
|
1458
1559
|
title: t("runList.openRightBarTip"),
|
|
1459
1560
|
onClick: (e) => {
|
|
@@ -1796,7 +1897,7 @@ window.__ModuleLoader__.load({
|
|
|
1796
1897
|
color: T.text2
|
|
1797
1898
|
} }, det.events.slice(-30).map((e, i) => h("div", { key: i }, `${fmtTime(e.at)} ${e.from || "—"} → ${e.to || "—"}${e.by ? ` · ${e.by}` : ""}${e.reason ? ` · ${e.reason}` : ""}`)))) : null);
|
|
1798
1899
|
}
|
|
1799
|
-
function RunDetailPane({ snap, product, api }) {
|
|
1900
|
+
function RunDetailPane({ snap, product, api, onCancel }) {
|
|
1800
1901
|
const [sel, setSel] = react.default.useState(null);
|
|
1801
1902
|
const [err, setErr] = react.default.useState(null);
|
|
1802
1903
|
react.default.useEffect(() => {
|
|
@@ -1850,7 +1951,16 @@ window.__ModuleLoader__.load({
|
|
|
1850
1951
|
fontSize: 10,
|
|
1851
1952
|
color: T.text2,
|
|
1852
1953
|
fontFamily: MONO
|
|
1853
|
-
} }, product) : null
|
|
1954
|
+
} }, product) : null, snap.status === "running" && onCancel ? h(CancelButton, {
|
|
1955
|
+
runId: snap.id,
|
|
1956
|
+
title: t("cancel.tip", { id: snap.id }),
|
|
1957
|
+
onConfirm: onCancel,
|
|
1958
|
+
style: {
|
|
1959
|
+
marginLeft: "auto",
|
|
1960
|
+
fontSize: 10.5,
|
|
1961
|
+
padding: "2px 9px"
|
|
1962
|
+
}
|
|
1963
|
+
}) : null), h("div", { style: {
|
|
1854
1964
|
fontSize: 12,
|
|
1855
1965
|
color: T.text,
|
|
1856
1966
|
lineHeight: 1.5
|
|
@@ -1902,7 +2012,7 @@ window.__ModuleLoader__.load({
|
|
|
1902
2012
|
fontSize: 11.5,
|
|
1903
2013
|
color: T.text,
|
|
1904
2014
|
fontWeight: 500
|
|
1905
|
-
} }, stageLabelOf(s)), chip(
|
|
2015
|
+
} }, stageLabelOf(s)), chip(stageStatusText(s.status), color, { dot: true }), s.outcome && s.outcome !== "completed" && s.outcome !== "cancelled" ? chip(String(s.outcome), stColor(s.outcome)) : null, h("span", { style: {
|
|
1906
2016
|
marginLeft: "auto",
|
|
1907
2017
|
fontFamily: MONO,
|
|
1908
2018
|
fontSize: 10,
|
|
@@ -1938,7 +2048,7 @@ window.__ModuleLoader__.load({
|
|
|
1938
2048
|
fontSize: 11.5,
|
|
1939
2049
|
fontWeight: 700,
|
|
1940
2050
|
color: T.text
|
|
1941
|
-
} }, t("detail.stageTitle", { seq: sel.seq })), chip(
|
|
2051
|
+
} }, t("detail.stageTitle", { seq: sel.seq })), chip(stageStatusText(sel.status), stColor(sel.status))), h("button", {
|
|
1942
2052
|
style: panelBtn,
|
|
1943
2053
|
onClick: () => setSel(null)
|
|
1944
2054
|
}, t("common.collapse"))), h("div", { style: {
|
|
@@ -1991,7 +2101,7 @@ window.__ModuleLoader__.load({
|
|
|
1991
2101
|
fontFamily: MONO,
|
|
1992
2102
|
color: T.text2
|
|
1993
2103
|
}
|
|
1994
|
-
}, h("span", null, `#${a.seq}`), chip(
|
|
2104
|
+
}, h("span", null, `#${a.seq}`), chip(stageStatusText(a.status), stColor(a.status)), a.outcome && a.outcome !== "cancelled" ? h("span", null, a.outcome) : null, h("span", { style: { marginLeft: "auto" } }, `${fmtTime(a.startedAt)} · ${fmtDur(a.startedAt, a.endedAt)}`))))) : null) : null, logs.length ? h("div", null, sectionTitle(t("detail.logs", { n: logs.length })), h("div", { style: {
|
|
1995
2105
|
display: "flex",
|
|
1996
2106
|
flexDirection: "column",
|
|
1997
2107
|
gap: 2,
|
|
@@ -2078,6 +2188,16 @@ window.__ModuleLoader__.load({
|
|
|
2078
2188
|
nonce,
|
|
2079
2189
|
readTab
|
|
2080
2190
|
]);
|
|
2191
|
+
/** 中断后重新拉快照(nonce 触发上面那个 effect 重跑;右栏 tab 是会话级地址,与产品线选择无关)。 */
|
|
2192
|
+
const onCancel = async (rid) => {
|
|
2193
|
+
if (!api) return;
|
|
2194
|
+
try {
|
|
2195
|
+
await api.cancel(rid);
|
|
2196
|
+
setNonce((n) => n + 1);
|
|
2197
|
+
} catch (e) {
|
|
2198
|
+
setErr(String(e && e.message || e));
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2081
2201
|
if (err && !snap) {
|
|
2082
2202
|
const pending = !address && !!readTab;
|
|
2083
2203
|
return h("div", { style: {
|
|
@@ -2097,7 +2217,8 @@ window.__ModuleLoader__.load({
|
|
|
2097
2217
|
return h(RunDetailPane, {
|
|
2098
2218
|
snap,
|
|
2099
2219
|
product,
|
|
2100
|
-
api
|
|
2220
|
+
api,
|
|
2221
|
+
onCancel
|
|
2101
2222
|
});
|
|
2102
2223
|
}
|
|
2103
2224
|
function productApi(remote, product) {
|
|
@@ -2105,7 +2226,13 @@ window.__ModuleLoader__.load({
|
|
|
2105
2226
|
view: async () => unwrap$1(await remote.productView(product), "productView"),
|
|
2106
2227
|
runDetail: async (runId) => unwrap$1(await remote.productRunDetail(product, runId), "productRunDetail"),
|
|
2107
2228
|
stageDetail: async (runId, seq) => unwrap$1(await remote.productStageDetail(product, runId, seq), "productStageDetail"),
|
|
2108
|
-
itemDetail: async (kind, id, sessionId) => unwrap$1(await remote.productItemDetail(product, kind, id, sessionId), "productItemDetail")
|
|
2229
|
+
itemDetail: async (kind, id, sessionId) => unwrap$1(await remote.productItemDetail(product, kind, id, sessionId), "productItemDetail"),
|
|
2230
|
+
/** 中断运行:runId 全局寻址(与产品线无关),放这里是为了让面板行/详情/右栏共用同一处解包与报错。 */
|
|
2231
|
+
cancel: async (runId) => {
|
|
2232
|
+
const r = unwrap$1(await remote.cancel(runId), "cancel");
|
|
2233
|
+
if (!r || r.ok !== true) throw new Error(t("cancel.failed"));
|
|
2234
|
+
return true;
|
|
2235
|
+
}
|
|
2109
2236
|
};
|
|
2110
2237
|
}
|
|
2111
2238
|
/** 全局面板 props:root scope 标准 props(useSessions 取当前会话)+ 插件注入(remote/打开回调)。 */
|
|
@@ -2165,8 +2292,10 @@ window.__ModuleLoader__.load({
|
|
|
2165
2292
|
/**
|
|
2166
2293
|
* **跳到资源所属的会话,再在那个会话的右栏打开**(全局面板的正确语义)。
|
|
2167
2294
|
* 右侧栏是会话级的:从全局面板看 tetris 的 run 却把 tab 挂到"用户当前所在会话"上没有意义
|
|
2168
|
-
* (用户 2026-09-11 提出)。所以先 `
|
|
2169
|
-
*
|
|
2295
|
+
* (用户 2026-09-11 提出)。所以先 `uiWorkspace.openSession(ownerSession)`,再小步重试等右栏 seat 就绪后 openResource。
|
|
2296
|
+
* **2026-09-23 迁移(宿主 0.1.7-alpha.1)**:`sessions.open` 与 `sessions.openSubagent` 同批被移除,
|
|
2297
|
+
* 跳会话唯一入口改为 `uiWorkspace.openSession(target)`;同时 `SessionListState` 已无 `current` 字段,
|
|
2298
|
+
* 故原先"等当前会话真的切过去"的判据删除(契约变更后它恒为 undefined,等于死代码),只留时间维度的重试。
|
|
2170
2299
|
* @param target.ownerSession - 资源所属会话(run 的发起会话 / 产物地址里的会话)
|
|
2171
2300
|
* @param target.address - host 生成的 dsh-resource 地址
|
|
2172
2301
|
* @param target.label - 提示用的名字
|
|
@@ -2174,13 +2303,13 @@ window.__ModuleLoader__.load({
|
|
|
2174
2303
|
*/
|
|
2175
2304
|
const goOwnerSessionAndOpen = (target) => {
|
|
2176
2305
|
const { ownerSession, address, label, fallback } = target || {};
|
|
2177
|
-
const
|
|
2178
|
-
if (!ownerSession || !
|
|
2306
|
+
const ws = props.uiWorkspace;
|
|
2307
|
+
if (!ownerSession || !ws || typeof ws.openSession !== "function") {
|
|
2179
2308
|
openInConversationRightbar(address, label, fallback);
|
|
2180
2309
|
return;
|
|
2181
2310
|
}
|
|
2182
2311
|
try {
|
|
2183
|
-
|
|
2312
|
+
ws.openSession(ownerSession);
|
|
2184
2313
|
} catch (e) {
|
|
2185
2314
|
setHint(t("panel.hintSessionGone", { sid: String(ownerSession).slice(0, 8) }));
|
|
2186
2315
|
if (fallback) fallback();
|
|
@@ -2192,13 +2321,7 @@ window.__ModuleLoader__.load({
|
|
|
2192
2321
|
let tries = 0;
|
|
2193
2322
|
const tick = () => {
|
|
2194
2323
|
tries += 1;
|
|
2195
|
-
|
|
2196
|
-
try {
|
|
2197
|
-
nowCurrent = sessions.list && sessions.list.getSnapshot ? sessions.list.getSnapshot().current : null;
|
|
2198
|
-
} catch (e) {
|
|
2199
|
-
nowCurrent = null;
|
|
2200
|
-
}
|
|
2201
|
-
if ((nowCurrent === ownerSession || tries >= 6) && props.openResource && address && props.openResource(address, label, tries < 6)) return;
|
|
2324
|
+
if (props.openResource && address && props.openResource(address, label, tries < 6)) return;
|
|
2202
2325
|
if (tries < 14) {
|
|
2203
2326
|
setTimeout(tick, 130);
|
|
2204
2327
|
return;
|
|
@@ -2328,6 +2451,25 @@ window.__ModuleLoader__.load({
|
|
|
2328
2451
|
}
|
|
2329
2452
|
};
|
|
2330
2453
|
const closeDetail = () => setDetail(null);
|
|
2454
|
+
/** 中断运行(面板内两个入口共用):成功后刷新产品线视图;详情浮层开着就顺手把快照也换新。 */
|
|
2455
|
+
const cancelRun = async (runId) => {
|
|
2456
|
+
if (!api) return;
|
|
2457
|
+
try {
|
|
2458
|
+
await api.cancel(runId);
|
|
2459
|
+
loadView(state.current, true);
|
|
2460
|
+
if (detail && detail.kind === "run" && detail.data && detail.data.id === runId) setDetail({
|
|
2461
|
+
kind: "run",
|
|
2462
|
+
data: await api.runDetail(runId),
|
|
2463
|
+
run: detail.run
|
|
2464
|
+
});
|
|
2465
|
+
setHint(t("cancel.sent"));
|
|
2466
|
+
} catch (e) {
|
|
2467
|
+
setState((s) => ({
|
|
2468
|
+
...s,
|
|
2469
|
+
err: String(e && e.message || e)
|
|
2470
|
+
}));
|
|
2471
|
+
}
|
|
2472
|
+
};
|
|
2331
2473
|
const openRun = (r) => {
|
|
2332
2474
|
goOwnerSessionAndOpen({
|
|
2333
2475
|
ownerSession: r.ownerSession,
|
|
@@ -2516,7 +2658,8 @@ window.__ModuleLoader__.load({
|
|
|
2516
2658
|
runs: visibleRuns,
|
|
2517
2659
|
activeRunId: detail && detail.kind === "run" && detail.run ? detail.run.id : null,
|
|
2518
2660
|
onOpenRun: openRun,
|
|
2519
|
-
onInlineRun: showInline
|
|
2661
|
+
onInlineRun: showInline,
|
|
2662
|
+
onCancel: cancelRun
|
|
2520
2663
|
}) : muted(t("panel.emptyRunFilter"), { fontSize: 10.5 }), muted(t("panel.runHint"), {
|
|
2521
2664
|
fontSize: 10,
|
|
2522
2665
|
marginTop: 8
|
|
@@ -2570,7 +2713,8 @@ window.__ModuleLoader__.load({
|
|
|
2570
2713
|
}, t("common.close")))), h(RunDetailPane, {
|
|
2571
2714
|
snap: detail && detail.data,
|
|
2572
2715
|
product: state.current,
|
|
2573
|
-
api
|
|
2716
|
+
api,
|
|
2717
|
+
onCancel: cancelRun
|
|
2574
2718
|
}))) : null));
|
|
2575
2719
|
}
|
|
2576
2720
|
//#endregion
|
|
@@ -2592,7 +2736,7 @@ window.__ModuleLoader__.load({
|
|
|
2592
2736
|
const inject = [
|
|
2593
2737
|
"remote",
|
|
2594
2738
|
"slots",
|
|
2595
|
-
"
|
|
2739
|
+
"uiWorkspace",
|
|
2596
2740
|
"locale"
|
|
2597
2741
|
];
|
|
2598
2742
|
const NODE_W = 300;
|
|
@@ -2613,7 +2757,7 @@ window.__ModuleLoader__.load({
|
|
|
2613
2757
|
let maxH = 0;
|
|
2614
2758
|
groups.forEach((g, i) => {
|
|
2615
2759
|
const anyRun = g.stages.some((s) => s.status === "running");
|
|
2616
|
-
const anyFail = g.stages.some((s) => s.status === "failed" || s.status === "needs-human"
|
|
2760
|
+
const anyFail = g.stages.some((s) => s.status === "failed" || s.status === "needs-human");
|
|
2617
2761
|
const allDone = g.stages.length > 0 && g.stages.every((s) => s.status === "done");
|
|
2618
2762
|
const headColor = anyRun ? T.brand : anyFail ? T.error : allDone ? T.success : T.text2;
|
|
2619
2763
|
const h = 48 + g.stages.reduce((a, s) => a + cardH(s), 0) + Math.max(0, g.stages.length - 1) * 7;
|
|
@@ -2725,7 +2869,7 @@ window.__ModuleLoader__.load({
|
|
|
2725
2869
|
lineHeight: "15px",
|
|
2726
2870
|
flex: "0 0 auto"
|
|
2727
2871
|
}
|
|
2728
|
-
}, `↻${s.attempts.length - 1}`) : null, chip(
|
|
2872
|
+
}, `↻${s.attempts.length - 1}`) : null, chip(stageStatusText(s.status), color, { dot: true }), h("span", { style: {
|
|
2729
2873
|
color: T.text2,
|
|
2730
2874
|
fontSize: 11,
|
|
2731
2875
|
opacity: .5
|
|
@@ -2838,7 +2982,7 @@ window.__ModuleLoader__.load({
|
|
|
2838
2982
|
/** 阶段详情抽屉(卡片点击打开;浮于画布右侧,不参与拖动/缩放)。
|
|
2839
2983
|
* 2026-09-06 状态机化:同任务多次尝试 → 顶部尝试时间线 + 选中展开(默认最新);
|
|
2840
2984
|
* 单次尝试保持现状(不渲染时间线)。 */
|
|
2841
|
-
function StageDetailDrawer({ det, onClose, sessionId,
|
|
2985
|
+
function StageDetailDrawer({ det, onClose, sessionId, uiWorkspace }) {
|
|
2842
2986
|
const [sel, setSel] = react.default.useState(null);
|
|
2843
2987
|
const st = det.stage;
|
|
2844
2988
|
const d = det.data;
|
|
@@ -2849,11 +2993,11 @@ window.__ModuleLoader__.load({
|
|
|
2849
2993
|
const ownerSession = d && d.ownerSession ? String(d.ownerSession) : null;
|
|
2850
2994
|
const mySession = sessionId ? String(sessionId) : null;
|
|
2851
2995
|
const crossSession = !!ownerSession && !!mySession && ownerSession !== mySession;
|
|
2852
|
-
const hasChild = !!(!crossSession && cur && cur.childId &&
|
|
2996
|
+
const hasChild = !!(!crossSession && cur && cur.childId && uiWorkspace && typeof uiWorkspace.openSession === "function");
|
|
2853
2997
|
const openChild = () => {
|
|
2854
2998
|
if (crossSession || !hasChild) return;
|
|
2855
2999
|
try {
|
|
2856
|
-
|
|
3000
|
+
uiWorkspace.openSession({
|
|
2857
3001
|
parentSessionId: ownerSession || sessionId,
|
|
2858
3002
|
childSessionId: cur.childId,
|
|
2859
3003
|
mode: "one-shot"
|
|
@@ -2928,7 +3072,7 @@ window.__ModuleLoader__.load({
|
|
|
2928
3072
|
marginTop: 1,
|
|
2929
3073
|
fontFamily: MONO,
|
|
2930
3074
|
fontVariantNumeric: "tabular-nums"
|
|
2931
|
-
} }, `${st ? `#${st.seq} · ${st.phase}` : ""}${st && (st.startedAt || st.endedAt) ? ` · ${fmtDur(st.startedAt, st.endedAt)}` : ""}`)), st ? chip(
|
|
3075
|
+
} }, `${st ? `#${st.seq} · ${st.phase}` : ""}${st && (st.startedAt || st.endedAt) ? ` · ${fmtDur(st.startedAt, st.endedAt)}` : ""}`)), st ? chip(stageStatusText(st.status), color, { dot: true }) : null, h("button", {
|
|
2932
3076
|
onClick: onClose,
|
|
2933
3077
|
style: closeBtn,
|
|
2934
3078
|
title: t("common.close")
|
|
@@ -2993,7 +3137,7 @@ window.__ModuleLoader__.load({
|
|
|
2993
3137
|
color: aColor,
|
|
2994
3138
|
flex: "0 0 64px",
|
|
2995
3139
|
fontWeight: 700
|
|
2996
|
-
} }, a.status === "done" ? t("stage.attemptDone") : a.status === "failed" ? t("stage.attemptFailed", { outcome: a.outcome || t("common.failed") }) : t("stage.attemptRunning")), h("span", { style: {
|
|
3140
|
+
} }, a.status === "done" ? t("stage.attemptDone") : a.status === "failed" ? t("stage.attemptFailed", { outcome: a.outcome || t("common.failed") }) : a.status === "cancelled" ? t("stageStatus.cancelled") : t("stage.attemptRunning")), h("span", { style: {
|
|
2997
3141
|
flex: 1,
|
|
2998
3142
|
minWidth: 0,
|
|
2999
3143
|
fontSize: 10.5,
|
|
@@ -3099,7 +3243,7 @@ window.__ModuleLoader__.load({
|
|
|
3099
3243
|
overflowY: "auto"
|
|
3100
3244
|
} }, outText))));
|
|
3101
3245
|
}
|
|
3102
|
-
function PipelinePanel({ active, api, runId, sessionId,
|
|
3246
|
+
function PipelinePanel({ active, api, runId, sessionId, uiWorkspace }) {
|
|
3103
3247
|
if (!active) return h("div", { style: {
|
|
3104
3248
|
color: T.text2,
|
|
3105
3249
|
fontSize: 13,
|
|
@@ -3438,7 +3582,7 @@ window.__ModuleLoader__.load({
|
|
|
3438
3582
|
det,
|
|
3439
3583
|
onClose: closeDet,
|
|
3440
3584
|
sessionId,
|
|
3441
|
-
|
|
3585
|
+
uiWorkspace
|
|
3442
3586
|
}) : null);
|
|
3443
3587
|
}
|
|
3444
3588
|
function BoardPanel({ backlog, api, onRefresh, sessionId, onShowRun, openArtifact }) {
|
|
@@ -4456,6 +4600,26 @@ window.__ModuleLoader__.load({
|
|
|
4456
4600
|
setBusy(false);
|
|
4457
4601
|
};
|
|
4458
4602
|
const anyRunning = runs.some((r) => r.status === "running" || r.status === "pending");
|
|
4603
|
+
const runningRun = !!(activeRun && activeRun.status === "running");
|
|
4604
|
+
/** 中断请求已发出:按钮先隐藏,等 2s 轮询把状态刷成 cancelled(避免重复点出「未生效」提示)。 */
|
|
4605
|
+
const [cancelSentFor, setCancelSentFor] = react.default.useState(null);
|
|
4606
|
+
react.default.useEffect(() => {
|
|
4607
|
+
setCancelSentFor(null);
|
|
4608
|
+
}, [activeRun && activeRun.id, activeRun && activeRun.status]);
|
|
4609
|
+
const onCancel = async (id) => {
|
|
4610
|
+
if (!api) return;
|
|
4611
|
+
try {
|
|
4612
|
+
const r = unwrap(await api.cancel(id), "cancel");
|
|
4613
|
+
if (!r || r.ok !== true) throw new Error(t("cancel.failed"));
|
|
4614
|
+
setCancelSentFor(id);
|
|
4615
|
+
refresh();
|
|
4616
|
+
} catch (e) {
|
|
4617
|
+
setState((s) => ({
|
|
4618
|
+
...s,
|
|
4619
|
+
err: String(e && e.message || e)
|
|
4620
|
+
}));
|
|
4621
|
+
}
|
|
4622
|
+
};
|
|
4459
4623
|
const btn = {
|
|
4460
4624
|
font: "inherit",
|
|
4461
4625
|
fontSize: 12,
|
|
@@ -4545,7 +4709,12 @@ window.__ModuleLoader__.load({
|
|
|
4545
4709
|
alignItems: "center",
|
|
4546
4710
|
gap: 5
|
|
4547
4711
|
}
|
|
4548
|
-
}, t("workbench.refresh")),
|
|
4712
|
+
}, t("workbench.refresh")), runningRun && cancelSentFor !== activeRun.id ? h(CancelButton, {
|
|
4713
|
+
runId: activeRun.id,
|
|
4714
|
+
label: t("cancel.btnWithId", { id: String(activeRun.id).slice(-6) }),
|
|
4715
|
+
title: t("cancel.tip", { id: activeRun.id }),
|
|
4716
|
+
onConfirm: onCancel
|
|
4717
|
+
}) : null, canResume ? h("button", {
|
|
4549
4718
|
onClick: onResume,
|
|
4550
4719
|
disabled: busy,
|
|
4551
4720
|
title: t("workbench.resumeTip", {
|
|
@@ -4690,7 +4859,7 @@ window.__ModuleLoader__.load({
|
|
|
4690
4859
|
api,
|
|
4691
4860
|
runId: activeRun ? activeRun.id : null,
|
|
4692
4861
|
sessionId: props.sessionId,
|
|
4693
|
-
|
|
4862
|
+
uiWorkspace: props.uiWorkspace
|
|
4694
4863
|
}) : h(BoardPanel, {
|
|
4695
4864
|
backlog,
|
|
4696
4865
|
api,
|
|
@@ -4769,7 +4938,7 @@ window.__ModuleLoader__.load({
|
|
|
4769
4938
|
locale: NS,
|
|
4770
4939
|
inject: () => ({
|
|
4771
4940
|
remote: teamflow,
|
|
4772
|
-
|
|
4941
|
+
uiWorkspace: ctx.get("uiWorkspace"),
|
|
4773
4942
|
layout: ctx.get("layout"),
|
|
4774
4943
|
openResource: openResourceSafe,
|
|
4775
4944
|
openArtifact
|
|
@@ -4800,7 +4969,7 @@ window.__ModuleLoader__.load({
|
|
|
4800
4969
|
inject: (sessionId) => ({
|
|
4801
4970
|
sessionId,
|
|
4802
4971
|
remote: teamflow,
|
|
4803
|
-
|
|
4972
|
+
uiWorkspace: ctx.get("uiWorkspace"),
|
|
4804
4973
|
openArtifact,
|
|
4805
4974
|
openResource: openResourceSafe
|
|
4806
4975
|
})
|
package/lib/descriptors.mjs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
//#region descriptors.ts
|
|
2
|
+
/** 恒等 parse:接受任意 JSON 值,原样返回。 */
|
|
3
|
+
const JSON_SCHEMA = { parse: (value) => value };
|
|
2
4
|
/** 统一 strict codec(本插件所有参数/结果均为自由 JSON)。 */
|
|
3
5
|
const strict = {
|
|
4
6
|
mode: "strict",
|
|
5
7
|
typeSymbol: "dsh-plugin-teamflow/types#Json",
|
|
6
|
-
schema:
|
|
8
|
+
schema: JSON_SCHEMA,
|
|
9
|
+
/** 首次跨边界使用时物化 schema(宿主按需调用,只调一次并缓存)。 */
|
|
10
|
+
create: () => JSON_SCHEMA
|
|
7
11
|
};
|
|
8
12
|
const p = (name) => ({
|
|
9
13
|
name,
|