@dsh-workbench/core 0.3.5 → 0.3.6
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/client.js +123 -11
- package/dist/client/client.body.js +123 -11
- package/dist/client/index.js +123 -11
- package/dist/host/index.js +406 -38
- package/dist/types/app/client.d.ts +36 -3
- package/dist/types/modules/panels/client.d.ts +1 -1
- package/dist/types/modules/panels/model/approval.d.ts +8 -0
- package/dist/types/modules/panels/model/long-task-tools.d.ts +15 -0
- package/dist/types/modules/panels/model/long-task-tools.test.d.ts +1 -0
- package/dist/types/modules/panels/unit.d.ts +5 -3
- package/dist/types/shared/api/host/approval-call.d.ts +16 -0
- package/dist/types/shared/api/host/long-task-notice.d.ts +30 -0
- package/dist/types/shared/api/host/mount-units.d.ts +3 -1
- package/dist/types/shared/hooks/use-long-task.d.ts +8 -2
- package/package.json +2 -2
- package/skills/workbench-module-diy/KIT.md +3 -3
- package/skills/workbench-module-diy/TOOLING.md +5 -1
- package/skills/workbench-module-diy/TOOLS.md +20 -2
package/dist/host/index.js
CHANGED
|
@@ -277,6 +277,14 @@ function unitFactScope(unit) {
|
|
|
277
277
|
function runsRouteOf(unit) {
|
|
278
278
|
return `${unit}/runs`;
|
|
279
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* 停止那条写路由(走 actions 通道的资源名):`<单元>/runs/stop`。
|
|
282
|
+
* @param unit 单元 id。
|
|
283
|
+
* @returns <unit>/runs/stop。
|
|
284
|
+
*/
|
|
285
|
+
function stopRouteOf(unit) {
|
|
286
|
+
return `${unit}/runs/stop`;
|
|
287
|
+
}
|
|
280
288
|
//#endregion
|
|
281
289
|
//#region ../shared/dist/lib/text.js
|
|
282
290
|
/**
|
|
@@ -350,7 +358,7 @@ var PHASES = [
|
|
|
350
358
|
function asRecord$8(value) {
|
|
351
359
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
352
360
|
}
|
|
353
|
-
function textOf$
|
|
361
|
+
function textOf$7(value, max) {
|
|
354
362
|
if (typeof value !== "string") return "";
|
|
355
363
|
const trimmed = value.trim();
|
|
356
364
|
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
@@ -363,8 +371,8 @@ function atOf(value) {
|
|
|
363
371
|
function errorOf(raw) {
|
|
364
372
|
const record = asRecord$8(raw);
|
|
365
373
|
if (record === void 0) return void 0;
|
|
366
|
-
const code = textOf$
|
|
367
|
-
const message = textOf$
|
|
374
|
+
const code = textOf$7(record["code"], 120);
|
|
375
|
+
const message = textOf$7(record["message"], PANEL_STATE_MAX_TEXT);
|
|
368
376
|
if (code === "" && message === "") return void 0;
|
|
369
377
|
return {
|
|
370
378
|
code: code === "" ? "unknown" : code,
|
|
@@ -383,7 +391,7 @@ function parseCardsReport(payload) {
|
|
|
383
391
|
ok: false,
|
|
384
392
|
message: "请求体要是一个对象"
|
|
385
393
|
};
|
|
386
|
-
const workspace = textOf$
|
|
394
|
+
const workspace = textOf$7(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
387
395
|
if (workspace === "") return {
|
|
388
396
|
ok: false,
|
|
389
397
|
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
@@ -397,9 +405,9 @@ function parseCardsReport(payload) {
|
|
|
397
405
|
for (const item of raw.slice(0, 32)) {
|
|
398
406
|
const card = asRecord$8(item);
|
|
399
407
|
if (card === void 0) continue;
|
|
400
|
-
const unit = textOf$
|
|
401
|
-
const route = textOf$
|
|
402
|
-
const phase = textOf$
|
|
408
|
+
const unit = textOf$7(card["unit"], 200);
|
|
409
|
+
const route = textOf$7(card["route"], 400);
|
|
410
|
+
const phase = textOf$7(card["phase"], 20);
|
|
403
411
|
const at = atOf(card["at"]);
|
|
404
412
|
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
405
413
|
const error = errorOf(card["error"]);
|
|
@@ -434,9 +442,9 @@ function parseCrashReport(payload) {
|
|
|
434
442
|
ok: false,
|
|
435
443
|
message: "请求体要是一个对象"
|
|
436
444
|
};
|
|
437
|
-
const workspace = textOf$
|
|
438
|
-
const unit = textOf$
|
|
439
|
-
const reason = textOf$
|
|
445
|
+
const workspace = textOf$7(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
446
|
+
const unit = textOf$7(record["unit"], 200);
|
|
447
|
+
const reason = textOf$7(record["reason"], PANEL_STATE_MAX_TEXT);
|
|
440
448
|
if (workspace === "") return {
|
|
441
449
|
ok: false,
|
|
442
450
|
message: "workspace 不能是空的"
|
|
@@ -1596,11 +1604,17 @@ var LIVE = /* @__PURE__ */ new Set(["running", "needs-login"]);
|
|
|
1596
1604
|
var RECENT_MS = 6e5;
|
|
1597
1605
|
function createLongTaskRegistry(deps) {
|
|
1598
1606
|
const runs = /* @__PURE__ */ new Map();
|
|
1607
|
+
const controls = /* @__PURE__ */ new Map();
|
|
1599
1608
|
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
1600
1609
|
let counter = 0;
|
|
1601
1610
|
const nextId = deps.nextId ?? (() => `run-${Date.now()}-${counter += 1}`);
|
|
1602
1611
|
const recentMs = deps.recentMs ?? RECENT_MS;
|
|
1603
1612
|
const listeners = /* @__PURE__ */ new Set();
|
|
1613
|
+
const live = (id) => {
|
|
1614
|
+
const current = runs.get(id);
|
|
1615
|
+
if (current === void 0 || !LIVE.has(current.status)) return null;
|
|
1616
|
+
return current;
|
|
1617
|
+
};
|
|
1604
1618
|
const notify = (change) => {
|
|
1605
1619
|
for (const listener of [...listeners]) try {
|
|
1606
1620
|
listener(change);
|
|
@@ -1635,7 +1649,10 @@ function createLongTaskRegistry(deps) {
|
|
|
1635
1649
|
if (snapshot.workspace !== workspace) continue;
|
|
1636
1650
|
newest.set(snapshot.target, snapshot);
|
|
1637
1651
|
}
|
|
1638
|
-
return [...newest.values()].filter((snapshot) => LIVE.has(snapshot.status) || Date.parse(snapshot.at) >= floor).reverse().map((snapshot) =>
|
|
1652
|
+
return [...newest.values()].filter((snapshot) => LIVE.has(snapshot.status) || Date.parse(snapshot.at) >= floor).reverse().map((snapshot) => ({
|
|
1653
|
+
...deps.view(snapshot),
|
|
1654
|
+
id: snapshot.id
|
|
1655
|
+
}));
|
|
1639
1656
|
},
|
|
1640
1657
|
start(options) {
|
|
1641
1658
|
const running = this.active(options.workspace, options.target);
|
|
@@ -1650,15 +1667,16 @@ function createLongTaskRegistry(deps) {
|
|
|
1650
1667
|
step: "",
|
|
1651
1668
|
steps: [],
|
|
1652
1669
|
startedAt,
|
|
1653
|
-
at: startedAt
|
|
1670
|
+
at: startedAt,
|
|
1671
|
+
...options.session === void 0 ? {} : { session: options.session }
|
|
1654
1672
|
});
|
|
1655
1673
|
notify({
|
|
1656
1674
|
kind: "start",
|
|
1657
1675
|
snapshot: runs.get(id)
|
|
1658
1676
|
});
|
|
1659
1677
|
const onStep = (step) => {
|
|
1660
|
-
const current =
|
|
1661
|
-
if (current ===
|
|
1678
|
+
const current = live(id);
|
|
1679
|
+
if (current === null) return;
|
|
1662
1680
|
const steps = [...current.steps, `${step.kind === "fail" ? "✗ " : ""}${step.text}`].slice(-20);
|
|
1663
1681
|
const extra = deps.step?.(step, current) ?? {};
|
|
1664
1682
|
if (step.loginUrl !== void 0) {
|
|
@@ -1685,17 +1703,24 @@ function createLongTaskRegistry(deps) {
|
|
|
1685
1703
|
step
|
|
1686
1704
|
});
|
|
1687
1705
|
};
|
|
1706
|
+
const control = new AbortController();
|
|
1707
|
+
controls.set(id, control);
|
|
1688
1708
|
deps.run({
|
|
1689
1709
|
payload: options.payload,
|
|
1690
|
-
onStep
|
|
1710
|
+
onStep,
|
|
1711
|
+
signal: control.signal
|
|
1691
1712
|
}).then((result) => {
|
|
1692
|
-
const current =
|
|
1713
|
+
const current = live(id);
|
|
1714
|
+
if (current === null) return;
|
|
1715
|
+
controls.delete(id);
|
|
1693
1716
|
patch(id, {
|
|
1694
1717
|
status: "ok",
|
|
1695
1718
|
...deps.outcome?.(result, current) ?? {},
|
|
1696
1719
|
loginUrl: void 0
|
|
1697
1720
|
}, { kind: "settle" });
|
|
1698
1721
|
}).catch((error) => {
|
|
1722
|
+
if (live(id) === null) return;
|
|
1723
|
+
controls.delete(id);
|
|
1699
1724
|
patch(id, {
|
|
1700
1725
|
status: "failed",
|
|
1701
1726
|
why: error instanceof Error ? error.message : String(error),
|
|
@@ -1704,6 +1729,21 @@ function createLongTaskRegistry(deps) {
|
|
|
1704
1729
|
});
|
|
1705
1730
|
return runs.get(id);
|
|
1706
1731
|
},
|
|
1732
|
+
stop(id, by) {
|
|
1733
|
+
const current = live(id);
|
|
1734
|
+
if (current === null) return false;
|
|
1735
|
+
controls.get(id)?.abort();
|
|
1736
|
+
controls.delete(id);
|
|
1737
|
+
patch(id, {
|
|
1738
|
+
...deps.stopped?.(current) ?? {},
|
|
1739
|
+
status: "stopped",
|
|
1740
|
+
loginUrl: void 0
|
|
1741
|
+
}, {
|
|
1742
|
+
kind: "settle",
|
|
1743
|
+
by
|
|
1744
|
+
});
|
|
1745
|
+
return true;
|
|
1746
|
+
},
|
|
1707
1747
|
snapshot(id) {
|
|
1708
1748
|
return runs.get(id) ?? null;
|
|
1709
1749
|
},
|
|
@@ -1736,6 +1776,7 @@ function createLongTaskHost(options) {
|
|
|
1736
1776
|
offs.push(registry.onChange((change) => {
|
|
1737
1777
|
const named = [runsRouteOf(options.unit), ...facts?.(change) ?? []];
|
|
1738
1778
|
options.bump([...new Set(named)]);
|
|
1779
|
+
options.notify?.(change);
|
|
1739
1780
|
}));
|
|
1740
1781
|
return registry;
|
|
1741
1782
|
} },
|
|
@@ -1802,7 +1843,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
1802
1843
|
function ledgerKeyOf(entry) {
|
|
1803
1844
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1804
1845
|
}
|
|
1805
|
-
function textOf$
|
|
1846
|
+
function textOf$6(value) {
|
|
1806
1847
|
if (typeof value !== "string") return void 0;
|
|
1807
1848
|
const trimmed = value.trim();
|
|
1808
1849
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -1810,13 +1851,13 @@ function textOf$5(value) {
|
|
|
1810
1851
|
function entryOf$1(value) {
|
|
1811
1852
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1812
1853
|
const box = value;
|
|
1813
|
-
const opencliHome = textOf$
|
|
1814
|
-
const pkg = textOf$
|
|
1815
|
-
const installedAt = textOf$
|
|
1854
|
+
const opencliHome = textOf$6(box["opencliHome"]);
|
|
1855
|
+
const pkg = textOf$6(box["package"]);
|
|
1856
|
+
const installedAt = textOf$6(box["installedAt"]);
|
|
1816
1857
|
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
1817
|
-
const name = textOf$
|
|
1818
|
-
const registry = textOf$
|
|
1819
|
-
const releasedAt = textOf$
|
|
1858
|
+
const name = textOf$6(box["name"]);
|
|
1859
|
+
const registry = textOf$6(box["registry"]);
|
|
1860
|
+
const releasedAt = textOf$6(box["releasedAt"]);
|
|
1820
1861
|
return {
|
|
1821
1862
|
opencliHome,
|
|
1822
1863
|
package: pkg,
|
|
@@ -2789,6 +2830,8 @@ var DIY_RECORD_PREPARE_TOOL = "workbench_diy_record_prepare";
|
|
|
2789
2830
|
var DIY_RECORD_READ_TOOL = "workbench_diy_record_read";
|
|
2790
2831
|
var DIY_RECORD_DETAIL_TOOL = "workbench_diy_record_detail";
|
|
2791
2832
|
var DIY_RECORD_STOP_TOOL = "workbench_diy_record_stop";
|
|
2833
|
+
var LONG_TASKS_LIST_TOOL = "workbench_long_tasks_list";
|
|
2834
|
+
var LONG_TASKS_STOP_TOOL = "workbench_long_tasks_stop";
|
|
2792
2835
|
var DIY_DEBUG_START_TOOL = "workbench_diy_debug_start";
|
|
2793
2836
|
var DIY_DEBUG_PAUSE_TOOL = "workbench_diy_debug_pause";
|
|
2794
2837
|
var DIY_DEBUG_RESUME_TOOL = "workbench_diy_debug_resume";
|
|
@@ -2821,7 +2864,8 @@ var READ_TOOLS = [
|
|
|
2821
2864
|
DIY_LOGS_STOP_TOOL,
|
|
2822
2865
|
DIY_RECORD_READ_TOOL,
|
|
2823
2866
|
DIY_RECORD_DETAIL_TOOL,
|
|
2824
|
-
DIY_RECORD_STOP_TOOL
|
|
2867
|
+
DIY_RECORD_STOP_TOOL,
|
|
2868
|
+
LONG_TASKS_LIST_TOOL
|
|
2825
2869
|
];
|
|
2826
2870
|
var WRITE_TOOLS = [
|
|
2827
2871
|
PANELS_SHOW_TOOL,
|
|
@@ -2843,7 +2887,8 @@ var WRITE_TOOLS = [
|
|
|
2843
2887
|
DIY_DEBUG_START_TOOL,
|
|
2844
2888
|
DIY_DEBUG_PAUSE_TOOL,
|
|
2845
2889
|
DIY_DEBUG_RESUME_TOOL,
|
|
2846
|
-
DIY_DEBUG_STOP_TOOL
|
|
2890
|
+
DIY_DEBUG_STOP_TOOL,
|
|
2891
|
+
LONG_TASKS_STOP_TOOL
|
|
2847
2892
|
];
|
|
2848
2893
|
var DIY_TOOLS = [
|
|
2849
2894
|
DIY_LIST_TOOL,
|
|
@@ -2902,6 +2947,7 @@ var panelsUnit = {
|
|
|
2902
2947
|
//#endregion
|
|
2903
2948
|
//#region src/modules/panels/model/approval.ts
|
|
2904
2949
|
var SPEC_LIMIT = 80;
|
|
2950
|
+
var TARGET_LIMIT = 20;
|
|
2905
2951
|
function missing(id) {
|
|
2906
2952
|
return id === void 0 || id === "";
|
|
2907
2953
|
}
|
|
@@ -2976,7 +3022,32 @@ function askOf(reason, question, fallback) {
|
|
|
2976
3022
|
fallback
|
|
2977
3023
|
};
|
|
2978
3024
|
}
|
|
3025
|
+
function textOf$5(raw, key) {
|
|
3026
|
+
const value = raw[key];
|
|
3027
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3028
|
+
}
|
|
3029
|
+
function clipped(text) {
|
|
3030
|
+
const chars = [...text];
|
|
3031
|
+
return chars.length > TARGET_LIMIT ? `${chars.slice(0, TARGET_LIMIT).join("")}…` : text;
|
|
3032
|
+
}
|
|
3033
|
+
function stopVerdict(call, runs) {
|
|
3034
|
+
const raw = argsOf$2(call);
|
|
3035
|
+
const unit = textOf$5(raw, "unit");
|
|
3036
|
+
const id = textOf$5(raw, "id");
|
|
3037
|
+
if (unit === "" || id === "") return { kind: "allow" };
|
|
3038
|
+
const run = runs?.().get(unit)?.snapshot(id) ?? null;
|
|
3039
|
+
if (runs !== void 0 && run === null) return { kind: "allow" };
|
|
3040
|
+
const session = run?.session ?? "";
|
|
3041
|
+
if (session !== "" && session === call.session) return { kind: "allow" };
|
|
3042
|
+
const target = run === null ? "" : run.target.trim();
|
|
3043
|
+
const where = target === "" ? `工作台 [${unit}] 那一趟长任务` : `工作台 [${unit}] 那一趟「${clipped(target)}」长任务`;
|
|
3044
|
+
const whereEn = target === "" ? `a long-running workbench job [${unit}]` : `a long-running workbench job [${unit}] "${clipped(target)}"`;
|
|
3045
|
+
const origin = session === "" ? "用户自己按的" : "别的会话起的";
|
|
3046
|
+
const originEn = session === "" ? "the user started" : "another session started";
|
|
3047
|
+
return askOf(`${LONG_TASKS_STOP_TOOL} ${unit} stops a long run someone else started`, pair(`会停掉${where},${origin},允许吗?`, `it stops ${whereEn} ${originEn}. Allow?`), pair(`agent 想停掉${where},${origin},允许吗?`, `The agent wants to stop ${whereEn} ${originEn}. Allow?`));
|
|
3048
|
+
}
|
|
2979
3049
|
var panelsApproval = (call) => {
|
|
3050
|
+
if (call.name === "workbench_long_tasks_stop") return stopVerdict(call, void 0);
|
|
2980
3051
|
if (call.name === "workbench_diy_debug_start" || call.name === "workbench_diy_debug_resume") {
|
|
2981
3052
|
const starting = call.name === DIY_DEBUG_START_TOOL;
|
|
2982
3053
|
const unit = argsOf$2(call)["unit"];
|
|
@@ -3048,6 +3119,15 @@ var panelsApproval = (call) => {
|
|
|
3048
3119
|
default: return;
|
|
3049
3120
|
}
|
|
3050
3121
|
};
|
|
3122
|
+
/**
|
|
3123
|
+
* 面板族的审批策略:`panelsApproval` 再认长任务那一档(写成功要用户点头)。
|
|
3124
|
+
* `longTaskRuns` 用来查"这一趟是谁起的" —— 自己起的免问,别的会话起的 / 用户按的要问。
|
|
3125
|
+
* @param longTaskRuns 当前挂着的单元各自那把长任务注册表;没给就按"别人的"问(保守的那一侧)。
|
|
3126
|
+
* @returns 审批策略:认领的写工具回 ask / allow,不是本族的回 undefined。
|
|
3127
|
+
*/
|
|
3128
|
+
function panelsApprovalFor(longTaskRuns) {
|
|
3129
|
+
return (call) => call.name === "workbench_long_tasks_stop" ? stopVerdict(call, longTaskRuns) : panelsApproval(call);
|
|
3130
|
+
}
|
|
3051
3131
|
//#endregion
|
|
3052
3132
|
//#region src/modules/panels/model/config.ts
|
|
3053
3133
|
var EMPTY_BOARD = {
|
|
@@ -3198,6 +3278,191 @@ function withMoved(board, id, index, visible) {
|
|
|
3198
3278
|
};
|
|
3199
3279
|
}
|
|
3200
3280
|
//#endregion
|
|
3281
|
+
//#region src/modules/panels/model/long-task-tools.ts
|
|
3282
|
+
var STATUS_WORDS = {
|
|
3283
|
+
running: "进行中",
|
|
3284
|
+
"needs-login": "要登录",
|
|
3285
|
+
stopped: "被停了",
|
|
3286
|
+
ok: "已完成",
|
|
3287
|
+
failed: "出错"
|
|
3288
|
+
};
|
|
3289
|
+
var NO_WORKSPACE = "不知道当前会话在哪个工作区 —— 长任务按工作区记,这条读不了。";
|
|
3290
|
+
var NO_RUNS = "这个工作区现在没有长任务在跑(也没有十分钟内刚收尾的)。";
|
|
3291
|
+
var NO_RUN = "没有这一趟 —— unit/id 对不上。";
|
|
3292
|
+
var STALE_REGISTRY = "这一份外壳还是上一版的注册表,停不了 —— 重新起一下工作台就能停";
|
|
3293
|
+
var LIST_OUTPUT = {
|
|
3294
|
+
schema: {
|
|
3295
|
+
type: "object",
|
|
3296
|
+
properties: {
|
|
3297
|
+
text: { type: "string" },
|
|
3298
|
+
runs: {
|
|
3299
|
+
type: "array",
|
|
3300
|
+
items: {
|
|
3301
|
+
type: "object",
|
|
3302
|
+
properties: {
|
|
3303
|
+
unit: { type: "string" },
|
|
3304
|
+
id: { type: "string" },
|
|
3305
|
+
target: { type: "string" },
|
|
3306
|
+
status: { type: "string" }
|
|
3307
|
+
},
|
|
3308
|
+
additionalProperties: true
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
},
|
|
3312
|
+
additionalProperties: true
|
|
3313
|
+
},
|
|
3314
|
+
render: (_args, value) => [{
|
|
3315
|
+
type: "text",
|
|
3316
|
+
text: typeof value?.text === "string" ? value.text : JSON.stringify(value)
|
|
3317
|
+
}]
|
|
3318
|
+
};
|
|
3319
|
+
var STOP_OUTPUT = {
|
|
3320
|
+
schema: {
|
|
3321
|
+
type: "object",
|
|
3322
|
+
properties: {
|
|
3323
|
+
text: { type: "string" },
|
|
3324
|
+
stopped: { type: "boolean" }
|
|
3325
|
+
},
|
|
3326
|
+
additionalProperties: true
|
|
3327
|
+
},
|
|
3328
|
+
render: (_args, value) => [{
|
|
3329
|
+
type: "text",
|
|
3330
|
+
text: typeof value?.text === "string" ? value.text : JSON.stringify(value)
|
|
3331
|
+
}]
|
|
3332
|
+
};
|
|
3333
|
+
function str(value) {
|
|
3334
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3335
|
+
}
|
|
3336
|
+
function arg(raw, key) {
|
|
3337
|
+
return str(typeof raw === "object" && raw !== null ? raw[key] : void 0);
|
|
3338
|
+
}
|
|
3339
|
+
function statusWord(status) {
|
|
3340
|
+
return STATUS_WORDS[status] ?? status;
|
|
3341
|
+
}
|
|
3342
|
+
function originOf(session, caller) {
|
|
3343
|
+
if (session === "") return "用户按的";
|
|
3344
|
+
return session === caller ? "我起的" : "别的会话起的";
|
|
3345
|
+
}
|
|
3346
|
+
function listed$1(runs, workspace) {
|
|
3347
|
+
const out = [];
|
|
3348
|
+
for (const [unit, registry] of runs?.() ?? []) for (const row of registry.current(workspace)) {
|
|
3349
|
+
const id = str(row["id"]);
|
|
3350
|
+
const run = id === "" ? null : registry.snapshot(id);
|
|
3351
|
+
out.push({
|
|
3352
|
+
unit,
|
|
3353
|
+
id,
|
|
3354
|
+
target: str(row["target"]) || str(run?.target),
|
|
3355
|
+
status: str(row["status"]) || str(run?.status),
|
|
3356
|
+
step: str(row["step"]) || str(row["why"]) || str(run?.step) || str(run?.why),
|
|
3357
|
+
session: str(row["session"]) || str(run?.session)
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
return out;
|
|
3361
|
+
}
|
|
3362
|
+
function lineOf(run, index, caller) {
|
|
3363
|
+
const parts = [
|
|
3364
|
+
run.unit,
|
|
3365
|
+
run.target,
|
|
3366
|
+
statusWord(run.status),
|
|
3367
|
+
run.step,
|
|
3368
|
+
run.id === "" ? "" : `id=${run.id}`,
|
|
3369
|
+
originOf(run.session, caller)
|
|
3370
|
+
];
|
|
3371
|
+
return `${String(index + 1)}. ${parts.filter((part) => part !== "").join(" · ")}`;
|
|
3372
|
+
}
|
|
3373
|
+
function renderList$1(runs, workspace, caller) {
|
|
3374
|
+
if (runs.length === 0) return NO_RUNS;
|
|
3375
|
+
return [`当前工作区:${workspace}`, ...runs.map((run, index) => lineOf(run, index, caller))].join("\n");
|
|
3376
|
+
}
|
|
3377
|
+
/**
|
|
3378
|
+
* 注册长任务那两把外壳工具:看得见(`workbench_long_tasks_list`)与停得了任何单元的一趟
|
|
3379
|
+
* (`workbench_long_tasks_stop`)。`longTaskRuns` 是晚绑定的取值器:两把都从它取注册表,
|
|
3380
|
+
* 所以单元晚挂上 / 被摘掉都跟得上;没给的话停那把一律回"没有这一趟"。
|
|
3381
|
+
* @param ctx 宿主上下文,`tools` 服务不在时什么都不注册。
|
|
3382
|
+
* @param longTaskRuns 当前挂着的单元各自那把长任务注册表。
|
|
3383
|
+
* @returns 注销函数(没有 tools 服务就是空数组)。
|
|
3384
|
+
*/
|
|
3385
|
+
function registerLongTaskTools(ctx, longTaskRuns) {
|
|
3386
|
+
const tools = ctx.get("tools");
|
|
3387
|
+
if (tools === void 0) {
|
|
3388
|
+
ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过外壳长任务工具注册");
|
|
3389
|
+
return [];
|
|
3390
|
+
}
|
|
3391
|
+
const listTool = {
|
|
3392
|
+
name: LONG_TASKS_LIST_TOOL,
|
|
3393
|
+
description: "看这个工作区在跑(或十分钟内刚收尾)的长任务:每行是 单元 · 目标 · 状态 · 当前一步 · id · 谁起的;要停哪一趟用 workbench_long_tasks_stop。",
|
|
3394
|
+
parameters: {
|
|
3395
|
+
type: "object",
|
|
3396
|
+
properties: {},
|
|
3397
|
+
additionalProperties: false
|
|
3398
|
+
},
|
|
3399
|
+
output: LIST_OUTPUT,
|
|
3400
|
+
async execute(_raw, exec) {
|
|
3401
|
+
const workspace = sessionWorkspace(exec);
|
|
3402
|
+
if (workspace === null) return {
|
|
3403
|
+
runs: [],
|
|
3404
|
+
text: NO_WORKSPACE
|
|
3405
|
+
};
|
|
3406
|
+
const runs = listed$1(longTaskRuns, workspace);
|
|
3407
|
+
return {
|
|
3408
|
+
runs,
|
|
3409
|
+
text: renderList$1(runs, workspace, sessionIdOf(exec))
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
};
|
|
3413
|
+
const stopTool = {
|
|
3414
|
+
name: LONG_TASKS_STOP_TOOL,
|
|
3415
|
+
description: "停掉某一趟长任务(unit 与 id 从 workbench_long_tasks_list 拿)。只是让外壳给它发中断信号,平台侧那趟活儿的收尾看模块自己。",
|
|
3416
|
+
parameters: {
|
|
3417
|
+
type: "object",
|
|
3418
|
+
properties: {
|
|
3419
|
+
unit: {
|
|
3420
|
+
type: "string",
|
|
3421
|
+
description: "那一趟属于的单元 id(如 cmdb)。"
|
|
3422
|
+
},
|
|
3423
|
+
id: {
|
|
3424
|
+
type: "string",
|
|
3425
|
+
description: "那一趟的 id(列表里 id= 后面那串)。"
|
|
3426
|
+
}
|
|
3427
|
+
},
|
|
3428
|
+
required: ["unit", "id"],
|
|
3429
|
+
additionalProperties: false
|
|
3430
|
+
},
|
|
3431
|
+
output: STOP_OUTPUT,
|
|
3432
|
+
async execute(raw, exec) {
|
|
3433
|
+
const unit = arg(raw, "unit");
|
|
3434
|
+
const id = arg(raw, "id");
|
|
3435
|
+
if (unit === "" || id === "") return {
|
|
3436
|
+
text: NO_RUN,
|
|
3437
|
+
stopped: false
|
|
3438
|
+
};
|
|
3439
|
+
const registry = longTaskRuns?.().get(unit);
|
|
3440
|
+
if (registry === void 0 || typeof registry.stop !== "function") return {
|
|
3441
|
+
text: registry === void 0 ? NO_RUN : STALE_REGISTRY,
|
|
3442
|
+
stopped: false
|
|
3443
|
+
};
|
|
3444
|
+
const run = registry.snapshot(id);
|
|
3445
|
+
if (run === null) return {
|
|
3446
|
+
text: NO_RUN,
|
|
3447
|
+
stopped: false
|
|
3448
|
+
};
|
|
3449
|
+
if (run.workspace !== sessionWorkspace(exec)) return {
|
|
3450
|
+
text: `这一趟不在你当前的工作区(它在 ${run.workspace})—— 没动它。`,
|
|
3451
|
+
stopped: false
|
|
3452
|
+
};
|
|
3453
|
+
if (!registry.stop(id, sessionIdOf(exec) ?? void 0)) return {
|
|
3454
|
+
text: `这一趟已经不在跑了(${statusWord(run.status)})—— 没动它。`,
|
|
3455
|
+
stopped: false
|
|
3456
|
+
};
|
|
3457
|
+
return {
|
|
3458
|
+
text: `停了:${unit} · ${run.target}(${id})。这一趟的活儿收到中断信号 —— 它认不认、停在哪一步看它自己。`,
|
|
3459
|
+
stopped: true
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
};
|
|
3463
|
+
return [tools.register(listTool), tools.register(stopTool)];
|
|
3464
|
+
}
|
|
3465
|
+
//#endregion
|
|
3201
3466
|
//#region src/shared/api/host/diy-components.ts
|
|
3202
3467
|
var SURFACE_TIMEOUT_MS = 3e4;
|
|
3203
3468
|
var PRUNE_CHARS = 8192;
|
|
@@ -9618,8 +9883,21 @@ function panelsHandoffEntry(target = {}) {
|
|
|
9618
9883
|
key: `path:${path}`
|
|
9619
9884
|
};
|
|
9620
9885
|
}
|
|
9886
|
+
function listedRunUnits(result) {
|
|
9887
|
+
const outer = typeof result === "object" && result !== null ? result : {};
|
|
9888
|
+
const unwrapped = outer["value"];
|
|
9889
|
+
const runs = (typeof unwrapped === "object" && unwrapped !== null ? unwrapped : outer)["runs"];
|
|
9890
|
+
if (!Array.isArray(runs)) return [];
|
|
9891
|
+
const units = /* @__PURE__ */ new Set();
|
|
9892
|
+
for (const row of runs) {
|
|
9893
|
+
const unit = typeof row === "object" && row !== null ? row.unit : void 0;
|
|
9894
|
+
if (typeof unit === "string" && unit !== "") units.add(unit);
|
|
9895
|
+
}
|
|
9896
|
+
return [...units];
|
|
9897
|
+
}
|
|
9621
9898
|
function panelsFactsOf(call, manifests) {
|
|
9622
9899
|
if (call.source === "workbench_panels_list") return [PANELS_FACT];
|
|
9900
|
+
if (call.source === "workbench_long_tasks_list") return listedRunUnits(call.result).map(runsRouteOf);
|
|
9623
9901
|
if (call.source === "workbench_modules_list") return [PANELS_FACT, CAPABILITY_FACT];
|
|
9624
9902
|
if (call.source === "workbench_modules_update_workbench") return [PANELS_FACT, CAPABILITY_FACT];
|
|
9625
9903
|
if (call.source === "workbench_diy_list") return [PANELS_FACT];
|
|
@@ -10183,7 +10461,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
10183
10461
|
var OWNED = [{
|
|
10184
10462
|
unit: panelsUnit.id,
|
|
10185
10463
|
manifest: panelsUnit,
|
|
10186
|
-
mount({ ctx, logger, manifests, ownedUnits, installable, opencliCommand, probe, store, values }) {
|
|
10464
|
+
mount({ ctx, logger, manifests, ownedUnits, installable, opencliCommand, probe, store, values, longTaskRuns }) {
|
|
10187
10465
|
const ledger = createPluginLedger({
|
|
10188
10466
|
store,
|
|
10189
10467
|
home: isolatedHome()
|
|
@@ -10271,10 +10549,11 @@ var OWNED = [{
|
|
|
10271
10549
|
...registerPanelTools(ctx, panels, values),
|
|
10272
10550
|
...registerMarketTools(ctx, market),
|
|
10273
10551
|
...registerDiyTools(ctx, market, panels, debug),
|
|
10274
|
-
...registerRecordTools(ctx, record)
|
|
10552
|
+
...registerRecordTools(ctx, record),
|
|
10553
|
+
...registerLongTaskTools(ctx, longTaskRuns)
|
|
10275
10554
|
],
|
|
10276
10555
|
factsOf: (call) => panelsFactsOf(call, manifests()),
|
|
10277
|
-
needsApproval:
|
|
10556
|
+
needsApproval: panelsApprovalFor(longTaskRuns),
|
|
10278
10557
|
gate: {
|
|
10279
10558
|
deniedTools: (context) => {
|
|
10280
10559
|
const boards = panels.boards(context.workspace);
|
|
@@ -10331,7 +10610,29 @@ function withRunsRoute(result, unit) {
|
|
|
10331
10610
|
runs: path === "" ? [] : runs.current(path)
|
|
10332
10611
|
};
|
|
10333
10612
|
}
|
|
10334
|
-
}
|
|
10613
|
+
},
|
|
10614
|
+
actions: [...result.actions ?? [], {
|
|
10615
|
+
resource: stopRouteOf(unit),
|
|
10616
|
+
handle(payload) {
|
|
10617
|
+
if (typeof runs.stop !== "function") return {
|
|
10618
|
+
ok: false,
|
|
10619
|
+
text: "这一份外壳还是上一版的注册表,停不了 —— 重新起一下工作台就能停"
|
|
10620
|
+
};
|
|
10621
|
+
const id = payload?.id;
|
|
10622
|
+
if (typeof id !== "string" || id.trim() === "") return {
|
|
10623
|
+
ok: false,
|
|
10624
|
+
text: "要停哪一趟?给一个 id 才成"
|
|
10625
|
+
};
|
|
10626
|
+
if (runs.snapshot(id) === null) return {
|
|
10627
|
+
ok: false,
|
|
10628
|
+
text: "没有这一趟 —— id 对不上,没什么可停的"
|
|
10629
|
+
};
|
|
10630
|
+
return {
|
|
10631
|
+
ok: true,
|
|
10632
|
+
stopped: runs.stop(id)
|
|
10633
|
+
};
|
|
10634
|
+
}
|
|
10635
|
+
}]
|
|
10335
10636
|
};
|
|
10336
10637
|
}
|
|
10337
10638
|
function declaresNote(definition) {
|
|
@@ -10500,6 +10801,7 @@ function mountUnits(units, deps) {
|
|
|
10500
10801
|
const declaredNotes = /* @__PURE__ */ new Set();
|
|
10501
10802
|
const skipped = [];
|
|
10502
10803
|
const longTaskDisposers = /* @__PURE__ */ new Map();
|
|
10804
|
+
const longTaskRuns = deps.longTaskRuns ?? /* @__PURE__ */ new Map();
|
|
10503
10805
|
const ledger = {
|
|
10504
10806
|
failed: /* @__PURE__ */ new Set(),
|
|
10505
10807
|
failures: []
|
|
@@ -10520,7 +10822,8 @@ function mountUnits(units, deps) {
|
|
|
10520
10822
|
const longTasks = deps.longTasks === void 0 ? void 0 : createLongTaskHost({
|
|
10521
10823
|
scope: deps.longTasks.scope,
|
|
10522
10824
|
unit,
|
|
10523
|
-
bump: deps.longTasks.bump
|
|
10825
|
+
bump: deps.longTasks.bump,
|
|
10826
|
+
...deps.longTasks.notify === void 0 ? {} : { notify: deps.longTasks.notify }
|
|
10524
10827
|
});
|
|
10525
10828
|
let result;
|
|
10526
10829
|
try {
|
|
@@ -10535,6 +10838,7 @@ function mountUnits(units, deps) {
|
|
|
10535
10838
|
...deps.probe === void 0 ? {} : { probe: deps.probe },
|
|
10536
10839
|
...deps.values === void 0 ? {} : { values: deps.values },
|
|
10537
10840
|
...longTasks === void 0 ? {} : { longTasks: longTasks.host },
|
|
10841
|
+
longTaskRuns: () => longTaskRuns,
|
|
10538
10842
|
store
|
|
10539
10843
|
});
|
|
10540
10844
|
} catch (error) {
|
|
@@ -10555,6 +10859,8 @@ function mountUnits(units, deps) {
|
|
|
10555
10859
|
};
|
|
10556
10860
|
mounted.push(item);
|
|
10557
10861
|
if (longTasks !== void 0) longTaskDisposers.set(item, longTasks.dispose);
|
|
10862
|
+
const runs = item.result.longTasks;
|
|
10863
|
+
if (runs !== void 0) longTaskRuns.set(unit, runs);
|
|
10558
10864
|
}
|
|
10559
10865
|
const collected = collectRoutes(mounted, ledger);
|
|
10560
10866
|
const prefs = collectNamed(mounted, ledger, (item) => item.result.prefs === void 0 ? [] : [item.result.prefs], "偏好资源");
|
|
@@ -10575,6 +10881,8 @@ function mountUnits(units, deps) {
|
|
|
10575
10881
|
if (!ledger.failed.has(item)) continue;
|
|
10576
10882
|
longTaskDisposers.get(item)?.();
|
|
10577
10883
|
longTaskDisposers.delete(item);
|
|
10884
|
+
const runs = item.result.longTasks;
|
|
10885
|
+
if (runs !== void 0 && longTaskRuns.get(item.host.manifest.id) === runs) longTaskRuns.delete(item.host.manifest.id);
|
|
10578
10886
|
}
|
|
10579
10887
|
return {
|
|
10580
10888
|
mounted: alive,
|
|
@@ -10594,6 +10902,10 @@ function mountUnits(units, deps) {
|
|
|
10594
10902
|
disposeLongTasks: () => {
|
|
10595
10903
|
for (const dispose of [...longTaskDisposers.values()]) dispose();
|
|
10596
10904
|
longTaskDisposers.clear();
|
|
10905
|
+
for (const item of mounted) {
|
|
10906
|
+
const runs = item.result.longTasks;
|
|
10907
|
+
if (runs !== void 0 && longTaskRuns.get(item.host.manifest.id) === runs) longTaskRuns.delete(item.host.manifest.id);
|
|
10908
|
+
}
|
|
10597
10909
|
}
|
|
10598
10910
|
};
|
|
10599
10911
|
}
|
|
@@ -12905,6 +13217,64 @@ function warn(ctx, message) {
|
|
|
12905
13217
|
ctx.logger?.warn?.(`[dsh-workbench] ${message}`);
|
|
12906
13218
|
}
|
|
12907
13219
|
//#endregion
|
|
13220
|
+
//#region src/shared/api/host/approval-call.ts
|
|
13221
|
+
/**
|
|
13222
|
+
* 把 dsh 交来的那次工具调用折成审批策略要看的那份 call:工作区与发起会话都从 `exec` 现取,取不到就不写那个键。
|
|
13223
|
+
* @param exec dsh 交来的执行上下文;没有就只有实参这一格。
|
|
13224
|
+
* @param name 工具名;`undefined` = 这次调用没名字,不写这个键。
|
|
13225
|
+
* @returns 交给 `approvalFor` 的 `ApprovalCall`。
|
|
13226
|
+
*/
|
|
13227
|
+
function approvalCallOf(exec, name) {
|
|
13228
|
+
const workspace = sessionWorkspace(exec) ?? void 0;
|
|
13229
|
+
const session = sessionIdOf(exec) ?? void 0;
|
|
13230
|
+
return {
|
|
13231
|
+
...name === void 0 ? {} : { name },
|
|
13232
|
+
arguments: exec?.arguments,
|
|
13233
|
+
...workspace === void 0 ? {} : { workspace },
|
|
13234
|
+
...session === void 0 ? {} : { session }
|
|
13235
|
+
};
|
|
13236
|
+
}
|
|
13237
|
+
//#endregion
|
|
13238
|
+
//#region src/shared/api/host/long-task-notice.ts
|
|
13239
|
+
/**
|
|
13240
|
+
* 这条 notice 的来源标识(`source.kind`)—— 长任务自己一格,与调试 / 录制分开。
|
|
13241
|
+
*/
|
|
13242
|
+
var LONG_TASK_NOTICE_KIND = "workbench-long-task";
|
|
13243
|
+
/**
|
|
13244
|
+
* 判这一趟被停要不要回流给发起它的那个会话,并把话写好。
|
|
13245
|
+
* 只有"结算 + `stopped` + 这一趟记得会话 + 不是它自己停的"才回一条;跑完 / 失败 / 界面起的(没记会话)/ 自己停的回 `null`。
|
|
13246
|
+
* @param unit 这一趟属于哪个单元(进文案)。
|
|
13247
|
+
* @param change 长任务那一次变更;只有 `kind: 'settle'` 且 `snapshot.status === 'stopped'` 才可能成。
|
|
13248
|
+
* @returns 该发的那条(带收件会话),或者 `null`(不发)。
|
|
13249
|
+
*/
|
|
13250
|
+
function longTaskStopNotice(unit, change) {
|
|
13251
|
+
if (change.kind !== "settle") return null;
|
|
13252
|
+
const snapshot = change.snapshot;
|
|
13253
|
+
if (snapshot.status !== "stopped") return null;
|
|
13254
|
+
const session = snapshot.session;
|
|
13255
|
+
if (session === void 0 || session === "") return null;
|
|
13256
|
+
if (change.by === session) return null;
|
|
13257
|
+
const target = snapshot.target;
|
|
13258
|
+
const summary = `长任务:${unit} 的「${target}」被停了`;
|
|
13259
|
+
return {
|
|
13260
|
+
session,
|
|
13261
|
+
summary,
|
|
13262
|
+
message: noticeMessage(`[长任务 · ${unit}]「${target}」那一趟被${change.by === void 0 ? "用户" : "别的会话"}停了 —— 别再把它当"还在跑";要接着做就重新起一趟(workbench_long_tasks_list 看当前这几趟)。`, summary, LONG_TASK_NOTICE_KIND)
|
|
13263
|
+
};
|
|
13264
|
+
}
|
|
13265
|
+
/**
|
|
13266
|
+
* 把"这一趟被停了"送出去:正在跑的会话 inject 一条、闲着的跳过、没有这个 agent 就当没发生(模式 `drop`)。
|
|
13267
|
+
* @param agents 会话表(宿主从 `ctx.get('agents')` 拿);拿不到就什么都不发。
|
|
13268
|
+
* @param unit 这一趟属于哪个单元。
|
|
13269
|
+
* @param change 长任务那一次变更。
|
|
13270
|
+
* @returns 送出去的结果;判据说不该发(`longTaskStopNotice` 回 `null`)就回 `null`。
|
|
13271
|
+
*/
|
|
13272
|
+
function notifyLongTaskStop(agents, unit, change) {
|
|
13273
|
+
const notice = longTaskStopNotice(unit, change);
|
|
13274
|
+
if (notice === null) return null;
|
|
13275
|
+
return deliverDebugNotice(agents, notice.session, notice.message, "drop");
|
|
13276
|
+
}
|
|
13277
|
+
//#endregion
|
|
12908
13278
|
//#region src/app/host.ts
|
|
12909
13279
|
var name = "dsh-workbench";
|
|
12910
13280
|
var inject = ["webServer"];
|
|
@@ -13024,6 +13394,7 @@ function apply(ctx) {
|
|
|
13024
13394
|
});
|
|
13025
13395
|
const mounted = /* @__PURE__ */ new Map();
|
|
13026
13396
|
const mountFailures = /* @__PURE__ */ new Map();
|
|
13397
|
+
const longTaskRuns = /* @__PURE__ */ new Map();
|
|
13027
13398
|
const factsOfMap = /* @__PURE__ */ new Map();
|
|
13028
13399
|
const routesOfUnitMap = /* @__PURE__ */ new Map();
|
|
13029
13400
|
const ownersMap = /* @__PURE__ */ new Map();
|
|
@@ -13076,8 +13447,10 @@ function apply(ctx) {
|
|
|
13076
13447
|
values,
|
|
13077
13448
|
longTasks: {
|
|
13078
13449
|
scope: process.env["DSH_HOME"] ?? "",
|
|
13079
|
-
bump: (names) => facts.bump(names)
|
|
13080
|
-
|
|
13450
|
+
bump: (names) => facts.bump(names),
|
|
13451
|
+
notify: (change) => notifyLongTaskStop(ctx.get("agents"), host.unit, change)
|
|
13452
|
+
},
|
|
13453
|
+
longTaskRuns
|
|
13081
13454
|
});
|
|
13082
13455
|
} catch (error) {
|
|
13083
13456
|
mountFailures.set(id, {
|
|
@@ -13255,12 +13628,7 @@ function apply(ctx) {
|
|
|
13255
13628
|
const tool = name === void 0 ? void 0 : unitsOfTool().get(name);
|
|
13256
13629
|
const owner = tool === void 0 ? void 0 : mounted.get(tool.unit)?.result;
|
|
13257
13630
|
const detailOf = owner === void 0 ? void 0 : (call) => owner.approvalDetail?.(call);
|
|
13258
|
-
const
|
|
13259
|
-
const call = {
|
|
13260
|
-
...name === void 0 ? {} : { name },
|
|
13261
|
-
arguments: exec.arguments,
|
|
13262
|
-
...workspace === void 0 ? {} : { workspace }
|
|
13263
|
-
};
|
|
13631
|
+
const call = approvalCallOf(exec, name);
|
|
13264
13632
|
const policy = owner?.needsApproval;
|
|
13265
13633
|
const declaredNote = name !== void 0 && declaredNoteTools().has(name);
|
|
13266
13634
|
return await approvalFor(call, {
|