@dsh-workbench/core 0.3.4 → 0.3.5
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 +1243 -78
- package/dist/client/client.body.js +1243 -78
- package/dist/client/index.js +1243 -78
- package/dist/host/index.js +623 -841
- package/dist/types/app/client.d.ts +201 -60
- package/dist/types/modules/panels/model/diy-context.d.ts +4 -5
- package/dist/types/modules/panels/model/diy-observers.d.ts +2 -2
- package/dist/types/modules/panels/model/diy-page.d.ts +1 -1
- package/dist/types/modules/panels/unit.d.ts +3 -4
- package/dist/types/shared/api/context-chips.d.ts +20 -0
- package/dist/types/shared/api/host/diy-components.d.ts +17 -6
- package/dist/types/shared/api/host/diy-workshop.d.ts +0 -3
- package/dist/types/shared/api/host/preset-scope.d.ts +16 -0
- package/dist/types/shared/api/host/tool-scope.d.ts +6 -2
- package/dist/types/shared/api/host/workshop-root.d.ts +7 -0
- package/dist/types/shared/components/BlockerState.d.ts +12 -0
- package/dist/types/shared/components/PanelFoot.d.ts +9 -0
- package/dist/types/shared/components/Tip.d.ts +8 -0
- package/dist/types/shared/hooks/bundle-queries.d.ts +12 -0
- package/dist/types/shared/hooks/fact-transports.d.ts +4 -0
- package/dist/types/shared/hooks/panel-state-report.d.ts +12 -0
- package/dist/types/shared/hooks/use-api-resource.d.ts +22 -0
- package/dist/types/shared/hooks/use-blocker-actions.d.ts +8 -0
- package/dist/types/shared/hooks/use-capabilities.d.ts +55 -0
- package/dist/types/shared/hooks/use-capability-gate.d.ts +16 -0
- package/dist/types/shared/hooks/use-card-summary.d.ts +7 -0
- package/dist/types/shared/hooks/use-diy-debug.d.ts +13 -0
- package/dist/types/shared/hooks/use-diy-record.d.ts +13 -0
- package/dist/types/shared/hooks/use-facts.d.ts +10 -0
- package/dist/types/shared/hooks/use-host-write.d.ts +12 -0
- package/dist/types/shared/hooks/use-long-task.d.ts +8 -0
- package/dist/types/shared/hooks/use-unit-board.d.ts +17 -0
- package/dist/types/shared/hooks/use-visible-busy.d.ts +17 -0
- package/package.json +2 -2
- package/skills/workbench-module-diy/DATASOURCE.md +2 -2
- package/skills/workbench-module-diy/FAMILY.md +5 -4
- package/skills/workbench-module-diy/KIT.md +105 -100
- package/skills/workbench-module-diy/PANEL.md +19 -11
- package/skills/workbench-module-diy/REVIEW.md +4 -4
- package/skills/workbench-module-diy/SKILL.md +5 -4
- package/skills/workbench-module-diy/TOOLING.md +2 -2
- package/skills/workbench-module-diy/TOOLS.md +1 -1
- package/skills/workbench-module-diy/WIRING.md +2 -1
- package/skills/workbench-module-diy/HOOKS.md +0 -95
package/dist/host/index.js
CHANGED
|
@@ -6,36 +6,71 @@ import { deflateSync, inflateSync } from "node:zlib";
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
//#region ../shared/dist/config/constants.js
|
|
9
|
+
/** 外壳插件的包名:浏览器模块表与宿主按它注册、认领这枚插件。 */
|
|
9
10
|
var PLUGIN_ID = "@dsh-workbench/core";
|
|
11
|
+
/** 与外壳同批发布的兄弟包名:`release.ts` 用它判一版三包是否都已在源上。 */
|
|
10
12
|
var RELEASE_SIBLINGS = ["@dsh-workbench/shared", "@dsh-workbench/module-tools"];
|
|
13
|
+
/** 外壳宿主 HTTP API 的根前缀;prefs / actions / provision 等路由都挂在它下面。 */
|
|
11
14
|
var API_PREFIX = "/workbench/api";
|
|
15
|
+
/** 模块偏好读写的路由前缀,挂在外壳 API 下:走写通道,拒写回 write-refused。 */
|
|
12
16
|
var PREFS_PREFIX = `${API_PREFIX}/prefs`;
|
|
17
|
+
/** 事实(facts)SSE 事件流的端点路径;前端 `EventSource` 按它带 `since` 游标续传。 */
|
|
13
18
|
var EVENTS_PATH = "/workbench/events";
|
|
19
|
+
/** 写动作的路由前缀,挂在外壳 API 下:模块的写通道 POST 到这里。 */
|
|
14
20
|
var ACTIONS_PREFIX = `${API_PREFIX}/actions`;
|
|
21
|
+
/** 机器供给(provision)的路由前缀:只收 POST,装机时用它铺一份运行环境。 */
|
|
15
22
|
var PROVISION_PREFIX = `${API_PREFIX}/provision`;
|
|
23
|
+
/** 面板运行态上报的路径段:崩溃与卡片收尾分别 POST 到 `panel-state/crash`、`panel-state/cards`。 */
|
|
16
24
|
var PANEL_STATE_PATH = "panel-state";
|
|
25
|
+
/** DIY 调试动作的路径段;宿主按它登记 `/workbench/api/diy-debug` 前缀。 */
|
|
17
26
|
var DIY_DEBUG_PATH = "diy-debug";
|
|
18
27
|
`${DIY_DEBUG_PATH}`;
|
|
28
|
+
/** DIY 录制交接的路径段;宿主按它登记 `/workbench/api/diy-record` 前缀。 */
|
|
19
29
|
var DIY_RECORD_PATH = "diy-record";
|
|
20
30
|
`${DIY_RECORD_PATH}`;
|
|
31
|
+
/** 面板运行态的路由前缀,挂在外壳 API 下:上报口用它,拒报回 report-refused。 */
|
|
21
32
|
var PANEL_STATE_PREFIX = `${API_PREFIX}/${PANEL_STATE_PATH}`;
|
|
33
|
+
/** 钉住的 opencli 插件版本:`opencli-runtime` 按它校验、取那一份。 */
|
|
22
34
|
var OPENCLI_PIN = "1.8.8";
|
|
35
|
+
/** 面板套件的 dsh 服务名(`workbenchPanels`):宿主经它取套件公布的清单与订阅。 */
|
|
23
36
|
var PANELS_SERVICE = "workbenchPanels";
|
|
37
|
+
/** 单元花名册服务的 dsh 服务名(`workbenchUnits`):模块经它认领自己的单元。 */
|
|
24
38
|
var UNITS_SERVICE = "workbenchUnits";
|
|
39
|
+
/** 面板运行态服务的 dsh 服务名(`workbenchPanelState`):宿主注册,读口按它取。 */
|
|
25
40
|
var PANEL_STATE_SERVICE = "workbenchPanelState";
|
|
41
|
+
/** DIY 调试约定服务的 dsh 服务名(`workbenchDiyDebug`):调试动作走它,不走 HTTP。 */
|
|
26
42
|
var DIY_DEBUG_SERVICE = "workbenchDiyDebug";
|
|
43
|
+
/** DIY 录制交接服务的 dsh 服务名(`workbenchDiyRecord`):`diy-acts.ts` 经它收发录制三拍。 */
|
|
27
44
|
var DIY_RECORD_SERVICE = "workbenchDiyRecord";
|
|
45
|
+
/** 会话投影服务的 dsh 服务名(`sessionProjections`):宿主经 `stateOf(session, 'agentPreset')` 读那一档。 */
|
|
46
|
+
var SESSION_PROJECTIONS_SERVICE = "sessionProjections";
|
|
47
|
+
/** 单次 opencli 调用的默认超时(毫秒);调用点没传 `timeoutMs` 时按它算。 */
|
|
28
48
|
var OPENCLI_TIMEOUT_MS = 12e4;
|
|
49
|
+
/** 启动预热站点那一跳的超时(毫秒);超时按未热对待,不挡面板。 */
|
|
29
50
|
var WARM_TIMEOUT_MS = 2e4;
|
|
51
|
+
/** 装 / 卸模块包的超时(毫秒):pnpm 与取包内容都按它算。 */
|
|
30
52
|
var INSTALL_TIMEOUT_MS = 3e5;
|
|
53
|
+
/** 查 registry 版本那一次请求的超时(毫秒)。 */
|
|
31
54
|
var VERSION_CHECK_TIMEOUT_MS = 1e4;
|
|
55
|
+
/** 单元探测结果的缓存有效期(毫秒)。 */
|
|
32
56
|
var PROBE_TTL_MS = 3e4;
|
|
57
|
+
/** 跑本地 git 命令的超时(毫秒);短于对外的 `OPENCLI_TIMEOUT_MS`。 */
|
|
33
58
|
var GIT_TIMEOUT_MS = 1e4;
|
|
34
59
|
var MARKER = new RegExp(`@工作台:([a-z][a-z0-9-]*:[A-Za-z0-9._~%#/-]+)`, "g");
|
|
35
60
|
var TRAILING_PUNCTUATION = /[.,;:!?]+$/;
|
|
61
|
+
/**
|
|
62
|
+
* 把引用拼回标记体里的那一段文本(不含 @ 与标记名)。
|
|
63
|
+
* @param ref 要拼的引用。
|
|
64
|
+
* @returns 形如 kind:part/part;分段里的 / 不转义,所以分段名本身不能含 /。
|
|
65
|
+
*/
|
|
36
66
|
function encodeRef(ref) {
|
|
37
67
|
return `${ref.kind}:${ref.parts.join("/")}`;
|
|
38
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* 把 kind:part/part 文本读回引用。
|
|
71
|
+
* @param text 待解析文本。
|
|
72
|
+
* @returns 没冒号、冒号在首尾、种类不是小写字母开头、或冒号后为空时给 undefined;不抛异常。
|
|
73
|
+
*/
|
|
39
74
|
function decodeRef(text) {
|
|
40
75
|
const at = text.indexOf(":");
|
|
41
76
|
if (at <= 0 || at === text.length - 1) return void 0;
|
|
@@ -48,6 +83,11 @@ function decodeRef(text) {
|
|
|
48
83
|
parts
|
|
49
84
|
};
|
|
50
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* 从一段文本里按出现顺序捞出所有标记引用,同一个引用只留第一次。
|
|
88
|
+
* @param text 任意文本;null / undefined 当空串。
|
|
89
|
+
* @returns 解析不出来的标记会被丢掉,所以结果只含合法引用,可能为空数组。
|
|
90
|
+
*/
|
|
51
91
|
function parseMarkers(text) {
|
|
52
92
|
const seen = /* @__PURE__ */ new Set();
|
|
53
93
|
const refs = [];
|
|
@@ -63,10 +103,21 @@ function parseMarkers(text) {
|
|
|
63
103
|
}
|
|
64
104
|
//#endregion
|
|
65
105
|
//#region ../shared/dist/api/unit.js
|
|
106
|
+
/**
|
|
107
|
+
* 判这个模块要不要往外装东西。
|
|
108
|
+
* @param manifest 模块清单。
|
|
109
|
+
* @returns 插件与共用库两张表都空时给 false。
|
|
110
|
+
*/
|
|
66
111
|
function hasEnvSupply(manifest) {
|
|
67
112
|
return (manifest.env?.plugins ?? []).length > 0 || (manifest.env?.libs ?? []).length > 0;
|
|
68
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* 外壳愿意认的协议版本;不在表里的模块当场被拒,不猜语义。
|
|
116
|
+
*/
|
|
69
117
|
var SUPPORTED_API_VERSIONS = [1];
|
|
118
|
+
/**
|
|
119
|
+
* 协议认得的全部键;写了别的键不会被采纳(外壳静默忽略),所以字段名拼错不会有提示。
|
|
120
|
+
*/
|
|
70
121
|
var DECLARATION_KEYS = [
|
|
71
122
|
"apiVersion",
|
|
72
123
|
"unit",
|
|
@@ -75,6 +126,9 @@ var DECLARATION_KEYS = [
|
|
|
75
126
|
"host",
|
|
76
127
|
"env"
|
|
77
128
|
];
|
|
129
|
+
/**
|
|
130
|
+
* 协议要求必写的键;env 不在其中(没有环境需要就不写)。
|
|
131
|
+
*/
|
|
78
132
|
var DECLARATION_REQUIRED_KEYS = [
|
|
79
133
|
"apiVersion",
|
|
80
134
|
"unit",
|
|
@@ -106,6 +160,12 @@ function stringArrayProblems(at, value) {
|
|
|
106
160
|
if (!Array.isArray(value)) return [`${at} 要是一串字符串,现在是 ${JSON.stringify(value)}`];
|
|
107
161
|
return value.flatMap((item, index) => text$1(item) === "" ? [`${at}[${index}] 不是非空字符串:${JSON.stringify(item)}`] : []);
|
|
108
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* 查清单里 dsh.workbench 那一段的形状,回一串给人看的问题(中文,带字段路径)。
|
|
165
|
+
* @param subject 报错时用的主语,一般是包名。
|
|
166
|
+
* @param raw 清单里那段原始值,可能什么都不是。
|
|
167
|
+
* @returns 空数组表示形状过得去;版本不对就当场返回(后面的字段无从判起)。
|
|
168
|
+
*/
|
|
109
169
|
function declarationProblemsOf(subject, raw) {
|
|
110
170
|
if (!isRecord(raw)) return [`${subject}: dsh.workbench 不是一个对象 —— ${JSON.stringify(raw)}`];
|
|
111
171
|
const version = raw.apiVersion;
|
|
@@ -167,6 +227,12 @@ function declarationProblemsOf(subject, raw) {
|
|
|
167
227
|
if (raw.env !== void 0 && !isRecord(raw.env)) problems.push(`${subject}: dsh.workbench.env 要是对象(command / plugins / libs)—— 语义由门禁的 env 判据查,这里只查形状`);
|
|
168
228
|
return problems;
|
|
169
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* 读一份清单:先查形状,过了再把声明规整成 manifest。
|
|
232
|
+
* @param subject 报错时用的主语。
|
|
233
|
+
* @param raw 清单里 dsh.workbench 那段原始值。
|
|
234
|
+
* @returns 有问题时只有 problems;没问题时 declaration 与 manifest 都给,manifest 里的字符串已 trim。
|
|
235
|
+
*/
|
|
170
236
|
function readDeclaration(subject, raw) {
|
|
171
237
|
const problems = declarationProblemsOf(subject, raw);
|
|
172
238
|
if (problems.length > 0) return { problems };
|
|
@@ -193,19 +259,40 @@ function readDeclaration(subject, raw) {
|
|
|
193
259
|
}
|
|
194
260
|
};
|
|
195
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* 由单元 id 拼出它那一族事实的范围名。
|
|
264
|
+
* @param unit 单元 id,不带后缀。
|
|
265
|
+
* @returns <unit>* —— 永远是范围名,不是某一条事实。
|
|
266
|
+
*/
|
|
196
267
|
function unitFactScope(unit) {
|
|
197
268
|
return `${unit}*`;
|
|
198
269
|
}
|
|
199
270
|
//#endregion
|
|
200
271
|
//#region ../shared/dist/api/long-task.js
|
|
272
|
+
/**
|
|
273
|
+
* 单元的长任务读口路由。
|
|
274
|
+
* @param unit 单元 id。
|
|
275
|
+
* @returns <unit>/runs。
|
|
276
|
+
*/
|
|
201
277
|
function runsRouteOf(unit) {
|
|
202
278
|
return `${unit}/runs`;
|
|
203
279
|
}
|
|
204
280
|
//#endregion
|
|
205
281
|
//#region ../shared/dist/lib/text.js
|
|
282
|
+
/**
|
|
283
|
+
* 把一个值裹成 shell 里的单引号字面量(值里的单引号一并转义),保证元字符按字面量传递。
|
|
284
|
+
* @param value - 任意值;不是字符串的先 `String()`。
|
|
285
|
+
* @returns 可安全拼进 `/bin/sh -c` 的一段文本 —— 含空格 / `$()` / `;` / 通配符的值都不会被当成语法。
|
|
286
|
+
*/
|
|
206
287
|
function quoteShellArg(value) {
|
|
207
288
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
208
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* 取一段文本的末尾若干行(如命令输出),顺带去掉首尾空白。
|
|
292
|
+
* @param text - 原文;`null` / `undefined` 当空串。
|
|
293
|
+
* @param lines - 最多留几行,默认 6;0 给全篇、负数从开头砍掉那么多行(都是 `slice` 语义),调用方传正数。
|
|
294
|
+
* @returns 末尾若干行、去首尾空行后的文本;原文行数不够就全给,空输入给空串。
|
|
295
|
+
*/
|
|
209
296
|
function tailLines(text, lines = 6) {
|
|
210
297
|
return String(text ?? "").trim().split("\n").slice(-lines).join("\n");
|
|
211
298
|
}
|
|
@@ -223,6 +310,11 @@ function candidateStarts(raw) {
|
|
|
223
310
|
}
|
|
224
311
|
return starts;
|
|
225
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* 宽松解析一段前后可能夹着人话的 JSON:整段不通就从行首的 `[` / `{` 起、截到最后一个闭合符再试。
|
|
315
|
+
* @param text - 原文;`null` / `undefined` 当空串。
|
|
316
|
+
* @returns 解析出的值(对象 / 数组 / 标量都原样);空输入或怎么都解析不出给 `undefined`,调用方据此报「输出认不出」。
|
|
317
|
+
*/
|
|
226
318
|
function parseJsonLoose(text) {
|
|
227
319
|
const raw = String(text ?? "").trim();
|
|
228
320
|
if (!raw) return void 0;
|
|
@@ -245,6 +337,9 @@ function parseJsonLoose(text) {
|
|
|
245
337
|
var DAY = 864e5;
|
|
246
338
|
30 * DAY;
|
|
247
339
|
365 * DAY;
|
|
340
|
+
/**
|
|
341
|
+
* 上报文本字段的截断长度(2000 字符;workspace / reason / message 共用)。
|
|
342
|
+
*/
|
|
248
343
|
var PANEL_STATE_MAX_TEXT = 2e3;
|
|
249
344
|
var PHASES = [
|
|
250
345
|
"idle",
|
|
@@ -252,10 +347,10 @@ var PHASES = [
|
|
|
252
347
|
"ready",
|
|
253
348
|
"error"
|
|
254
349
|
];
|
|
255
|
-
function asRecord$
|
|
350
|
+
function asRecord$8(value) {
|
|
256
351
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
257
352
|
}
|
|
258
|
-
function textOf$
|
|
353
|
+
function textOf$6(value, max) {
|
|
259
354
|
if (typeof value !== "string") return "";
|
|
260
355
|
const trimmed = value.trim();
|
|
261
356
|
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
@@ -266,23 +361,29 @@ function atOf(value) {
|
|
|
266
361
|
return Math.trunc(value);
|
|
267
362
|
}
|
|
268
363
|
function errorOf(raw) {
|
|
269
|
-
const record = asRecord$
|
|
364
|
+
const record = asRecord$8(raw);
|
|
270
365
|
if (record === void 0) return void 0;
|
|
271
|
-
const code = textOf$
|
|
272
|
-
const message = textOf$
|
|
366
|
+
const code = textOf$6(record["code"], 120);
|
|
367
|
+
const message = textOf$6(record["message"], PANEL_STATE_MAX_TEXT);
|
|
273
368
|
if (code === "" && message === "") return void 0;
|
|
274
369
|
return {
|
|
275
370
|
code: code === "" ? "unknown" : code,
|
|
276
371
|
message
|
|
277
372
|
};
|
|
278
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* 收一批卡片读数上报。
|
|
376
|
+
* @param payload - `POST` 的请求体。
|
|
377
|
+
* @returns 至少收到一条能认的卡片才 `ok: true`;请求体不是对象、`workspace` 为空、`cards` 不是数组,
|
|
378
|
+
* 或一条都认不出(`unit` / `route` / `phase` / `at` 缺一)时 `ok: false` + 中文原因;认不出的单条静默跳过。
|
|
379
|
+
*/
|
|
279
380
|
function parseCardsReport(payload) {
|
|
280
|
-
const record = asRecord$
|
|
381
|
+
const record = asRecord$8(payload);
|
|
281
382
|
if (record === void 0) return {
|
|
282
383
|
ok: false,
|
|
283
384
|
message: "请求体要是一个对象"
|
|
284
385
|
};
|
|
285
|
-
const workspace = textOf$
|
|
386
|
+
const workspace = textOf$6(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
286
387
|
if (workspace === "") return {
|
|
287
388
|
ok: false,
|
|
288
389
|
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
@@ -294,11 +395,11 @@ function parseCardsReport(payload) {
|
|
|
294
395
|
};
|
|
295
396
|
const cards = [];
|
|
296
397
|
for (const item of raw.slice(0, 32)) {
|
|
297
|
-
const card = asRecord$
|
|
398
|
+
const card = asRecord$8(item);
|
|
298
399
|
if (card === void 0) continue;
|
|
299
|
-
const unit = textOf$
|
|
300
|
-
const route = textOf$
|
|
301
|
-
const phase = textOf$
|
|
400
|
+
const unit = textOf$6(card["unit"], 200);
|
|
401
|
+
const route = textOf$6(card["route"], 400);
|
|
402
|
+
const phase = textOf$6(card["phase"], 20);
|
|
302
403
|
const at = atOf(card["at"]);
|
|
303
404
|
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
304
405
|
const error = errorOf(card["error"]);
|
|
@@ -322,15 +423,20 @@ function parseCardsReport(payload) {
|
|
|
322
423
|
}
|
|
323
424
|
};
|
|
324
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* 收一次卡片崩溃上报。
|
|
428
|
+
* @param payload - `POST` 的请求体。
|
|
429
|
+
* @returns `workspace` / `unit` / `reason` 都不能空,缺一给 `ok: false` + 中文原因;`at` 缺省或不合法时取当前时间。
|
|
430
|
+
*/
|
|
325
431
|
function parseCrashReport(payload) {
|
|
326
|
-
const record = asRecord$
|
|
432
|
+
const record = asRecord$8(payload);
|
|
327
433
|
if (record === void 0) return {
|
|
328
434
|
ok: false,
|
|
329
435
|
message: "请求体要是一个对象"
|
|
330
436
|
};
|
|
331
|
-
const workspace = textOf$
|
|
332
|
-
const unit = textOf$
|
|
333
|
-
const reason = textOf$
|
|
437
|
+
const workspace = textOf$6(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
438
|
+
const unit = textOf$6(record["unit"], 200);
|
|
439
|
+
const reason = textOf$6(record["reason"], PANEL_STATE_MAX_TEXT);
|
|
334
440
|
if (workspace === "") return {
|
|
335
441
|
ok: false,
|
|
336
442
|
message: "workspace 不能是空的"
|
|
@@ -353,6 +459,10 @@ function parseCrashReport(payload) {
|
|
|
353
459
|
}
|
|
354
460
|
};
|
|
355
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* 造一份面板运行态的留存(进程内存、按工作区分桶,带上面那几条上限的 LRU)。
|
|
464
|
+
* @returns 留存口:`cards` / `crash` 收上报,`snapshot(workspace)` 取全量;没存过的工作区(含 `null`)给一份空的。
|
|
465
|
+
*/
|
|
356
466
|
function createPanelStateStore() {
|
|
357
467
|
const workspaces = /* @__PURE__ */ new Map();
|
|
358
468
|
function trim(entry, max) {
|
|
@@ -443,17 +553,35 @@ function createPanelStateStore() {
|
|
|
443
553
|
}
|
|
444
554
|
//#endregion
|
|
445
555
|
//#region ../shared/dist/lib/board-fact.js
|
|
556
|
+
/**
|
|
557
|
+
* 判一个路径落不落在这个工作区里(按路径前缀比,不是字符串包含)。
|
|
558
|
+
* @param root - 工作区根路径。
|
|
559
|
+
* @param target - 要判的路径。
|
|
560
|
+
* @returns 同一个路径或在其下给 `true`;`root` 末尾带不带斜杠都认(`/a` 不匹配 `/ab`)。
|
|
561
|
+
*/
|
|
446
562
|
function insideWorkshop$1(root, target) {
|
|
447
563
|
if (target === root) return true;
|
|
448
564
|
const base = root.endsWith("/") ? root : `${root}/`;
|
|
449
565
|
return target.startsWith(base);
|
|
450
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* 把一次探针读数归成椅面归属。
|
|
569
|
+
* @param probe - 探针读数。
|
|
570
|
+
* @param workshop - 当前工作区根路径;`null` 表示还不知道当前是哪个工作区。
|
|
571
|
+
* @returns 外壳不在、或板路径与工作区缺一 → `unknown`;右栏没开或没画出来 → `hidden`;板在本工作区 → `workshop`,否则 `other`。
|
|
572
|
+
*/
|
|
451
573
|
function boardFactOf(probe, workshop) {
|
|
452
574
|
if (!probe.workbench) return "unknown";
|
|
453
575
|
if (!probe.bar || !probe.open || !probe.drawn) return "hidden";
|
|
454
576
|
if (probe.board === "" || workshop === null) return "unknown";
|
|
455
577
|
return insideWorkshop$1(workshop, probe.board) ? "workshop" : "other";
|
|
456
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* 把探针读数归成左栏那句人话的档位。
|
|
581
|
+
* @param probe - 探针读数。
|
|
582
|
+
* @param workshop - 当前工作区根路径;`null` 一律 `unknown`。
|
|
583
|
+
* @returns 椅面画着 → 本板 `ok` / 别板 `other-board`;没画着 → 页面已走 `off-workspace`,否则 `panel-closed`。
|
|
584
|
+
*/
|
|
457
585
|
function boardSituation(probe, workshop) {
|
|
458
586
|
if (!probe.workbench || workshop === null) return "unknown";
|
|
459
587
|
if (probe.bar && probe.open && probe.drawn) {
|
|
@@ -465,6 +593,11 @@ function boardSituation(probe, workshop) {
|
|
|
465
593
|
}
|
|
466
594
|
//#endregion
|
|
467
595
|
//#region ../shared/dist/lib/diy-debug.js
|
|
596
|
+
/**
|
|
597
|
+
* 把工具入参收成人话动作。
|
|
598
|
+
* @param value - 任意入参值。
|
|
599
|
+
* @returns `'pause'` / `'resume'` / `'stop'` 原样;别的值(含 `undefined`、多余空白、大小写不同)给 `null`,由调用方拒。
|
|
600
|
+
*/
|
|
468
601
|
function parseDebugIntent(value) {
|
|
469
602
|
return value === "pause" || value === "resume" || value === "stop" ? value : null;
|
|
470
603
|
}
|
|
@@ -477,6 +610,10 @@ function ownerOf$1(state, sessionId, now) {
|
|
|
477
610
|
if (held === null) return "none";
|
|
478
611
|
return held.sessionId === sessionId ? held : "not-owner";
|
|
479
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* 造一份新的调试约定存储(进程内存,同一时刻只可能有一份有效约定)。
|
|
615
|
+
* @returns 存取口;动作成不成都在返回的 `DebugMove` 里,不抛异常。
|
|
616
|
+
*/
|
|
480
617
|
function createDebugStore() {
|
|
481
618
|
let held = null;
|
|
482
619
|
const live = (now) => alive$1(held, now);
|
|
@@ -491,7 +628,8 @@ function createDebugStore() {
|
|
|
491
628
|
const state = {
|
|
492
629
|
phase: "debugging",
|
|
493
630
|
sessionId: pact.sessionId,
|
|
494
|
-
|
|
631
|
+
workspacePath: pact.workspacePath,
|
|
632
|
+
title: pact.title,
|
|
495
633
|
board: pact.board,
|
|
496
634
|
unit: pact.unit,
|
|
497
635
|
name: pact.name,
|
|
@@ -560,6 +698,11 @@ function createDebugStore() {
|
|
|
560
698
|
}
|
|
561
699
|
};
|
|
562
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* 把工具入参收成人话动作。
|
|
703
|
+
* @param raw - 任意入参值。
|
|
704
|
+
* @returns `'peek'` / `'halt'` / `'stop'` 原样;别的值(含 `undefined`、多余空白)给 `null`,由调用方拒。
|
|
705
|
+
*/
|
|
563
706
|
function parseRecordIntent(raw) {
|
|
564
707
|
return raw === "peek" || raw === "halt" || raw === "stop" ? raw : null;
|
|
565
708
|
}
|
|
@@ -572,6 +715,10 @@ function ownerOf(state, sessionId, now) {
|
|
|
572
715
|
if (held === null) return "none";
|
|
573
716
|
return held.sessionId === sessionId ? held : "not-owner";
|
|
574
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* 造一份新的录制约定存储(进程内存,同一时刻只可能有一份有效约定)。
|
|
720
|
+
* @returns 存取口;动作成不成都在返回的 `RecordMove` 里,不抛异常。
|
|
721
|
+
*/
|
|
575
722
|
function createRecordStore() {
|
|
576
723
|
let held = null;
|
|
577
724
|
const live = (now) => alive(held, now);
|
|
@@ -706,481 +853,8 @@ async function runShell(shell, command, options = {}) {
|
|
|
706
853
|
//#endregion
|
|
707
854
|
//#region ../shared/dist/host/browser-read.js
|
|
708
855
|
var BROWSER_SCREEN_SESSION = "workbench";
|
|
856
|
+
var BROWSER_READ_SESSION = "workbench-read";
|
|
709
857
|
var BROWSER_RECORD_SESSION = "workbench-rec";
|
|
710
|
-
var DEFAULT_TIMEOUT_MS$2 = 25e3;
|
|
711
|
-
var DEFAULT_RETRY_MS = 1200;
|
|
712
|
-
var DEFAULT_SETTLE_MS$1 = 900;
|
|
713
|
-
var AUTH_CODES = /* @__PURE__ */ new Set([
|
|
714
|
-
"NOT_LOGGED_IN",
|
|
715
|
-
"NOT_LOGIN",
|
|
716
|
-
"AUTH_FAILED",
|
|
717
|
-
"AUTH_REQUIRED",
|
|
718
|
-
"UNAUTHENTICATED",
|
|
719
|
-
"UNAUTHORIZED",
|
|
720
|
-
"TOKEN_EXPIRED"
|
|
721
|
-
]);
|
|
722
|
-
var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
723
|
-
"FORBIDDEN",
|
|
724
|
-
"PERMISSION_DENIED",
|
|
725
|
-
"NO_PERMISSION",
|
|
726
|
-
"EACCES"
|
|
727
|
-
]);
|
|
728
|
-
function asRecord$9(value) {
|
|
729
|
-
return typeof value === "object" && value !== null ? value : void 0;
|
|
730
|
-
}
|
|
731
|
-
function textOf$7(value) {
|
|
732
|
-
return typeof value === "string" ? value.trim() : "";
|
|
733
|
-
}
|
|
734
|
-
function codeOf(page) {
|
|
735
|
-
const record = asRecord$9(page);
|
|
736
|
-
return record === void 0 ? "" : textOf$7(record["code"]);
|
|
737
|
-
}
|
|
738
|
-
function messageOf$2(page) {
|
|
739
|
-
const record = asRecord$9(page);
|
|
740
|
-
return record === void 0 ? "" : textOf$7(record["message"]);
|
|
741
|
-
}
|
|
742
|
-
function hintOf$1(page) {
|
|
743
|
-
const record = asRecord$9(page);
|
|
744
|
-
const hint = record === void 0 ? "" : textOf$7(record["hint"]);
|
|
745
|
-
return hint === "" ? void 0 : hint;
|
|
746
|
-
}
|
|
747
|
-
function pageFailed(page) {
|
|
748
|
-
const record = asRecord$9(page);
|
|
749
|
-
return record !== void 0 && record["ok"] === false;
|
|
750
|
-
}
|
|
751
|
-
function blockerFor(code) {
|
|
752
|
-
const normalized = code.trim().toUpperCase().replace(/-/g, "_");
|
|
753
|
-
if (normalized === "") return void 0;
|
|
754
|
-
const matches = (set) => [...set].some((one) => normalized === one || normalized.endsWith(`_${one}`));
|
|
755
|
-
if (matches(PERMISSION_CODES)) return "permission-denied";
|
|
756
|
-
if (matches(AUTH_CODES) || normalized.includes("UNAUTH")) return "auth-required";
|
|
757
|
-
}
|
|
758
|
-
function sleep$1(ms) {
|
|
759
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
760
|
-
}
|
|
761
|
-
async function shellRun(shell, command, argv, timeoutMs) {
|
|
762
|
-
const result = await runShell(shell, backgroundLine(command, argv), { timeoutMs });
|
|
763
|
-
if (result.timedOut) return {
|
|
764
|
-
kind: "timeout",
|
|
765
|
-
detail: `没在 ${Math.round(timeoutMs / 1e3)} 秒内返回`,
|
|
766
|
-
stderr: result.stderr
|
|
767
|
-
};
|
|
768
|
-
if (result.exitCode !== 0) return {
|
|
769
|
-
kind: "failed",
|
|
770
|
-
detail: tailLines(result.stderr || result.stdout, 4),
|
|
771
|
-
stderr: result.stderr
|
|
772
|
-
};
|
|
773
|
-
return {
|
|
774
|
-
kind: "ok",
|
|
775
|
-
stdout: result.stdout
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
async function evalOnce(shell, command, session, script, timeoutMs, retryMs) {
|
|
779
|
-
const run = async () => {
|
|
780
|
-
let attempt;
|
|
781
|
-
try {
|
|
782
|
-
attempt = await shellRun(shell, command, [
|
|
783
|
-
"browser",
|
|
784
|
-
session,
|
|
785
|
-
"eval",
|
|
786
|
-
script,
|
|
787
|
-
"--window",
|
|
788
|
-
"background"
|
|
789
|
-
], timeoutMs);
|
|
790
|
-
} catch (error) {
|
|
791
|
-
return {
|
|
792
|
-
kind: "failure",
|
|
793
|
-
result: {
|
|
794
|
-
ok: false,
|
|
795
|
-
code: "cli-exec-failed",
|
|
796
|
-
message: "读不了:opencli 起不来",
|
|
797
|
-
hint: "确认这台机器上 opencli 能跑(工作台会自己把它挂好)",
|
|
798
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
799
|
-
}
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
if (attempt.kind === "timeout") return {
|
|
803
|
-
kind: "failure",
|
|
804
|
-
result: {
|
|
805
|
-
ok: false,
|
|
806
|
-
code: "cli-timeout",
|
|
807
|
-
message: "这次没读到(等超时了)",
|
|
808
|
-
hint: "稍后重试",
|
|
809
|
-
detail: attempt.detail
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
if (attempt.kind === "failed") {
|
|
813
|
-
const normalized = attempt.stderr.trim().toUpperCase().replace(/-/g, "_");
|
|
814
|
-
const forbidden = [...PERMISSION_CODES].some((one) => normalized.includes(one));
|
|
815
|
-
return {
|
|
816
|
-
kind: "failure",
|
|
817
|
-
result: {
|
|
818
|
-
ok: false,
|
|
819
|
-
code: forbidden ? "permission-denied" : "cli-exec-failed",
|
|
820
|
-
message: forbidden ? "没有权限在这台机器上读" : "读不了(命令失败)",
|
|
821
|
-
hint: forbidden ? "换一个有权限的账号,或让用户开权限" : "把下面这段原始输出看一眼再重试",
|
|
822
|
-
detail: attempt.detail
|
|
823
|
-
}
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
const value = parseJsonLoose(attempt.stdout);
|
|
827
|
-
if (value === void 0) return {
|
|
828
|
-
kind: "failure",
|
|
829
|
-
result: {
|
|
830
|
-
ok: false,
|
|
831
|
-
code: "cli-exec-failed",
|
|
832
|
-
message: "读到了、但不是 JSON",
|
|
833
|
-
hint: "脚本必须回 JSON",
|
|
834
|
-
detail: tailLines(attempt.stdout, 4)
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
return {
|
|
838
|
-
kind: "value",
|
|
839
|
-
value
|
|
840
|
-
};
|
|
841
|
-
};
|
|
842
|
-
const first = await run();
|
|
843
|
-
if (first.kind === "value") return first;
|
|
844
|
-
if (first.result.code !== "cli-exec-failed") return first;
|
|
845
|
-
await sleep$1(retryMs);
|
|
846
|
-
return run();
|
|
847
|
-
}
|
|
848
|
-
async function openOnce$1(shell, command, session, url, timeoutMs) {
|
|
849
|
-
let attempt;
|
|
850
|
-
try {
|
|
851
|
-
attempt = await shellRun(shell, command, [
|
|
852
|
-
"browser",
|
|
853
|
-
session,
|
|
854
|
-
"open",
|
|
855
|
-
url,
|
|
856
|
-
"--window",
|
|
857
|
-
"background"
|
|
858
|
-
], timeoutMs);
|
|
859
|
-
} catch (error) {
|
|
860
|
-
return {
|
|
861
|
-
ok: false,
|
|
862
|
-
code: "cli-exec-failed",
|
|
863
|
-
message: "打不开那个页面",
|
|
864
|
-
hint: "确认 opencli 能跑起来",
|
|
865
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
866
|
-
helpUrl: url
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
if (attempt.kind === "ok") return void 0;
|
|
870
|
-
return {
|
|
871
|
-
ok: false,
|
|
872
|
-
code: attempt.kind === "timeout" ? "cli-timeout" : "cli-exec-failed",
|
|
873
|
-
message: "打不开那个页面",
|
|
874
|
-
hint: "在浏览器里手动打开一次,然后重试",
|
|
875
|
-
detail: attempt.detail,
|
|
876
|
-
helpUrl: url
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
async function browserRead(options) {
|
|
880
|
-
const { shell, script, url, loginUrl, open = true } = options;
|
|
881
|
-
const command = options.command ?? "opencli";
|
|
882
|
-
const session = options.session ?? "workbench-read";
|
|
883
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS$2;
|
|
884
|
-
const retryMs = options.retryMs ?? DEFAULT_RETRY_MS;
|
|
885
|
-
const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS$1;
|
|
886
|
-
const at = Date.now();
|
|
887
|
-
const first = await evalOnce(shell, command, session, script, timeoutMs, retryMs);
|
|
888
|
-
if (first.kind === "failure") return {
|
|
889
|
-
...first.result,
|
|
890
|
-
at
|
|
891
|
-
};
|
|
892
|
-
let page = first.value;
|
|
893
|
-
if (codeOf(page) === "not-on-site") {
|
|
894
|
-
if (!open || url === void 0) return {
|
|
895
|
-
ok: false,
|
|
896
|
-
code: "cli-exec-failed",
|
|
897
|
-
at,
|
|
898
|
-
message: "没停在那套系统上",
|
|
899
|
-
hint: "先在那个浏览器里把它打开,再读一次",
|
|
900
|
-
detail: JSON.stringify(page),
|
|
901
|
-
...url === void 0 ? {} : { helpUrl: url }
|
|
902
|
-
};
|
|
903
|
-
const opened = await openOnce$1(shell, command, session, url, timeoutMs);
|
|
904
|
-
if (opened !== void 0) return {
|
|
905
|
-
...opened,
|
|
906
|
-
at
|
|
907
|
-
};
|
|
908
|
-
await sleep$1(settleMs);
|
|
909
|
-
const second = await evalOnce(shell, command, session, script, timeoutMs, retryMs);
|
|
910
|
-
if (second.kind === "failure") return {
|
|
911
|
-
...second.result,
|
|
912
|
-
at
|
|
913
|
-
};
|
|
914
|
-
page = second.value;
|
|
915
|
-
if (codeOf(page) === "not-on-site") return {
|
|
916
|
-
ok: false,
|
|
917
|
-
code: "cli-exec-failed",
|
|
918
|
-
at,
|
|
919
|
-
message: "打开了页面、又读了一遍,仍然不在那套系统上",
|
|
920
|
-
hint: "确认这个地址就是那套系统(或者它跳到了别的域)",
|
|
921
|
-
detail: JSON.stringify(page),
|
|
922
|
-
helpUrl: url
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
if (!pageFailed(page)) return {
|
|
926
|
-
ok: true,
|
|
927
|
-
code: "ok",
|
|
928
|
-
at,
|
|
929
|
-
value: page,
|
|
930
|
-
message: "读到了"
|
|
931
|
-
};
|
|
932
|
-
const pageCode = codeOf(page) || "read-failed";
|
|
933
|
-
const message = messageOf$2(page) || `读不到(${pageCode})`;
|
|
934
|
-
const blocker = blockerFor(pageCode);
|
|
935
|
-
if (blocker === "auth-required") return {
|
|
936
|
-
ok: false,
|
|
937
|
-
code: blocker,
|
|
938
|
-
at,
|
|
939
|
-
pageCode,
|
|
940
|
-
message,
|
|
941
|
-
hint: "去登录一次,再重试",
|
|
942
|
-
detail: JSON.stringify(page),
|
|
943
|
-
...loginUrl === void 0 && url === void 0 ? {} : { helpUrl: loginUrl ?? url }
|
|
944
|
-
};
|
|
945
|
-
if (blocker === "permission-denied") return {
|
|
946
|
-
ok: false,
|
|
947
|
-
code: blocker,
|
|
948
|
-
at,
|
|
949
|
-
pageCode,
|
|
950
|
-
message,
|
|
951
|
-
hint: "这个账号没有权限",
|
|
952
|
-
detail: JSON.stringify(page),
|
|
953
|
-
...loginUrl === void 0 && url === void 0 ? {} : { helpUrl: loginUrl ?? url }
|
|
954
|
-
};
|
|
955
|
-
return {
|
|
956
|
-
ok: false,
|
|
957
|
-
code: pageCode,
|
|
958
|
-
at,
|
|
959
|
-
pageCode,
|
|
960
|
-
message,
|
|
961
|
-
detail: JSON.stringify(page),
|
|
962
|
-
...hintOf$1(page) === void 0 ? {} : { hint: hintOf$1(page) }
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
//#endregion
|
|
966
|
-
//#region ../shared/dist/host/browser-find.js
|
|
967
|
-
var DEFAULT_TIMEOUT_MS$1 = 25e3;
|
|
968
|
-
var DEFAULT_SETTLE_MS = 2500;
|
|
969
|
-
var URL_TIMEOUT_MS = 8e3;
|
|
970
|
-
var DEFAULT_LIMIT = 50;
|
|
971
|
-
var DEFAULT_TEXT_MAX = 200;
|
|
972
|
-
function asRecord$8(value) {
|
|
973
|
-
return typeof value === "object" && value !== null ? value : void 0;
|
|
974
|
-
}
|
|
975
|
-
function textOf$6(value) {
|
|
976
|
-
return typeof value === "string" ? value.trim() : "";
|
|
977
|
-
}
|
|
978
|
-
function entriesOf(value) {
|
|
979
|
-
const record = asRecord$8(value);
|
|
980
|
-
const raw = record === void 0 ? void 0 : record["entries"];
|
|
981
|
-
if (!Array.isArray(raw)) return [];
|
|
982
|
-
const entries = [];
|
|
983
|
-
for (const one of raw) {
|
|
984
|
-
const item = asRecord$8(one);
|
|
985
|
-
if (item === void 0) continue;
|
|
986
|
-
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
987
|
-
entries.push({
|
|
988
|
-
text: textOf$6(item["text"]),
|
|
989
|
-
tag: textOf$6(item["tag"]),
|
|
990
|
-
nth
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
return entries;
|
|
994
|
-
}
|
|
995
|
-
function reasonOf(stdout, stderr) {
|
|
996
|
-
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
997
|
-
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
998
|
-
const message = error === void 0 ? "" : textOf$6(error["message"]);
|
|
999
|
-
if (message !== "") return message;
|
|
1000
|
-
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
1001
|
-
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
1002
|
-
}
|
|
1003
|
-
function hintOf(stdout) {
|
|
1004
|
-
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
1005
|
-
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
1006
|
-
const hint = error === void 0 ? "" : textOf$6(error["hint"]);
|
|
1007
|
-
return hint === "" ? void 0 : hint;
|
|
1008
|
-
}
|
|
1009
|
-
function sameOrigin(left, right) {
|
|
1010
|
-
try {
|
|
1011
|
-
return new URL(left).origin === new URL(right).origin;
|
|
1012
|
-
} catch {
|
|
1013
|
-
return false;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
function urlOf(stdout) {
|
|
1017
|
-
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
1018
|
-
if (bare.startsWith("{")) {
|
|
1019
|
-
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
1020
|
-
const url = parsed === void 0 ? "" : textOf$6(parsed["url"]);
|
|
1021
|
-
if (url !== "") return url;
|
|
1022
|
-
}
|
|
1023
|
-
return bare;
|
|
1024
|
-
}
|
|
1025
|
-
async function currentUrl(shell, command, session) {
|
|
1026
|
-
const line = backgroundLine(command, [
|
|
1027
|
-
"browser",
|
|
1028
|
-
session,
|
|
1029
|
-
"get",
|
|
1030
|
-
"url",
|
|
1031
|
-
"--window",
|
|
1032
|
-
"background"
|
|
1033
|
-
]);
|
|
1034
|
-
try {
|
|
1035
|
-
const result = await runShell(shell, line, { timeoutMs: URL_TIMEOUT_MS });
|
|
1036
|
-
if (result.exitCode !== 0) return "";
|
|
1037
|
-
return urlOf(result.stdout);
|
|
1038
|
-
} catch {
|
|
1039
|
-
return "";
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
function sleep(ms) {
|
|
1043
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1044
|
-
}
|
|
1045
|
-
async function findOnce(shell, command, session, css, limit, textMax, timeoutMs) {
|
|
1046
|
-
const result = await runShell(shell, backgroundLine(command, [
|
|
1047
|
-
"browser",
|
|
1048
|
-
session,
|
|
1049
|
-
"find",
|
|
1050
|
-
"--css",
|
|
1051
|
-
css,
|
|
1052
|
-
"--limit",
|
|
1053
|
-
String(limit),
|
|
1054
|
-
"--text-max",
|
|
1055
|
-
String(textMax),
|
|
1056
|
-
"--window",
|
|
1057
|
-
"background"
|
|
1058
|
-
]), { timeoutMs });
|
|
1059
|
-
if (result.timedOut) return {
|
|
1060
|
-
kind: "timeout",
|
|
1061
|
-
detail: `没在 ${Math.round(timeoutMs / 1e3)} 秒内返回`
|
|
1062
|
-
};
|
|
1063
|
-
if (result.exitCode !== 0) return {
|
|
1064
|
-
kind: "failed",
|
|
1065
|
-
detail: reasonOf(result.stdout, result.stderr),
|
|
1066
|
-
...hintOf(result.stdout) === void 0 ? {} : { hint: hintOf(result.stdout) }
|
|
1067
|
-
};
|
|
1068
|
-
return {
|
|
1069
|
-
kind: "ok",
|
|
1070
|
-
entries: entriesOf(parseJsonLoose(result.stdout)),
|
|
1071
|
-
stdout: result.stdout
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
async function openOnce(shell, command, session, url, timeoutMs) {
|
|
1075
|
-
const line = backgroundLine(command, [
|
|
1076
|
-
"browser",
|
|
1077
|
-
session,
|
|
1078
|
-
"open",
|
|
1079
|
-
url,
|
|
1080
|
-
"--window",
|
|
1081
|
-
"background"
|
|
1082
|
-
]);
|
|
1083
|
-
let result;
|
|
1084
|
-
try {
|
|
1085
|
-
result = await runShell(shell, line, { timeoutMs });
|
|
1086
|
-
} catch (error) {
|
|
1087
|
-
return error instanceof Error ? error.message : String(error);
|
|
1088
|
-
}
|
|
1089
|
-
if (result.timedOut) return `没在 ${Math.round(timeoutMs / 1e3)} 秒内打开`;
|
|
1090
|
-
if (result.exitCode !== 0) {
|
|
1091
|
-
const reason = reasonOf(result.stdout, result.stderr);
|
|
1092
|
-
return reason === "" ? `opencli 打开失败(退出码 ${String(result.exitCode ?? "?")})` : reason;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
async function browserFind(options) {
|
|
1096
|
-
const { shell, css, url, open = true } = options;
|
|
1097
|
-
const command = options.command ?? "opencli";
|
|
1098
|
-
const session = options.session ?? "workbench-read";
|
|
1099
|
-
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
1100
|
-
const textMax = options.textMax ?? DEFAULT_TEXT_MAX;
|
|
1101
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
|
|
1102
|
-
const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
|
|
1103
|
-
const at = Date.now();
|
|
1104
|
-
const find = async () => {
|
|
1105
|
-
try {
|
|
1106
|
-
return await findOnce(shell, command, session, css, limit, textMax, timeoutMs);
|
|
1107
|
-
} catch (error) {
|
|
1108
|
-
return {
|
|
1109
|
-
kind: "failed",
|
|
1110
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
1111
|
-
hint: "确认这台机器上 opencli 能跑(工作台会自己把它挂好)"
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1115
|
-
const canMove = open && url !== void 0;
|
|
1116
|
-
if (canMove) {
|
|
1117
|
-
const here = await currentUrl(shell, command, session);
|
|
1118
|
-
if (here !== "" && !sameOrigin(here, url)) {
|
|
1119
|
-
const opened = await openOnce(shell, command, session, url, timeoutMs);
|
|
1120
|
-
if (opened !== void 0) return {
|
|
1121
|
-
ok: false,
|
|
1122
|
-
code: "cli-exec-failed",
|
|
1123
|
-
at,
|
|
1124
|
-
entries: [],
|
|
1125
|
-
count: 0,
|
|
1126
|
-
message: "打不开那个页面",
|
|
1127
|
-
hint: "在浏览器里手动打开一次,然后重试",
|
|
1128
|
-
detail: opened,
|
|
1129
|
-
helpUrl: url
|
|
1130
|
-
};
|
|
1131
|
-
await sleep(settleMs);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
let outcome = await find();
|
|
1135
|
-
if (outcome === void 0) return {
|
|
1136
|
-
ok: false,
|
|
1137
|
-
code: "cli-exec-failed",
|
|
1138
|
-
at,
|
|
1139
|
-
entries: [],
|
|
1140
|
-
count: 0,
|
|
1141
|
-
message: "没找到元素"
|
|
1142
|
-
};
|
|
1143
|
-
if (outcome.kind === "failed" && canMove) {
|
|
1144
|
-
if (await openOnce(shell, command, session, url, timeoutMs) === void 0) {
|
|
1145
|
-
await sleep(settleMs);
|
|
1146
|
-
const second = await find();
|
|
1147
|
-
if (second !== void 0) outcome = second;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
if (outcome.kind === "timeout") return {
|
|
1151
|
-
ok: false,
|
|
1152
|
-
code: "cli-timeout",
|
|
1153
|
-
at,
|
|
1154
|
-
entries: [],
|
|
1155
|
-
count: 0,
|
|
1156
|
-
message: "这次没找到元素(等超时了)",
|
|
1157
|
-
hint: "稍后重试",
|
|
1158
|
-
detail: outcome.detail,
|
|
1159
|
-
...url === void 0 ? {} : { helpUrl: url }
|
|
1160
|
-
};
|
|
1161
|
-
if (outcome.kind === "failed") {
|
|
1162
|
-
const detail = outcome.detail;
|
|
1163
|
-
return {
|
|
1164
|
-
ok: false,
|
|
1165
|
-
code: "cli-exec-failed",
|
|
1166
|
-
at,
|
|
1167
|
-
entries: [],
|
|
1168
|
-
count: 0,
|
|
1169
|
-
message: detail === "" ? "没找到元素(opencli 命令失败)" : `没找到元素:${detail}`,
|
|
1170
|
-
...outcome.hint === void 0 ? {} : { hint: outcome.hint },
|
|
1171
|
-
...detail === "" ? {} : { detail },
|
|
1172
|
-
...url === void 0 ? {} : { helpUrl: url }
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
|
-
return {
|
|
1176
|
-
ok: true,
|
|
1177
|
-
code: "ok",
|
|
1178
|
-
at,
|
|
1179
|
-
entries: outcome.entries,
|
|
1180
|
-
count: outcome.entries.length,
|
|
1181
|
-
message: outcome.entries.length === 0 ? "页面上没有匹配的元素" : `找到 ${String(outcome.entries.length)} 个元素`
|
|
1182
|
-
};
|
|
1183
|
-
}
|
|
1184
858
|
//#endregion
|
|
1185
859
|
//#region ../shared/dist/host/cli-error.js
|
|
1186
860
|
function unquote(raw) {
|
|
@@ -2552,6 +2226,8 @@ function createWorkspaceStore(options) {
|
|
|
2552
2226
|
}
|
|
2553
2227
|
};
|
|
2554
2228
|
}
|
|
2229
|
+
var WORKSHOP_RELATIVE = "workbench/diy";
|
|
2230
|
+
var WORKSHOP_TITLE = "工作台DIY";
|
|
2555
2231
|
//#endregion
|
|
2556
2232
|
//#region src/shared/api/bundle-install.ts
|
|
2557
2233
|
var BUNDLE_REGISTRIES_ROUTE = "bundle-registries";
|
|
@@ -3068,110 +2744,6 @@ function createRegistryNotebook(dir) {
|
|
|
3068
2744
|
};
|
|
3069
2745
|
}
|
|
3070
2746
|
//#endregion
|
|
3071
|
-
//#region src/shared/api/diy.ts
|
|
3072
|
-
var WORKSHOP_RELATIVE = "workbench/diy";
|
|
3073
|
-
var WORKSHOP_TITLE = "工作台DIY";
|
|
3074
|
-
//#endregion
|
|
3075
|
-
//#region src/shared/api/host/diy.ts
|
|
3076
|
-
function workshopPathOf(ctx) {
|
|
3077
|
-
const homePath = ctx.get("dshHomePath");
|
|
3078
|
-
if (typeof homePath !== "function") return null;
|
|
3079
|
-
try {
|
|
3080
|
-
return homePath(WORKSHOP_RELATIVE);
|
|
3081
|
-
} catch {
|
|
3082
|
-
return null;
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
//#endregion
|
|
3086
|
-
//#region src/shared/api/host/diy-workshop.ts
|
|
3087
|
-
function asRecord$4(value) {
|
|
3088
|
-
return typeof value === "object" && value !== null ? value : void 0;
|
|
3089
|
-
}
|
|
3090
|
-
function asText$3(value) {
|
|
3091
|
-
return typeof value === "string" ? value.trim() : "";
|
|
3092
|
-
}
|
|
3093
|
-
function workshopPackagesDir(ctx) {
|
|
3094
|
-
const root = workshopPathOf(ctx);
|
|
3095
|
-
return root === null ? null : join(root, "packages");
|
|
3096
|
-
}
|
|
3097
|
-
function insideWorkshop(root, target) {
|
|
3098
|
-
return insideWorkshop$1(existsSync(root) ? realpathSync(root) : resolve(root), existsSync(target) ? realpathSync(target) : resolve(target));
|
|
3099
|
-
}
|
|
3100
|
-
function workshopModules(packagesDir, installed = []) {
|
|
3101
|
-
if (!existsSync(packagesDir)) return [];
|
|
3102
|
-
const already = new Set(installed);
|
|
3103
|
-
return readdirSync(packagesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).flatMap((entry) => {
|
|
3104
|
-
const dir = join(packagesDir, entry.name);
|
|
3105
|
-
let manifest;
|
|
3106
|
-
try {
|
|
3107
|
-
manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
3108
|
-
} catch {
|
|
3109
|
-
return [];
|
|
3110
|
-
}
|
|
3111
|
-
const record = asRecord$4(manifest);
|
|
3112
|
-
const declared = record === void 0 ? void 0 : asRecord$4(asRecord$4(record.dsh)?.workbench);
|
|
3113
|
-
const unit = declared === void 0 ? "" : asText$3(declared.unit);
|
|
3114
|
-
if (unit === "") return [];
|
|
3115
|
-
return [{
|
|
3116
|
-
unit,
|
|
3117
|
-
name: asText$3(record?.name),
|
|
3118
|
-
description: asText$3(record?.description),
|
|
3119
|
-
dir,
|
|
3120
|
-
installed: already.has(unit)
|
|
3121
|
-
}];
|
|
3122
|
-
}).sort((left, right) => left.unit.localeCompare(right.unit));
|
|
3123
|
-
}
|
|
3124
|
-
function readDesignOfModule(dir) {
|
|
3125
|
-
const file = join(dir, "design.json");
|
|
3126
|
-
if (!existsSync(file)) return void 0;
|
|
3127
|
-
try {
|
|
3128
|
-
return JSON.parse(readFileSync(file, "utf8"));
|
|
3129
|
-
} catch {
|
|
3130
|
-
return;
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
function inWorkshopSession(ctx, workspace) {
|
|
3134
|
-
const root = workshopPathOf(ctx);
|
|
3135
|
-
return root !== null && workspace !== null && insideWorkshop(root, workspace);
|
|
3136
|
-
}
|
|
3137
|
-
function workshopSyncOf(workshopRoot) {
|
|
3138
|
-
let parsed;
|
|
3139
|
-
try {
|
|
3140
|
-
parsed = JSON.parse(readFileSync(join(workshopRoot, ".sync.json"), "utf8"));
|
|
3141
|
-
} catch {
|
|
3142
|
-
return;
|
|
3143
|
-
}
|
|
3144
|
-
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
3145
|
-
const record = parsed;
|
|
3146
|
-
const state = asText$3(record["state"]);
|
|
3147
|
-
if (state !== "ok" && state !== "syncing" && state !== "failed") return void 0;
|
|
3148
|
-
const reason = asText$3(record["reason"]);
|
|
3149
|
-
return {
|
|
3150
|
-
state,
|
|
3151
|
-
version: asText$3(record["version"]),
|
|
3152
|
-
at: asText$3(record["at"]),
|
|
3153
|
-
...reason === "" ? {} : { reason }
|
|
3154
|
-
};
|
|
3155
|
-
}
|
|
3156
|
-
function workshopToolchainOf(workshopRoot) {
|
|
3157
|
-
const versionOf = (name) => {
|
|
3158
|
-
try {
|
|
3159
|
-
return asText$3(JSON.parse(readFileSync(join(workshopRoot, "node_modules", "@dsh-workbench", name, "package.json"), "utf8")).version);
|
|
3160
|
-
} catch {
|
|
3161
|
-
return "";
|
|
3162
|
-
}
|
|
3163
|
-
};
|
|
3164
|
-
const core = versionOf("core");
|
|
3165
|
-
const moduleTools = versionOf("module-tools");
|
|
3166
|
-
const shared = versionOf("shared");
|
|
3167
|
-
if (core === "" || moduleTools === "" || shared === "") return void 0;
|
|
3168
|
-
return {
|
|
3169
|
-
core,
|
|
3170
|
-
moduleTools,
|
|
3171
|
-
shared
|
|
3172
|
-
};
|
|
3173
|
-
}
|
|
3174
|
-
//#endregion
|
|
3175
2747
|
//#region src/modules/panels/unit.ts
|
|
3176
2748
|
var PANELS_LIST_TOOL = "workbench_panels_list";
|
|
3177
2749
|
var PANELS_SHOW_TOOL = "workbench_panels_show";
|
|
@@ -3194,7 +2766,6 @@ var DIY_PANEL_TOOL = "workbench_diy_panel";
|
|
|
3194
2766
|
var DIY_COMPONENTS_TOOL = "workbench_diy_components";
|
|
3195
2767
|
var DIY_RECON_TOOL = "workbench_diy_recon";
|
|
3196
2768
|
var DIY_SPEC_TOOL = "workbench_diy_spec";
|
|
3197
|
-
var DIY_PROBE_TOOL = "workbench_diy_probe";
|
|
3198
2769
|
var DIY_INSTALL_TOOL = "workbench_diy_install";
|
|
3199
2770
|
var DIY_SHOW_TOOL = "workbench_diy_show";
|
|
3200
2771
|
var DIY_STATE_TOOL = "workbench_diy_state";
|
|
@@ -3231,7 +2802,6 @@ var READ_TOOLS = [
|
|
|
3231
2802
|
DIY_COMPONENTS_TOOL,
|
|
3232
2803
|
DIY_RECON_TOOL,
|
|
3233
2804
|
DIY_SPEC_TOOL,
|
|
3234
|
-
DIY_PROBE_TOOL,
|
|
3235
2805
|
DIY_STATE_TOOL,
|
|
3236
2806
|
DIY_HTML_TOOL,
|
|
3237
2807
|
DIY_FIND_TOOL,
|
|
@@ -3282,7 +2852,6 @@ var DIY_TOOLS = [
|
|
|
3282
2852
|
DIY_COMPONENTS_TOOL,
|
|
3283
2853
|
DIY_RECON_TOOL,
|
|
3284
2854
|
DIY_SPEC_TOOL,
|
|
3285
|
-
DIY_PROBE_TOOL,
|
|
3286
2855
|
DIY_INSTALL_TOOL,
|
|
3287
2856
|
DIY_SHOW_TOOL,
|
|
3288
2857
|
DIY_STATE_TOOL,
|
|
@@ -3412,7 +2981,7 @@ var panelsApproval = (call) => {
|
|
|
3412
2981
|
const starting = call.name === DIY_DEBUG_START_TOOL;
|
|
3413
2982
|
const unit = argsOf$2(call)["unit"];
|
|
3414
2983
|
const target = typeof unit === "string" && unit !== "" ? ` [${unit}]` : "";
|
|
3415
|
-
return askOf(`${call.name}${target} takes over the user's screen to debug a workbench module`, pair(starting ? "
|
|
2984
|
+
return askOf(`${call.name}${target} takes over the user's screen to debug a workbench module`, pair(starting ? "要在这个工作区的那块板上开始调试 —— 调试中请留在那一屏,允许吗?" : "要接着调试 —— 请回到那一屏,允许吗?", starting ? "it takes over your screen to debug on this workspace's board — please stay on that screen. Allow?" : "it needs your screen again to keep debugging — please go back to that screen. Allow?"), pair(starting ? "agent 要占用你的屏幕开始调试工作台模块,允许吗?" : "agent 要接着占用你的屏幕,继续调试工作台模块,允许吗?", starting ? "The agent wants to take over your screen to start debugging a workbench module. Allow?" : "The agent wants your screen again to keep debugging a workbench module. Allow?"));
|
|
3416
2985
|
}
|
|
3417
2986
|
if (call.name === "workbench_diy_debug_pause" || call.name === "workbench_diy_debug_stop") return { kind: "allow" };
|
|
3418
2987
|
if (call.name === "workbench_diy_record_prepare") {
|
|
@@ -3650,21 +3219,38 @@ function toolchainBinOf(root) {
|
|
|
3650
3219
|
const script = join(root, "node_modules", "@dsh-workbench", "module-tools", "bin", "dsh-wb-module.mjs");
|
|
3651
3220
|
return existsSync(script) ? script : null;
|
|
3652
3221
|
}
|
|
3222
|
+
function toolchainBinAmong(root, dirs) {
|
|
3223
|
+
const direct = toolchainBinOf(root);
|
|
3224
|
+
if (direct !== null) return direct;
|
|
3225
|
+
for (const dir of dirs) {
|
|
3226
|
+
const bin = toolchainBinOf(dir);
|
|
3227
|
+
if (bin !== null) return bin;
|
|
3228
|
+
}
|
|
3229
|
+
return null;
|
|
3230
|
+
}
|
|
3653
3231
|
function queryCommandOf(bin, only) {
|
|
3654
3232
|
return `${quoteShellArg(bin)} check --only ${only} --json`;
|
|
3655
3233
|
}
|
|
3656
|
-
async function runSurfaceQuery(shell, bin, only, root) {
|
|
3234
|
+
async function runSurfaceQuery(shell, bin, only, root, fallbackBin) {
|
|
3657
3235
|
const result = await runShell(shell, queryCommandOf(bin, only), {
|
|
3658
3236
|
workdir: root,
|
|
3659
3237
|
timeoutMs: SURFACE_TIMEOUT_MS
|
|
3660
3238
|
});
|
|
3661
3239
|
const parsed = jsonOf$1(result.stdout);
|
|
3662
3240
|
if (parsed !== null) return withoutProblems(parsed);
|
|
3241
|
+
const retry = fallbackBin === void 0 || fallbackBin === bin ? void 0 : fallbackBin;
|
|
3242
|
+
if (retry !== void 0) {
|
|
3243
|
+
const secondParsed = jsonOf$1((await runShell(shell, queryCommandOf(retry, only), {
|
|
3244
|
+
workdir: root,
|
|
3245
|
+
timeoutMs: SURFACE_TIMEOUT_MS
|
|
3246
|
+
})).stdout);
|
|
3247
|
+
if (secondParsed !== null) return withoutProblems(secondParsed);
|
|
3248
|
+
}
|
|
3663
3249
|
if (result.exitCode !== 0) {
|
|
3664
3250
|
const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-3).join(" | ");
|
|
3665
3251
|
throw new Error(`工坊工具链的 \`check --only ${only} --json\` 没跑通(exit ${String(result.exitCode)})—— 这不是工具链坏了,是工坊里有模块过不了判据,所以连清单也没打出来${tail === "" ? "" : `:${tail}`}`);
|
|
3666
3252
|
}
|
|
3667
|
-
throw new Error(`工坊工具链的 \`check --only ${only} --json\` 没回 JSON:${result.stdout.trim().split("\n").slice(-2).join(" | ")}`);
|
|
3253
|
+
throw new Error(`工坊工具链的 \`check --only ${only} --json\` 没回 JSON:${result.stdout.trim().split("\n").slice(-2).join(" | ")}` + (retry === void 0 ? ` —— 这份工具链可能太旧(没有 --json):在 ${root} 里把 @dsh-workbench/module-tools 升到 ^0.3.4(pnpm install)` : " —— 连跑着的那份外壳自带的工具链也没回 JSON,这条得问人"));
|
|
3668
3254
|
}
|
|
3669
3255
|
function jsonOf$1(stdout) {
|
|
3670
3256
|
try {
|
|
@@ -3682,7 +3268,7 @@ function withoutProblems(payload) {
|
|
|
3682
3268
|
function recordOf$1(value) {
|
|
3683
3269
|
return typeof value === "object" && value !== null ? value : {};
|
|
3684
3270
|
}
|
|
3685
|
-
function asText$
|
|
3271
|
+
function asText$3(value) {
|
|
3686
3272
|
return typeof value === "string" ? value : "";
|
|
3687
3273
|
}
|
|
3688
3274
|
function stringsOf$3(value) {
|
|
@@ -3764,43 +3350,18 @@ function excerptAt(file, maxLines = 60) {
|
|
|
3764
3350
|
return "(打不开那份类型文件 —— 照上面的路径自己看一眼)";
|
|
3765
3351
|
}
|
|
3766
3352
|
}
|
|
3353
|
+
/** 指引目录:从宿主产物往上找 `skills/workbench-module-diy`(找不到回 `null`)。 */
|
|
3767
3354
|
function skillDirOf(from) {
|
|
3768
3355
|
let dir = from;
|
|
3769
3356
|
for (let hop = 0; hop < 6; hop += 1) {
|
|
3770
3357
|
const candidate = join(dir, "skills", "workbench-module-diy");
|
|
3771
|
-
if (existsSync(
|
|
3358
|
+
if (existsSync(candidate)) return candidate;
|
|
3772
3359
|
const parent = dirname(dir);
|
|
3773
3360
|
if (parent === dir) break;
|
|
3774
3361
|
dir = parent;
|
|
3775
3362
|
}
|
|
3776
3363
|
return null;
|
|
3777
3364
|
}
|
|
3778
|
-
function symbolsIn(heading) {
|
|
3779
|
-
return [...heading.matchAll(/`([^`]+)`/g)].map((one) => (one[1] ?? "").trim()).map((one) => one.replace(/[<({[].*$/u, "").trim()).map((one) => one.replace(/[.::,,\s].*$/u, "").trim()).filter((one) => /^[\w$]+$/u.test(one));
|
|
3780
|
-
}
|
|
3781
|
-
function sectionOf(text, name) {
|
|
3782
|
-
const rows = text.split("\n");
|
|
3783
|
-
const at = rows.findIndex((row) => row.startsWith("### ") && symbolsIn(row).includes(name));
|
|
3784
|
-
if (at === -1) return "";
|
|
3785
|
-
const rest = rows.slice(at + 1).findIndex((row) => row.startsWith("## ") || row.startsWith("### "));
|
|
3786
|
-
return rows.slice(at, rest === -1 ? rows.length : at + 1 + rest).join("\n").trim();
|
|
3787
|
-
}
|
|
3788
|
-
function docSectionOf(name, dir) {
|
|
3789
|
-
if (dir === null) return null;
|
|
3790
|
-
for (const doc of ["HOOKS.md", "KIT.md"]) {
|
|
3791
|
-
const file = join(dir, doc);
|
|
3792
|
-
try {
|
|
3793
|
-
const lines = sectionOf(readFileSync(file, "utf8"), name);
|
|
3794
|
-
if (lines !== "") return {
|
|
3795
|
-
file,
|
|
3796
|
-
lines
|
|
3797
|
-
};
|
|
3798
|
-
} catch {
|
|
3799
|
-
continue;
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
return null;
|
|
3803
|
-
}
|
|
3804
3365
|
function truncated(names, all) {
|
|
3805
3366
|
if (all || names.length <= 12) return names.join(", ");
|
|
3806
3367
|
return `${names.slice(0, 12).join(", ")} …+${String(names.length - 12)}`;
|
|
@@ -3823,48 +3384,105 @@ function byLocationOf(payload) {
|
|
|
3823
3384
|
function isComponents(where) {
|
|
3824
3385
|
return where.split("/").at(-1) === "components";
|
|
3825
3386
|
}
|
|
3387
|
+
function kitBucketsOf(payload) {
|
|
3388
|
+
const groups = byLocationOf(payload);
|
|
3389
|
+
const components = groups.filter((one) => isComponents(one.where)).flatMap((one) => one.names).sort();
|
|
3390
|
+
return [...components.length === 0 ? [] : [{
|
|
3391
|
+
label: "组件",
|
|
3392
|
+
key: "components",
|
|
3393
|
+
names: components
|
|
3394
|
+
}], ...groups.filter((one) => !isComponents(one.where)).map((one) => ({
|
|
3395
|
+
label: one.where,
|
|
3396
|
+
key: one.where,
|
|
3397
|
+
names: one.names
|
|
3398
|
+
}))];
|
|
3399
|
+
}
|
|
3400
|
+
function matchesBucket(where, bucket) {
|
|
3401
|
+
const wanted = bucket.trim().toLowerCase();
|
|
3402
|
+
if (wanted === "") return false;
|
|
3403
|
+
const one = where.trim().toLowerCase();
|
|
3404
|
+
return one === wanted || one.split("/").at(-1) === wanted;
|
|
3405
|
+
}
|
|
3406
|
+
function primitiveBucketsOf(payload) {
|
|
3407
|
+
return Object.entries(recordOf$1(payload["onlyPrimitives"])).map(([bucket, value]) => ({
|
|
3408
|
+
bucket,
|
|
3409
|
+
names: [...stringsOf$3(value)].sort()
|
|
3410
|
+
})).filter((one) => one.names.length > 0);
|
|
3411
|
+
}
|
|
3412
|
+
function bucketNamesOf(query) {
|
|
3413
|
+
const out = [];
|
|
3414
|
+
if (query.kit !== void 0) {
|
|
3415
|
+
const keys = kitBucketsOf(query.kit).map((one) => one.key);
|
|
3416
|
+
if (keys.length > 0) out.push({
|
|
3417
|
+
face: "kit",
|
|
3418
|
+
keys
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
if (query.primitives !== void 0) {
|
|
3422
|
+
const keys = primitiveBucketsOf(query.primitives).map((one) => one.bucket);
|
|
3423
|
+
if (keys.length > 0) out.push({
|
|
3424
|
+
face: "primitives",
|
|
3425
|
+
keys
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3428
|
+
return out;
|
|
3429
|
+
}
|
|
3430
|
+
var TWO_FACE_ALL = "⚠️ 两个面一起不给全表 —— 要全表就指名一面:scope=kit 或 scope=primitives。";
|
|
3431
|
+
function refusesEvery(scope, all, focused) {
|
|
3432
|
+
return all && scope === "all" && !focused;
|
|
3433
|
+
}
|
|
3826
3434
|
function renderSurfaceText(query, options) {
|
|
3435
|
+
const bucket = (options.bucket ?? "").trim();
|
|
3436
|
+
const focused = bucket !== "";
|
|
3437
|
+
if (refusesEvery(options.scope, options.all, focused)) return TWO_FACE_ALL;
|
|
3438
|
+
const both = options.scope === "all";
|
|
3439
|
+
const every = options.all && !both;
|
|
3440
|
+
let hit = false;
|
|
3827
3441
|
const lines = [];
|
|
3828
3442
|
const kitNames = Object.keys(filesOf$1(query.kit)).sort();
|
|
3829
3443
|
if (query.kit !== void 0) {
|
|
3830
3444
|
const groups = byLocationOf(query.kit);
|
|
3831
3445
|
if (kitNames.length === 0 && groups.length === 0) {
|
|
3832
3446
|
const anyNames = [...new Set(Object.values(recordOf$1(query.kit["buckets"])).flatMap((value) => stringsOf$3(value)))].sort();
|
|
3833
|
-
lines.push(anyNames.length === 0 ? "kit:读不到(没找到 client.js)" : `kit:类型面扫不到,只有名字 ${truncated(anyNames,
|
|
3447
|
+
lines.push(anyNames.length === 0 ? "kit:读不到(没找到 client.js)" : `kit:类型面扫不到,只有名字 ${truncated(anyNames, every)}`);
|
|
3834
3448
|
} else {
|
|
3835
|
-
const
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
lines.push(` ${group.where}(${group.names.length}):${truncated(group.names, options.all)}`);
|
|
3843
|
-
}
|
|
3449
|
+
const buckets = kitBucketsOf(query.kit);
|
|
3450
|
+
lines.push(`kit ${kitNames.length}:${buckets.map((one) => `${one.label} ${one.names.length}`).join(" · ")}`);
|
|
3451
|
+
if (focused) {
|
|
3452
|
+
const matched = buckets.filter((one) => matchesBucket(one.key, bucket));
|
|
3453
|
+
for (const one of matched) lines.push(` ${one.label}(${one.names.length}):${one.names.join(", ")}`);
|
|
3454
|
+
if (matched.length > 0) hit = true;
|
|
3455
|
+
} else for (const one of buckets) lines.push(` ${one.label}(${one.names.length}):${truncated(one.names, every)}`);
|
|
3844
3456
|
}
|
|
3845
3457
|
}
|
|
3846
3458
|
if (query.primitives !== void 0) {
|
|
3847
3459
|
const version = installedVersionOf(query.primitives);
|
|
3848
|
-
const only =
|
|
3849
|
-
bucket,
|
|
3850
|
-
names: [...stringsOf$3(value)].sort()
|
|
3851
|
-
})).filter((one) => one.names.length > 0);
|
|
3460
|
+
const only = primitiveBucketsOf(query.primitives);
|
|
3852
3461
|
const covered = (Array.isArray(query.primitives["kitCovered"]) ? query.primitives["kitCovered"] : []).map((one) => recordOf$1(one)["name"]).filter((one) => typeof one === "string");
|
|
3853
3462
|
if (version === null) {
|
|
3854
|
-
const want = asText$
|
|
3463
|
+
const want = asText$3(recordOf$1(recordOf$1(query.primitives["snippet"])["devDependency"])["version"]);
|
|
3855
3464
|
lines.push(`原语:没装 —— devDependencies 加 "${PRIMITIVES_PKG}": "${want === "" ? "<外壳钉的那版>" : want}" 再 pnpm install`);
|
|
3856
|
-
} else
|
|
3465
|
+
} else if (focused) {
|
|
3466
|
+
lines.push(`原语 ${version}${only.length === 0 ? "" : `:只有它有 ${only.map((one) => `${one.bucket} ${one.names.length}`).join(" · ")}`}`);
|
|
3467
|
+
const matched = only.filter((one) => matchesBucket(one.bucket, bucket));
|
|
3468
|
+
for (const one of matched) lines.push(` ${one.bucket}(${one.names.length}):${one.names.join(", ")}`);
|
|
3469
|
+
if (matched.length > 0) hit = true;
|
|
3470
|
+
} else lines.push(`原语 ${version}${only.length === 0 ? "" : `:只有它有 ${only.map((one) => `${one.bucket} ${truncated(one.names, every)}`).join(" · ")}`}`);
|
|
3857
3471
|
if (only.length > 0 && version !== null) lines.push("(只有原语有的,用之前先在 dsh.client.external 里声明)");
|
|
3858
3472
|
if (covered.length > 0) lines.push(`kit 里已有:${covered.join(", ")}`);
|
|
3859
3473
|
}
|
|
3474
|
+
if (focused && !hit) {
|
|
3475
|
+
const keys = bucketNamesOf(query);
|
|
3476
|
+
lines.push(keys.length === 0 ? `没有「${bucket}」这一桶,这次也读不到任何桶。` : `没有「${bucket}」这一桶。可用的桶:${keys.map((one) => `${one.face} → ${one.keys.join(" / ")}`).join(";")}`);
|
|
3477
|
+
}
|
|
3860
3478
|
const roots = [...Object.entries(rootsOf(query.kit)).map(([key, dir]) => `${key}=${dir}`), ...Object.entries(rootsOf(query.primitives)).map(([key, dir]) => `${key}=${dir}`)];
|
|
3861
3479
|
if (roots.length > 0) lines.push(`类型面:${roots.join(" · ")}`);
|
|
3862
|
-
lines.push(faceStateOf(query, "primitives") === "unreadable" ? "name=<件> 看它怎么用 · all=true 出全表(原语装过才能问原语那类)" : "name=<件> 看它怎么用 · all=true 出全表");
|
|
3863
3480
|
const text = lines.join("\n");
|
|
3864
3481
|
if (text.length <= 8192) return text;
|
|
3482
|
+
const commands = (options.scope === "all" ? ["kit", "primitives"] : [options.scope]).map((one) => `\`dsh-wb-module check --only ${one} --all\``).join(" 或 ");
|
|
3865
3483
|
return [
|
|
3866
|
-
`⚠️ 这份清单 ${String(text.length)} 字符,超过 ${String(PRUNE_CHARS)}
|
|
3867
|
-
|
|
3484
|
+
`⚠️ 这份清单 ${String(text.length)} 字符,超过 ${String(PRUNE_CHARS)} 会被修剪器裁掉中间 —— 按桶问装得下;要一份全的走 shell:${commands} 再 grep。`,
|
|
3485
|
+
" (shell 那份按 barrel 列,计数比这里的落点分组大 —— 两边口径不同。)",
|
|
3868
3486
|
"",
|
|
3869
3487
|
text
|
|
3870
3488
|
].join("\n");
|
|
@@ -3876,7 +3494,7 @@ function listedNamesOf(query) {
|
|
|
3876
3494
|
for (const value of Object.values(recordOf$1(payload["buckets"]))) for (const one of stringsOf$3(value)) out.add(one);
|
|
3877
3495
|
for (const value of Object.values(recordOf$1(payload["onlyPrimitives"]))) for (const one of stringsOf$3(value)) out.add(one);
|
|
3878
3496
|
for (const one of Array.isArray(payload["kitCovered"]) ? payload["kitCovered"] : []) {
|
|
3879
|
-
const covered = asText$
|
|
3497
|
+
const covered = asText$3(recordOf$1(one)["name"]);
|
|
3880
3498
|
if (covered !== "") out.add(covered);
|
|
3881
3499
|
}
|
|
3882
3500
|
}
|
|
@@ -3893,8 +3511,8 @@ function faceStateOf(query, face) {
|
|
|
3893
3511
|
}
|
|
3894
3512
|
function installSnippetOf(query) {
|
|
3895
3513
|
const dependency = recordOf$1(recordOf$1(query.primitives?.["snippet"])["devDependency"]);
|
|
3896
|
-
const name = asText$
|
|
3897
|
-
const version = asText$
|
|
3514
|
+
const name = asText$3(dependency["name"]) || "@deepseek-ai/dsh-client-ui-primitives";
|
|
3515
|
+
const version = asText$3(dependency["version"]);
|
|
3898
3516
|
return `devDependencies 加 "${name}": "${version === "" ? "<外壳钉的那版>" : version}" 再 pnpm install`;
|
|
3899
3517
|
}
|
|
3900
3518
|
function renderNameText(name, found, query) {
|
|
@@ -3912,23 +3530,156 @@ function renderNameText(name, found, query) {
|
|
|
3912
3530
|
return lines.join("\n");
|
|
3913
3531
|
}
|
|
3914
3532
|
const body = excerptAt(found.file);
|
|
3533
|
+
const head = `${name} → ${found.file}(${found.from === "kit" ? "kit" : "原语"})`;
|
|
3915
3534
|
if (body.includes("/**")) return [
|
|
3916
|
-
|
|
3535
|
+
head,
|
|
3917
3536
|
"",
|
|
3918
3537
|
body,
|
|
3919
3538
|
"",
|
|
3920
|
-
found.from === "kit" ? "
|
|
3539
|
+
found.from === "kit" ? "(上面就是它的声明与 JSDoc —— 用法、默认值、边界都在里面。跨件的口径与最小写法看 KIT.md)" : "(上面就是它的声明与 JSDoc —— 用法与默认值都在里面。原语件要先在 dsh.client.external 里声明它)"
|
|
3921
3540
|
].join("\n");
|
|
3922
|
-
const
|
|
3923
|
-
const doc = docSectionOf(name, skillDir);
|
|
3541
|
+
const second = skillDirOf(dirname(fileURLToPath(import.meta.url))) === null ? "这份外壳连指引也没装 —— 升级它。" : `跨件的口径看 KIT.md(组件怎么拼)· LINKAGE.md / PANEL.md / TOOLS.md(取数 / 写 / 长任务)。`;
|
|
3924
3542
|
return [
|
|
3925
|
-
|
|
3543
|
+
head,
|
|
3926
3544
|
"",
|
|
3927
3545
|
body,
|
|
3928
3546
|
"",
|
|
3929
|
-
|
|
3547
|
+
found.from === "kit" ? `(声明没说明文字(没有 JSDoc)—— kit 的每个成员都该有,selfcheck 里有一条自检钉着;多半是你手上这份外壳比工具旧,升级外壳与工坊工具链再看。${second})` : `(声明没说明文字(没有 JSDoc)—— 装机那份原语本该自带;升级它再看。${second})`
|
|
3930
3548
|
].join("\n");
|
|
3931
3549
|
}
|
|
3550
|
+
//#endregion
|
|
3551
|
+
//#region src/shared/api/host/diy.ts
|
|
3552
|
+
function workshopPathOf(ctx) {
|
|
3553
|
+
const homePath = ctx.get("dshHomePath");
|
|
3554
|
+
if (typeof homePath !== "function") return null;
|
|
3555
|
+
try {
|
|
3556
|
+
return homePath(WORKSHOP_RELATIVE);
|
|
3557
|
+
} catch {
|
|
3558
|
+
return null;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
//#endregion
|
|
3562
|
+
//#region src/shared/api/host/diy-workshop.ts
|
|
3563
|
+
function asRecord$4(value) {
|
|
3564
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
3565
|
+
}
|
|
3566
|
+
function asText$2(value) {
|
|
3567
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3568
|
+
}
|
|
3569
|
+
function insideWorkshop(root, target) {
|
|
3570
|
+
return insideWorkshop$1(existsSync(root) ? realpathSync(root) : resolve(root), existsSync(target) ? realpathSync(target) : resolve(target));
|
|
3571
|
+
}
|
|
3572
|
+
function workshopModules(packagesDir, installed = []) {
|
|
3573
|
+
if (!existsSync(packagesDir)) return [];
|
|
3574
|
+
const already = new Set(installed);
|
|
3575
|
+
return readdirSync(packagesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).flatMap((entry) => {
|
|
3576
|
+
const dir = join(packagesDir, entry.name);
|
|
3577
|
+
let manifest;
|
|
3578
|
+
try {
|
|
3579
|
+
manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
3580
|
+
} catch {
|
|
3581
|
+
return [];
|
|
3582
|
+
}
|
|
3583
|
+
const record = asRecord$4(manifest);
|
|
3584
|
+
const declared = record === void 0 ? void 0 : asRecord$4(asRecord$4(record.dsh)?.workbench);
|
|
3585
|
+
const unit = declared === void 0 ? "" : asText$2(declared.unit);
|
|
3586
|
+
if (unit === "") return [];
|
|
3587
|
+
return [{
|
|
3588
|
+
unit,
|
|
3589
|
+
name: asText$2(record?.name),
|
|
3590
|
+
description: asText$2(record?.description),
|
|
3591
|
+
dir,
|
|
3592
|
+
installed: already.has(unit)
|
|
3593
|
+
}];
|
|
3594
|
+
}).sort((left, right) => left.unit.localeCompare(right.unit));
|
|
3595
|
+
}
|
|
3596
|
+
function readDesignOfModule(dir) {
|
|
3597
|
+
const file = join(dir, "design.json");
|
|
3598
|
+
if (!existsSync(file)) return void 0;
|
|
3599
|
+
try {
|
|
3600
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
3601
|
+
} catch {
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
function workshopSyncOf(workshopRoot) {
|
|
3606
|
+
let parsed;
|
|
3607
|
+
try {
|
|
3608
|
+
parsed = JSON.parse(readFileSync(join(workshopRoot, ".sync.json"), "utf8"));
|
|
3609
|
+
} catch {
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
3613
|
+
const record = parsed;
|
|
3614
|
+
const state = asText$2(record["state"]);
|
|
3615
|
+
if (state !== "ok" && state !== "syncing" && state !== "failed") return void 0;
|
|
3616
|
+
const reason = asText$2(record["reason"]);
|
|
3617
|
+
return {
|
|
3618
|
+
state,
|
|
3619
|
+
version: asText$2(record["version"]),
|
|
3620
|
+
at: asText$2(record["at"]),
|
|
3621
|
+
...reason === "" ? {} : { reason }
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
function workshopToolchainOf(workshopRoot) {
|
|
3625
|
+
const versionOf = (name) => {
|
|
3626
|
+
try {
|
|
3627
|
+
return asText$2(JSON.parse(readFileSync(join(workshopRoot, "node_modules", "@dsh-workbench", name, "package.json"), "utf8")).version);
|
|
3628
|
+
} catch {
|
|
3629
|
+
return "";
|
|
3630
|
+
}
|
|
3631
|
+
};
|
|
3632
|
+
const core = versionOf("core");
|
|
3633
|
+
const moduleTools = versionOf("module-tools");
|
|
3634
|
+
const shared = versionOf("shared");
|
|
3635
|
+
if (core === "" || moduleTools === "" || shared === "") return void 0;
|
|
3636
|
+
return {
|
|
3637
|
+
core,
|
|
3638
|
+
moduleTools,
|
|
3639
|
+
shared
|
|
3640
|
+
};
|
|
3641
|
+
}
|
|
3642
|
+
//#endregion
|
|
3643
|
+
//#region src/shared/api/host/workshop-root.ts
|
|
3644
|
+
function rootAt(root, title) {
|
|
3645
|
+
return {
|
|
3646
|
+
root,
|
|
3647
|
+
packagesDir: join(root, "packages"),
|
|
3648
|
+
title
|
|
3649
|
+
};
|
|
3650
|
+
}
|
|
3651
|
+
async function resolveAt(registry, dir) {
|
|
3652
|
+
try {
|
|
3653
|
+
return await registry.resolveByPath(dir);
|
|
3654
|
+
} catch {
|
|
3655
|
+
return;
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
function looksLikeWorkshop(dir) {
|
|
3659
|
+
return existsSync(join(dir, "packages")) || existsSync(join(dir, "pnpm-workspace.yaml"));
|
|
3660
|
+
}
|
|
3661
|
+
async function workshopRootOf(ctx, workspace) {
|
|
3662
|
+
const start = typeof workspace === "string" ? workspace.trim() : "";
|
|
3663
|
+
if (start === "") return null;
|
|
3664
|
+
const registry = ctx.get("workspaceRegistry");
|
|
3665
|
+
if (registry !== void 0) {
|
|
3666
|
+
let dir = start;
|
|
3667
|
+
for (;;) {
|
|
3668
|
+
const hit = await resolveAt(registry, dir);
|
|
3669
|
+
if (hit !== void 0) return rootAt(hit.path, hit.title);
|
|
3670
|
+
const parent = dirname(dir);
|
|
3671
|
+
if (parent === dir) break;
|
|
3672
|
+
dir = parent;
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
let dir = start;
|
|
3676
|
+
for (;;) {
|
|
3677
|
+
if (looksLikeWorkshop(dir)) return rootAt(dir, basename(dir));
|
|
3678
|
+
const parent = dirname(dir);
|
|
3679
|
+
if (parent === dir) return null;
|
|
3680
|
+
dir = parent;
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3932
3683
|
var OUTPUT$2 = {
|
|
3933
3684
|
schema: {
|
|
3934
3685
|
type: "object",
|
|
@@ -3988,26 +3739,6 @@ function verdictOf$1(change) {
|
|
|
3988
3739
|
const tail = (change.packageResult?.output ?? "").trim().split("\n").slice(-3).join(" | ");
|
|
3989
3740
|
return `没装上(${change.errorCode ?? "failed"})${tail === "" ? "" : `:${tail}`}`;
|
|
3990
3741
|
}
|
|
3991
|
-
function renderProbe(out) {
|
|
3992
|
-
if (out.ok) return `读到了 —— 这就是那条读的返回值,把它固化成模块宿主半侧的那一条:\n${(JSON.stringify(out.value, null, 2) ?? "undefined").slice(0, 2e3)}`;
|
|
3993
|
-
const lines = [`没读到(${out.code}):${out.message}`];
|
|
3994
|
-
if (out.hint !== void 0) lines.push(`照做:${out.hint}`);
|
|
3995
|
-
if (out.helpUrl !== void 0) lines.push(`入口:${out.helpUrl}`);
|
|
3996
|
-
if (out.pageCode !== void 0) lines.push(`页面自己回的码:${out.pageCode}`);
|
|
3997
|
-
return lines.join("\n");
|
|
3998
|
-
}
|
|
3999
|
-
function renderFind(out) {
|
|
4000
|
-
if (!out.ok) {
|
|
4001
|
-
const lines = [`没找到(${out.code}):${out.message}`];
|
|
4002
|
-
if (out.hint !== void 0) lines.push(`照做:${out.hint}`);
|
|
4003
|
-
if (out.helpUrl !== void 0) lines.push(`入口:${out.helpUrl}`);
|
|
4004
|
-
return lines.join("\n");
|
|
4005
|
-
}
|
|
4006
|
-
if (out.count === 0) return "找到了 0 个元素 —— 选择器没匹配到东西。先确认停在哪一页(probe 会自己问页面),再把选择器放宽一点(例如 table tr 而不是某个 id)。";
|
|
4007
|
-
const head = out.entries.slice(0, 20).map((one, index) => `${String(index + 1)}. ${one.text.replace(/\n+/g, " | ").slice(0, 200)}`);
|
|
4008
|
-
const more = out.entries.length > 20 ? `\n… 还有 ${String(out.entries.length - 20)} 个` : "";
|
|
4009
|
-
return `找到了 ${String(out.count)} 个元素 —— 每行的单元格按换行拼在 text 里;把它固化成模块宿主半侧的那一条(host 的 browserFind + 切格 + 按列序取值):\n${head.join("\n")}${more}`;
|
|
4010
|
-
}
|
|
4011
3742
|
function showOrReason(panels, unit, workspace) {
|
|
4012
3743
|
try {
|
|
4013
3744
|
panels.show(unit, workspace);
|
|
@@ -4019,14 +3750,14 @@ function showOrReason(panels, unit, workspace) {
|
|
|
4019
3750
|
};
|
|
4020
3751
|
}
|
|
4021
3752
|
}
|
|
4022
|
-
function workshopOf(ctx, exec) {
|
|
4023
|
-
const packagesDir = workshopPackagesDir(ctx);
|
|
4024
|
-
if (packagesDir === null) throw new Error("算不出 DIY 工坊在哪(这台机器没给出 dshHomePath)—— 让用户跑一次 dsh-workbench setup");
|
|
3753
|
+
async function workshopOf(ctx, exec) {
|
|
4025
3754
|
const session = sessionWorkspace(exec);
|
|
4026
|
-
|
|
3755
|
+
const root = await workshopRootOf(ctx, session);
|
|
3756
|
+
if (root === null) throw new Error(`这个工作区没有模块包可落地:DIY 工具把模块放在会话工作区的 packages/ 下(这个会话的工作区 ${session ?? "(不知道)"})—— 换到一个有模块包的工作区,或者按工程域在仓库里用 dsh-wb-module 新建一个模块包`);
|
|
4027
3757
|
return {
|
|
4028
|
-
packagesDir,
|
|
4029
|
-
workspace:
|
|
3758
|
+
packagesDir: root.packagesDir,
|
|
3759
|
+
workspace: root.root,
|
|
3760
|
+
title: root.title
|
|
4030
3761
|
};
|
|
4031
3762
|
}
|
|
4032
3763
|
function modulesOf(ctx, packagesDir) {
|
|
@@ -4036,6 +3767,9 @@ function modulesOf(ctx, packagesDir) {
|
|
|
4036
3767
|
modules: workshopModules(packagesDir, installed)
|
|
4037
3768
|
};
|
|
4038
3769
|
}
|
|
3770
|
+
function moduleDirsOf(packagesDir) {
|
|
3771
|
+
return workshopModules(packagesDir).map((one) => one.dir);
|
|
3772
|
+
}
|
|
4039
3773
|
function panelEntriesOf(panels, workspace) {
|
|
4040
3774
|
try {
|
|
4041
3775
|
return panels.list(workspace);
|
|
@@ -4048,7 +3782,7 @@ function panelEntriesOf(panels, workspace) {
|
|
|
4048
3782
|
function componentsTool(ctx) {
|
|
4049
3783
|
return {
|
|
4050
3784
|
name: DIY_COMPONENTS_TOOL,
|
|
4051
|
-
description: "工坊里能用什么:dsh 原语与外壳 kit 的清单、每件的类型文件在哪(给 name 连 JSDoc 与 prop
|
|
3785
|
+
description: "工坊里能用什么:dsh 原语与外壳 kit 的清单、每件的类型文件在哪(给 name 连 JSDoc 与 prop 一起回;给 bucket 把那一桶列全 —— 桶名见清单)。",
|
|
4052
3786
|
parameters: {
|
|
4053
3787
|
type: "object",
|
|
4054
3788
|
properties: {
|
|
@@ -4058,33 +3792,48 @@ function componentsTool(ctx) {
|
|
|
4058
3792
|
},
|
|
4059
3793
|
scope: {
|
|
4060
3794
|
type: "string",
|
|
4061
|
-
enum: [
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
description: "
|
|
3795
|
+
enum: ["kit", "primitives"],
|
|
3796
|
+
description: "只看某一面;不填就两个面都给目录。"
|
|
3797
|
+
},
|
|
3798
|
+
bucket: {
|
|
3799
|
+
type: "string",
|
|
3800
|
+
description: "只看某一桶并列全(例 components / icons / shared/hooks;桶名以清单里那份为准)。"
|
|
4067
3801
|
},
|
|
4068
3802
|
all: {
|
|
4069
3803
|
type: "boolean",
|
|
4070
|
-
description: "列全表(默认每组前 12
|
|
3804
|
+
description: "列全表(默认每组前 12 个)—— 只在指名 scope 时生效;不指名 scope 时只回一句提示,不带目录。"
|
|
4071
3805
|
}
|
|
4072
3806
|
},
|
|
4073
3807
|
additionalProperties: false
|
|
4074
3808
|
},
|
|
4075
3809
|
output: OUTPUT$2,
|
|
4076
3810
|
async execute(raw, exec) {
|
|
4077
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
3811
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
4078
3812
|
const root = dirname(packagesDir);
|
|
4079
|
-
const bin = toolchainBinOf(root);
|
|
4080
|
-
if (bin === null) throw new Error(`工坊里还没装工具链(${join(root, "node_modules")} 里没有 dsh-wb-module)—— 在工坊根目录跑一次 pnpm install 就有了;workbench_diy_status 会告诉你卡在哪一步`);
|
|
4081
|
-
const shell = ctx.get("shell");
|
|
4082
|
-
if (shell === void 0) throw new Error("这台机器没给 shell 服务,读不了组件清单");
|
|
4083
3813
|
const args = asRecord$3(raw);
|
|
4084
3814
|
const scope = scopeOf$1(asText$1(args["scope"]));
|
|
4085
3815
|
const name = asText$1(args["name"]);
|
|
3816
|
+
const bucket = asText$1(args["bucket"]);
|
|
4086
3817
|
const all = args["all"] === true;
|
|
4087
|
-
|
|
3818
|
+
if (name === "" && refusesEvery(scope, all, bucket !== "")) return plainJson({
|
|
3819
|
+
workshop: root,
|
|
3820
|
+
scope,
|
|
3821
|
+
name,
|
|
3822
|
+
bucket,
|
|
3823
|
+
all,
|
|
3824
|
+
file: null,
|
|
3825
|
+
from: null,
|
|
3826
|
+
primitives: null,
|
|
3827
|
+
kit: null,
|
|
3828
|
+
text: TWO_FACE_ALL
|
|
3829
|
+
});
|
|
3830
|
+
const bin = toolchainBinAmong(root, moduleDirsOf(packagesDir));
|
|
3831
|
+
if (bin === null) throw new Error(`工坊里还没装工具链(${join(root, "node_modules")} 与各模块自己的 node_modules 里都没有 dsh-wb-module)—— 在工坊根目录跑一次 pnpm install 就有了;workbench_diy_status 会告诉你卡在哪一步`);
|
|
3832
|
+
const shell = ctx.get("shell");
|
|
3833
|
+
if (shell === void 0) throw new Error("这台机器没给 shell 服务,读不了组件清单");
|
|
3834
|
+
const builtin = workshopPathOf(ctx);
|
|
3835
|
+
const fallbackBin = builtin === null ? void 0 : toolchainBinOf(builtin) ?? void 0;
|
|
3836
|
+
const [primitives, kit] = await Promise.all([wantsPrimitives(scope) ? runSurfaceQuery(shell, bin, "primitives", root, fallbackBin) : Promise.resolve(void 0), wantsKit(scope) ? runSurfaceQuery(shell, bin, "kit", root, fallbackBin) : Promise.resolve(void 0)]);
|
|
4088
3837
|
const query = {
|
|
4089
3838
|
...primitives === void 0 ? {} : { primitives },
|
|
4090
3839
|
...kit === void 0 ? {} : { kit }
|
|
@@ -4092,12 +3841,14 @@ function componentsTool(ctx) {
|
|
|
4092
3841
|
const found = name === "" ? null : findFileOf(name, query);
|
|
4093
3842
|
const text = name === "" ? renderSurfaceText(query, {
|
|
4094
3843
|
scope,
|
|
4095
|
-
all
|
|
3844
|
+
all,
|
|
3845
|
+
bucket
|
|
4096
3846
|
}) : renderNameText(name, found, query);
|
|
4097
3847
|
return plainJson({
|
|
4098
3848
|
workshop: root,
|
|
4099
3849
|
scope,
|
|
4100
3850
|
name,
|
|
3851
|
+
bucket,
|
|
4101
3852
|
all,
|
|
4102
3853
|
file: found?.file ?? null,
|
|
4103
3854
|
from: found?.from ?? null,
|
|
@@ -4274,8 +4025,9 @@ function debugForeignPactText(held) {
|
|
|
4274
4025
|
return `另一场调试正占着那块屏(${held.sessionId} 在调试 ${held.unit})—— 一台机器同时只允许一场:让它先 workbench_diy_debug_stop,或等它收工。`;
|
|
4275
4026
|
}
|
|
4276
4027
|
function debugOffBoardText(held, situation = "other-board") {
|
|
4277
|
-
const
|
|
4278
|
-
const
|
|
4028
|
+
const where = held.title === "" ? "这一场调试所在的工作区" : `「${held.title}」`;
|
|
4029
|
+
const what = situation === "off-workspace" ? `用户现在不在${where}(外壳选中的是别的工作区)` : situation === "panel-closed" ? "工作台面板没画在屏上(右栏收着,或那一列让给了别的面板)" : `你现在这一页画的不是${where}那块板`;
|
|
4030
|
+
const next = situation === "off-workspace" ? `让用户在左栏切回${where}` : situation === "panel-closed" ? "让用户把「工作台面板」切回来(或把右栏展开)" : `让用户把那一页切回${where}`;
|
|
4279
4031
|
return `${what} —— 看 / 动 / 网络这一族现在发不出去,我不拿别的板当目标。${held.unit} 这场调试仍然**进行中**(我没有自动替你落 paused):${next};要停下来就自己 workbench_diy_debug_pause(不问审批),页面切回来了就接着看。`;
|
|
4280
4032
|
}
|
|
4281
4033
|
function boardProbeOf(page) {
|
|
@@ -4291,13 +4043,14 @@ function boardProbeOf(page) {
|
|
|
4291
4043
|
function boardStateOf(page, workshop) {
|
|
4292
4044
|
return boardFactOf(boardProbeOf(page), workshop);
|
|
4293
4045
|
}
|
|
4294
|
-
function boardGateText(situation, unit, page) {
|
|
4295
|
-
|
|
4046
|
+
function boardGateText(situation, unit, page, title = "") {
|
|
4047
|
+
const where = title === "" ? "这一场调试所在的工作区" : `「${title}」`;
|
|
4048
|
+
if (situation === "off-workspace") return `用户现在不在${where} —— 外壳选中的是 ${page.where || "(问不出来是哪一块)"}。${where}那块板没在屏上,${unit} 的这张卡我看不到,也不拿别的板当目标。让用户在左栏切回${where};这之前我可以继续改源码 / build / 跑门禁。`;
|
|
4296
4049
|
if (situation === "panel-closed") return `工作台面板没画在屏上(右栏收着,或者这一页让给了别的面板:文件预览、模块市场等)。让用户把「工作台面板」切回来(或把右栏展开),我再接着看 ${unit}。`;
|
|
4297
|
-
return `你现在这一页画的是 ${page.board || "(不知道哪一块)"}
|
|
4050
|
+
return `你现在这一页画的是 ${page.board || "(不知道哪一块)"} 的面板,${where}那块板没在这一屏 —— ${unit} 的这张卡不在屏上,我不拿别的板当目标。请用户在左栏切回${where};这之前我可以继续改源码 / build / 跑门禁。`;
|
|
4298
4051
|
}
|
|
4299
4052
|
async function pageAt(ctx, exec, raw, options = {}) {
|
|
4300
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
4053
|
+
const { packagesDir, workspace, title } = await workshopOf(ctx, exec);
|
|
4301
4054
|
const args = asRecord$3(raw);
|
|
4302
4055
|
const asked = asText$1(args["unit"]);
|
|
4303
4056
|
const modules = modulesOf(ctx, packagesDir).modules;
|
|
@@ -4320,11 +4073,10 @@ async function pageAt(ctx, exec, raw, options = {}) {
|
|
|
4320
4073
|
const { page } = handshake;
|
|
4321
4074
|
throw new Error(`你现在这一页不是工作台(${page.title || "(没有标题)"} ${page.url})—— 把工作台那一页切到前台,再喊我一次。我只看用户正在看的那一页。`);
|
|
4322
4075
|
}
|
|
4323
|
-
const
|
|
4324
|
-
const
|
|
4325
|
-
const situation = boardSituation(boardProbeOf(handshake.page), workshop);
|
|
4076
|
+
const board = boardStateOf(handshake.page, workspace);
|
|
4077
|
+
const situation = boardSituation(boardProbeOf(handshake.page), workspace);
|
|
4326
4078
|
if (options.requirePact !== false && held !== null && (board === "other" || board === "hidden")) throw new Error(debugOffBoardText(held, situation));
|
|
4327
|
-
if (options.requireBoard !== false && (board === "other" || board === "hidden")) throw new Error(boardGateText(situation, wanted, handshake.page));
|
|
4079
|
+
if (options.requireBoard !== false && (board === "other" || board === "hidden")) throw new Error(boardGateText(situation, wanted, handshake.page, title));
|
|
4328
4080
|
return {
|
|
4329
4081
|
packagesDir,
|
|
4330
4082
|
unit: wanted,
|
|
@@ -4365,7 +4117,7 @@ function renderDebugPause(state) {
|
|
|
4365
4117
|
return [`调试暂停了(${state.unit})—— 动作族关闸,不再拿屏当目标。`, "改源码 / build / 跑门禁照常;要看屏再 workbench_diy_debug_resume(会再要用户点头一次)。"].join("\n");
|
|
4366
4118
|
}
|
|
4367
4119
|
function renderDebugResume(state) {
|
|
4368
|
-
return [`继续调试 ${state.unit} ——
|
|
4120
|
+
return [`继续调试 ${state.unit} —— 请用户回到「${state.title}」那一屏。`, "卡不在屏上时我会当场拒(不拿别的板当目标)。"].join("\n");
|
|
4369
4121
|
}
|
|
4370
4122
|
function renderDebugStop(state) {
|
|
4371
4123
|
return `收工了:${state.unit} 的调试态清掉了(占用解除)。`;
|
|
@@ -4389,7 +4141,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
4389
4141
|
},
|
|
4390
4142
|
output: OUTPUT$2,
|
|
4391
4143
|
async execute(raw, exec) {
|
|
4392
|
-
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
4144
|
+
const { packagesDir, workspace, title } = await workshopOf(ctx, exec);
|
|
4393
4145
|
const unit = asText$1(asRecord$3(raw)["unit"]);
|
|
4394
4146
|
if (unit === "") {
|
|
4395
4147
|
const { modules } = modulesOf(ctx, packagesDir);
|
|
@@ -4402,7 +4154,8 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
4402
4154
|
});
|
|
4403
4155
|
const state = moveOrThrow$1(store.begin({
|
|
4404
4156
|
sessionId: sessionIdOf(exec) ?? "",
|
|
4405
|
-
|
|
4157
|
+
workspacePath: workspace ?? "",
|
|
4158
|
+
title,
|
|
4406
4159
|
board: page.boardPath,
|
|
4407
4160
|
unit,
|
|
4408
4161
|
name: modulesOf(ctx, packagesDir).modules.find((one) => one.unit === unit)?.name ?? unit,
|
|
@@ -4424,7 +4177,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
4424
4177
|
parameters: EMPTY,
|
|
4425
4178
|
output: OUTPUT$2,
|
|
4426
4179
|
async execute(_raw, exec) {
|
|
4427
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
4180
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
4428
4181
|
const held = store.live(Date.now());
|
|
4429
4182
|
const state = moveOrThrow$1(store.pause(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
4430
4183
|
return plainJson({
|
|
@@ -4438,11 +4191,11 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
4438
4191
|
};
|
|
4439
4192
|
const resumeTool = {
|
|
4440
4193
|
name: DIY_DEBUG_RESUME_TOOL,
|
|
4441
|
-
description: "
|
|
4194
|
+
description: "继续调试(要点头一次):请用户回到这一场调试所在的那一屏。",
|
|
4442
4195
|
parameters: EMPTY,
|
|
4443
4196
|
output: OUTPUT$2,
|
|
4444
4197
|
async execute(_raw, exec) {
|
|
4445
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
4198
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
4446
4199
|
const held = store.live(Date.now());
|
|
4447
4200
|
const page = await pageAt(ctx, exec, { unit: held?.unit ?? "" }, {
|
|
4448
4201
|
requireBoard: false,
|
|
@@ -4466,7 +4219,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
4466
4219
|
parameters: EMPTY,
|
|
4467
4220
|
output: OUTPUT$2,
|
|
4468
4221
|
async execute(_raw, exec) {
|
|
4469
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
4222
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
4470
4223
|
const held = store.live(Date.now());
|
|
4471
4224
|
const state = moveOrThrow$1(store.stop(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
4472
4225
|
await store.settle?.();
|
|
@@ -5149,7 +4902,11 @@ function parametersOf(row) {
|
|
|
5149
4902
|
},
|
|
5150
4903
|
timeoutMs: {
|
|
5151
4904
|
type: "number",
|
|
5152
|
-
description: "超时毫秒(默认 30000
|
|
4905
|
+
description: "超时毫秒(默认 30000)。"
|
|
4906
|
+
},
|
|
4907
|
+
read: {
|
|
4908
|
+
type: "string",
|
|
4909
|
+
description: "只读线:给站点地址(不碰你那一屏、不要调试约定)。"
|
|
5153
4910
|
}
|
|
5154
4911
|
};
|
|
5155
4912
|
if (row.css !== void 0) properties["scope"] = {
|
|
@@ -5217,7 +4974,80 @@ function needsCardOf(row, raw) {
|
|
|
5217
4974
|
if (row.css !== void 0 && asText$1(raw["scope"]) === "card") return true;
|
|
5218
4975
|
return stringsOf$2(raw["args"]).some((one) => one.includes(CARD_TOKEN));
|
|
5219
4976
|
}
|
|
4977
|
+
function hostOf$1(url) {
|
|
4978
|
+
try {
|
|
4979
|
+
return new URL(url).host;
|
|
4980
|
+
} catch {
|
|
4981
|
+
return "";
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
async function onSiteOnce(shell, command, site) {
|
|
4985
|
+
const step = await runBrowserStep(shell, command, [
|
|
4986
|
+
"browser",
|
|
4987
|
+
BROWSER_READ_SESSION,
|
|
4988
|
+
"get",
|
|
4989
|
+
"url",
|
|
4990
|
+
"--window",
|
|
4991
|
+
"background"
|
|
4992
|
+
], LOOK_TIMEOUT_MS);
|
|
4993
|
+
const want = hostOf$1(site);
|
|
4994
|
+
if (want === "" || hostOf$1(step.stdout.trim()) === want) return false;
|
|
4995
|
+
await runBrowserStep(shell, command, [
|
|
4996
|
+
"browser",
|
|
4997
|
+
BROWSER_READ_SESSION,
|
|
4998
|
+
"open",
|
|
4999
|
+
site,
|
|
5000
|
+
"--window",
|
|
5001
|
+
"background"
|
|
5002
|
+
], LOOK_TIMEOUT_MS);
|
|
5003
|
+
return true;
|
|
5004
|
+
}
|
|
5005
|
+
function loginHintOf(stdout, site) {
|
|
5006
|
+
const code = jsonOf(stdout)?.["code"];
|
|
5007
|
+
if (code === "not-logged-in") return `没登录:去 ${site} 登一次再重跑(读线的约定:脚本回 { ok: false, code: "not-logged-in" })。`;
|
|
5008
|
+
if (code === "not-on-site") return `还不在 ${site} 上:我开过一次了 —— 让用户在那扇窗里切过去登一次,再重跑。`;
|
|
5009
|
+
return "";
|
|
5010
|
+
}
|
|
5011
|
+
function readRefusalOf(row, raw) {
|
|
5012
|
+
if (asText$1(raw["unit"]) !== "" || asText$1(raw["scope"]) === "card" || asText$1(raw["in"]) === "card" || stringsOf$2(raw["args"]).some((one) => one.includes("$card"))) return "只读线上没有「用户那张卡」:unit / scope=card / in=card / $card 只在屏那一档能用。";
|
|
5013
|
+
return row.clip === true ? "只读线不截图裁剪 —— clip 是屏那一档的事(要图走 _screenshot)。" : "";
|
|
5014
|
+
}
|
|
5015
|
+
async function runReadTool(row, ctx, raw, site) {
|
|
5016
|
+
const refused = readRefusalOf(row, raw);
|
|
5017
|
+
if (refused !== "") throw new Error(refused);
|
|
5018
|
+
const shell = ctx.get("shell");
|
|
5019
|
+
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,驱动不了浏览器");
|
|
5020
|
+
const command = opencliCommandOf(ctx).command;
|
|
5021
|
+
const requested = asNumber(raw["timeoutMs"]);
|
|
5022
|
+
const timeoutMs = requested !== void 0 && requested > 0 ? requested : DEFAULT_TIMEOUT_MS;
|
|
5023
|
+
const argv = stringsOf$2(raw["args"]);
|
|
5024
|
+
const opened = await onSiteOnce(shell, command, site);
|
|
5025
|
+
const step = await runBrowserStep(shell, command, [
|
|
5026
|
+
"browser",
|
|
5027
|
+
BROWSER_READ_SESSION,
|
|
5028
|
+
...row.verb,
|
|
5029
|
+
...argv,
|
|
5030
|
+
"--window",
|
|
5031
|
+
"background"
|
|
5032
|
+
], timeoutMs);
|
|
5033
|
+
const hint = row.tool === "workbench_diy_eval" ? loginHintOf(step.stdout, site) : "";
|
|
5034
|
+
return {
|
|
5035
|
+
session: BROWSER_READ_SESSION,
|
|
5036
|
+
verb: row.verb.join(" "),
|
|
5037
|
+
argv,
|
|
5038
|
+
opened,
|
|
5039
|
+
ok: step.ok,
|
|
5040
|
+
exitCode: step.exitCode,
|
|
5041
|
+
timedOut: step.timedOut,
|
|
5042
|
+
truncated: step.truncated,
|
|
5043
|
+
stdout: hint === "" ? step.stdout : `${step.stdout}\n${hint}`,
|
|
5044
|
+
stderr: step.stderr
|
|
5045
|
+
};
|
|
5046
|
+
}
|
|
5220
5047
|
async function runBrowserTool(row, ctx, raw, exec) {
|
|
5048
|
+
const args = asRecord$3(raw);
|
|
5049
|
+
const read = asText$1(args["read"]);
|
|
5050
|
+
if (read !== "") return runReadTool(row, ctx, args, read);
|
|
5221
5051
|
const page = await pageAt(ctx, exec, raw, { requireUnit: false });
|
|
5222
5052
|
if (needsCardOf(row, page.args) && page.unit === "") throw new Error("要卡作用域(scope=card / $card / in=card)就得给一个 unit —— 工坊里不止一个模块时,我不知道你说哪一张卡。");
|
|
5223
5053
|
const requested = asNumber(page.args["timeoutMs"]);
|
|
@@ -5525,7 +5355,7 @@ function nextOf(answer) {
|
|
|
5525
5355
|
}
|
|
5526
5356
|
const plugin = answer.plugins[0];
|
|
5527
5357
|
if (plugin !== void 0) return `这台机器另装的插件 ${plugin.name} 里有匹配:opencli ${plugin.name} --help 看全部命令`;
|
|
5528
|
-
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:
|
|
5358
|
+
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:workbench_diy_eval / _find 带 read=<地址>(在 workbench-read 上跑,借用户已登录的那扇窗)";
|
|
5529
5359
|
}
|
|
5530
5360
|
function signatureOf$1(args) {
|
|
5531
5361
|
const shown = args.slice(0, ARG_LIMIT).map((one) => {
|
|
@@ -6055,7 +5885,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6055
5885
|
},
|
|
6056
5886
|
output: OUTPUT$2,
|
|
6057
5887
|
async execute(_raw, exec) {
|
|
6058
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
5888
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
6059
5889
|
const { modules } = modulesOf(ctx, packagesDir);
|
|
6060
5890
|
return plainJson({
|
|
6061
5891
|
workshop: packagesDir,
|
|
@@ -6078,7 +5908,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6078
5908
|
},
|
|
6079
5909
|
output: OUTPUT$2,
|
|
6080
5910
|
async execute(raw, exec) {
|
|
6081
|
-
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
5911
|
+
const { packagesDir, workspace } = await workshopOf(ctx, exec);
|
|
6082
5912
|
const { modules } = modulesOf(ctx, packagesDir);
|
|
6083
5913
|
const registry = ctx.get(UNITS_SERVICE);
|
|
6084
5914
|
const panelEntries = panelEntriesOf(panels, workspace);
|
|
@@ -6118,7 +5948,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6118
5948
|
},
|
|
6119
5949
|
output: OUTPUT$2,
|
|
6120
5950
|
async execute(raw, exec) {
|
|
6121
|
-
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
5951
|
+
const { packagesDir, workspace } = await workshopOf(ctx, exec);
|
|
6122
5952
|
const { installed, modules } = modulesOf(ctx, packagesDir);
|
|
6123
5953
|
const panelState = ctx.get(PANEL_STATE_SERVICE);
|
|
6124
5954
|
const registry = ctx.get(UNITS_SERVICE);
|
|
@@ -6186,7 +6016,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6186
6016
|
},
|
|
6187
6017
|
output: OUTPUT$2,
|
|
6188
6018
|
async execute(raw, exec) {
|
|
6189
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
6019
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
6190
6020
|
const args = asRecord$3(raw);
|
|
6191
6021
|
const keywords = keywordsOf(asText$1(args["keyword"]), asText$1(args["url"]));
|
|
6192
6022
|
if (keywords.length === 0) throw new Error("recon 要一个关键词(站点名 / 系统名 / 域名都行):先拿它去 opencli 自带的那份目录里找有没有现成的能力,别一上来就自己写 adapter;用户不知道 opencli 是什么,这一步别问他。");
|
|
@@ -6199,96 +6029,6 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6199
6029
|
});
|
|
6200
6030
|
}
|
|
6201
6031
|
};
|
|
6202
|
-
const probeTool = {
|
|
6203
|
-
name: DIY_PROBE_TOOL,
|
|
6204
|
-
description: "用用户浏览器里现有的登录探一条读:script 读接口 / css 读 DOM。不在站点上会自己打开 url。",
|
|
6205
|
-
parameters: {
|
|
6206
|
-
type: "object",
|
|
6207
|
-
properties: {
|
|
6208
|
-
script: {
|
|
6209
|
-
type: "string",
|
|
6210
|
-
description: "与 css 二选一:页面里跑一段回 JSON 的脚本。不在站点回 { ok: false, code: \"not-on-site\" },登录过期回 \"not-logged-in\",成功回 { ok: true, ...数据 }。"
|
|
6211
|
-
},
|
|
6212
|
-
css: {
|
|
6213
|
-
type: "string",
|
|
6214
|
-
description: "与 script 二选一:给一个 CSS 选择器,宿主回匹配到的元素文本(每行单元格按换行拼在 text 里)。"
|
|
6215
|
-
},
|
|
6216
|
-
limit: {
|
|
6217
|
-
type: "number",
|
|
6218
|
-
description: "配 css 用:最多回几个元素(默认 50)。"
|
|
6219
|
-
},
|
|
6220
|
-
textMax: {
|
|
6221
|
-
type: "number",
|
|
6222
|
-
description: "配 css 用:每个元素文本最多留几个字(默认 200)。"
|
|
6223
|
-
},
|
|
6224
|
-
url: {
|
|
6225
|
-
type: "string",
|
|
6226
|
-
description: "那套系统的地址(不在站点上时自己打开它再读一遍)。"
|
|
6227
|
-
},
|
|
6228
|
-
loginUrl: {
|
|
6229
|
-
type: "string",
|
|
6230
|
-
description: "要登录时那颗\"去登录\"该去哪(默认用 url)。"
|
|
6231
|
-
},
|
|
6232
|
-
session: {
|
|
6233
|
-
type: "string",
|
|
6234
|
-
description: "会话名(默认读那条,一般不用填)。"
|
|
6235
|
-
},
|
|
6236
|
-
open: {
|
|
6237
|
-
type: "boolean",
|
|
6238
|
-
description: "不在站点上时要不要顺手打开(默认要)。"
|
|
6239
|
-
}
|
|
6240
|
-
},
|
|
6241
|
-
additionalProperties: false
|
|
6242
|
-
},
|
|
6243
|
-
output: OUTPUT$2,
|
|
6244
|
-
async execute(raw, exec) {
|
|
6245
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
6246
|
-
const args = asRecord$3(raw);
|
|
6247
|
-
const script = asText$1(args["script"]);
|
|
6248
|
-
const css = asText$1(args["css"]);
|
|
6249
|
-
if (script === "" && css === "") throw new Error("probe 要 script(页面脚本,回 JSON)或 css(读 DOM 那一行的选择器)之一。script 的约定:不在那个站点上就回 { ok: false, code: \"not-on-site\" }(我会自己把它打开再读一遍);登录过期回 { ok: false, code: \"not-logged-in\" };成功回 { ok: true, ...数据 }。页面里压根没有接口时给 css,别写 document.querySelectorAll(宿主产物不许出现 DOM 字样)。");
|
|
6250
|
-
const shell = ctx.get("shell");
|
|
6251
|
-
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,probe 跑不了");
|
|
6252
|
-
const url = asText$1(args["url"]);
|
|
6253
|
-
const loginUrl = asText$1(args["loginUrl"]);
|
|
6254
|
-
const session = asText$1(args["session"]);
|
|
6255
|
-
if (session === "workbench") throw new Error(`"${BROWSER_SCREEN_SESSION}" 是用户那一屏那条会话 —— probe 只读自己的线(不填就落在读那条上,想给自己一条专线就传自己的 unit)。要看 / 动他那一屏走 workbench_diy_state / _find / _click 那一族,而且要先 workbench_diy_debug_start(会请他点头一次)。`);
|
|
6256
|
-
const open = typeof args["open"] === "boolean" ? args["open"] : void 0;
|
|
6257
|
-
if (css !== "") {
|
|
6258
|
-
const limit = asNumber(args["limit"]);
|
|
6259
|
-
const textMax = asNumber(args["textMax"]);
|
|
6260
|
-
const found = await browserFind({
|
|
6261
|
-
shell,
|
|
6262
|
-
css,
|
|
6263
|
-
command: opencliCommandOf(ctx).command,
|
|
6264
|
-
...url === "" ? {} : { url },
|
|
6265
|
-
...session === "" ? {} : { session },
|
|
6266
|
-
...open === void 0 ? {} : { open },
|
|
6267
|
-
...limit === void 0 ? {} : { limit },
|
|
6268
|
-
...textMax === void 0 ? {} : { textMax }
|
|
6269
|
-
});
|
|
6270
|
-
return plainJson({
|
|
6271
|
-
workshop: packagesDir,
|
|
6272
|
-
...found,
|
|
6273
|
-
text: renderFind(found)
|
|
6274
|
-
});
|
|
6275
|
-
}
|
|
6276
|
-
const out = await browserRead({
|
|
6277
|
-
shell,
|
|
6278
|
-
script,
|
|
6279
|
-
command: opencliCommandOf(ctx).command,
|
|
6280
|
-
...url === "" ? {} : { url },
|
|
6281
|
-
...loginUrl === "" ? {} : { loginUrl },
|
|
6282
|
-
...session === "" ? {} : { session },
|
|
6283
|
-
...open === void 0 ? {} : { open }
|
|
6284
|
-
});
|
|
6285
|
-
return plainJson({
|
|
6286
|
-
workshop: packagesDir,
|
|
6287
|
-
...out,
|
|
6288
|
-
text: renderProbe(out)
|
|
6289
|
-
});
|
|
6290
|
-
}
|
|
6291
|
-
};
|
|
6292
6032
|
function unitOf(packagesDir, raw, label) {
|
|
6293
6033
|
const { modules } = modulesOf(ctx, packagesDir);
|
|
6294
6034
|
const unit = asText$1(asRecord$3(raw)["unit"]);
|
|
@@ -6312,7 +6052,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6312
6052
|
},
|
|
6313
6053
|
output: OUTPUT$2,
|
|
6314
6054
|
async execute(raw, exec) {
|
|
6315
|
-
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
6055
|
+
const { packagesDir, workspace } = await workshopOf(ctx, exec);
|
|
6316
6056
|
const found = unitOf(packagesDir, raw, "workbench_diy_show");
|
|
6317
6057
|
if (!found.installed) throw new Error(`${found.unit} 还没装 —— 只上板的前提是它装过了。先用 workbench_diy_install(第一次)`);
|
|
6318
6058
|
const panel = showOrReason(panels, found.unit, workspace);
|
|
@@ -6340,7 +6080,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6340
6080
|
},
|
|
6341
6081
|
output: OUTPUT$2,
|
|
6342
6082
|
async execute(raw, exec) {
|
|
6343
|
-
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
6083
|
+
const { packagesDir, workspace } = await workshopOf(ctx, exec);
|
|
6344
6084
|
const found = unitOf(packagesDir, raw, "workbench_diy_install");
|
|
6345
6085
|
if (found.installed) {
|
|
6346
6086
|
const panel = showOrReason(panels, found.unit, workspace);
|
|
@@ -6385,7 +6125,6 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6385
6125
|
...registerComponentsTool(ctx, tools),
|
|
6386
6126
|
tools.register(reconTool),
|
|
6387
6127
|
...registerSpecTool(ctx, tools),
|
|
6388
|
-
tools.register(probeTool),
|
|
6389
6128
|
...registerLogTools(ctx, tools),
|
|
6390
6129
|
...registerBrowserTools(ctx, tools),
|
|
6391
6130
|
...registerDebugTools(ctx, tools, debug),
|
|
@@ -6395,14 +6134,13 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
6395
6134
|
}
|
|
6396
6135
|
//#endregion
|
|
6397
6136
|
//#region src/modules/panels/model/diy-observers.ts
|
|
6398
|
-
async function stopObservers(ctx, session = BROWSER_SCREEN_SESSION) {
|
|
6399
|
-
const packagesDir = workshopPackagesDir(ctx);
|
|
6137
|
+
async function stopObservers(ctx, workspace, session = BROWSER_SCREEN_SESSION) {
|
|
6400
6138
|
const shell = ctx.get("shell");
|
|
6401
|
-
if (
|
|
6139
|
+
if (workspace === null || workspace === void 0 || workspace === "" || shell === void 0) return {
|
|
6402
6140
|
net: false,
|
|
6403
6141
|
logs: false
|
|
6404
6142
|
};
|
|
6405
|
-
const file = join(
|
|
6143
|
+
const file = join(workspace, "shots", `net-${session}.jsonl`);
|
|
6406
6144
|
const command = opencliCommandOf(ctx).command;
|
|
6407
6145
|
const [net, step] = await Promise.all([stopNetworkExact(shell, file), actBrowser(shell, command, session, ["eval", LOGS_STOP_SCRIPT], SWEEP_TIMEOUT_MS)]);
|
|
6408
6146
|
return {
|
|
@@ -6413,13 +6151,13 @@ async function stopObservers(ctx, session = BROWSER_SCREEN_SESSION) {
|
|
|
6413
6151
|
function withObserverCleanup(store, cleanup, logger) {
|
|
6414
6152
|
let pending = Promise.resolve();
|
|
6415
6153
|
let epoch = 0;
|
|
6416
|
-
const sweep = async (at) => {
|
|
6154
|
+
const sweep = async (at, workspace) => {
|
|
6417
6155
|
if (epoch !== at) {
|
|
6418
6156
|
logger?.warn?.("收工那一拍:已经有新的调试约定开始了 —— 跳过收观察(新那一场自己收)");
|
|
6419
6157
|
return;
|
|
6420
6158
|
}
|
|
6421
6159
|
try {
|
|
6422
|
-
const out = await cleanup();
|
|
6160
|
+
const out = await cleanup(workspace);
|
|
6423
6161
|
if (!out.net || !out.logs) logger?.warn?.(`收工那一拍没把观察收干净(网络 ${String(out.net)} / 日志 ${String(out.logs)})—— 网络那条最多 15 分钟自己收工,日志钩子随页面刷新消失`);
|
|
6424
6162
|
} catch (error) {
|
|
6425
6163
|
logger?.warn?.(`收工那一拍收观察抛了:${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -6437,7 +6175,8 @@ function withObserverCleanup(store, cleanup, logger) {
|
|
|
6437
6175
|
const move = store.stop(sessionId, now);
|
|
6438
6176
|
if (move.ok) {
|
|
6439
6177
|
const at = epoch;
|
|
6440
|
-
|
|
6178
|
+
const workspace = move.state.workspacePath;
|
|
6179
|
+
pending = pending.then(() => sweep(at, workspace));
|
|
6441
6180
|
}
|
|
6442
6181
|
return move;
|
|
6443
6182
|
},
|
|
@@ -6494,13 +6233,13 @@ var INTENT_WORD$1 = {
|
|
|
6494
6233
|
stop: "结束"
|
|
6495
6234
|
};
|
|
6496
6235
|
function debugNoticeText(intent, state) {
|
|
6497
|
-
const head =
|
|
6236
|
+
const head = `[调试 · ${state.title === "" ? "工作区" : state.title}]用户按了「${INTENT_WORD$1[intent]}」:`;
|
|
6498
6237
|
if (intent === "pause") return `${head}${state.unit} 这场调试暂停了 —— 现在别再把那一屏当目标(看 / 动 / 网络那一族发不出去)。要接着看屏就等用户按「继续」;在那之前照常改源码 / build / 跑门禁。`;
|
|
6499
6238
|
if (intent === "resume") return `${head}${state.unit} 这场调试重新开始了 —— 那一屏又归你看。屏 / 板不对的话照旧会被拒,拒了就照实说清是哪种不对。`;
|
|
6500
6239
|
return `${head}${state.unit} 这场调试收工了(占用解除)。要再调试就重新 start 一场。`;
|
|
6501
6240
|
}
|
|
6502
6241
|
function debugNoticeSummary(intent, state) {
|
|
6503
|
-
return
|
|
6242
|
+
return `调试:用户按了${INTENT_WORD$1[intent]}(${state.unit})`;
|
|
6504
6243
|
}
|
|
6505
6244
|
function debugNotice(intent, state) {
|
|
6506
6245
|
const summary = debugNoticeSummary(intent, state);
|
|
@@ -7702,7 +7441,7 @@ function registerRecordTools(ctx, store) {
|
|
|
7702
7441
|
},
|
|
7703
7442
|
output: OUTPUT$2,
|
|
7704
7443
|
async execute(raw, exec) {
|
|
7705
|
-
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
7444
|
+
const { packagesDir, workspace } = await workshopOf(ctx, exec);
|
|
7706
7445
|
const args = asRecord$3(raw);
|
|
7707
7446
|
const unit = asText$1(args["unit"]);
|
|
7708
7447
|
const modules = modulesOf(ctx, packagesDir).modules;
|
|
@@ -7811,7 +7550,7 @@ function registerRecordTools(ctx, store) {
|
|
|
7811
7550
|
},
|
|
7812
7551
|
output: OUTPUT$2,
|
|
7813
7552
|
async execute(raw, exec) {
|
|
7814
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
7553
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
7815
7554
|
const shell = ctx.get("shell");
|
|
7816
7555
|
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
|
|
7817
7556
|
const sessionId = sessionIdOf(exec) ?? "";
|
|
@@ -7927,7 +7666,7 @@ function registerRecordTools(ctx, store) {
|
|
|
7927
7666
|
},
|
|
7928
7667
|
output: OUTPUT$2,
|
|
7929
7668
|
async execute(raw, exec) {
|
|
7930
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
7669
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
7931
7670
|
const shell = ctx.get("shell");
|
|
7932
7671
|
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,取不了详情");
|
|
7933
7672
|
const args = asRecord$3(raw);
|
|
@@ -8007,7 +7746,7 @@ function registerRecordTools(ctx, store) {
|
|
|
8007
7746
|
},
|
|
8008
7747
|
output: OUTPUT$2,
|
|
8009
7748
|
async execute(_raw, exec) {
|
|
8010
|
-
const { packagesDir } = workshopOf(ctx, exec);
|
|
7749
|
+
const { packagesDir } = await workshopOf(ctx, exec);
|
|
8011
7750
|
const shell = ctx.get("shell");
|
|
8012
7751
|
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
|
|
8013
7752
|
const sessionId = sessionIdOf(exec) ?? "";
|
|
@@ -10457,7 +10196,7 @@ var OWNED = [{
|
|
|
10457
10196
|
logger
|
|
10458
10197
|
});
|
|
10459
10198
|
ctx.provide(PANELS_SERVICE, panels);
|
|
10460
|
-
const debug = withObserverCleanup(createDebugStore(), () => stopObservers(ctx), logger);
|
|
10199
|
+
const debug = withObserverCleanup(createDebugStore(), (workspace) => stopObservers(ctx, workspace), logger);
|
|
10461
10200
|
ctx.provide(DIY_DEBUG_SERVICE, debug);
|
|
10462
10201
|
const record = {
|
|
10463
10202
|
...createRecordStore(),
|
|
@@ -10537,13 +10276,13 @@ var OWNED = [{
|
|
|
10537
10276
|
factsOf: (call) => panelsFactsOf(call, manifests()),
|
|
10538
10277
|
needsApproval: panelsApproval,
|
|
10539
10278
|
gate: {
|
|
10540
|
-
deniedTools: (
|
|
10541
|
-
const boards = panels.boards(workspace);
|
|
10279
|
+
deniedTools: (context) => {
|
|
10280
|
+
const boards = panels.boards(context.workspace);
|
|
10542
10281
|
const owned = new Set(ownedUnits());
|
|
10543
10282
|
return [...manifests().flatMap((manifest) => owned.has(manifest.id) || resolveScope({
|
|
10544
10283
|
id: manifest.id,
|
|
10545
10284
|
boards
|
|
10546
|
-
}) ? [] : manifest.tools.map((tool) => tool.name)), ...
|
|
10285
|
+
}) ? [] : manifest.tools.map((tool) => tool.name)), ...context.preset === "workbench-diy" ? [] : [
|
|
10547
10286
|
...DIY_TOOLS,
|
|
10548
10287
|
...DIY_DEBUG_TOOLS,
|
|
10549
10288
|
...DIY_RECORD_TOOLS
|
|
@@ -13038,9 +12777,15 @@ function configSyncAction(deps) {
|
|
|
13038
12777
|
function agentOf(value) {
|
|
13039
12778
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
13040
12779
|
}
|
|
13041
|
-
function
|
|
13042
|
-
const
|
|
13043
|
-
|
|
12780
|
+
function contextOf(handle, options) {
|
|
12781
|
+
const header = handle.session?.header;
|
|
12782
|
+
const cwd = header?.cwd;
|
|
12783
|
+
const declared = header?.agentPreset;
|
|
12784
|
+
const read = options.presetOf?.(handle);
|
|
12785
|
+
return {
|
|
12786
|
+
workspace: typeof cwd === "string" && cwd !== "" ? cwd : null,
|
|
12787
|
+
preset: read ?? (typeof declared === "string" && declared !== "" ? declared : null)
|
|
12788
|
+
};
|
|
13044
12789
|
}
|
|
13045
12790
|
function createToolScope(options) {
|
|
13046
12791
|
const applied = /* @__PURE__ */ new WeakMap();
|
|
@@ -13057,9 +12802,9 @@ function createToolScope(options) {
|
|
|
13057
12802
|
options.logger?.warn?.("[dsh-workbench] 拿不到 agent 作用域的工具面:这次不收窄(下次再试)");
|
|
13058
12803
|
return;
|
|
13059
12804
|
}
|
|
13060
|
-
const
|
|
13061
|
-
const deny = [...options.deniedTools(
|
|
13062
|
-
const signature = `${workspace ?? ""}\u0000${deny.join(",")}`;
|
|
12805
|
+
const context = contextOf(handle, options);
|
|
12806
|
+
const deny = [...options.deniedTools(context)];
|
|
12807
|
+
const signature = `${context.workspace ?? ""}\u0000${context.preset ?? ""}\u0000${deny.join(",")}`;
|
|
13063
12808
|
const previous = applied.get(handle);
|
|
13064
12809
|
if (previous !== void 0 && previous.signature === signature) return;
|
|
13065
12810
|
if (previous === void 0) seen.add(new WeakRef(handle));
|
|
@@ -13069,12 +12814,13 @@ function createToolScope(options) {
|
|
|
13069
12814
|
applied.delete(handle);
|
|
13070
12815
|
return;
|
|
13071
12816
|
}
|
|
12817
|
+
const where = context.preset === null ? context.workspace : `${context.workspace ?? "(未知工作区)"} · ${context.preset}`;
|
|
13072
12818
|
try {
|
|
13073
12819
|
applied.set(handle, {
|
|
13074
12820
|
signature,
|
|
13075
12821
|
dispose: tools.restrict({ deny })
|
|
13076
12822
|
});
|
|
13077
|
-
options.logger?.info?.(`[dsh-workbench]
|
|
12823
|
+
options.logger?.info?.(`[dsh-workbench] 按会话收窄工具面:${where ?? "(未知工作区)"} 里收起 ${deny.length} 个工具(${deny.join(", ")})`);
|
|
13078
12824
|
} catch (error) {
|
|
13079
12825
|
options.logger?.warn?.(`[dsh-workbench] 收窄工具面失败:${String(error)}`);
|
|
13080
12826
|
}
|
|
@@ -13089,7 +12835,7 @@ function createToolScope(options) {
|
|
|
13089
12835
|
const handle = agentOf(agent);
|
|
13090
12836
|
const tools = assembly?.tools;
|
|
13091
12837
|
if (handle === void 0 || tools === void 0) return assembly;
|
|
13092
|
-
const deny = new Set(options.deniedTools(
|
|
12838
|
+
const deny = new Set(options.deniedTools(contextOf(handle, options)));
|
|
13093
12839
|
if (deny.size === 0) return assembly;
|
|
13094
12840
|
const kept = tools.filter((tool) => !deny.has(tool.name));
|
|
13095
12841
|
return kept.length === tools.length ? assembly : {
|
|
@@ -13116,6 +12862,41 @@ function createToolScope(options) {
|
|
|
13116
12862
|
};
|
|
13117
12863
|
}
|
|
13118
12864
|
//#endregion
|
|
12865
|
+
//#region src/shared/api/host/preset-scope.ts
|
|
12866
|
+
var PRESET_KEY = "agentPreset";
|
|
12867
|
+
function presetOf(ctx, handle) {
|
|
12868
|
+
const projections = ctx.get(SESSION_PROJECTIONS_SERVICE);
|
|
12869
|
+
if (projections === void 0 || handle.session === void 0) return null;
|
|
12870
|
+
try {
|
|
12871
|
+
const preset = projections.stateOf(handle.session, PRESET_KEY);
|
|
12872
|
+
return typeof preset === "string" && preset !== "" ? preset : null;
|
|
12873
|
+
} catch (error) {
|
|
12874
|
+
ctx.logger?.warn?.(`[dsh-workbench] 读 preset 投影失败,退回会话创建事实:${String(error)}`);
|
|
12875
|
+
return null;
|
|
12876
|
+
}
|
|
12877
|
+
}
|
|
12878
|
+
function watchPreset(ctx, listener) {
|
|
12879
|
+
ctx.inject([SESSION_PROJECTIONS_SERVICE], (scoped) => {
|
|
12880
|
+
const projections = scoped.get(SESSION_PROJECTIONS_SERVICE);
|
|
12881
|
+
if (projections === void 0) return;
|
|
12882
|
+
scoped.effect(() => {
|
|
12883
|
+
const off = projections.onChanged((_session, key) => {
|
|
12884
|
+
if (key === PRESET_KEY) listener();
|
|
12885
|
+
});
|
|
12886
|
+
return () => off?.();
|
|
12887
|
+
}, "dsh-workbench: preset 选中就重算工具面(新会话屏是先建会话、再把选择 select 上去)");
|
|
12888
|
+
});
|
|
12889
|
+
}
|
|
12890
|
+
function createPresetScope(options) {
|
|
12891
|
+
const scope = createToolScope({
|
|
12892
|
+
deniedTools: options.deniedTools,
|
|
12893
|
+
presetOf: (handle) => presetOf(options.ctx, handle),
|
|
12894
|
+
logger: options.logger
|
|
12895
|
+
});
|
|
12896
|
+
watchPreset(options.ctx, () => scope.reapply());
|
|
12897
|
+
return scope;
|
|
12898
|
+
}
|
|
12899
|
+
//#endregion
|
|
13119
12900
|
//#region src/shared/api/host/host-args.ts
|
|
13120
12901
|
function asArgs(value) {
|
|
13121
12902
|
return typeof value === "object" && value !== null ? value : {};
|
|
@@ -13219,8 +13000,9 @@ function apply(ctx) {
|
|
|
13219
13000
|
};
|
|
13220
13001
|
const store = createWorkspaceStore({ logger: ctx.logger });
|
|
13221
13002
|
const unitsOfTool = () => toolUnits(liveManifests());
|
|
13222
|
-
const toolScope =
|
|
13223
|
-
|
|
13003
|
+
const toolScope = createPresetScope({
|
|
13004
|
+
ctx,
|
|
13005
|
+
deniedTools: (context) => deniedNow(context),
|
|
13224
13006
|
logger: ctx.logger
|
|
13225
13007
|
});
|
|
13226
13008
|
const pluginTools = shell === void 0 ? void 0 : createPluginUnits({
|
|
@@ -13247,7 +13029,7 @@ function apply(ctx) {
|
|
|
13247
13029
|
const ownersMap = /* @__PURE__ */ new Map();
|
|
13248
13030
|
const factsOfUnit = (unit) => [...routesOfUnitMap.get(unit) ?? [], "capability"];
|
|
13249
13031
|
const callFacts = (unit, call) => factsOfMap.get(unit)?.(call) ?? factsOfUnit(unit);
|
|
13250
|
-
const deniedNow = (
|
|
13032
|
+
const deniedNow = (context) => [...new Set([...mounted.values()].flatMap((item) => item.plan.gate?.deniedTools(context) ?? []))];
|
|
13251
13033
|
const declaredNoteTools = () => declaredNotesOf([...mounted.values()].map((item) => item.plan));
|
|
13252
13034
|
const wrote = (resource, write) => {
|
|
13253
13035
|
const unit = ownersMap.get(resource);
|