@dsh-workbench/core 0.1.18 → 0.2.1
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 +2241 -955
- package/dist/client/client.body.js +2241 -955
- package/dist/client/index.js +2242 -956
- package/dist/host/index.js +1840 -332
- package/dist/types/app/client.d.ts +225 -81
- package/dist/types/app/layouts/__tests__/async-feedback.test.d.ts +1 -0
- package/dist/types/app/layouts/market/__tests__/add-module-stages.test.d.ts +1 -0
- package/dist/types/app/layouts/market/add-module-text.d.ts +16 -1
- package/dist/types/app/layouts/market/use-add-module.d.ts +3 -8
- package/dist/types/app/layouts/market/use-module-versions.d.ts +2 -1
- package/dist/types/app/layouts/rightbar/MarketView.d.ts +7 -1
- package/dist/types/app/layouts/rightbar/ModulesPage.d.ts +4 -2
- package/dist/types/app/layouts/rightbar/PanelGroupRows.d.ts +10 -0
- package/dist/types/app/layouts/rightbar/PanelModules.d.ts +2 -2
- package/dist/types/app/layouts/rightbar/PanelRowControls.d.ts +13 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-catalog.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-dots.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-row-icon.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-section-icon.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-state-rules.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-workbench.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-dismiss.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-drag-target.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-plugin-rows.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-scope-ui.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-write.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/board-selection.d.ts +20 -0
- package/dist/types/app/layouts/rightbar/dsh-borrow.d.ts +15 -0
- package/dist/types/app/layouts/rightbar/dsh-borrow.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/dsh-surface.d.ts +11 -0
- package/dist/types/app/layouts/rightbar/dsh-surface.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/market-act.d.ts +21 -0
- package/dist/types/app/layouts/rightbar/market-installed-mock.d.ts +9 -0
- package/dist/types/app/layouts/rightbar/market-mock-types.d.ts +58 -0
- package/dist/types/app/layouts/rightbar/market-mock.d.ts +2 -43
- package/dist/types/app/layouts/rightbar/market-plugin-rows.d.ts +3 -1
- package/dist/types/app/layouts/rightbar/market-row-icon.d.ts +7 -0
- package/dist/types/app/layouts/rightbar/market-row-types.d.ts +37 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +13 -53
- package/dist/types/app/layouts/rightbar/market-section-icon.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/market-section.d.ts +18 -0
- package/dist/types/app/layouts/rightbar/market-shared-rows.d.ts +21 -0
- package/dist/types/app/layouts/rightbar/market-view-text.d.ts +7 -1
- package/dist/types/app/layouts/rightbar/market-workbench-row.d.ts +5 -0
- package/dist/types/app/layouts/rightbar/panel-dismiss.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/panel-mock.d.ts +3 -2
- package/dist/types/app/layouts/rightbar/panel-write.d.ts +15 -0
- package/dist/types/app/layouts/rightbar/use-drag-listeners.d.ts +6 -0
- package/dist/types/app/layouts/rightbar/use-panels.d.ts +15 -1
- package/dist/types/app/layouts/sidebar/__tests__/session-row-ui.test.d.ts +1 -0
- package/dist/types/app/units/registry.d.ts +5 -2
- package/dist/types/modules/panels/__tests__/diy-harness.d.ts +19 -0
- package/dist/types/modules/panels/__tests__/diy-report.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/diy-tools-health.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/diy-tools-panel.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-panel-market-parity.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-panel.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-tool-units.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/scope-machine.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/scope-support.d.ts +51 -0
- package/dist/types/modules/panels/model/config.d.ts +23 -3
- package/dist/types/modules/panels/model/diy-report.d.ts +63 -0
- package/dist/types/modules/panels/model/market-dispatch.d.ts +4 -0
- package/dist/types/modules/panels/model/market-registry.d.ts +12 -0
- package/dist/types/modules/panels/model/market-registry.test.d.ts +1 -0
- package/dist/types/modules/panels/model/market-types.d.ts +1 -0
- package/dist/types/modules/panels/model/market.d.ts +3 -2
- package/dist/types/modules/panels/model/plugin-panel.d.ts +3 -0
- package/dist/types/modules/panels/model/prefs.d.ts +3 -1
- package/dist/types/modules/panels/model/service.d.ts +5 -1
- package/dist/types/shared/api/bundle-install.d.ts +15 -1
- package/dist/types/shared/api/host/__tests__/approval-note-injection.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/panel-state-route.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-catalog.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-login-hint.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-tools.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-inspect-kind.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-routes-self.test.d.ts +1 -0
- package/dist/types/shared/api/host/installed-module.d.ts +12 -0
- package/dist/types/shared/api/host/mount-units.d.ts +1 -0
- package/dist/types/shared/api/host/plugin-catalog.d.ts +60 -0
- package/dist/types/shared/api/host/plugin-login-hint.d.ts +2 -0
- package/dist/types/shared/api/host/plugin-tools.d.ts +10 -0
- package/dist/types/shared/api/host/plugin-units.d.ts +47 -0
- package/dist/types/shared/api/host/unit-registry.d.ts +9 -0
- package/dist/types/shared/api/unit-manifests.d.ts +1 -0
- package/dist/types/shared/components/PanelFoot.d.ts +1 -1
- package/dist/types/shared/hooks/panel-state-report.d.ts +13 -0
- package/dist/types/shared/hooks/panel-state-report.test.d.ts +1 -0
- package/docs/HANDOFF.md +7 -0
- package/package.json +2 -2
- package/preset/diy.patch.yml +5 -26
- package/skills/workbench-module-diy/REVIEW.md +22 -0
- package/skills/workbench-module-diy/SKILL.md +3 -1
- package/skills/workbench-module-diy/TOOLING.md +2 -0
package/dist/host/index.js
CHANGED
|
@@ -11,9 +11,11 @@ var PREFS_PREFIX = `${API_PREFIX}/prefs`;
|
|
|
11
11
|
var EVENTS_PATH = "/workbench/events";
|
|
12
12
|
var ACTIONS_PREFIX = `${API_PREFIX}/actions`;
|
|
13
13
|
var PROVISION_PREFIX = `${API_PREFIX}/provision`;
|
|
14
|
+
var PANEL_STATE_PREFIX = `${API_PREFIX}/panel-state`;
|
|
14
15
|
var OPENCLI_PIN = "1.8.8";
|
|
15
16
|
var PANELS_SERVICE = "workbenchPanels";
|
|
16
17
|
var UNITS_SERVICE = "workbenchUnits";
|
|
18
|
+
var PANEL_STATE_SERVICE = "workbenchPanelState";
|
|
17
19
|
var OPENCLI_TIMEOUT_MS = 12e4;
|
|
18
20
|
var WARM_TIMEOUT_MS = 2e4;
|
|
19
21
|
var INSTALL_TIMEOUT_MS = 3e5;
|
|
@@ -50,6 +52,11 @@ function parseMarkers(text) {
|
|
|
50
52
|
}
|
|
51
53
|
return refs;
|
|
52
54
|
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region ../shared/dist/api/unit.js
|
|
57
|
+
function hasEnvSupply(manifest) {
|
|
58
|
+
return (manifest.env?.plugins ?? []).length > 0 || (manifest.env?.libs ?? []).length > 0;
|
|
59
|
+
}
|
|
53
60
|
var SUPPORTED_API_VERSIONS = [1];
|
|
54
61
|
var DECLARATION_KEYS = [
|
|
55
62
|
"apiVersion",
|
|
@@ -212,6 +219,267 @@ function parseJsonLoose(text) {
|
|
|
212
219
|
}
|
|
213
220
|
}
|
|
214
221
|
}
|
|
222
|
+
var PANEL_STATE_MAX_TEXT = 2e3;
|
|
223
|
+
var PHASES = [
|
|
224
|
+
"idle",
|
|
225
|
+
"loading",
|
|
226
|
+
"ready",
|
|
227
|
+
"error"
|
|
228
|
+
];
|
|
229
|
+
function asRecord$10(value) {
|
|
230
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
231
|
+
}
|
|
232
|
+
function textOf$6(value, max) {
|
|
233
|
+
if (typeof value !== "string") return "";
|
|
234
|
+
const trimmed = value.trim();
|
|
235
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
236
|
+
}
|
|
237
|
+
function atOf(value) {
|
|
238
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
239
|
+
if (value < 0) return null;
|
|
240
|
+
return Math.trunc(value);
|
|
241
|
+
}
|
|
242
|
+
function errorOf(raw) {
|
|
243
|
+
const record = asRecord$10(raw);
|
|
244
|
+
if (record === void 0) return void 0;
|
|
245
|
+
const code = textOf$6(record["code"], 120);
|
|
246
|
+
const message = textOf$6(record["message"], PANEL_STATE_MAX_TEXT);
|
|
247
|
+
if (code === "" && message === "") return void 0;
|
|
248
|
+
return {
|
|
249
|
+
code: code === "" ? "unknown" : code,
|
|
250
|
+
message
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function parseCardsReport(payload) {
|
|
254
|
+
const record = asRecord$10(payload);
|
|
255
|
+
if (record === void 0) return {
|
|
256
|
+
ok: false,
|
|
257
|
+
message: "请求体要是一个对象"
|
|
258
|
+
};
|
|
259
|
+
const workspace = textOf$6(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
260
|
+
if (workspace === "") return {
|
|
261
|
+
ok: false,
|
|
262
|
+
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
263
|
+
};
|
|
264
|
+
const raw = record["cards"];
|
|
265
|
+
if (!Array.isArray(raw)) return {
|
|
266
|
+
ok: false,
|
|
267
|
+
message: "cards 要是一个数组"
|
|
268
|
+
};
|
|
269
|
+
const cards = [];
|
|
270
|
+
for (const item of raw.slice(0, 32)) {
|
|
271
|
+
const card = asRecord$10(item);
|
|
272
|
+
if (card === void 0) continue;
|
|
273
|
+
const unit = textOf$6(card["unit"], 200);
|
|
274
|
+
const route = textOf$6(card["route"], 400);
|
|
275
|
+
const phase = textOf$6(card["phase"], 20);
|
|
276
|
+
const at = atOf(card["at"]);
|
|
277
|
+
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
278
|
+
const error = errorOf(card["error"]);
|
|
279
|
+
cards.push({
|
|
280
|
+
unit,
|
|
281
|
+
route,
|
|
282
|
+
phase,
|
|
283
|
+
at,
|
|
284
|
+
...error === void 0 ? {} : { error }
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (cards.length === 0) return {
|
|
288
|
+
ok: false,
|
|
289
|
+
message: "cards 里没有一条能认的(unit / route / phase / at 都要有)"
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
value: {
|
|
294
|
+
workspace,
|
|
295
|
+
cards
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function parseCrashReport(payload) {
|
|
300
|
+
const record = asRecord$10(payload);
|
|
301
|
+
if (record === void 0) return {
|
|
302
|
+
ok: false,
|
|
303
|
+
message: "请求体要是一个对象"
|
|
304
|
+
};
|
|
305
|
+
const workspace = textOf$6(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
306
|
+
const unit = textOf$6(record["unit"], 200);
|
|
307
|
+
const reason = textOf$6(record["reason"], PANEL_STATE_MAX_TEXT);
|
|
308
|
+
if (workspace === "") return {
|
|
309
|
+
ok: false,
|
|
310
|
+
message: "workspace 不能是空的"
|
|
311
|
+
};
|
|
312
|
+
if (unit === "") return {
|
|
313
|
+
ok: false,
|
|
314
|
+
message: "unit 不能是空的"
|
|
315
|
+
};
|
|
316
|
+
if (reason === "") return {
|
|
317
|
+
ok: false,
|
|
318
|
+
message: "reason 不能是空的(没有原因的崩溃没法查)"
|
|
319
|
+
};
|
|
320
|
+
return {
|
|
321
|
+
ok: true,
|
|
322
|
+
value: {
|
|
323
|
+
workspace,
|
|
324
|
+
unit,
|
|
325
|
+
reason,
|
|
326
|
+
at: atOf(record["at"]) ?? Date.now()
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function createPanelStateStore() {
|
|
331
|
+
const workspaces = /* @__PURE__ */ new Map();
|
|
332
|
+
function trim(entry, max) {
|
|
333
|
+
while (entry.size > max) {
|
|
334
|
+
const oldest = entry.keys().next();
|
|
335
|
+
if (oldest.done === true) return;
|
|
336
|
+
entry.delete(oldest.value);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function workspaceEntryOf(workspace) {
|
|
340
|
+
const existing = workspaces.get(workspace);
|
|
341
|
+
if (existing !== void 0) return existing;
|
|
342
|
+
const created = {
|
|
343
|
+
at: 0,
|
|
344
|
+
units: /* @__PURE__ */ new Map()
|
|
345
|
+
};
|
|
346
|
+
workspaces.set(workspace, created);
|
|
347
|
+
trim(workspaces, 64);
|
|
348
|
+
return created;
|
|
349
|
+
}
|
|
350
|
+
function unitEntryOf(entry, unit) {
|
|
351
|
+
const existing = entry.units.get(unit);
|
|
352
|
+
if (existing !== void 0) return existing;
|
|
353
|
+
const created = {
|
|
354
|
+
at: 0,
|
|
355
|
+
cards: /* @__PURE__ */ new Map()
|
|
356
|
+
};
|
|
357
|
+
entry.units.set(unit, created);
|
|
358
|
+
return created;
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
cards(report) {
|
|
362
|
+
const entry = workspaceEntryOf(report.workspace);
|
|
363
|
+
for (const card of report.cards) {
|
|
364
|
+
const unit = unitEntryOf(entry, card.unit);
|
|
365
|
+
unit.cards.delete(card.route);
|
|
366
|
+
unit.cards.set(card.route, card);
|
|
367
|
+
trim(unit.cards, 12);
|
|
368
|
+
if (card.at > unit.at) unit.at = card.at;
|
|
369
|
+
if (card.at > entry.at) entry.at = card.at;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
crash(report) {
|
|
373
|
+
const entry = workspaceEntryOf(report.workspace);
|
|
374
|
+
const unit = unitEntryOf(entry, report.unit);
|
|
375
|
+
const previous = unit.crash;
|
|
376
|
+
const counted = previous !== void 0 && previous.reason === report.reason && report.at >= previous.at && report.at - previous.at <= 6e5;
|
|
377
|
+
unit.crash = {
|
|
378
|
+
unit: report.unit,
|
|
379
|
+
reason: report.reason,
|
|
380
|
+
at: report.at,
|
|
381
|
+
count: counted ? previous.count + 1 : 1
|
|
382
|
+
};
|
|
383
|
+
if (report.at > unit.at) unit.at = report.at;
|
|
384
|
+
if (report.at > entry.at) entry.at = report.at;
|
|
385
|
+
},
|
|
386
|
+
snapshot(workspace) {
|
|
387
|
+
if (workspace === null) return {
|
|
388
|
+
workspace: null,
|
|
389
|
+
at: 0,
|
|
390
|
+
units: []
|
|
391
|
+
};
|
|
392
|
+
const entry = workspaces.get(workspace);
|
|
393
|
+
if (entry === void 0) return {
|
|
394
|
+
workspace,
|
|
395
|
+
at: 0,
|
|
396
|
+
units: []
|
|
397
|
+
};
|
|
398
|
+
const units = [...entry.units.entries()].map(([unit, one]) => {
|
|
399
|
+
const cards = [...one.cards.values()].sort((a, b) => a.route.localeCompare(b.route));
|
|
400
|
+
const okAt = cards.reduce((best, card) => card.phase === "ready" && (best === null || card.at > best) ? card.at : best, null);
|
|
401
|
+
return {
|
|
402
|
+
unit,
|
|
403
|
+
at: one.at,
|
|
404
|
+
okAt,
|
|
405
|
+
cards,
|
|
406
|
+
...one.crash === void 0 ? {} : { crash: one.crash }
|
|
407
|
+
};
|
|
408
|
+
});
|
|
409
|
+
units.sort((a, b) => b.at - a.at || a.unit.localeCompare(b.unit));
|
|
410
|
+
return {
|
|
411
|
+
workspace,
|
|
412
|
+
at: entry.at,
|
|
413
|
+
units
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region ../shared/dist/host/cli-error.js
|
|
420
|
+
function unquote(raw) {
|
|
421
|
+
const value = raw.trim();
|
|
422
|
+
if (value.length >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
|
|
423
|
+
return value;
|
|
424
|
+
}
|
|
425
|
+
function joinBlock(previous, line, fold) {
|
|
426
|
+
if (previous === "") return line;
|
|
427
|
+
return fold ? `${previous} ${line}` : `${previous}\n${line}`;
|
|
428
|
+
}
|
|
429
|
+
function parseCliErrorEnvelope(text) {
|
|
430
|
+
if (text === void 0 || text.trim() === "") return void 0;
|
|
431
|
+
const lines = text.split("\n");
|
|
432
|
+
const start = lines.findIndex((line) => /^error:\s*$/.test(line.trim()) || /^error:\s*\S/.test(line.trim()));
|
|
433
|
+
if (start === -1) return void 0;
|
|
434
|
+
const scalars = {};
|
|
435
|
+
let current;
|
|
436
|
+
let block;
|
|
437
|
+
const errorIndent = lines[start]?.search(/\S/) ?? 0;
|
|
438
|
+
for (const line of lines.slice(start + 1)) {
|
|
439
|
+
if (line.trim() === "") continue;
|
|
440
|
+
const indent = line.search(/\S/);
|
|
441
|
+
if (block !== void 0 && indent > block.indent) {
|
|
442
|
+
scalars[block.key] = joinBlock(scalars[block.key] ?? "", line.trim(), block.fold);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
block = void 0;
|
|
446
|
+
if (indent <= errorIndent) break;
|
|
447
|
+
const match = /^\s*(code|message|help|exitCode):\s*(.*)$/.exec(line);
|
|
448
|
+
if (match === null) {
|
|
449
|
+
if (current !== void 0) scalars[current] = `${scalars[current] ?? ""} ${line.trim()}`.trim();
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const [, key, value] = match;
|
|
453
|
+
if (key === void 0) continue;
|
|
454
|
+
const raw = (value ?? "").trim();
|
|
455
|
+
const marker = /^([|>])[0-9]*([+-]?)$/.exec(raw);
|
|
456
|
+
if (marker !== null) {
|
|
457
|
+
scalars[key] = "";
|
|
458
|
+
block = {
|
|
459
|
+
key,
|
|
460
|
+
fold: marker[1] === ">",
|
|
461
|
+
indent
|
|
462
|
+
};
|
|
463
|
+
current = key;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
scalars[key] = unquote(raw);
|
|
467
|
+
current = key;
|
|
468
|
+
}
|
|
469
|
+
const code = scalars["code"];
|
|
470
|
+
const message = scalars["message"];
|
|
471
|
+
if (code === void 0 || code === "") return void 0;
|
|
472
|
+
const exitCode = scalars["exitCode"];
|
|
473
|
+
return {
|
|
474
|
+
code,
|
|
475
|
+
message: message ?? code,
|
|
476
|
+
...scalars["help"] === void 0 || scalars["help"] === "" ? {} : { help: scalars["help"] },
|
|
477
|
+
...exitCode === void 0 || Number.isNaN(Number(exitCode)) ? {} : { exitCode: Number(exitCode) }
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function helpUrlOf(envelope) {
|
|
481
|
+
return /https?:\/\/[^\s"'`]+/.exec(envelope.help ?? "")?.[0];
|
|
482
|
+
}
|
|
215
483
|
//#endregion
|
|
216
484
|
//#region ../shared/dist/host/data-root.js
|
|
217
485
|
function dshHome() {
|
|
@@ -282,20 +550,20 @@ var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
|
282
550
|
function asRecord$9(value) {
|
|
283
551
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
284
552
|
}
|
|
285
|
-
function textOf$
|
|
553
|
+
function textOf$5(value) {
|
|
286
554
|
return typeof value === "string" ? value.trim() : "";
|
|
287
555
|
}
|
|
288
556
|
function codeOf(page) {
|
|
289
557
|
const record = asRecord$9(page);
|
|
290
|
-
return record === void 0 ? "" : textOf$
|
|
558
|
+
return record === void 0 ? "" : textOf$5(record["code"]);
|
|
291
559
|
}
|
|
292
|
-
function messageOf$
|
|
560
|
+
function messageOf$2(page) {
|
|
293
561
|
const record = asRecord$9(page);
|
|
294
|
-
return record === void 0 ? "" : textOf$
|
|
562
|
+
return record === void 0 ? "" : textOf$5(record["message"]);
|
|
295
563
|
}
|
|
296
564
|
function hintOf$1(page) {
|
|
297
565
|
const record = asRecord$9(page);
|
|
298
|
-
const hint = record === void 0 ? "" : textOf$
|
|
566
|
+
const hint = record === void 0 ? "" : textOf$5(record["hint"]);
|
|
299
567
|
return hint === "" ? void 0 : hint;
|
|
300
568
|
}
|
|
301
569
|
function pageFailed(page) {
|
|
@@ -484,7 +752,7 @@ async function browserRead(options) {
|
|
|
484
752
|
message: "读到了"
|
|
485
753
|
};
|
|
486
754
|
const pageCode = codeOf(page) || "read-failed";
|
|
487
|
-
const message = messageOf$
|
|
755
|
+
const message = messageOf$2(page) || `读不到(${pageCode})`;
|
|
488
756
|
const blocker = blockerFor(pageCode);
|
|
489
757
|
if (blocker === "auth-required") return {
|
|
490
758
|
ok: false,
|
|
@@ -526,7 +794,7 @@ var DEFAULT_TEXT_MAX = 200;
|
|
|
526
794
|
function asRecord$8(value) {
|
|
527
795
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
528
796
|
}
|
|
529
|
-
function textOf$
|
|
797
|
+
function textOf$4(value) {
|
|
530
798
|
return typeof value === "string" ? value.trim() : "";
|
|
531
799
|
}
|
|
532
800
|
function entriesOf(value) {
|
|
@@ -539,8 +807,8 @@ function entriesOf(value) {
|
|
|
539
807
|
if (item === void 0) continue;
|
|
540
808
|
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
541
809
|
entries.push({
|
|
542
|
-
text: textOf$
|
|
543
|
-
tag: textOf$
|
|
810
|
+
text: textOf$4(item["text"]),
|
|
811
|
+
tag: textOf$4(item["tag"]),
|
|
544
812
|
nth
|
|
545
813
|
});
|
|
546
814
|
}
|
|
@@ -549,7 +817,7 @@ function entriesOf(value) {
|
|
|
549
817
|
function reasonOf(stdout, stderr) {
|
|
550
818
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
551
819
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
552
|
-
const message = error === void 0 ? "" : textOf$
|
|
820
|
+
const message = error === void 0 ? "" : textOf$4(error["message"]);
|
|
553
821
|
if (message !== "") return message;
|
|
554
822
|
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
555
823
|
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
@@ -557,7 +825,7 @@ function reasonOf(stdout, stderr) {
|
|
|
557
825
|
function hintOf(stdout) {
|
|
558
826
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
559
827
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
560
|
-
const hint = error === void 0 ? "" : textOf$
|
|
828
|
+
const hint = error === void 0 ? "" : textOf$4(error["hint"]);
|
|
561
829
|
return hint === "" ? void 0 : hint;
|
|
562
830
|
}
|
|
563
831
|
function sameOrigin(left, right) {
|
|
@@ -571,7 +839,7 @@ function urlOf(stdout) {
|
|
|
571
839
|
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
572
840
|
if (bare.startsWith("{")) {
|
|
573
841
|
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
574
|
-
const url = parsed === void 0 ? "" : textOf$
|
|
842
|
+
const url = parsed === void 0 ? "" : textOf$4(parsed["url"]);
|
|
575
843
|
if (url !== "") return url;
|
|
576
844
|
}
|
|
577
845
|
return bare;
|
|
@@ -796,7 +1064,8 @@ var PROVISION_ACTIONS = [
|
|
|
796
1064
|
"cancel-install",
|
|
797
1065
|
"update-dependency",
|
|
798
1066
|
"update-plugin",
|
|
799
|
-
"uninstall-plugin"
|
|
1067
|
+
"uninstall-plugin",
|
|
1068
|
+
"update-workbench"
|
|
800
1069
|
];
|
|
801
1070
|
var DEPENDENCY_ACTIONS = ["update-dependency"];
|
|
802
1071
|
function isDependencyAction(action) {
|
|
@@ -807,6 +1076,7 @@ function provisionTargetOf(action) {
|
|
|
807
1076
|
if (action === "install-bundle") return "spec";
|
|
808
1077
|
if (action === "remove-bundle") return "name";
|
|
809
1078
|
if (action === "update-plugin" || action === "uninstall-plugin") return "name";
|
|
1079
|
+
if (action === "update-workbench") return "name";
|
|
810
1080
|
if (action === "cancel-install") return "requestId";
|
|
811
1081
|
return "unit";
|
|
812
1082
|
}
|
|
@@ -820,7 +1090,7 @@ var TARGET_CODE = {
|
|
|
820
1090
|
function asRecord$7(value) {
|
|
821
1091
|
return typeof value === "object" && value !== null ? value : {};
|
|
822
1092
|
}
|
|
823
|
-
function stringsOf$
|
|
1093
|
+
function stringsOf$3(value) {
|
|
824
1094
|
if (!Array.isArray(value)) return void 0;
|
|
825
1095
|
const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
|
|
826
1096
|
return items.length === 0 ? void 0 : items;
|
|
@@ -842,6 +1112,10 @@ function requestOf$1(action, value) {
|
|
|
842
1112
|
action,
|
|
843
1113
|
name: value
|
|
844
1114
|
};
|
|
1115
|
+
if (action === "update-workbench") return {
|
|
1116
|
+
action,
|
|
1117
|
+
name: value
|
|
1118
|
+
};
|
|
845
1119
|
if (action === "cancel-install") return {
|
|
846
1120
|
action,
|
|
847
1121
|
requestId: value
|
|
@@ -872,7 +1146,7 @@ function parseProvisionBody(body) {
|
|
|
872
1146
|
request: requestOf$1(wanted, value.trim())
|
|
873
1147
|
};
|
|
874
1148
|
const registry = typeof raw.registry === "string" && raw.registry.trim() !== "" ? raw.registry.trim() : void 0;
|
|
875
|
-
const approvedBuilds = stringsOf$
|
|
1149
|
+
const approvedBuilds = stringsOf$3(raw.approvedBuilds);
|
|
876
1150
|
const requestId = typeof raw.requestId === "string" && raw.requestId.trim() !== "" ? raw.requestId.trim() : void 0;
|
|
877
1151
|
return {
|
|
878
1152
|
ok: true,
|
|
@@ -886,6 +1160,7 @@ function parseProvisionBody(body) {
|
|
|
886
1160
|
}
|
|
887
1161
|
//#endregion
|
|
888
1162
|
//#region ../shared/dist/host/opencli.js
|
|
1163
|
+
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["FORBIDDEN", "PERMISSION_DENIED"]);
|
|
889
1164
|
var SITE_SESSION_ARGS = ["--site-session", "persistent"];
|
|
890
1165
|
var WorkbenchDataError = class extends Error {
|
|
891
1166
|
code;
|
|
@@ -913,6 +1188,102 @@ function opencliCommandLine(command, argv, session = "persistent") {
|
|
|
913
1188
|
"json"
|
|
914
1189
|
].join(" ");
|
|
915
1190
|
}
|
|
1191
|
+
function createOpencli(options) {
|
|
1192
|
+
const { shell, plugin, logger, command = "opencli", timeoutMs = OPENCLI_TIMEOUT_MS, maxConcurrent = 4 } = options;
|
|
1193
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
1194
|
+
const MAX_CONCURRENT = maxConcurrent;
|
|
1195
|
+
let running = 0;
|
|
1196
|
+
const waiting = [];
|
|
1197
|
+
async function acquire() {
|
|
1198
|
+
if (running < MAX_CONCURRENT) {
|
|
1199
|
+
running += 1;
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
await new Promise((resolve) => waiting.push(resolve));
|
|
1203
|
+
}
|
|
1204
|
+
function release() {
|
|
1205
|
+
const next = waiting.shift();
|
|
1206
|
+
if (next === void 0) running -= 1;
|
|
1207
|
+
else next();
|
|
1208
|
+
}
|
|
1209
|
+
async function runLimited(task) {
|
|
1210
|
+
await acquire();
|
|
1211
|
+
try {
|
|
1212
|
+
return await task();
|
|
1213
|
+
} finally {
|
|
1214
|
+
release();
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
async function call(commandLine, workdir) {
|
|
1218
|
+
let result;
|
|
1219
|
+
try {
|
|
1220
|
+
result = await runShell(shell, commandLine, {
|
|
1221
|
+
...workdir === void 0 ? {} : { workdir },
|
|
1222
|
+
timeoutMs
|
|
1223
|
+
});
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
throw new WorkbenchDataError("cli-exec-failed", `执行 opencli 失败:${String(error)}`, { hint: "确认 opencli 已安装且在 PATH 里(which opencli)。" });
|
|
1226
|
+
}
|
|
1227
|
+
if (result.timedOut) throw new WorkbenchDataError("cli-timeout", `opencli 超过 ${Math.round(timeoutMs / 1e3)} 秒没有返回`, { hint: "无头浏览器可能没起来,重试一次通常就好。" });
|
|
1228
|
+
if (result.exitCode !== 0) {
|
|
1229
|
+
const detail = tailLines(result.stderr || result.stdout, 6);
|
|
1230
|
+
const envelope = parseCliErrorEnvelope(result.stderr) ?? parseCliErrorEnvelope(result.stdout);
|
|
1231
|
+
const url = envelope === void 0 ? void 0 : helpUrlOf(envelope);
|
|
1232
|
+
if (envelope?.code === "AUTH_REQUIRED") throw new WorkbenchDataError("auth-required", envelope.message, {
|
|
1233
|
+
detail,
|
|
1234
|
+
...url === void 0 ? {} : { helpUrl: url }
|
|
1235
|
+
});
|
|
1236
|
+
if (envelope !== void 0 && FORBIDDEN_CODES.has(envelope.code)) throw new WorkbenchDataError("permission-denied", envelope.message, {
|
|
1237
|
+
detail,
|
|
1238
|
+
...url === void 0 ? {} : { helpUrl: url }
|
|
1239
|
+
});
|
|
1240
|
+
if (envelope !== void 0) throw new WorkbenchDataError("cli-command-failed", `${envelope.code}:${envelope.message}`, {
|
|
1241
|
+
detail,
|
|
1242
|
+
...envelope.help === void 0 ? {} : { hint: envelope.help }
|
|
1243
|
+
});
|
|
1244
|
+
throw new WorkbenchDataError("cli-unclassified", `opencli 这条命令没跑通(退出码 ${result.exitCode}),而且没有结构化错误码`, {
|
|
1245
|
+
detail,
|
|
1246
|
+
hint: "多半是插件里没有这条命令(版本旧就会缺),也可能是命令自己失败了:先确认命令存在,再更新插件重试。"
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
const parsed = parseJsonLoose(result.stdout);
|
|
1250
|
+
if (parsed === void 0) throw new WorkbenchDataError("cli-bad-output", "opencli 输出不是 JSON", { detail: tailLines(result.stdout, 4) });
|
|
1251
|
+
return parsed;
|
|
1252
|
+
}
|
|
1253
|
+
async function json(args, callOptions = {}) {
|
|
1254
|
+
const { key, workdir } = callOptions;
|
|
1255
|
+
const session = callOptions.session ?? "persistent";
|
|
1256
|
+
const callKey = key ?? args.map((arg) => String(arg)).join(" ");
|
|
1257
|
+
const pending = inflight.get(callKey);
|
|
1258
|
+
if (pending !== void 0) return pending;
|
|
1259
|
+
const commandLine = opencliCommandLine(command, [plugin, ...args], session);
|
|
1260
|
+
const once = async () => {
|
|
1261
|
+
return {
|
|
1262
|
+
value: await call(commandLine, workdir),
|
|
1263
|
+
at: Date.now()
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
const task = runLimited(async () => {
|
|
1267
|
+
try {
|
|
1268
|
+
return await once();
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
if (!(error instanceof WorkbenchDataError) || error.code !== "auth-required") throw error;
|
|
1271
|
+
return await once();
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
inflight.set(callKey, task);
|
|
1275
|
+
try {
|
|
1276
|
+
return await task;
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1279
|
+
logger?.warn?.(`[dsh-workbench] opencli 调用失败:${message}`);
|
|
1280
|
+
throw error;
|
|
1281
|
+
} finally {
|
|
1282
|
+
inflight.delete(callKey);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return { json };
|
|
1286
|
+
}
|
|
916
1287
|
//#endregion
|
|
917
1288
|
//#region ../shared/dist/host/write-result.js
|
|
918
1289
|
function writeResultForClient(value, code) {
|
|
@@ -1235,6 +1606,42 @@ function registerActions(ctx, resources, wrote) {
|
|
|
1235
1606
|
...wrote === void 0 ? {} : { wrote }
|
|
1236
1607
|
});
|
|
1237
1608
|
}
|
|
1609
|
+
function registerPanelState(ctx, store) {
|
|
1610
|
+
return registerPost(ctx, {
|
|
1611
|
+
prefix: PANEL_STATE_PREFIX,
|
|
1612
|
+
what: "面板运行态",
|
|
1613
|
+
refusedCode: "report-refused",
|
|
1614
|
+
resources: [{
|
|
1615
|
+
resource: "cards",
|
|
1616
|
+
handle(payload) {
|
|
1617
|
+
const parsed = parseCardsReport(payload);
|
|
1618
|
+
if (!parsed.ok) return {
|
|
1619
|
+
ok: false,
|
|
1620
|
+
text: parsed.message
|
|
1621
|
+
};
|
|
1622
|
+
store.cards(parsed.value);
|
|
1623
|
+
return {
|
|
1624
|
+
ok: true,
|
|
1625
|
+
at: Date.now()
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
}, {
|
|
1629
|
+
resource: "crash",
|
|
1630
|
+
handle(payload) {
|
|
1631
|
+
const parsed = parseCrashReport(payload);
|
|
1632
|
+
if (!parsed.ok) return {
|
|
1633
|
+
ok: false,
|
|
1634
|
+
text: parsed.message
|
|
1635
|
+
};
|
|
1636
|
+
store.crash(parsed.value);
|
|
1637
|
+
return {
|
|
1638
|
+
ok: true,
|
|
1639
|
+
at: Date.now()
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
}]
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1238
1645
|
//#endregion
|
|
1239
1646
|
//#region ../shared/dist/host/long-task.js
|
|
1240
1647
|
var LIVE = /* @__PURE__ */ new Set(["running", "needs-login"]);
|
|
@@ -1447,7 +1854,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
1447
1854
|
function ledgerKeyOf(entry) {
|
|
1448
1855
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1449
1856
|
}
|
|
1450
|
-
function textOf$
|
|
1857
|
+
function textOf$3(value) {
|
|
1451
1858
|
if (typeof value !== "string") return void 0;
|
|
1452
1859
|
const trimmed = value.trim();
|
|
1453
1860
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -1455,13 +1862,13 @@ function textOf$2(value) {
|
|
|
1455
1862
|
function entryOf(value) {
|
|
1456
1863
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1457
1864
|
const box = value;
|
|
1458
|
-
const opencliHome = textOf$
|
|
1459
|
-
const pkg = textOf$
|
|
1460
|
-
const installedAt = textOf$
|
|
1865
|
+
const opencliHome = textOf$3(box["opencliHome"]);
|
|
1866
|
+
const pkg = textOf$3(box["package"]);
|
|
1867
|
+
const installedAt = textOf$3(box["installedAt"]);
|
|
1461
1868
|
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
1462
|
-
const name = textOf$
|
|
1463
|
-
const registry = textOf$
|
|
1464
|
-
const releasedAt = textOf$
|
|
1869
|
+
const name = textOf$3(box["name"]);
|
|
1870
|
+
const registry = textOf$3(box["registry"]);
|
|
1871
|
+
const releasedAt = textOf$3(box["releasedAt"]);
|
|
1465
1872
|
return {
|
|
1466
1873
|
opencliHome,
|
|
1467
1874
|
package: pkg,
|
|
@@ -1628,15 +2035,48 @@ function resolvePluginSource(source, env, options = {}) {
|
|
|
1628
2035
|
}
|
|
1629
2036
|
//#endregion
|
|
1630
2037
|
//#region ../shared/dist/host/tool-approval.js
|
|
1631
|
-
|
|
2038
|
+
var APPROVAL_NOTE_ARG = "approval_note";
|
|
2039
|
+
var NOTE_SCHEMA = {
|
|
2040
|
+
type: "string",
|
|
2041
|
+
description: "一句话说明你这次想拿它干什么(要点头的那几通会显示在审批屏上)。"
|
|
2042
|
+
};
|
|
2043
|
+
function withApprovalNote(parameters, options = {}) {
|
|
2044
|
+
const properties = parameters["properties"];
|
|
2045
|
+
if (typeof properties !== "object" || properties === null) return parameters;
|
|
2046
|
+
const map = properties;
|
|
2047
|
+
if ("approval_note" in map) return parameters;
|
|
2048
|
+
const kept = (Array.isArray(parameters["required"]) ? parameters["required"] : []).filter((name) => name !== APPROVAL_NOTE_ARG);
|
|
2049
|
+
const required = options.required === true ? [...kept, APPROVAL_NOTE_ARG] : kept;
|
|
2050
|
+
return {
|
|
2051
|
+
...parameters,
|
|
2052
|
+
properties: {
|
|
2053
|
+
...map,
|
|
2054
|
+
[APPROVAL_NOTE_ARG]: NOTE_SCHEMA
|
|
2055
|
+
},
|
|
2056
|
+
...required.length === 0 ? {} : { required }
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function approvalNoteOf(call) {
|
|
2060
|
+
const raw = call.arguments;
|
|
2061
|
+
const value = typeof raw === "object" && raw !== null ? raw[APPROVAL_NOTE_ARG] : void 0;
|
|
2062
|
+
if (typeof value !== "string") return void 0;
|
|
2063
|
+
const line = value.replace(/\s+/g, " ").trim();
|
|
2064
|
+
if (line === "") return void 0;
|
|
2065
|
+
return line.length > 160 ? `${line.slice(0, 160)}…` : line;
|
|
2066
|
+
}
|
|
2067
|
+
function messageOf$1(error) {
|
|
1632
2068
|
return error instanceof Error ? error.message : String(error);
|
|
1633
2069
|
}
|
|
1634
|
-
function compose(detail, verdict) {
|
|
1635
|
-
|
|
1636
|
-
return {
|
|
2070
|
+
function compose(detail, verdict, note) {
|
|
2071
|
+
const base = detail === void 0 || typeof detail.zh !== "string" || detail.zh.trim() === "" ? verdict.fallback : {
|
|
1637
2072
|
en: `${detail.en} — ${verdict.question.en}`,
|
|
1638
2073
|
zh: `${detail.zh} —— ${verdict.question.zh}`
|
|
1639
2074
|
};
|
|
2075
|
+
if (note === void 0) return base;
|
|
2076
|
+
return {
|
|
2077
|
+
en: `${base.en} — ${note}`,
|
|
2078
|
+
zh: `${base.zh} —— ${note}`
|
|
2079
|
+
};
|
|
1640
2080
|
}
|
|
1641
2081
|
async function approvalFor(call, context = {}) {
|
|
1642
2082
|
if (context.writes !== true) return void 0;
|
|
@@ -1646,7 +2086,7 @@ async function approvalFor(call, context = {}) {
|
|
|
1646
2086
|
} catch (error) {
|
|
1647
2087
|
return {
|
|
1648
2088
|
kind: "deny",
|
|
1649
|
-
reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf(error)}`
|
|
2089
|
+
reason: `问"要不要审批"的时候套件的策略抛了错,已经停下:${messageOf$1(error)}`
|
|
1650
2090
|
};
|
|
1651
2091
|
}
|
|
1652
2092
|
if (verdict?.kind === "read") return void 0;
|
|
@@ -1660,7 +2100,7 @@ async function approvalFor(call, context = {}) {
|
|
|
1660
2100
|
} catch (error) {
|
|
1661
2101
|
return {
|
|
1662
2102
|
kind: "deny",
|
|
1663
|
-
reason: `拿不到"这次会发生什么",已经停下:${messageOf(error)}`
|
|
2103
|
+
reason: `拿不到"这次会发生什么",已经停下:${messageOf$1(error)}`
|
|
1664
2104
|
};
|
|
1665
2105
|
}
|
|
1666
2106
|
if (facts !== void 0 && "blocked" in facts) return {
|
|
@@ -1670,7 +2110,7 @@ async function approvalFor(call, context = {}) {
|
|
|
1670
2110
|
return {
|
|
1671
2111
|
kind: "ask",
|
|
1672
2112
|
reason: verdict.reason,
|
|
1673
|
-
displayReason: compose(facts, verdict)
|
|
2113
|
+
displayReason: compose(facts, verdict, approvalNoteOf(call))
|
|
1674
2114
|
};
|
|
1675
2115
|
}
|
|
1676
2116
|
//#endregion
|
|
@@ -1679,6 +2119,15 @@ function sessionWorkspace(exec) {
|
|
|
1679
2119
|
const cwd = exec?.agent?.session?.header?.cwd;
|
|
1680
2120
|
return typeof cwd === "string" && cwd.trim() !== "" ? cwd.trim() : null;
|
|
1681
2121
|
}
|
|
2122
|
+
function textOutput(text) {
|
|
2123
|
+
return [{
|
|
2124
|
+
type: "text",
|
|
2125
|
+
text
|
|
2126
|
+
}];
|
|
2127
|
+
}
|
|
2128
|
+
function jsonText(value) {
|
|
2129
|
+
return JSON.stringify(value);
|
|
2130
|
+
}
|
|
1682
2131
|
//#endregion
|
|
1683
2132
|
//#region ../shared/dist/host/value-handoff.js
|
|
1684
2133
|
function handoffId(entry) {
|
|
@@ -2000,7 +2449,7 @@ function profileDirOf(ctx) {
|
|
|
2000
2449
|
const dir = typeof profile?.dir === "string" ? profile.dir.trim() : "";
|
|
2001
2450
|
return dir === "" ? null : dir;
|
|
2002
2451
|
}
|
|
2003
|
-
function stringsOf$
|
|
2452
|
+
function stringsOf$2(value) {
|
|
2004
2453
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2005
2454
|
}
|
|
2006
2455
|
function profileManifestOf(profileDir) {
|
|
@@ -2014,7 +2463,7 @@ function profileManifestOf(profileDir) {
|
|
|
2014
2463
|
function profileModules(profileDir) {
|
|
2015
2464
|
const record = profileManifestOf(profileDir);
|
|
2016
2465
|
if (record === void 0) return [];
|
|
2017
|
-
const bundles = stringsOf$
|
|
2466
|
+
const bundles = stringsOf$2(asRecord$5(asRecord$5(record.dsh)?.profile)?.bundles);
|
|
2018
2467
|
const dependencies = asRecord$5(record.dependencies) ?? {};
|
|
2019
2468
|
const names = bundles.length > 0 ? bundles : Object.keys(dependencies);
|
|
2020
2469
|
const found = [];
|
|
@@ -2025,11 +2474,27 @@ function profileModules(profileDir) {
|
|
|
2025
2474
|
unit: read.unit,
|
|
2026
2475
|
version: read.version,
|
|
2027
2476
|
...read.registry === void 0 ? {} : { registry: read.registry },
|
|
2028
|
-
local: asText$3(dependencies[read.pkg])
|
|
2477
|
+
local: isLocalDependencySpec(asText$3(dependencies[read.pkg]))
|
|
2029
2478
|
});
|
|
2030
2479
|
}
|
|
2031
2480
|
return found;
|
|
2032
2481
|
}
|
|
2482
|
+
function createDeclarationLookup(profileDir) {
|
|
2483
|
+
let roster;
|
|
2484
|
+
const scan = () => new Map(profileModules(profileDir).map((row) => [row.unit, row.pkg]));
|
|
2485
|
+
return (unit) => {
|
|
2486
|
+
roster ??= scan();
|
|
2487
|
+
let pkg = roster.get(unit);
|
|
2488
|
+
if (pkg === void 0) {
|
|
2489
|
+
roster = scan();
|
|
2490
|
+
pkg = roster.get(unit);
|
|
2491
|
+
}
|
|
2492
|
+
return pkg === void 0 ? void 0 : {
|
|
2493
|
+
pkg,
|
|
2494
|
+
declaration: moduleDeclarationOf(profileDir, pkg)
|
|
2495
|
+
};
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2033
2498
|
function registryFromLockfile(profileDir, pkg) {
|
|
2034
2499
|
let text;
|
|
2035
2500
|
try {
|
|
@@ -2070,6 +2535,20 @@ function declarationGaps(profileDir) {
|
|
|
2070
2535
|
}
|
|
2071
2536
|
return gaps;
|
|
2072
2537
|
}
|
|
2538
|
+
function declaredPluginCommands(profileDir) {
|
|
2539
|
+
const declared = /* @__PURE__ */ new Map();
|
|
2540
|
+
for (const module of profileModules(profileDir)) {
|
|
2541
|
+
const read = readDeclaration(module.pkg, moduleDeclarationOf(profileDir, module.pkg));
|
|
2542
|
+
for (const need of read.manifest?.env?.plugins ?? []) {
|
|
2543
|
+
const name = need.name.trim();
|
|
2544
|
+
if (name === "") continue;
|
|
2545
|
+
const commands = declared.get(name) ?? /* @__PURE__ */ new Set();
|
|
2546
|
+
for (const command of need.commands ?? []) commands.add(command);
|
|
2547
|
+
declared.set(name, commands);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
return declared;
|
|
2551
|
+
}
|
|
2073
2552
|
function readInstalledModule(profileDir, pkg) {
|
|
2074
2553
|
if (profileDir === null || !isPackageName(pkg)) return {
|
|
2075
2554
|
kind: "unknown",
|
|
@@ -2105,6 +2584,32 @@ function readInstalledModule(profileDir, pkg) {
|
|
|
2105
2584
|
...registry === "" ? {} : { registry }
|
|
2106
2585
|
};
|
|
2107
2586
|
}
|
|
2587
|
+
function isLocalDependencySpec(spec) {
|
|
2588
|
+
const trimmed = spec.trim();
|
|
2589
|
+
return trimmed.startsWith("link:") || trimmed.startsWith("file:") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/");
|
|
2590
|
+
}
|
|
2591
|
+
function readProfilePackage(profileDir, pkg) {
|
|
2592
|
+
if (profileDir === null || !isPackageName(pkg)) return void 0;
|
|
2593
|
+
let manifest;
|
|
2594
|
+
try {
|
|
2595
|
+
manifest = JSON.parse(fs.readFileSync(path.join(profileDir, "node_modules", pkg, "package.json"), "utf8"));
|
|
2596
|
+
} catch {
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
const record = asRecord$5(manifest);
|
|
2600
|
+
if (record === void 0) return void 0;
|
|
2601
|
+
const version = asText$3(record.version);
|
|
2602
|
+
if (version === "") return void 0;
|
|
2603
|
+
const declaredRegistry = asText$3(asRecord$5(record.publishConfig)?.registry);
|
|
2604
|
+
const registry = declaredRegistry === "" ? registryFromLockfile(profileDir, pkg) : declaredRegistry;
|
|
2605
|
+
const local = isLocalDependencySpec(asText$3((asRecord$5(profileManifestOf(profileDir)?.dependencies) ?? {})[pkg]));
|
|
2606
|
+
return {
|
|
2607
|
+
pkg,
|
|
2608
|
+
version,
|
|
2609
|
+
...registry === "" ? {} : { registry },
|
|
2610
|
+
...local ? { local: true } : {}
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2108
2613
|
//#endregion
|
|
2109
2614
|
//#region src/shared/api/host/profile-registries.ts
|
|
2110
2615
|
var REGISTRY_MARKER = "# dsh-workbench 写的:这一行的包只在这个源上(dsh 卸载没有源参数,pnpm 得问得到)";
|
|
@@ -2350,6 +2855,13 @@ var panelsApproval = (call) => {
|
|
|
2350
2855
|
zh: `agent 想${verb} opencli 插件 ${what}(会改这台机器上装的东西)。允许吗?`
|
|
2351
2856
|
});
|
|
2352
2857
|
}
|
|
2858
|
+
if (action === "update-workbench") return askOf("workbench_modules update-workbench replaces the workbench itself with the new version from the registry", {
|
|
2859
|
+
en: "it updates the workbench itself on this machine (the panel picks the new version up by itself). Allow?",
|
|
2860
|
+
zh: "会把工作台自己更新到源上的新版(面板会自己换新)。允许吗?"
|
|
2861
|
+
}, {
|
|
2862
|
+
en: "The agent wants to update the workbench itself to the new version from the registry (it changes what is installed on this machine; the panel picks the new version up by itself). Allow?",
|
|
2863
|
+
zh: "agent 想把工作台自己更新到源上的新版(会改这台机器上装的东西,面板会自己换新)。允许吗?"
|
|
2864
|
+
});
|
|
2353
2865
|
if (action === "update-dependency") return askOf(`workbench_modules update-dependency ${id ?? ""} upgrades a shared dependency on this machine`, {
|
|
2354
2866
|
en: "it upgrades that shared dependency on this machine. Allow?",
|
|
2355
2867
|
zh: `会升级共用依赖 ${what}。允许吗?`
|
|
@@ -2423,46 +2935,99 @@ var EMPTY_BOARD = {
|
|
|
2423
2935
|
};
|
|
2424
2936
|
var BOARD_TARGETS = ["workspace", "machine"];
|
|
2425
2937
|
var DEFAULT_BOARD_TARGET = "workspace";
|
|
2426
|
-
function
|
|
2427
|
-
|
|
2428
|
-
|
|
2938
|
+
function overrideOf(board, id) {
|
|
2939
|
+
if (board === null) return void 0;
|
|
2940
|
+
if (board.hidden.includes(id)) return false;
|
|
2941
|
+
if (board.shown.includes(id)) return true;
|
|
2942
|
+
}
|
|
2943
|
+
function defaultOf(board, id) {
|
|
2429
2944
|
if (board === null) return false;
|
|
2430
2945
|
if (board.hidden.includes(id)) return false;
|
|
2431
2946
|
return board.shown.includes(id);
|
|
2432
2947
|
}
|
|
2948
|
+
function resolveScope(query) {
|
|
2949
|
+
const { id, boards } = query;
|
|
2950
|
+
return overrideOf(boards.workspace, id) ?? defaultOf(boards.machine, id);
|
|
2951
|
+
}
|
|
2433
2952
|
function strings(raw) {
|
|
2434
2953
|
return Array.isArray(raw) ? raw.filter((item) => typeof item === "string") : [];
|
|
2435
2954
|
}
|
|
2436
2955
|
function parseBoard(raw) {
|
|
2437
2956
|
if (typeof raw !== "object" || raw === null) return EMPTY_BOARD;
|
|
2438
2957
|
const value = raw;
|
|
2958
|
+
const order = strings(value.order);
|
|
2439
2959
|
return {
|
|
2440
2960
|
shown: strings(value.shown),
|
|
2441
|
-
hidden: strings(value.hidden)
|
|
2961
|
+
hidden: strings(value.hidden),
|
|
2962
|
+
...order.length === 0 ? {} : { order }
|
|
2442
2963
|
};
|
|
2443
2964
|
}
|
|
2444
2965
|
function parseReleased(raw) {
|
|
2445
2966
|
if (typeof raw !== "object" || raw === null) return [];
|
|
2446
2967
|
return strings(raw.ids);
|
|
2447
2968
|
}
|
|
2448
|
-
function resolvePanels(manifests, boards) {
|
|
2449
|
-
const
|
|
2450
|
-
const
|
|
2451
|
-
|
|
2969
|
+
function resolvePanels(manifests, boards, owned = /* @__PURE__ */ new Set()) {
|
|
2970
|
+
const listed = manifests.filter((manifest) => manifest.card !== null || !owned.has(manifest.id));
|
|
2971
|
+
const carded = listed.filter((manifest) => manifest.card !== null);
|
|
2972
|
+
const declared = (manifest) => manifest.card?.order ?? 0;
|
|
2973
|
+
const byDeclared = (a, b) => declared(a) - declared(b) || a.id.localeCompare(b.id);
|
|
2974
|
+
const queued = [...new Set(boards.workspace?.order ?? [])].map((id) => carded.find((manifest) => manifest.id === id)).filter((manifest) => manifest !== void 0).filter((manifest) => resolveScope({
|
|
2975
|
+
id: manifest.id,
|
|
2976
|
+
boards
|
|
2977
|
+
}));
|
|
2978
|
+
const placed = new Set(queued.map((manifest) => manifest.id));
|
|
2979
|
+
const ordered = [...queued, ...carded.filter((manifest) => !placed.has(manifest.id) && resolveScope({
|
|
2980
|
+
id: manifest.id,
|
|
2981
|
+
boards
|
|
2982
|
+
})).sort(byDeclared)];
|
|
2983
|
+
const rank = new Map(ordered.map((manifest, index) => [manifest.id, index]));
|
|
2984
|
+
return listed.map((manifest) => {
|
|
2985
|
+
const hasCard = manifest.card !== null;
|
|
2452
2986
|
const on = resolveScope({
|
|
2453
2987
|
id: manifest.id,
|
|
2454
2988
|
boards
|
|
2455
2989
|
});
|
|
2456
|
-
const order = rank.get(manifest.id) ?? ordered.length + (manifest
|
|
2990
|
+
const order = hasCard ? rank.get(manifest.id) ?? ordered.length + declared(manifest) : 0;
|
|
2457
2991
|
return {
|
|
2458
2992
|
id: manifest.id,
|
|
2459
2993
|
titleKey: manifest.card?.titleKey ?? manifest.id,
|
|
2460
2994
|
order,
|
|
2461
2995
|
shown: on,
|
|
2462
|
-
source:
|
|
2463
|
-
hasCard
|
|
2996
|
+
source: overrideOf(boards.workspace, manifest.id) === void 0 ? "default" : "user",
|
|
2997
|
+
hasCard
|
|
2998
|
+
};
|
|
2999
|
+
}).sort((a, b) => {
|
|
3000
|
+
if (a.hasCard !== b.hasCard) return a.hasCard ? -1 : 1;
|
|
3001
|
+
return a.order - b.order || a.id.localeCompare(b.id);
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
var PLUGIN_PANEL_PREFIX = "plugin:";
|
|
3005
|
+
function pluginPanelId(pkg) {
|
|
3006
|
+
return `${PLUGIN_PANEL_PREFIX}${pkg}`;
|
|
3007
|
+
}
|
|
3008
|
+
function resolvePluginPanels(plugins, boards) {
|
|
3009
|
+
const ordered = [...new Set(boards.workspace?.order ?? [])];
|
|
3010
|
+
const rank = new Map(ordered.map((id, index) => [id, index]));
|
|
3011
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3012
|
+
return plugins.map((plugin) => {
|
|
3013
|
+
const id = pluginPanelId(plugin.pkg);
|
|
3014
|
+
return {
|
|
3015
|
+
id,
|
|
3016
|
+
pkg: plugin.pkg,
|
|
3017
|
+
title: plugin.title,
|
|
3018
|
+
...plugin.name === void 0 ? {} : { name: plugin.name },
|
|
3019
|
+
order: rank.get(id) ?? ordered.length,
|
|
3020
|
+
shown: resolveScope({
|
|
3021
|
+
id,
|
|
3022
|
+
boards
|
|
3023
|
+
}),
|
|
3024
|
+
source: overrideOf(boards.workspace, id) === void 0 ? "default" : "user"
|
|
2464
3025
|
};
|
|
2465
|
-
}).
|
|
3026
|
+
}).filter((entry) => {
|
|
3027
|
+
if (seen.has(entry.id)) return false;
|
|
3028
|
+
seen.add(entry.id);
|
|
3029
|
+
return true;
|
|
3030
|
+
}).sort((a, b) => a.order - b.order || a.pkg.localeCompare(b.pkg));
|
|
2466
3031
|
}
|
|
2467
3032
|
function withoutReleased(released, id) {
|
|
2468
3033
|
return released.filter((item) => item !== id);
|
|
@@ -2476,16 +3041,22 @@ function added(list, id) {
|
|
|
2476
3041
|
function dropped(list, id) {
|
|
2477
3042
|
return list.filter((item) => item !== id);
|
|
2478
3043
|
}
|
|
2479
|
-
function withShown(board, id) {
|
|
2480
|
-
|
|
3044
|
+
function withShown(board, id, visible) {
|
|
3045
|
+
const next = {
|
|
2481
3046
|
shown: added(board.shown, id),
|
|
2482
3047
|
hidden: dropped(board.hidden, id)
|
|
2483
3048
|
};
|
|
3049
|
+
return visible === void 0 ? next : {
|
|
3050
|
+
...next,
|
|
3051
|
+
order: added(board.order ?? visible, id)
|
|
3052
|
+
};
|
|
2484
3053
|
}
|
|
2485
3054
|
function withHidden(board, id) {
|
|
3055
|
+
const order = dropped(board.order ?? [], id);
|
|
2486
3056
|
return {
|
|
2487
3057
|
shown: dropped(board.shown, id),
|
|
2488
|
-
hidden: added(board.hidden, id)
|
|
3058
|
+
hidden: added(board.hidden, id),
|
|
3059
|
+
...order.length === 0 ? {} : { order }
|
|
2489
3060
|
};
|
|
2490
3061
|
}
|
|
2491
3062
|
function withoutPanel(board, id) {
|
|
@@ -2494,120 +3065,64 @@ function withoutPanel(board, id) {
|
|
|
2494
3065
|
hidden: dropped(board.hidden, id)
|
|
2495
3066
|
};
|
|
2496
3067
|
}
|
|
2497
|
-
function withMoved(board, id, index) {
|
|
2498
|
-
const current = [...
|
|
3068
|
+
function withMoved(board, id, index, visible) {
|
|
3069
|
+
const current = visible.includes(id) ? [...visible] : [...visible, id];
|
|
2499
3070
|
const from = current.indexOf(id);
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
if (from === -1) current.splice(to, 0, id);
|
|
2503
|
-
else if (from !== to) current.splice(to, 0, ...current.splice(from, 1));
|
|
3071
|
+
const to = Math.max(0, Math.min(current.length - 1, Math.trunc(index)));
|
|
3072
|
+
if (from !== to) current.splice(to, 0, ...current.splice(from, 1));
|
|
2504
3073
|
return {
|
|
2505
|
-
shown:
|
|
2506
|
-
hidden: dropped(board.hidden, id)
|
|
3074
|
+
shown: added(board.shown, id),
|
|
3075
|
+
hidden: dropped(board.hidden, id),
|
|
3076
|
+
order: current
|
|
2507
3077
|
};
|
|
2508
3078
|
}
|
|
2509
3079
|
//#endregion
|
|
2510
|
-
//#region src/shared/api/host/
|
|
2511
|
-
|
|
3080
|
+
//#region src/shared/api/host/opencli-command.ts
|
|
3081
|
+
var OPENCLI_PATH_FALLBACK = "opencli";
|
|
3082
|
+
function isolatedOpencliPathOf(ctx) {
|
|
3083
|
+
const homePath = ctx.get("dshHomePath");
|
|
3084
|
+
if (typeof homePath !== "function") return null;
|
|
2512
3085
|
try {
|
|
2513
|
-
|
|
2514
|
-
return (typeof manifest.exports === "object" && manifest.exports !== null ? Object.values(manifest.exports) : []).filter((one) => typeof one === "string");
|
|
3086
|
+
return join(homePath("workbench/opencli/bin/opencli"));
|
|
2515
3087
|
} catch {
|
|
2516
|
-
return
|
|
3088
|
+
return null;
|
|
2517
3089
|
}
|
|
2518
3090
|
}
|
|
2519
|
-
function
|
|
3091
|
+
function opencliCommandOf(ctx) {
|
|
3092
|
+
const isolated = isolatedOpencliPathOf(ctx);
|
|
3093
|
+
const fromEnv = process.env.DSH_WORKBENCH_OPENCLI;
|
|
3094
|
+
if (fromEnv !== void 0 && fromEnv !== "") return {
|
|
3095
|
+
command: fromEnv,
|
|
3096
|
+
source: "env",
|
|
3097
|
+
isolated
|
|
3098
|
+
};
|
|
3099
|
+
if (isolated !== null && existsSync(isolated)) return {
|
|
3100
|
+
command: isolated,
|
|
3101
|
+
source: "isolated",
|
|
3102
|
+
isolated
|
|
3103
|
+
};
|
|
3104
|
+
return {
|
|
3105
|
+
command: OPENCLI_PATH_FALLBACK,
|
|
3106
|
+
source: "path",
|
|
3107
|
+
isolated
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
//#endregion
|
|
3111
|
+
//#region src/shared/api/host/diy-recon.ts
|
|
3112
|
+
var SITE_LIMIT = 6;
|
|
3113
|
+
var COMMAND_LIMIT = 12;
|
|
3114
|
+
var SHOWN_LIMIT = 6;
|
|
3115
|
+
var PLUGIN_PREFIX = "opencli-plugin-";
|
|
3116
|
+
function text(value) {
|
|
3117
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3118
|
+
}
|
|
3119
|
+
function stringsOf$1(value) {
|
|
3120
|
+
return Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
3121
|
+
}
|
|
3122
|
+
function manifestCommandsOf(raw) {
|
|
3123
|
+
let parsed;
|
|
2520
3124
|
try {
|
|
2521
|
-
|
|
2522
|
-
} catch {
|
|
2523
|
-
return;
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
function newestMtime(dir) {
|
|
2527
|
-
let newest;
|
|
2528
|
-
const walk = (at) => {
|
|
2529
|
-
let entries;
|
|
2530
|
-
try {
|
|
2531
|
-
entries = readdirSync(at, { withFileTypes: true });
|
|
2532
|
-
} catch {
|
|
2533
|
-
return;
|
|
2534
|
-
}
|
|
2535
|
-
for (const entry of entries) {
|
|
2536
|
-
const full = join(at, entry.name);
|
|
2537
|
-
if (entry.isDirectory()) walk(full);
|
|
2538
|
-
else if (entry.isFile()) {
|
|
2539
|
-
const stamp = mtimeOf(full);
|
|
2540
|
-
if (stamp !== void 0 && (newest === void 0 || stamp > newest)) newest = stamp;
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
};
|
|
2544
|
-
walk(dir);
|
|
2545
|
-
return newest;
|
|
2546
|
-
}
|
|
2547
|
-
function moduleBuildFacts(dir) {
|
|
2548
|
-
const artifacts = asExported(dir).map((rel) => ({
|
|
2549
|
-
path: rel,
|
|
2550
|
-
mtime: mtimeOf(join(dir, rel))
|
|
2551
|
-
}));
|
|
2552
|
-
const present = artifacts.filter((one) => one.mtime !== void 0);
|
|
2553
|
-
const oldestBuilt = present.reduce((min, one) => min === void 0 || one.mtime < min ? one.mtime : min, void 0);
|
|
2554
|
-
const newestSource = newestMtime(join(dir, "src"));
|
|
2555
|
-
return {
|
|
2556
|
-
artifacts: artifacts.map((one) => ({
|
|
2557
|
-
path: one.path,
|
|
2558
|
-
at: one.mtime === void 0 ? null : new Date(one.mtime).toISOString()
|
|
2559
|
-
})),
|
|
2560
|
-
builtAll: present.length === artifacts.length && artifacts.length > 0,
|
|
2561
|
-
stale: newestSource !== void 0 && oldestBuilt !== void 0 && newestSource > oldestBuilt
|
|
2562
|
-
};
|
|
2563
|
-
}
|
|
2564
|
-
//#endregion
|
|
2565
|
-
//#region src/shared/api/host/opencli-command.ts
|
|
2566
|
-
var OPENCLI_PATH_FALLBACK = "opencli";
|
|
2567
|
-
function isolatedOpencliPathOf(ctx) {
|
|
2568
|
-
const homePath = ctx.get("dshHomePath");
|
|
2569
|
-
if (typeof homePath !== "function") return null;
|
|
2570
|
-
try {
|
|
2571
|
-
return join(homePath("workbench/opencli/bin/opencli"));
|
|
2572
|
-
} catch {
|
|
2573
|
-
return null;
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
function opencliCommandOf(ctx) {
|
|
2577
|
-
const isolated = isolatedOpencliPathOf(ctx);
|
|
2578
|
-
const fromEnv = process.env.DSH_WORKBENCH_OPENCLI;
|
|
2579
|
-
if (fromEnv !== void 0 && fromEnv !== "") return {
|
|
2580
|
-
command: fromEnv,
|
|
2581
|
-
source: "env",
|
|
2582
|
-
isolated
|
|
2583
|
-
};
|
|
2584
|
-
if (isolated !== null && existsSync(isolated)) return {
|
|
2585
|
-
command: isolated,
|
|
2586
|
-
source: "isolated",
|
|
2587
|
-
isolated
|
|
2588
|
-
};
|
|
2589
|
-
return {
|
|
2590
|
-
command: OPENCLI_PATH_FALLBACK,
|
|
2591
|
-
source: "path",
|
|
2592
|
-
isolated
|
|
2593
|
-
};
|
|
2594
|
-
}
|
|
2595
|
-
//#endregion
|
|
2596
|
-
//#region src/shared/api/host/diy-recon.ts
|
|
2597
|
-
var SITE_LIMIT = 6;
|
|
2598
|
-
var COMMAND_LIMIT = 12;
|
|
2599
|
-
var SHOWN_LIMIT = 6;
|
|
2600
|
-
var PLUGIN_PREFIX = "opencli-plugin-";
|
|
2601
|
-
function text(value) {
|
|
2602
|
-
return typeof value === "string" ? value.trim() : "";
|
|
2603
|
-
}
|
|
2604
|
-
function stringsOf(value) {
|
|
2605
|
-
return Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
2606
|
-
}
|
|
2607
|
-
function manifestCommandsOf(raw) {
|
|
2608
|
-
let parsed;
|
|
2609
|
-
try {
|
|
2610
|
-
parsed = JSON.parse(raw);
|
|
3125
|
+
parsed = JSON.parse(raw);
|
|
2611
3126
|
} catch {
|
|
2612
3127
|
return [];
|
|
2613
3128
|
}
|
|
@@ -2632,7 +3147,7 @@ function manifestCommandsOf(raw) {
|
|
|
2632
3147
|
strategy: text(row["strategy"]).toLowerCase(),
|
|
2633
3148
|
browser: row["browser"] === true,
|
|
2634
3149
|
example: text(row["example"]),
|
|
2635
|
-
columns: stringsOf(row["columns"])
|
|
3150
|
+
columns: stringsOf$1(row["columns"])
|
|
2636
3151
|
});
|
|
2637
3152
|
}
|
|
2638
3153
|
return commands;
|
|
@@ -2724,7 +3239,7 @@ function reconOf(commands, plugins, keywords) {
|
|
|
2724
3239
|
};
|
|
2725
3240
|
return {
|
|
2726
3241
|
...withNext,
|
|
2727
|
-
text: textOf$
|
|
3242
|
+
text: textOf$2(withNext)
|
|
2728
3243
|
};
|
|
2729
3244
|
}
|
|
2730
3245
|
function nextOf(answer) {
|
|
@@ -2737,7 +3252,7 @@ function nextOf(answer) {
|
|
|
2737
3252
|
if (plugin !== void 0) return `这台机器另装的插件 ${plugin.name} 里有匹配:opencli ${plugin.name} --help 看全部命令`;
|
|
2738
3253
|
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:workbench_diy action=probe(script 读接口 / css 读 DOM)";
|
|
2739
3254
|
}
|
|
2740
|
-
function textOf$
|
|
3255
|
+
function textOf$2(answer) {
|
|
2741
3256
|
const lines = [];
|
|
2742
3257
|
if (answer.sites.length > 0) {
|
|
2743
3258
|
lines.push(`opencli 自带这 ${String(answer.sites.length)} 组能力(不用装、不用点头):`);
|
|
@@ -2781,10 +3296,169 @@ function installedPluginsOf(dir = vendorPluginsDir()) {
|
|
|
2781
3296
|
}));
|
|
2782
3297
|
}
|
|
2783
3298
|
//#endregion
|
|
3299
|
+
//#region src/shared/api/host/diy-facts.ts
|
|
3300
|
+
function asExported(dir) {
|
|
3301
|
+
try {
|
|
3302
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
3303
|
+
return (typeof manifest.exports === "object" && manifest.exports !== null ? Object.values(manifest.exports) : []).filter((one) => typeof one === "string");
|
|
3304
|
+
} catch {
|
|
3305
|
+
return [];
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
function mtimeOf(file) {
|
|
3309
|
+
try {
|
|
3310
|
+
return statSync(file).mtimeMs;
|
|
3311
|
+
} catch {
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
function newestMtime(dir) {
|
|
3316
|
+
let newest;
|
|
3317
|
+
const walk = (at) => {
|
|
3318
|
+
let entries;
|
|
3319
|
+
try {
|
|
3320
|
+
entries = readdirSync(at, { withFileTypes: true });
|
|
3321
|
+
} catch {
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
for (const entry of entries) {
|
|
3325
|
+
const full = join(at, entry.name);
|
|
3326
|
+
if (entry.isDirectory()) walk(full);
|
|
3327
|
+
else if (entry.isFile()) {
|
|
3328
|
+
const stamp = mtimeOf(full);
|
|
3329
|
+
if (stamp !== void 0 && (newest === void 0 || stamp > newest)) newest = stamp;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
};
|
|
3333
|
+
walk(dir);
|
|
3334
|
+
return newest;
|
|
3335
|
+
}
|
|
3336
|
+
function moduleBuildFacts(dir) {
|
|
3337
|
+
const artifacts = asExported(dir).map((rel) => ({
|
|
3338
|
+
path: rel,
|
|
3339
|
+
mtime: mtimeOf(join(dir, rel))
|
|
3340
|
+
}));
|
|
3341
|
+
const present = artifacts.filter((one) => one.mtime !== void 0);
|
|
3342
|
+
const oldestBuilt = present.reduce((min, one) => min === void 0 || one.mtime < min ? one.mtime : min, void 0);
|
|
3343
|
+
const newestSource = newestMtime(join(dir, "src"));
|
|
3344
|
+
return {
|
|
3345
|
+
artifacts: artifacts.map((one) => ({
|
|
3346
|
+
path: one.path,
|
|
3347
|
+
at: one.mtime === void 0 ? null : new Date(one.mtime).toISOString()
|
|
3348
|
+
})),
|
|
3349
|
+
builtAll: present.length === artifacts.length && artifacts.length > 0,
|
|
3350
|
+
stale: newestSource !== void 0 && oldestBuilt !== void 0 && newestSource > oldestBuilt
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
//#endregion
|
|
3354
|
+
//#region src/modules/panels/model/diy-report.ts
|
|
3355
|
+
function healthOf(options) {
|
|
3356
|
+
const { registry, unit, installed } = options;
|
|
3357
|
+
const refused = registry?.refusals().find((one) => one.unit === unit);
|
|
3358
|
+
if (refused !== void 0) return {
|
|
3359
|
+
state: "failed",
|
|
3360
|
+
reason: refused.reasons.join(";")
|
|
3361
|
+
};
|
|
3362
|
+
const failed = registry?.failureOf(unit);
|
|
3363
|
+
if (failed !== void 0) return {
|
|
3364
|
+
state: "failed",
|
|
3365
|
+
reason: failed
|
|
3366
|
+
};
|
|
3367
|
+
if (registry?.all().some((one) => one.unit === unit) === true) return { state: "ok" };
|
|
3368
|
+
return {
|
|
3369
|
+
state: "absent",
|
|
3370
|
+
reason: installed ? "工作台还没读到它的声明 —— 新装的模块要重启一次工作台" : "还没装"
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
function healthText(health) {
|
|
3374
|
+
if (health.state === "ok") return "是";
|
|
3375
|
+
if (health.state === "failed") return `不是 —— ${health.reason ?? "(没给原因)"}`;
|
|
3376
|
+
return `还没有(${health.reason ?? "还不知道"})`;
|
|
3377
|
+
}
|
|
3378
|
+
function ageText(at, now) {
|
|
3379
|
+
const seconds = Math.max(0, Math.round((now - at) / 1e3));
|
|
3380
|
+
if (seconds < 10) return "刚刚";
|
|
3381
|
+
if (seconds < 60) return `${String(seconds)} 秒前`;
|
|
3382
|
+
const minutes = Math.round(seconds / 60);
|
|
3383
|
+
if (minutes < 60) return `${String(minutes)} 分钟前`;
|
|
3384
|
+
return `${String(Math.round(minutes / 60))} 小时前`;
|
|
3385
|
+
}
|
|
3386
|
+
function stampOf(at) {
|
|
3387
|
+
return `${new Date(at).toISOString().slice(11, 19)}Z`;
|
|
3388
|
+
}
|
|
3389
|
+
function readText$1(read, now) {
|
|
3390
|
+
const when = `${stampOf(read.at)} · ${ageText(read.at, now)}`;
|
|
3391
|
+
if (read.phase === "ready") return `${read.route} 读到了(${when})`;
|
|
3392
|
+
if (read.phase === "loading") return `${read.route} 正在读(${when})`;
|
|
3393
|
+
if (read.phase === "idle") return `${read.route} 没在取数(${when})`;
|
|
3394
|
+
const code = read.error?.code ?? "unknown";
|
|
3395
|
+
const message = read.error?.message ?? "";
|
|
3396
|
+
return `${read.route} 读不到(${code})${message === "" ? "" : `:${message}`}(${when})`;
|
|
3397
|
+
}
|
|
3398
|
+
function renderPanel(options) {
|
|
3399
|
+
const { packagesDir, workspace, rows, board, at, now } = options;
|
|
3400
|
+
const head = `工坊:${packagesDir}\n这个工作区:${workspace ?? "(会话不在任何工作区上)"}`;
|
|
3401
|
+
const note = at === 0 ? "\n还没有一条面板运行态上报 —— 面板上一个取数 / 渲染都没发生过:要么这个工作区的面板上没开卡片,要么用户那一页还是刷新前的旧版本(上报要页面刷一次才带上)。" : `\n面板上有 ${String(board.length)} 张卡|运行态来自用户那一页(最后一次上报 ${stampOf(at)} · ${ageText(at, now)})`;
|
|
3402
|
+
const body = rows.map((row) => {
|
|
3403
|
+
const title = `—— ${row.unit}${row.name === "" ? "" : `(${row.name})`} · ${healthText(row.health)}`;
|
|
3404
|
+
const report = row.report;
|
|
3405
|
+
if (report === void 0) return [title, ` 没上报:卡片没渲染出来,或者用户那一页还没刷新到带这份补丁的版本`].join("\n");
|
|
3406
|
+
const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText$1(one, now)}`);
|
|
3407
|
+
const more = report.cards.length > 6 ? [` 数据 · …还有 ${String(report.cards.length - 6)} 条`] : [];
|
|
3408
|
+
const crash = report.crash === void 0 ? " 崩过:没有" : ` 崩过:是 —— ${report.crash.reason}(${stampOf(report.crash.at)} · 共 ${String(report.crash.count)} 次)`;
|
|
3409
|
+
return [
|
|
3410
|
+
title,
|
|
3411
|
+
...reads,
|
|
3412
|
+
...more,
|
|
3413
|
+
crash
|
|
3414
|
+
].join("\n");
|
|
3415
|
+
});
|
|
3416
|
+
return body.length === 0 ? `${head}${note}` : `${head}${note}\n${body.join("\n")}`;
|
|
3417
|
+
}
|
|
3418
|
+
function statusOf(options) {
|
|
3419
|
+
const { dir, unit, name, installed, known, open, health } = options;
|
|
3420
|
+
const facts = moduleBuildFacts(dir);
|
|
3421
|
+
const next = health.state === "failed" ? `它起不来:${health.reason ?? "(没给原因)"} —— 先把这一条修掉(改模块源码 → build;改了 package.json / cordis.patch.yml 要重启工作台),别去重装` : !installed ? "装:action=install(第一次,会让用户点头一次)" : !known ? "面板还不认得这个单元 —— 重启一次工作台(新装的模块要重启才注册进面板),重启后再 action=open" : !open ? "上板:action=open(只上板,不重装)" : !facts.builtAll ? "还没构建:pnpm exec dsh-wb-module build" : facts.stale ? "源码比产物新:pnpm exec dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新页面)" : "都在生效:改源码 → build(宿主侧秒级热换;改了 client.js 刷新页面),不要再装一遍";
|
|
3422
|
+
return {
|
|
3423
|
+
unit,
|
|
3424
|
+
name,
|
|
3425
|
+
dir,
|
|
3426
|
+
installed,
|
|
3427
|
+
known,
|
|
3428
|
+
open,
|
|
3429
|
+
artifacts: facts.artifacts,
|
|
3430
|
+
stale: facts.stale,
|
|
3431
|
+
health,
|
|
3432
|
+
next
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
function renderStatus(packagesDir, lines) {
|
|
3436
|
+
if (lines.length === 0) return renderList(packagesDir, []);
|
|
3437
|
+
return `工坊:${packagesDir}\n${lines.map((one) => {
|
|
3438
|
+
const head = `—— ${one.unit}(${one.name})`;
|
|
3439
|
+
const facts = ` 装:${one.installed ? "是" : "没有"} · 面板认得:${one.known ? "是" : "不是"} · 这个项目上开着:${one.open ? "是" : "没有"}`;
|
|
3440
|
+
const artifacts = one.artifacts.length === 0 ? " 产物:没有" : ` 产物:${one.artifacts.map((item) => `${item.path}${item.at === null ? "(缺)" : ` ${item.at.slice(11, 19)}Z`}`).join(" · ")} · 源码比产物新:${one.stale ? "是" : "没有"}`;
|
|
3441
|
+
return [
|
|
3442
|
+
head,
|
|
3443
|
+
facts,
|
|
3444
|
+
` 起得来:${healthText(one.health)}`,
|
|
3445
|
+
artifacts,
|
|
3446
|
+
` 下一步:${one.next}`
|
|
3447
|
+
].join("\n");
|
|
3448
|
+
}).join("\n")}`;
|
|
3449
|
+
}
|
|
3450
|
+
function renderList(packagesDir, modules) {
|
|
3451
|
+
if (modules.length === 0) return `工坊里还没有模块(${packagesDir})—— 在里面造一个:pnpm exec dsh-wb-module init <名字> --dir <工坊>/packages/module-<名字> --template diy`;
|
|
3452
|
+
const lines = modules.map((one) => `${one.unit} · ${one.installed ? "已装" : "没装"} · ${one.name}` + (one.description === "" ? "" : `\n${" ".repeat(one.unit.length)} ${one.description}`));
|
|
3453
|
+
const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:workbench_diy(action=install, unit=<上面那个 unit>)";
|
|
3454
|
+
return `工坊:${packagesDir}\n${lines.join("\n")}\n\n${footer}`;
|
|
3455
|
+
}
|
|
3456
|
+
//#endregion
|
|
2784
3457
|
//#region src/modules/panels/model/diy-tools.ts
|
|
2785
|
-
var ACTIONS$
|
|
3458
|
+
var ACTIONS$2 = [
|
|
2786
3459
|
"list",
|
|
2787
3460
|
"status",
|
|
3461
|
+
"panel",
|
|
2788
3462
|
"probe",
|
|
2789
3463
|
"recon",
|
|
2790
3464
|
"install",
|
|
@@ -2840,33 +3514,6 @@ function verdictOf$1(change) {
|
|
|
2840
3514
|
const tail = (change.packageResult?.output ?? "").trim().split("\n").slice(-3).join(" | ");
|
|
2841
3515
|
return `没装上(${change.errorCode ?? "failed"})${tail === "" ? "" : `:${tail}`}`;
|
|
2842
3516
|
}
|
|
2843
|
-
function statusOf(options) {
|
|
2844
|
-
const { dir, unit, name, installed, known, open } = options;
|
|
2845
|
-
const facts = moduleBuildFacts(dir);
|
|
2846
|
-
const next = !installed ? "装:action=install(第一次,会让用户点头一次)" : !known ? "面板还不认得这个单元 —— 重启一次工作台(新装的模块要重启才注册进面板),重启后再 action=open" : !open ? "上板:action=open(只上板,不重装)" : !facts.builtAll ? "还没构建:pnpm exec dsh-wb-module build" : facts.stale ? "源码比产物新:pnpm exec dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新页面)" : "都在生效:改源码 → build(宿主侧秒级热换;改了 client.js 刷新页面),不要再装一遍";
|
|
2847
|
-
return {
|
|
2848
|
-
unit,
|
|
2849
|
-
name,
|
|
2850
|
-
dir,
|
|
2851
|
-
installed,
|
|
2852
|
-
known,
|
|
2853
|
-
open,
|
|
2854
|
-
artifacts: facts.artifacts,
|
|
2855
|
-
stale: facts.stale,
|
|
2856
|
-
next
|
|
2857
|
-
};
|
|
2858
|
-
}
|
|
2859
|
-
function renderStatus(packagesDir, lines) {
|
|
2860
|
-
if (lines.length === 0) return renderList(packagesDir, []);
|
|
2861
|
-
return `工坊:${packagesDir}\n${lines.map((one) => {
|
|
2862
|
-
return [
|
|
2863
|
-
`—— ${one.unit}(${one.name})`,
|
|
2864
|
-
` 装:${one.installed ? "是" : "没有"} · 面板认得:${one.known ? "是" : "不是"} · 这个项目上开着:${one.open ? "是" : "没有"}`,
|
|
2865
|
-
one.artifacts.length === 0 ? " 产物:没有" : ` 产物:${one.artifacts.map((item) => `${item.path}${item.at === null ? "(缺)" : ` ${item.at.slice(11, 19)}Z`}`).join(" · ")} · 源码比产物新:${one.stale ? "是" : "没有"}`,
|
|
2866
|
-
` 下一步:${one.next}`
|
|
2867
|
-
].join("\n");
|
|
2868
|
-
}).join("\n")}`;
|
|
2869
|
-
}
|
|
2870
3517
|
function renderProbe(out) {
|
|
2871
3518
|
if (out.ok) return `读到了 —— 这就是那条读的返回值,把它固化成模块宿主半侧的那一条:\n${(JSON.stringify(out.value, null, 2) ?? "undefined").slice(0, 2e3)}`;
|
|
2872
3519
|
const lines = [`没读到(${out.code}):${out.message}`];
|
|
@@ -2887,12 +3534,6 @@ function renderFind(out) {
|
|
|
2887
3534
|
const more = out.entries.length > 20 ? `\n… 还有 ${String(out.entries.length - 20)} 个` : "";
|
|
2888
3535
|
return `找到了 ${String(out.count)} 个元素 —— 每行的单元格按换行拼在 text 里;把它固化成模块宿主半侧的那一条(host 的 browserFind + 切格 + 按列序取值):\n${head.join("\n")}${more}`;
|
|
2889
3536
|
}
|
|
2890
|
-
function renderList(packagesDir, modules) {
|
|
2891
|
-
if (modules.length === 0) return `工坊里还没有模块(${packagesDir})—— 在里面造一个:pnpm exec dsh-wb-module init <名字> --dir <工坊>/packages/module-<名字> --template diy`;
|
|
2892
|
-
const lines = modules.map((one) => `${one.unit} · ${one.installed ? "已装" : "没装"} · ${one.name}` + (one.description === "" ? "" : `\n${" ".repeat(one.unit.length)} ${one.description}`));
|
|
2893
|
-
const footer = modules.filter((one) => !one.installed).length === 0 ? "都已装过 —— **不要再装**:改源码 + dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面)。" : "装:workbench_diy(action=install, unit=<上面那个 unit>)";
|
|
2894
|
-
return `工坊:${packagesDir}\n${lines.join("\n")}\n\n${footer}`;
|
|
2895
|
-
}
|
|
2896
3537
|
function showOrReason(panels, unit, workspace) {
|
|
2897
3538
|
try {
|
|
2898
3539
|
panels.show(unit, workspace);
|
|
@@ -2912,18 +3553,18 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
2912
3553
|
}
|
|
2913
3554
|
const tool = {
|
|
2914
3555
|
name: "workbench_diy",
|
|
2915
|
-
description: "看 DIY 工坊里的模块现在走到哪一步,或者把它们装上 / 上板。只认工坊里的模块(不是市场 —— 装别的东西用 workbench_modules)。status
|
|
2916
|
-
parameters: {
|
|
3556
|
+
description: "看 DIY 工坊里的模块现在走到哪一步,或者把它们装上 / 上板。只认工坊里的模块(不是市场 —— 装别的东西用 workbench_modules)。status 会把六件事一次说清(装了没 / 面板认得没 / 这个项目上开着没 / **起得来没** / 产物齐不齐 / 源码是否比产物新)并给出下一步,所以别自己去拼这些状态、也别反复 install。panel 是**面板上现在究竟是什么样**:这个工作区开了哪几张卡、每张最后是读到了还是读不到(带宿主回的原文)、有没有崩过 —— 用户说\"卡片不对 / 一片空白 / 一直转圈\"就先问它,别靠猜、也别让他截图给你。recon 是**先看有没有现成的**:拿一个关键词(站点名 / 系统名 / 域名)去 opencli 自带的那份目录里找,回它有哪些命令、要不要用户的 Chrome 登录;都没有就老实说,并给出\"写 adapter\"或\"探一条读\"两条路。用户不知道 opencli 是什么 —— 这一步别问他,自己查。probe 是**探一条读**:用用户浏览器里现有的登录去读一次,把结果与失败码归一(两拍、重试、平台码映射都在里面),探明之后再把它固化成模块宿主半侧的那一条。两种读法二选一:script(页面里跑一段回 JSON 的脚本,读接口)或 css(给一个选择器,读 DOM 那一行 —— 页面里没有接口时用这个,别写 document.querySelectorAll)。装是这台机器上的持久变更,要用户点头;**一辈子只装一次**:之后改源码只需 dsh-wb-module build(宿主侧秒级热换;改了 client.js 刷新一下页面),不要再调它。",
|
|
3557
|
+
parameters: withApprovalNote({
|
|
2917
3558
|
type: "object",
|
|
2918
3559
|
properties: {
|
|
2919
3560
|
action: {
|
|
2920
3561
|
type: "string",
|
|
2921
|
-
description: "list=工坊里有哪些模块(默认);status=某个模块现在走到哪一步、下一步做什么;probe=在用户浏览器里探一条读;recon=先查 opencli 现成的能力(自带 adapter / 本机插件);open=只把它在这个项目上板(不装);install=装上并上板(第一次、会让用户点头)。",
|
|
2922
|
-
enum: [...ACTIONS$
|
|
3562
|
+
description: "list=工坊里有哪些模块(默认);status=某个模块现在走到哪一步、下一步做什么;panel=这个工作区的面板上现在是什么样(谁读到了 / 谁读不到 / 谁崩过);probe=在用户浏览器里探一条读;recon=先查 opencli 现成的能力(自带 adapter / 本机插件);open=只把它在这个项目上板(不装);install=装上并上板(第一次、会让用户点头)。",
|
|
3563
|
+
enum: [...ACTIONS$2]
|
|
2923
3564
|
},
|
|
2924
3565
|
unit: {
|
|
2925
3566
|
type: "string",
|
|
2926
|
-
description: "status / open / install 填:工坊里那个模块的单元 id(list 会给)。"
|
|
3567
|
+
description: "status / panel / open / install 填:工坊里那个模块的单元 id(list 会给)。"
|
|
2927
3568
|
},
|
|
2928
3569
|
script: {
|
|
2929
3570
|
type: "string",
|
|
@@ -2964,7 +3605,7 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
2964
3605
|
},
|
|
2965
3606
|
required: ["action"],
|
|
2966
3607
|
additionalProperties: false
|
|
2967
|
-
},
|
|
3608
|
+
}, { required: true }),
|
|
2968
3609
|
output: {
|
|
2969
3610
|
schema: OUTPUT_SCHEMA$2,
|
|
2970
3611
|
render: (_args, value) => {
|
|
@@ -2978,13 +3619,16 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
2978
3619
|
async execute(raw, exec) {
|
|
2979
3620
|
const args = asRecord$3(raw) ?? {};
|
|
2980
3621
|
const action = asText$1(args.action) || "list";
|
|
2981
|
-
if (!ACTIONS$
|
|
3622
|
+
if (!ACTIONS$2.includes(action)) throw new Error(`不认识的 action:${action}(可用:${ACTIONS$2.join(" / ")})`);
|
|
2982
3623
|
const packagesDir = workshopPackagesDir(ctx);
|
|
2983
3624
|
if (packagesDir === null) throw new Error("算不出 DIY 工坊在哪(这台机器没给出 dshHomePath)—— 让用户跑一次 dsh-workbench setup");
|
|
2984
3625
|
const session = sessionWorkspace(exec);
|
|
2985
3626
|
if (!inWorkshopSession(ctx, session)) throw new Error(`这把工具只在 DIY 工坊里用:当前会话不在工坊里(这个会话的工作区 ${session ?? "(不知道)"})—— 普通项目里装模块用 workbench_modules,工坊的入口是左栏那一行「工作台DIY」`);
|
|
2986
3627
|
const workspace = session;
|
|
2987
|
-
const
|
|
3628
|
+
const installed = profileModules(profileDirOf(ctx)).map((row) => row.unit);
|
|
3629
|
+
const modules = workshopModules(packagesDir, installed);
|
|
3630
|
+
const registry = ctx.get(UNITS_SERVICE);
|
|
3631
|
+
const panelState = ctx.get(PANEL_STATE_SERVICE);
|
|
2988
3632
|
if (action === "list") return plainJson({
|
|
2989
3633
|
workshop: packagesDir,
|
|
2990
3634
|
modules,
|
|
@@ -3003,7 +3647,12 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
3003
3647
|
name: one.name,
|
|
3004
3648
|
installed: one.installed,
|
|
3005
3649
|
known: panelEntries.some((entry) => entry.id === one.unit),
|
|
3006
|
-
open: panels.isEnabled(one.unit, workspace)
|
|
3650
|
+
open: panels.isEnabled(one.unit, workspace),
|
|
3651
|
+
health: healthOf({
|
|
3652
|
+
registry,
|
|
3653
|
+
unit: one.unit,
|
|
3654
|
+
installed: one.installed
|
|
3655
|
+
})
|
|
3007
3656
|
});
|
|
3008
3657
|
if (action === "status") {
|
|
3009
3658
|
const wanted = asText$1(args.unit);
|
|
@@ -3016,6 +3665,49 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
3016
3665
|
text: renderStatus(packagesDir, rows)
|
|
3017
3666
|
});
|
|
3018
3667
|
}
|
|
3668
|
+
if (action === "panel") {
|
|
3669
|
+
const snapshot = panelState?.snapshot(workspace) ?? {
|
|
3670
|
+
workspace,
|
|
3671
|
+
at: 0,
|
|
3672
|
+
units: []
|
|
3673
|
+
};
|
|
3674
|
+
const wanted = asText$1(args.unit);
|
|
3675
|
+
const board = panelEntries.filter((entry) => entry.shown && entry.hasCard).map((entry) => entry.id);
|
|
3676
|
+
const nameOf = new Map(modules.map((one) => [one.unit, one.name]));
|
|
3677
|
+
const reported = new Map(snapshot.units.map((one) => [one.unit, one]));
|
|
3678
|
+
const rows = [.../* @__PURE__ */ new Set([
|
|
3679
|
+
...board,
|
|
3680
|
+
...reported.keys(),
|
|
3681
|
+
...nameOf.keys()
|
|
3682
|
+
])].filter((unit) => wanted === "" || unit === wanted).map((unit) => {
|
|
3683
|
+
const report = reported.get(unit);
|
|
3684
|
+
return {
|
|
3685
|
+
unit,
|
|
3686
|
+
name: nameOf.get(unit) ?? "",
|
|
3687
|
+
health: healthOf({
|
|
3688
|
+
registry,
|
|
3689
|
+
unit,
|
|
3690
|
+
installed: installed.includes(unit)
|
|
3691
|
+
}),
|
|
3692
|
+
...report === void 0 ? {} : { report }
|
|
3693
|
+
};
|
|
3694
|
+
});
|
|
3695
|
+
return plainJson({
|
|
3696
|
+
workshop: packagesDir,
|
|
3697
|
+
workspace,
|
|
3698
|
+
board,
|
|
3699
|
+
report: snapshot,
|
|
3700
|
+
panels: rows,
|
|
3701
|
+
text: renderPanel({
|
|
3702
|
+
packagesDir,
|
|
3703
|
+
workspace,
|
|
3704
|
+
rows,
|
|
3705
|
+
board,
|
|
3706
|
+
at: snapshot.at,
|
|
3707
|
+
now: Date.now()
|
|
3708
|
+
})
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3019
3711
|
if (action === "recon") {
|
|
3020
3712
|
const keywords = keywordsOf(asText$1(args.keyword), asText$1(args.url));
|
|
3021
3713
|
if (keywords.length === 0) throw new Error("recon 要一个关键词(站点名 / 系统名 / 域名都行):先拿它去 opencli 自带的那份目录里找有没有现成的能力,别一上来就自己写 adapter;用户不知道 opencli 是什么,这一步别问他。");
|
|
@@ -3907,11 +4599,47 @@ function createEnvDropper(options) {
|
|
|
3907
4599
|
};
|
|
3908
4600
|
}
|
|
3909
4601
|
//#endregion
|
|
4602
|
+
//#region src/modules/panels/model/market-registry.ts
|
|
4603
|
+
async function rememberInstalledSource(deps, manager, pkg, registry) {
|
|
4604
|
+
const { registries, logger } = deps;
|
|
4605
|
+
if (registries === void 0 || registry === void 0) return;
|
|
4606
|
+
const scope = scopeOf(pkg);
|
|
4607
|
+
if (scope === null) return;
|
|
4608
|
+
const known = await manager.registries().catch(() => null);
|
|
4609
|
+
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
4610
|
+
try {
|
|
4611
|
+
const added = registries.remember([{
|
|
4612
|
+
scope,
|
|
4613
|
+
registry
|
|
4614
|
+
}]);
|
|
4615
|
+
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
4616
|
+
} catch (error) {
|
|
4617
|
+
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
async function retryAfterRegistryFix(deps, manager, pkg, change) {
|
|
4621
|
+
const { registries, logger } = deps;
|
|
4622
|
+
if (registries === void 0 || !registryFailure(change)) return change;
|
|
4623
|
+
const known = await manager.registries().catch(() => null);
|
|
4624
|
+
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
4625
|
+
if (missing.length === 0) return change;
|
|
4626
|
+
let added;
|
|
4627
|
+
try {
|
|
4628
|
+
added = registries.remember(missing);
|
|
4629
|
+
} catch (error) {
|
|
4630
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
4631
|
+
return change;
|
|
4632
|
+
}
|
|
4633
|
+
if (added.length === 0) return change;
|
|
4634
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
4635
|
+
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
4636
|
+
}
|
|
4637
|
+
//#endregion
|
|
3910
4638
|
//#region src/modules/panels/model/market.ts
|
|
3911
4639
|
function createMarketService(options) {
|
|
3912
|
-
const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, registries, ledger, references } = options;
|
|
4640
|
+
const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, ledger, references } = options;
|
|
3913
4641
|
const command = options.command ?? "opencli";
|
|
3914
|
-
const catalog = () =>
|
|
4642
|
+
const catalog = () => manifestsNow?.() ?? manifests;
|
|
3915
4643
|
function requireEntry(unit) {
|
|
3916
4644
|
const manifest = catalog().find((item) => item.id === unit);
|
|
3917
4645
|
if (manifest === void 0) throw new Error(`市场里没有这个模块:${unit}(可安装:${catalog().map((item) => item.id).join(", ") || "(空)"})`);
|
|
@@ -4054,44 +4782,16 @@ function createMarketService(options) {
|
|
|
4054
4782
|
};
|
|
4055
4783
|
}
|
|
4056
4784
|
}
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
const known = await manager.registries().catch(() => null);
|
|
4062
|
-
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
4063
|
-
try {
|
|
4064
|
-
const added = registries.remember([{
|
|
4065
|
-
scope,
|
|
4066
|
-
registry
|
|
4067
|
-
}]);
|
|
4068
|
-
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
4069
|
-
} catch (error) {
|
|
4070
|
-
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
4071
|
-
}
|
|
4072
|
-
}
|
|
4073
|
-
async function retryAfterRegistryFix(manager, pkg, change) {
|
|
4074
|
-
if (registries === void 0 || !registryFailure(change)) return change;
|
|
4075
|
-
const known = await manager.registries().catch(() => null);
|
|
4076
|
-
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
4077
|
-
if (missing.length === 0) return change;
|
|
4078
|
-
let added;
|
|
4079
|
-
try {
|
|
4080
|
-
added = registries.remember(missing);
|
|
4081
|
-
} catch (error) {
|
|
4082
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
4083
|
-
return change;
|
|
4084
|
-
}
|
|
4085
|
-
if (added.length === 0) return change;
|
|
4086
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
4087
|
-
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
4088
|
-
}
|
|
4785
|
+
const registryFix = {
|
|
4786
|
+
...registries === void 0 ? {} : { registries },
|
|
4787
|
+
...logger === void 0 ? {} : { logger }
|
|
4788
|
+
};
|
|
4089
4789
|
function routeDeps(manager) {
|
|
4090
4790
|
return {
|
|
4091
4791
|
manager,
|
|
4092
4792
|
command,
|
|
4093
4793
|
runAll,
|
|
4094
|
-
rememberSource: rememberInstalledSource,
|
|
4794
|
+
rememberSource: (manager, pkg, registry) => rememberInstalledSource(registryFix, manager, pkg, registry),
|
|
4095
4795
|
supplyAfter,
|
|
4096
4796
|
...shell === void 0 ? {} : { shell },
|
|
4097
4797
|
...installed === void 0 ? {} : { installed },
|
|
@@ -4237,7 +4937,7 @@ function createMarketService(options) {
|
|
|
4237
4937
|
const before = unit === void 0 ? void 0 : await capabilityOf(unit, true);
|
|
4238
4938
|
const first = await manager.remove(pkg);
|
|
4239
4939
|
if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
|
|
4240
|
-
const change = await retryAfterRegistryFix(manager, pkg, first);
|
|
4940
|
+
const change = await retryAfterRegistryFix(registryFix, manager, pkg, first);
|
|
4241
4941
|
if (change.application !== "applied" || unit === void 0) return {
|
|
4242
4942
|
change,
|
|
4243
4943
|
keptFor: []
|
|
@@ -4273,6 +4973,24 @@ function createMarketService(options) {
|
|
|
4273
4973
|
await service.update(unit).catch(() => void 0);
|
|
4274
4974
|
return outcome;
|
|
4275
4975
|
},
|
|
4976
|
+
async updateWorkbench(pkg) {
|
|
4977
|
+
const target = pkg.trim() === "" ? PLUGIN_ID : pkg.trim();
|
|
4978
|
+
if (target !== "@dsh-workbench/core") throw new Error(`这条路只更新工作台自己那一份(${PLUGIN_ID}):${target}`);
|
|
4979
|
+
const read = workbench?.(target);
|
|
4980
|
+
if (read === void 0) throw new Error(`读不到 profile 里装的 ${target} —— 没法判断装的是哪一份,更新做不了。`);
|
|
4981
|
+
if (read.local === true) throw new Error("这份工作台是从本仓装进来的(开发副本),不从源上更新。");
|
|
4982
|
+
const registry = read.registry;
|
|
4983
|
+
const latest = shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
4984
|
+
shell,
|
|
4985
|
+
name: read.pkg,
|
|
4986
|
+
registry,
|
|
4987
|
+
force: true
|
|
4988
|
+
});
|
|
4989
|
+
const spec = latest === void 0 ? `${read.pkg}@latest` : `${read.pkg}@${latest}`;
|
|
4990
|
+
const outcome = await installDshBundle(routeDeps(requireBundles()), spec, { ...registry === void 0 ? {} : { registry } });
|
|
4991
|
+
logger?.info?.(`[dsh-workbench] 已更新工作台 ${read.version} → ${latest ?? "最新版"}`);
|
|
4992
|
+
return outcome;
|
|
4993
|
+
},
|
|
4276
4994
|
async updatePlugin(pkg) {
|
|
4277
4995
|
const outcome = await plugins.update(pkg);
|
|
4278
4996
|
await reprobePlugin(pkg, outcome.name);
|
|
@@ -4337,6 +5055,11 @@ async function dispatchMarket(market, request) {
|
|
|
4337
5055
|
action: "cancel-install",
|
|
4338
5056
|
cancellation: await market.cancelInstall(request.requestId ?? "")
|
|
4339
5057
|
};
|
|
5058
|
+
if (request.action === "update-workbench") return {
|
|
5059
|
+
ok: true,
|
|
5060
|
+
action: "update-workbench",
|
|
5061
|
+
change: (await market.updateWorkbench(request.name ?? "")).change
|
|
5062
|
+
};
|
|
4340
5063
|
if (request.action === "update-plugin") {
|
|
4341
5064
|
await market.updatePlugin(request.name ?? "");
|
|
4342
5065
|
return {
|
|
@@ -4431,12 +5154,16 @@ function describePluginUninstall(pkg, outcome) {
|
|
|
4431
5154
|
if (outcome.removed) return `已卸载 opencli 插件 ${pkg}:没人再用它,文件也删了。`;
|
|
4432
5155
|
return `已从「你装的插件」里去掉 ${pkg}。${outcome.kept === void 0 ? "" : pluginKeptText(outcome.kept)}`;
|
|
4433
5156
|
}
|
|
5157
|
+
function describeWorkbenchUpdate(change) {
|
|
5158
|
+
if (change.application === "failed" || change.application === "cancelled") return `没成功:${change.errorCode ?? change.application}`;
|
|
5159
|
+
return change.application === "restart-required" ? "工作台更新到新版了 —— 这一版要重启一次 dsh 才生效(重启由用户来,别自己重启)。" : "工作台更新到新版了 —— 客户端与宿主自己换上,不用重启。";
|
|
5160
|
+
}
|
|
4434
5161
|
function usersOf(shared, names) {
|
|
4435
5162
|
const hit = shared.find((item) => names.some((name) => name !== void 0 && name !== "" && item.id === `plugin:${name}`));
|
|
4436
5163
|
if (hit === void 0 || hit.usedBy.length === 0) return "";
|
|
4437
5164
|
return `它被 ${hit.usedBy.join("、")} 用着(动的是它们的供给,它们的状态跟着重探了一遍)`;
|
|
4438
5165
|
}
|
|
4439
|
-
var ACTIONS = [
|
|
5166
|
+
var ACTIONS$1 = [
|
|
4440
5167
|
"list",
|
|
4441
5168
|
"install",
|
|
4442
5169
|
"update",
|
|
@@ -4444,7 +5171,8 @@ var ACTIONS = [
|
|
|
4444
5171
|
"install-bundle",
|
|
4445
5172
|
"update-dependency",
|
|
4446
5173
|
"update-plugin",
|
|
4447
|
-
"uninstall-plugin"
|
|
5174
|
+
"uninstall-plugin",
|
|
5175
|
+
"update-workbench"
|
|
4448
5176
|
];
|
|
4449
5177
|
function registerMarketTools(ctx, market) {
|
|
4450
5178
|
const tools = ctx.get("tools");
|
|
@@ -4454,14 +5182,14 @@ function registerMarketTools(ctx, market) {
|
|
|
4454
5182
|
}
|
|
4455
5183
|
const tool = {
|
|
4456
5184
|
name: "workbench_modules",
|
|
4457
|
-
description: "看 / 装 / 升 / 卸工作台的面板模块,以及用户自己装的 opencli 插件。install-bundle 按 spec(包名,可带版本;git / tarball / 本地路径也行)把模块包装进 profile —— 装完会连带装它声明的 opencli 插件;其余 action 按模块 id 动它要的外部供给;update-
|
|
4458
|
-
parameters: {
|
|
5185
|
+
description: "看 / 装 / 升 / 卸工作台的面板模块,以及用户自己装的 opencli 插件。install-bundle 按 spec(包名,可带版本;git / tarball / 本地路径也行)把模块包装进 profile —— 装完会连带装它声明的 opencli 插件;其余 action 按模块 id 动它要的外部供给;update-workbench 把工作台自己更新到源上的新版(客户端与宿主一般会自己换上;真要重启时结果里会说,重启由用户来,别自己重启)。卸载只动工作台运行时,不影响终端那套;共用依赖只有升级。",
|
|
5186
|
+
parameters: withApprovalNote({
|
|
4459
5187
|
type: "object",
|
|
4460
5188
|
properties: {
|
|
4461
5189
|
action: {
|
|
4462
5190
|
type: "string",
|
|
4463
|
-
description: "install-bundle=按 spec 装模块包(要 spec),update-dependency=升级共用依赖(要 id),update-plugin / uninstall-plugin=动用户自己装的 opencli 插件(要 plugin
|
|
4464
|
-
enum: [...ACTIONS]
|
|
5191
|
+
description: "install-bundle=按 spec 装模块包(要 spec),update-dependency=升级共用依赖(要 id),update-plugin / uninstall-plugin=动用户自己装的 opencli 插件(要 plugin),update-workbench=把工作台自己更新到新版(不用别的参数),其余=对模块(要 id)。",
|
|
5192
|
+
enum: [...ACTIONS$1]
|
|
4465
5193
|
},
|
|
4466
5194
|
id: {
|
|
4467
5195
|
type: "string",
|
|
@@ -4482,7 +5210,7 @@ function registerMarketTools(ctx, market) {
|
|
|
4482
5210
|
},
|
|
4483
5211
|
required: ["action"],
|
|
4484
5212
|
additionalProperties: false
|
|
4485
|
-
},
|
|
5213
|
+
}, { required: true }),
|
|
4486
5214
|
output: {
|
|
4487
5215
|
schema: OUTPUT_SCHEMA$1,
|
|
4488
5216
|
render: (_args, value) => {
|
|
@@ -4495,7 +5223,7 @@ function registerMarketTools(ctx, market) {
|
|
|
4495
5223
|
},
|
|
4496
5224
|
async execute(args) {
|
|
4497
5225
|
const action = args?.action ?? "list";
|
|
4498
|
-
if (!ACTIONS.includes(action)) throw new Error(`不认识的 action:${action}(可用:${ACTIONS.join(" / ")})`);
|
|
5226
|
+
if (!ACTIONS$1.includes(action)) throw new Error(`不认识的 action:${action}(可用:${ACTIONS$1.join(" / ")})`);
|
|
4499
5227
|
if (action === "list") {
|
|
4500
5228
|
const { modules, shared } = await market.list();
|
|
4501
5229
|
return {
|
|
@@ -4516,6 +5244,13 @@ function registerMarketTools(ctx, market) {
|
|
|
4516
5244
|
text: describeInstall(spec, outcome.module, outcome.supply, outcome.change)
|
|
4517
5245
|
};
|
|
4518
5246
|
}
|
|
5247
|
+
if (action === "update-workbench") {
|
|
5248
|
+
const outcome = await market.updateWorkbench("");
|
|
5249
|
+
return {
|
|
5250
|
+
change: outcome.change,
|
|
5251
|
+
text: describeWorkbenchUpdate(outcome.change)
|
|
5252
|
+
};
|
|
5253
|
+
}
|
|
4519
5254
|
if (action === "update-plugin" || action === "uninstall-plugin") {
|
|
4520
5255
|
const plugin = args?.plugin?.trim() ?? "";
|
|
4521
5256
|
if (plugin === "") throw new Error(`${action} 需要一个 plugin:opencli 插件的包名或插件名 —— 就是市场「已安装 opencli 插件」那一段里的名字。`);
|
|
@@ -4566,53 +5301,70 @@ function registerMarketTools(ctx, market) {
|
|
|
4566
5301
|
//#endregion
|
|
4567
5302
|
//#region src/modules/panels/model/prefs.ts
|
|
4568
5303
|
var PANELS_PREF_RESOURCE = "panels";
|
|
5304
|
+
var ACTIONS = [
|
|
5305
|
+
"show",
|
|
5306
|
+
"hide",
|
|
5307
|
+
"move",
|
|
5308
|
+
"reset",
|
|
5309
|
+
"follow"
|
|
5310
|
+
];
|
|
4569
5311
|
function parsePanelsPrefAction(payload) {
|
|
4570
|
-
if (typeof payload !== "object" || payload === null) throw new Error("偏好写需要一个对象:{ action, id?, index?, path? }");
|
|
5312
|
+
if (typeof payload !== "object" || payload === null) throw new Error("偏好写需要一个对象:{ action, id?, index?, path?, scope? }");
|
|
4571
5313
|
const value = payload;
|
|
4572
5314
|
const action = value.action;
|
|
4573
|
-
if (
|
|
5315
|
+
if (typeof action !== "string" || !ACTIONS.includes(action)) throw new Error(`不认识的 action:${String(action)}(可用:${ACTIONS.join(" / ")})`);
|
|
5316
|
+
if (value.scope !== void 0 && value.scope !== "machine" && value.scope !== "workspace") throw new Error(`不认识的 scope:${String(value.scope)}(可用:workspace / machine)`);
|
|
4574
5317
|
return {
|
|
4575
5318
|
action,
|
|
4576
5319
|
...typeof value.id === "string" ? { id: value.id } : {},
|
|
4577
5320
|
...typeof value.index === "number" ? { index: value.index } : {},
|
|
4578
|
-
...typeof value.path === "string" ? { path: value.path } : {}
|
|
5321
|
+
...typeof value.path === "string" ? { path: value.path } : {},
|
|
5322
|
+
...value.scope === void 0 ? {} : { scope: value.scope }
|
|
4579
5323
|
};
|
|
4580
5324
|
}
|
|
4581
5325
|
function applyPanelsPref(panels, payload) {
|
|
4582
5326
|
const action = parsePanelsPrefAction(payload);
|
|
4583
5327
|
const path = action.path ?? null;
|
|
5328
|
+
const scope = action.scope ?? "workspace";
|
|
5329
|
+
if (action.action === "follow") {
|
|
5330
|
+
if (scope === "machine") throw new Error("机器级默认板没有\"回到跟随\"一说:它就是那份默认本身(要回到的是\"没配过\")。");
|
|
5331
|
+
return {
|
|
5332
|
+
ok: true,
|
|
5333
|
+
units: action.id === void 0 ? panels.follow(path) : panels.follow(path, action.id)
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
4584
5336
|
if (action.action === "reset") return {
|
|
4585
5337
|
ok: true,
|
|
4586
|
-
units: panels.reset(path)
|
|
5338
|
+
units: panels.reset(path, scope)
|
|
4587
5339
|
};
|
|
4588
5340
|
if (action.id === void 0) throw new Error(`${action.action} 需要一个 id(当前套件:${panels.list(path).map((entry) => entry.id).join(", ")})`);
|
|
4589
5341
|
if (action.action === "show") return {
|
|
4590
5342
|
ok: true,
|
|
4591
|
-
units: panels.show(action.id, path)
|
|
5343
|
+
units: panels.show(action.id, path, scope)
|
|
4592
5344
|
};
|
|
4593
5345
|
if (action.action === "hide") return {
|
|
4594
5346
|
ok: true,
|
|
4595
|
-
units: panels.hide(action.id, path)
|
|
5347
|
+
units: panels.hide(action.id, path, scope)
|
|
4596
5348
|
};
|
|
4597
5349
|
return {
|
|
4598
5350
|
ok: true,
|
|
4599
|
-
units: panels.move(action.id, action.index ?? 0, path)
|
|
5351
|
+
units: panels.move(action.id, action.index ?? 0, path, scope)
|
|
4600
5352
|
};
|
|
4601
5353
|
}
|
|
4602
5354
|
//#endregion
|
|
4603
5355
|
//#region src/modules/panels/model/handoff.ts
|
|
4604
5356
|
var PANELS_FACT = "panels";
|
|
4605
5357
|
var CAPABILITY_FACT = "capability";
|
|
4606
|
-
function textOf(value) {
|
|
5358
|
+
function textOf$1(value) {
|
|
4607
5359
|
return typeof value === "string" ? value.trim() : "";
|
|
4608
5360
|
}
|
|
4609
5361
|
function actionOf(call) {
|
|
4610
|
-
return textOf(call.args["action"]) || "list";
|
|
5362
|
+
return textOf$1(call.args["action"]) || "list";
|
|
4611
5363
|
}
|
|
4612
5364
|
function requestOf(call) {
|
|
4613
5365
|
const action = actionOf(call);
|
|
4614
5366
|
if (!PROVISION_ACTIONS.includes(action)) return void 0;
|
|
4615
|
-
const target = textOf(call.args["unit"]) || textOf(call.args["id"]);
|
|
5367
|
+
const target = textOf$1(call.args["unit"]) || textOf$1(call.args["id"]);
|
|
4616
5368
|
if (target === "") return void 0;
|
|
4617
5369
|
const wanted = action;
|
|
4618
5370
|
return isDependencyAction(wanted) ? {
|
|
@@ -4624,7 +5376,7 @@ function requestOf(call) {
|
|
|
4624
5376
|
};
|
|
4625
5377
|
}
|
|
4626
5378
|
function panelsHandoffEntry(target = {}) {
|
|
4627
|
-
const path = textOf(target.path);
|
|
5379
|
+
const path = textOf$1(target.path);
|
|
4628
5380
|
return path === "" ? void 0 : {
|
|
4629
5381
|
fact: PANELS_FACT,
|
|
4630
5382
|
key: `path:${path}`
|
|
@@ -4635,6 +5387,7 @@ function panelsFactsOf(call, manifests) {
|
|
|
4635
5387
|
if (call.source === "workbench_diy" && actionOf(call) === "list") return [PANELS_FACT];
|
|
4636
5388
|
if (call.source !== "workbench_modules" && call.source !== "workbench_diy") return void 0;
|
|
4637
5389
|
if (actionOf(call) === "list") return [PANELS_FACT, CAPABILITY_FACT];
|
|
5390
|
+
if (actionOf(call) === "update-workbench") return [PANELS_FACT, CAPABILITY_FACT];
|
|
4638
5391
|
const request = requestOf(call);
|
|
4639
5392
|
if (request === void 0) return void 0;
|
|
4640
5393
|
return [
|
|
@@ -4668,9 +5421,11 @@ function createPanelsService(options) {
|
|
|
4668
5421
|
return readBoard(folder) ?? EMPTY_BOARD;
|
|
4669
5422
|
}
|
|
4670
5423
|
function writeBoard(folder, next) {
|
|
5424
|
+
const order = next.order ?? [];
|
|
4671
5425
|
folder.write(PANELS_FILE, {
|
|
4672
5426
|
shown: next.shown,
|
|
4673
|
-
hidden: next.hidden
|
|
5427
|
+
hidden: next.hidden,
|
|
5428
|
+
...order.length === 0 ? {} : { order }
|
|
4674
5429
|
});
|
|
4675
5430
|
}
|
|
4676
5431
|
function boardsOf(workspace) {
|
|
@@ -4701,6 +5456,9 @@ function createPanelsService(options) {
|
|
|
4701
5456
|
if (next.length === 0) machine.remove(RELEASED_FILE);
|
|
4702
5457
|
else machine.write(RELEASED_FILE, { ids: next });
|
|
4703
5458
|
}
|
|
5459
|
+
function ownedSet() {
|
|
5460
|
+
return new Set(owned?.() ?? []);
|
|
5461
|
+
}
|
|
4704
5462
|
function notify() {
|
|
4705
5463
|
for (const listener of [...listeners]) try {
|
|
4706
5464
|
listener();
|
|
@@ -4711,7 +5469,7 @@ function createPanelsService(options) {
|
|
|
4711
5469
|
function withPersisted(folder, next, key) {
|
|
4712
5470
|
writeBoard(folder, next);
|
|
4713
5471
|
notify();
|
|
4714
|
-
return resolvePanels(manifests(), boardsOf(key));
|
|
5472
|
+
return resolvePanels(manifests(), boardsOf(key), ownedSet());
|
|
4715
5473
|
}
|
|
4716
5474
|
function takeBackReleased(id) {
|
|
4717
5475
|
const current = readReleased();
|
|
@@ -4722,18 +5480,31 @@ function createPanelsService(options) {
|
|
|
4722
5480
|
const all = manifests();
|
|
4723
5481
|
if (!all.some((manifest) => manifest.id === id)) throw new Error(`没有这个套件:${id}(可用:${all.map((manifest) => manifest.id).join(", ")})`);
|
|
4724
5482
|
}
|
|
5483
|
+
function pluginRefs() {
|
|
5484
|
+
return options.plugins?.() ?? [];
|
|
5485
|
+
}
|
|
5486
|
+
function pluginIds() {
|
|
5487
|
+
return new Set(pluginRefs().map((plugin) => pluginPanelId(plugin.pkg)));
|
|
5488
|
+
}
|
|
4725
5489
|
function requireSwitchable(id) {
|
|
5490
|
+
if (pluginIds().has(id)) return;
|
|
4726
5491
|
requireKnown(id);
|
|
4727
|
-
if (
|
|
5492
|
+
if (manifests().find((item) => item.id === id)?.card === null && ownedSet().has(id)) throw new Error(`${id} 没有卡片,不能收起(它是把别的套件放回来的入口)`);
|
|
4728
5493
|
}
|
|
4729
5494
|
function requireWorkspace(workspace) {
|
|
4730
5495
|
const key = workspace?.trim() ?? "";
|
|
4731
5496
|
if (key === "") throw new Error("这个会话不属于任何工作区:面板按工作区存,先打开或新建一个工作区");
|
|
4732
5497
|
return key;
|
|
4733
5498
|
}
|
|
5499
|
+
function visibleOrder(workspace) {
|
|
5500
|
+
return resolvePanels(manifests(), boardsOf(workspace), ownedSet()).filter((entry) => entry.shown && entry.hasCard).map((entry) => entry.id);
|
|
5501
|
+
}
|
|
4734
5502
|
return {
|
|
4735
5503
|
list(workspace, target) {
|
|
4736
|
-
return resolvePanels(manifests(), target === "machine" ? boardsOf(null) : boardsOf(workspace));
|
|
5504
|
+
return resolvePanels(manifests(), target === "machine" ? boardsOf(null) : boardsOf(workspace), ownedSet());
|
|
5505
|
+
},
|
|
5506
|
+
listPlugins(workspace, target) {
|
|
5507
|
+
return resolvePluginPanels(pluginRefs(), target === "machine" ? boardsOf(null) : boardsOf(workspace));
|
|
4737
5508
|
},
|
|
4738
5509
|
boards(workspace) {
|
|
4739
5510
|
return boardsOf(workspace);
|
|
@@ -4775,7 +5546,7 @@ function createPanelsService(options) {
|
|
|
4775
5546
|
requireSwitchable(id);
|
|
4776
5547
|
const { folder, key } = targetOf(target ?? "workspace", workspace);
|
|
4777
5548
|
takeBackReleased(id);
|
|
4778
|
-
return withPersisted(folder, withShown(boardIn(folder), id), key);
|
|
5549
|
+
return withPersisted(folder, withShown(boardIn(folder), id, key === null ? void 0 : visibleOrder(key)), key);
|
|
4779
5550
|
},
|
|
4780
5551
|
hide(id, workspace, target) {
|
|
4781
5552
|
requireSwitchable(id);
|
|
@@ -4784,14 +5555,34 @@ function createPanelsService(options) {
|
|
|
4784
5555
|
},
|
|
4785
5556
|
move(id, index, workspace, target) {
|
|
4786
5557
|
requireSwitchable(id);
|
|
4787
|
-
const
|
|
5558
|
+
const scope = target ?? "workspace";
|
|
5559
|
+
if (scope === "machine") throw new Error("机器级默认板没有顺序这一说:它只回答\"默认开哪几行\"。顺序是每个工作区自己的 —— 要排顺序就用默认作用域的那一次 move(或面板里拖动)。");
|
|
5560
|
+
const { folder, key } = targetOf(scope, workspace);
|
|
4788
5561
|
const current = boardIn(folder);
|
|
4789
5562
|
if (!current.shown.includes(id)) takeBackReleased(id);
|
|
4790
|
-
return withPersisted(folder, withMoved(current, id, index), key);
|
|
5563
|
+
return withPersisted(folder, withMoved(current, id, index, visibleOrder(key)), key);
|
|
4791
5564
|
},
|
|
4792
5565
|
reset(workspace, target) {
|
|
4793
5566
|
const { folder, key } = targetOf(target ?? "workspace", workspace);
|
|
4794
5567
|
return withPersisted(folder, EMPTY_BOARD, key);
|
|
5568
|
+
},
|
|
5569
|
+
changedBy() {
|
|
5570
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5571
|
+
for (const folder of store.scan(PANELS_MODULE_ID)) {
|
|
5572
|
+
const board = readBoard(folder);
|
|
5573
|
+
if (board === null) continue;
|
|
5574
|
+
for (const id of /* @__PURE__ */ new Set([...board.shown, ...board.hidden])) counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
5575
|
+
}
|
|
5576
|
+
return counts;
|
|
5577
|
+
},
|
|
5578
|
+
follow(workspace, id) {
|
|
5579
|
+
const { folder, key } = targetOf("workspace", workspace);
|
|
5580
|
+
if (id === void 0) {
|
|
5581
|
+
folder.remove(PANELS_FILE);
|
|
5582
|
+
notify();
|
|
5583
|
+
return resolvePanels(manifests(), boardsOf(key), ownedSet());
|
|
5584
|
+
}
|
|
5585
|
+
return withPersisted(folder, withoutPanel(boardIn(folder), id), key);
|
|
4795
5586
|
}
|
|
4796
5587
|
};
|
|
4797
5588
|
}
|
|
@@ -4802,13 +5593,15 @@ function panelRoutes(panels, values) {
|
|
|
4802
5593
|
const path = param(search, "path") ?? null;
|
|
4803
5594
|
const entry = panelsHandoffEntry(path === null ? {} : { path });
|
|
4804
5595
|
const handed = entry === void 0 ? void 0 : values?.take(entry);
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
units: handed.value
|
|
4808
|
-
};
|
|
5596
|
+
const defaults = [...panels.list(null, "machine").filter((unit) => unit.shown).map((unit) => unit.id), ...panels.listPlugins(null, "machine").filter((plugin) => plugin.shown).map((plugin) => plugin.id)];
|
|
5597
|
+
const own = panels.boards(path).workspace !== null;
|
|
4809
5598
|
return {
|
|
4810
5599
|
ok: true,
|
|
4811
|
-
units: panels.list(path)
|
|
5600
|
+
units: handed === void 0 ? panels.list(path) : handed.value,
|
|
5601
|
+
plugins: panels.listPlugins(path),
|
|
5602
|
+
defaults,
|
|
5603
|
+
own,
|
|
5604
|
+
changed: Object.fromEntries(panels.changedBy())
|
|
4812
5605
|
};
|
|
4813
5606
|
} };
|
|
4814
5607
|
}
|
|
@@ -4867,6 +5660,18 @@ function createPluginLedger(options) {
|
|
|
4867
5660
|
};
|
|
4868
5661
|
}
|
|
4869
5662
|
//#endregion
|
|
5663
|
+
//#region src/modules/panels/model/plugin-panel.ts
|
|
5664
|
+
function pluginPanelRefs(ledger) {
|
|
5665
|
+
return ledger.list().map((entry) => {
|
|
5666
|
+
const name = pluginNameOf(entry);
|
|
5667
|
+
return {
|
|
5668
|
+
pkg: entry.package,
|
|
5669
|
+
title: name ?? entry.package,
|
|
5670
|
+
...name === void 0 ? {} : { name }
|
|
5671
|
+
};
|
|
5672
|
+
});
|
|
5673
|
+
}
|
|
5674
|
+
//#endregion
|
|
4870
5675
|
//#region src/shared/api/installed-plugins.ts
|
|
4871
5676
|
var INSTALLED_PLUGINS_ROUTE = "panels/plugins";
|
|
4872
5677
|
//#endregion
|
|
@@ -4956,38 +5761,39 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
4956
5761
|
}
|
|
4957
5762
|
const tool = {
|
|
4958
5763
|
name: "workbench_panels",
|
|
4959
|
-
description: "看或调右侧面板:启用哪些套件、什么顺序。启用/收起 = 卡片与 agent 工具一起上下线(别的项目不受影响,不删数据)。收起是\"这个工作区明确不要它\",连那些在机器级默认板上开着的套件也照样在这个工作区收起来,之后 show
|
|
4960
|
-
parameters: {
|
|
5764
|
+
description: "看或调右侧面板:启用哪些套件、什么顺序。启用/收起 = 卡片与 agent 工具一起上下线(别的项目不受影响,不删数据)。收起是\"这个工作区明确不要它\",连那些在机器级默认板上开着的套件也照样在这个工作区收起来,之后 show 能放回来。顺序只属于工作区:没排过的按各模块自己声明的次序(开着的在前、关着的跟在后面);show 或 move 过就以那次为准。机器级默认板只回答\"默认开哪几行\",没有顺序这一说。scope=machine 动的是\"机器级默认板\" —— 它只是还没配过的工作区的默认值,已经自己配过的工作区保持自己的选择,不会被它改。在那份板上 hide = 明确写下\"不要它\",这一笔比名单更管用:名单里还列着它也算不要,没提过它的工作区因此看不到它;show 会把这笔划掉、把它放回去。本工作区那份板只记**改过的那几行**(逐行覆盖):没提到的行跟着机器级默认板走,所以那份板以后改了,没改过的工作区会跟着变。",
|
|
5765
|
+
parameters: withApprovalNote({
|
|
4961
5766
|
type: "object",
|
|
4962
5767
|
properties: {
|
|
4963
5768
|
action: {
|
|
4964
5769
|
type: "string",
|
|
4965
|
-
description: "show=这个工作区要它(之前收起的也放回来);hide=这个工作区明确不要它(卡片与工具一起下线,默认在机器上也开着的套件同样收起,别的项目不受影响);move
|
|
5770
|
+
description: "show=这个工作区要它(之前收起的也放回来);hide=这个工作区明确不要它(卡片与工具一起下线,默认在机器上也开着的套件同样收起,别的项目不受影响);move=调这个工作区的顺序(新开一行时它排在最后);reset=清掉这个工作区改过的那些行(改完就全部回到跟着机器级默认板走);follow=把某一行放回跟随(带 id = 只放回那一行;不带 id = 全部放回)。",
|
|
4966
5771
|
enum: [
|
|
4967
5772
|
"list",
|
|
4968
5773
|
"show",
|
|
4969
5774
|
"hide",
|
|
4970
5775
|
"move",
|
|
4971
|
-
"reset"
|
|
5776
|
+
"reset",
|
|
5777
|
+
"follow"
|
|
4972
5778
|
]
|
|
4973
5779
|
},
|
|
4974
5780
|
id: {
|
|
4975
5781
|
type: "string",
|
|
4976
|
-
description: "show / hide / move
|
|
5782
|
+
description: "show / hide / move 必填;follow 可选(带 id = 只把那一行放回跟随)。"
|
|
4977
5783
|
},
|
|
4978
5784
|
index: {
|
|
4979
5785
|
type: "integer",
|
|
4980
|
-
description: "move 的位置,0
|
|
5786
|
+
description: "move 的位置,0 基(数的是当前开着的那几行)。"
|
|
4981
5787
|
},
|
|
4982
5788
|
scope: {
|
|
4983
5789
|
type: "string",
|
|
4984
|
-
description: "workspace(默认)=只动当前工作区;machine=动机器级默认板,那是还没配过的工作区的默认值 ——
|
|
5790
|
+
description: "workspace(默认)=只动当前工作区;machine=动机器级默认板,那是还没配过的工作区的默认值 —— 已经自己配过的工作区保持自己的选择,不受它影响。机器级板没有顺序这一说(move 会报错)。",
|
|
4985
5791
|
enum: ["workspace", "machine"]
|
|
4986
5792
|
}
|
|
4987
5793
|
},
|
|
4988
5794
|
required: ["action"],
|
|
4989
5795
|
additionalProperties: false
|
|
4990
|
-
},
|
|
5796
|
+
}, { required: true }),
|
|
4991
5797
|
output: {
|
|
4992
5798
|
schema: OUTPUT_SCHEMA,
|
|
4993
5799
|
render: (_args, value) => {
|
|
@@ -5009,8 +5815,9 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
5009
5815
|
"show",
|
|
5010
5816
|
"hide",
|
|
5011
5817
|
"move",
|
|
5012
|
-
"reset"
|
|
5013
|
-
|
|
5818
|
+
"reset",
|
|
5819
|
+
"follow"
|
|
5820
|
+
].includes(action)) throw new Error(`不认识的 action:${action}(可用:list / show / hide / move / reset / follow)`);
|
|
5014
5821
|
if (action === "list") {
|
|
5015
5822
|
const units = list();
|
|
5016
5823
|
if (scope === "workspace") {
|
|
@@ -5022,11 +5829,19 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
5022
5829
|
text: render(units, workspace, scope)
|
|
5023
5830
|
};
|
|
5024
5831
|
}
|
|
5832
|
+
if (action === "follow") {
|
|
5833
|
+
if (scope === "machine") throw new Error("机器级默认板没有\"放回跟随\"一说:它就是那份默认本身,直接 show / hide 写它。");
|
|
5834
|
+
const units = panels.follow(workspace, id);
|
|
5835
|
+
return {
|
|
5836
|
+
units,
|
|
5837
|
+
text: id === void 0 ? `这个工作区改过的行全部清掉 —— 每一行都回到跟着机器级默认板走(那份板以后改了,这里跟着变)。\n${render(units, workspace, scope)}` : `${id} 放回跟随 —— 它不再由这个工作区定,改跟机器级默认板走。\n${render(units, workspace, scope)}`
|
|
5838
|
+
};
|
|
5839
|
+
}
|
|
5025
5840
|
if (action === "reset") {
|
|
5026
5841
|
const units = panels.reset(workspace, scope);
|
|
5027
5842
|
return {
|
|
5028
5843
|
units,
|
|
5029
|
-
text: scope === "machine" ?
|
|
5844
|
+
text: scope === "machine" ? `机器级默认板已清空 —— 从今往后,没在这一行改过的工作区默认什么都不开。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `这个工作区改过的行已全部清掉 —— 每一行都回到跟着机器级默认板走。\n${render(units, workspace, scope)}`
|
|
5030
5845
|
};
|
|
5031
5846
|
}
|
|
5032
5847
|
if (id === void 0) throw new Error(`${action} 需要一个 id(例如 coding-issues)。当前套件:${list().map((entry) => entry.id).join(", ")}`);
|
|
@@ -5058,29 +5873,37 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
5058
5873
|
var OWNED = [{
|
|
5059
5874
|
unit: panelsUnit.id,
|
|
5060
5875
|
manifest: panelsUnit,
|
|
5061
|
-
mount({ ctx, logger, manifests, ownedUnits, opencliCommand, probe, store, values }) {
|
|
5876
|
+
mount({ ctx, logger, manifests, ownedUnits, installable, opencliCommand, probe, store, values }) {
|
|
5877
|
+
const ledger = createPluginLedger({
|
|
5878
|
+
store,
|
|
5879
|
+
home: isolatedHome()
|
|
5880
|
+
});
|
|
5062
5881
|
const panels = createPanelsService({
|
|
5063
5882
|
manifests,
|
|
5064
5883
|
store,
|
|
5065
5884
|
owned: ownedUnits,
|
|
5885
|
+
plugins: () => pluginPanelRefs(ledger),
|
|
5066
5886
|
logger
|
|
5067
5887
|
});
|
|
5068
5888
|
ctx.provide(PANELS_SERVICE, panels);
|
|
5069
5889
|
const profileDir = profileDirOf(ctx);
|
|
5070
5890
|
const shell = ctx.get("shell");
|
|
5071
|
-
const
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5891
|
+
const moduleManifests = () => {
|
|
5892
|
+
const list = manifests();
|
|
5893
|
+
if (installable === void 0) return list.filter(hasEnvSupply);
|
|
5894
|
+
const ids = new Set(installable());
|
|
5895
|
+
return list.filter((manifest) => ids.has(manifest.id));
|
|
5896
|
+
};
|
|
5075
5897
|
const market = createMarketService({
|
|
5076
|
-
manifests:
|
|
5077
|
-
manifestsNow:
|
|
5898
|
+
manifests: moduleManifests(),
|
|
5899
|
+
manifestsNow: moduleManifests,
|
|
5078
5900
|
...probe === void 0 ? {} : { probe },
|
|
5079
5901
|
shell,
|
|
5080
5902
|
...opencliCommand === void 0 ? {} : { command: opencliCommand },
|
|
5081
5903
|
bundles: readBundleManager(ctx),
|
|
5082
5904
|
installed: (pkg) => readInstalledModule(profileDir, pkg),
|
|
5083
5905
|
modules: () => profileModules(profileDir),
|
|
5906
|
+
workbench: (pkg) => readProfilePackage(profileDir, pkg),
|
|
5084
5907
|
registries: createRegistryNotebook(profileDir),
|
|
5085
5908
|
ledger,
|
|
5086
5909
|
references: () => {
|
|
@@ -5460,6 +6283,24 @@ function bundleRegistriesHandler(ctx) {
|
|
|
5460
6283
|
};
|
|
5461
6284
|
}
|
|
5462
6285
|
var refusalLog = /* @__PURE__ */ new Map();
|
|
6286
|
+
async function workbenchVersion(input) {
|
|
6287
|
+
const read = readProfilePackage(input.dir, PLUGIN_ID);
|
|
6288
|
+
if (read === void 0) return void 0;
|
|
6289
|
+
const registry = read.registry ?? input.fallback;
|
|
6290
|
+
const queried = read.local === true || input.shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
6291
|
+
shell: input.shell,
|
|
6292
|
+
name: read.pkg,
|
|
6293
|
+
registry,
|
|
6294
|
+
force: input.force
|
|
6295
|
+
});
|
|
6296
|
+
const latest = queried !== void 0 && isNewerVersion(queried, read.version) ? queried : void 0;
|
|
6297
|
+
return {
|
|
6298
|
+
pkg: read.pkg,
|
|
6299
|
+
installed: read.version,
|
|
6300
|
+
...latest === void 0 ? {} : { latest },
|
|
6301
|
+
...read.local === true ? { local: true } : {}
|
|
6302
|
+
};
|
|
6303
|
+
}
|
|
5463
6304
|
function moduleVersionsHandler(ctx, shell) {
|
|
5464
6305
|
return async (search) => {
|
|
5465
6306
|
const manager = readBundleManager(ctx);
|
|
@@ -5472,36 +6313,44 @@ function moduleVersionsHandler(ctx, shell) {
|
|
|
5472
6313
|
const refusals = new Map((ctx.get("workbenchUnits")?.refusals() ?? []).filter((one) => one.unit !== "").map((one) => [one.unit, one.reasons]));
|
|
5473
6314
|
const force = param(search, "force") === "1";
|
|
5474
6315
|
const fallback = (await manager.registries())?.resolved ?? void 0;
|
|
6316
|
+
const modules = await Promise.all(found.map(async (module) => {
|
|
6317
|
+
const registry = module.registry ?? fallback;
|
|
6318
|
+
const latest = shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
6319
|
+
shell,
|
|
6320
|
+
name: module.pkg,
|
|
6321
|
+
registry,
|
|
6322
|
+
force
|
|
6323
|
+
});
|
|
6324
|
+
const raw = moduleDeclarationOf(dir, module.pkg);
|
|
6325
|
+
const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
|
|
6326
|
+
const refused = refusals.get(module.unit) ?? (declared.length === 0 ? void 0 : declared);
|
|
6327
|
+
if (refused === void 0) refusalLog.delete(module.pkg);
|
|
6328
|
+
else {
|
|
6329
|
+
const line = refused.join(";");
|
|
6330
|
+
if (refusalLog.get(module.pkg) !== line) {
|
|
6331
|
+
refusalLog.set(module.pkg, line);
|
|
6332
|
+
ctx.logger?.warn?.(`[dsh-workbench] ${module.pkg} 装上了但用不了:${line}`);
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
return {
|
|
6336
|
+
unit: module.unit,
|
|
6337
|
+
pkg: module.pkg,
|
|
6338
|
+
installed: module.version,
|
|
6339
|
+
...latest === void 0 ? {} : { latest },
|
|
6340
|
+
...module.local === true ? { local: true } : {},
|
|
6341
|
+
...refused === void 0 ? {} : { refused }
|
|
6342
|
+
};
|
|
6343
|
+
}));
|
|
6344
|
+
const workbench = await workbenchVersion({
|
|
6345
|
+
dir,
|
|
6346
|
+
shell,
|
|
6347
|
+
fallback,
|
|
6348
|
+
force
|
|
6349
|
+
});
|
|
5475
6350
|
return {
|
|
5476
6351
|
ok: true,
|
|
5477
|
-
modules
|
|
5478
|
-
|
|
5479
|
-
const latest = shell === void 0 || registry === void 0 ? void 0 : await latestVersion({
|
|
5480
|
-
shell,
|
|
5481
|
-
name: module.pkg,
|
|
5482
|
-
registry,
|
|
5483
|
-
force
|
|
5484
|
-
});
|
|
5485
|
-
const raw = moduleDeclarationOf(dir, module.pkg);
|
|
5486
|
-
const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
|
|
5487
|
-
const refused = refusals.get(module.unit) ?? (declared.length === 0 ? void 0 : declared);
|
|
5488
|
-
if (refused === void 0) refusalLog.delete(module.pkg);
|
|
5489
|
-
else {
|
|
5490
|
-
const line = refused.join(";");
|
|
5491
|
-
if (refusalLog.get(module.pkg) !== line) {
|
|
5492
|
-
refusalLog.set(module.pkg, line);
|
|
5493
|
-
ctx.logger?.warn?.(`[dsh-workbench] ${module.pkg} 装上了但用不了:${line}`);
|
|
5494
|
-
}
|
|
5495
|
-
}
|
|
5496
|
-
return {
|
|
5497
|
-
unit: module.unit,
|
|
5498
|
-
pkg: module.pkg,
|
|
5499
|
-
installed: module.version,
|
|
5500
|
-
...latest === void 0 ? {} : { latest },
|
|
5501
|
-
...module.local === true ? { local: true } : {},
|
|
5502
|
-
...refused === void 0 ? {} : { refused }
|
|
5503
|
-
};
|
|
5504
|
-
}))
|
|
6352
|
+
modules,
|
|
6353
|
+
...workbench === void 0 ? {} : { workbench }
|
|
5505
6354
|
};
|
|
5506
6355
|
};
|
|
5507
6356
|
}
|
|
@@ -5528,15 +6377,26 @@ async function declarationRefusal(spec, inspection, ctx, manager) {
|
|
|
5528
6377
|
});
|
|
5529
6378
|
return refusedInspection(declarationRefusalOf(inspection.name, raw));
|
|
5530
6379
|
}
|
|
5531
|
-
async function
|
|
5532
|
-
if (inspection.status !== "accepted") return void 0;
|
|
6380
|
+
async function packageOverride(spec, inspection, ctx, manager, asked) {
|
|
5533
6381
|
const shell = ctx.get("shell");
|
|
5534
|
-
const registry = typeof inspection.registry === "string" ? inspection.registry : (await manager.registries())?.resolved ?? void 0;
|
|
5535
|
-
|
|
6382
|
+
const registry = (inspection.status === "accepted" && typeof inspection.registry === "string" ? inspection.registry : void 0) ?? (typeof asked === "string" ? asked : (await manager.registries())?.resolved ?? void 0);
|
|
6383
|
+
const route = await packageRouteOf({
|
|
5536
6384
|
spec,
|
|
5537
6385
|
...shell === void 0 ? {} : { shell },
|
|
5538
6386
|
...registry === void 0 ? {} : { registry }
|
|
5539
|
-
})
|
|
6387
|
+
});
|
|
6388
|
+
if (route?.kind === "opencli-plugin") {
|
|
6389
|
+
if (inspection.status !== "refused") return void 0;
|
|
6390
|
+
const ref = splitNpmSpec(spec);
|
|
6391
|
+
return {
|
|
6392
|
+
status: "plugin",
|
|
6393
|
+
pkg: route.pkg ?? ref?.name ?? spec.trim(),
|
|
6394
|
+
registry: registry ?? null,
|
|
6395
|
+
...route.pluginName === void 0 ? {} : { pluginName: route.pluginName },
|
|
6396
|
+
...ref?.version === void 0 ? {} : { version: ref.version }
|
|
6397
|
+
};
|
|
6398
|
+
}
|
|
6399
|
+
return route?.kind === "neither" && inspection.status === "accepted" ? {
|
|
5540
6400
|
status: "refused",
|
|
5541
6401
|
problem: "not-a-module-or-plugin",
|
|
5542
6402
|
reason: refusalReason()
|
|
@@ -5560,7 +6420,8 @@ function bundleInspectHandler(ctx) {
|
|
|
5560
6420
|
message: "这个 profile 里没有插件管理器,装不了模块。"
|
|
5561
6421
|
}
|
|
5562
6422
|
};
|
|
5563
|
-
const
|
|
6423
|
+
const asked = registryOf(search);
|
|
6424
|
+
const inspection = await manager.inspect(spec, asked);
|
|
5564
6425
|
if (inspection === null) return {
|
|
5565
6426
|
ok: false,
|
|
5566
6427
|
error: {
|
|
@@ -5570,7 +6431,7 @@ function bundleInspectHandler(ctx) {
|
|
|
5570
6431
|
};
|
|
5571
6432
|
return {
|
|
5572
6433
|
ok: true,
|
|
5573
|
-
inspection: await
|
|
6434
|
+
inspection: await packageOverride(spec, inspection, ctx, manager, asked) ?? await declarationRefusal(spec, inspection, ctx, manager) ?? inspection
|
|
5574
6435
|
};
|
|
5575
6436
|
};
|
|
5576
6437
|
}
|
|
@@ -6275,6 +7136,28 @@ function withRunsRoute(result, unit) {
|
|
|
6275
7136
|
}
|
|
6276
7137
|
};
|
|
6277
7138
|
}
|
|
7139
|
+
function notedRegistry(registry, writesOf) {
|
|
7140
|
+
return { register(definition) {
|
|
7141
|
+
if (!writesOf(definition.name)) return registry.register(definition);
|
|
7142
|
+
const noted = withApprovalNote(definition.parameters, { required: true });
|
|
7143
|
+
return registry.register(noted === definition.parameters ? definition : {
|
|
7144
|
+
...definition,
|
|
7145
|
+
parameters: noted
|
|
7146
|
+
});
|
|
7147
|
+
} };
|
|
7148
|
+
}
|
|
7149
|
+
function scopedMountCtx(ctx, registry, writesOf) {
|
|
7150
|
+
const tools = notedRegistry(registry, writesOf);
|
|
7151
|
+
const get = (name) => {
|
|
7152
|
+
const value = ctx.get(name);
|
|
7153
|
+
return name === "tools" && value !== void 0 ? tools : value;
|
|
7154
|
+
};
|
|
7155
|
+
return new Proxy(ctx, { get(target, key) {
|
|
7156
|
+
if (key === "get") return get;
|
|
7157
|
+
const value = Reflect.get(target, key, target);
|
|
7158
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
7159
|
+
} });
|
|
7160
|
+
}
|
|
6278
7161
|
function failureReasonOf(error) {
|
|
6279
7162
|
if (error instanceof Error) return error.message;
|
|
6280
7163
|
return typeof error === "string" ? error : String(error);
|
|
@@ -6418,6 +7301,9 @@ function mountUnits(units, deps) {
|
|
|
6418
7301
|
continue;
|
|
6419
7302
|
}
|
|
6420
7303
|
const unit = host.manifest.id;
|
|
7304
|
+
const tools = deps.ctx.get("tools");
|
|
7305
|
+
const writes = toolUnits(deps.manifests());
|
|
7306
|
+
const mountCtx = tools === void 0 ? deps.ctx : scopedMountCtx(deps.ctx, tools, (name) => writes.get(name)?.writes === true);
|
|
6421
7307
|
const longTasks = deps.longTasks === void 0 ? void 0 : createLongTaskHost({
|
|
6422
7308
|
scope: deps.longTasks.scope,
|
|
6423
7309
|
unit,
|
|
@@ -6427,10 +7313,11 @@ function mountUnits(units, deps) {
|
|
|
6427
7313
|
try {
|
|
6428
7314
|
result = host.face.mount({
|
|
6429
7315
|
manifest: host.manifest,
|
|
6430
|
-
ctx:
|
|
7316
|
+
ctx: mountCtx,
|
|
6431
7317
|
logger: deps.logger,
|
|
6432
7318
|
manifests: deps.manifests,
|
|
6433
7319
|
ownedUnits,
|
|
7320
|
+
...deps.installable === void 0 ? {} : { installable: deps.installable },
|
|
6434
7321
|
...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
|
|
6435
7322
|
...deps.probe === void 0 ? {} : { probe: deps.probe },
|
|
6436
7323
|
...deps.values === void 0 ? {} : { values: deps.values },
|
|
@@ -6495,13 +7382,19 @@ function mountUnits(units, deps) {
|
|
|
6495
7382
|
}
|
|
6496
7383
|
//#endregion
|
|
6497
7384
|
//#region src/shared/api/host/unit-registry.ts
|
|
7385
|
+
function installableUnitsOf(registry) {
|
|
7386
|
+
return registry.all().filter((host) => host.source.kind === "package").map((host) => host.unit);
|
|
7387
|
+
}
|
|
6498
7388
|
function sourceLabel(source) {
|
|
6499
|
-
|
|
7389
|
+
if (source.kind === "owned") return "外壳自己";
|
|
7390
|
+
if (source.kind === "plugin") return "按已装 opencli 插件合成";
|
|
7391
|
+
return source.pkg;
|
|
6500
7392
|
}
|
|
6501
7393
|
function createUnitRegistry(options) {
|
|
6502
7394
|
const refusals = [];
|
|
6503
7395
|
const hosts = [];
|
|
6504
7396
|
const byUnit = /* @__PURE__ */ new Map();
|
|
7397
|
+
const failures = /* @__PURE__ */ new Map();
|
|
6505
7398
|
const listeners = /* @__PURE__ */ new Set();
|
|
6506
7399
|
function notify() {
|
|
6507
7400
|
for (const listener of listeners) listener();
|
|
@@ -6556,6 +7449,14 @@ function createUnitRegistry(options) {
|
|
|
6556
7449
|
source: { kind: "owned" }
|
|
6557
7450
|
});
|
|
6558
7451
|
},
|
|
7452
|
+
registerPlugin(face) {
|
|
7453
|
+
return add({
|
|
7454
|
+
unit: face.manifest.id,
|
|
7455
|
+
manifest: face.manifest,
|
|
7456
|
+
face,
|
|
7457
|
+
source: { kind: "plugin" }
|
|
7458
|
+
});
|
|
7459
|
+
},
|
|
6559
7460
|
all: () => hosts,
|
|
6560
7461
|
owned: () => hosts.filter((host) => host.source.kind === "owned").map((host) => host.unit),
|
|
6561
7462
|
refusals: () => refusals,
|
|
@@ -6563,7 +7464,14 @@ function createUnitRegistry(options) {
|
|
|
6563
7464
|
subscribe(listener) {
|
|
6564
7465
|
listeners.add(listener);
|
|
6565
7466
|
return () => listeners.delete(listener);
|
|
6566
|
-
}
|
|
7467
|
+
},
|
|
7468
|
+
fail(unit, reason) {
|
|
7469
|
+
failures.set(unit, reason);
|
|
7470
|
+
},
|
|
7471
|
+
recovered(unit) {
|
|
7472
|
+
failures.delete(unit);
|
|
7473
|
+
},
|
|
7474
|
+
failureOf: (unit) => failures.get(unit)
|
|
6567
7475
|
};
|
|
6568
7476
|
}
|
|
6569
7477
|
//#endregion
|
|
@@ -6581,6 +7489,564 @@ function bindRosterFacts(options) {
|
|
|
6581
7489
|
});
|
|
6582
7490
|
}
|
|
6583
7491
|
//#endregion
|
|
7492
|
+
//#region src/shared/api/host/plugin-catalog.ts
|
|
7493
|
+
function recordOf(value) {
|
|
7494
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
7495
|
+
}
|
|
7496
|
+
function textOf(value) {
|
|
7497
|
+
return typeof value === "string" ? value : "";
|
|
7498
|
+
}
|
|
7499
|
+
function stringsOf(value) {
|
|
7500
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
7501
|
+
}
|
|
7502
|
+
function argsOf(raw) {
|
|
7503
|
+
if (!Array.isArray(raw)) return [];
|
|
7504
|
+
return raw.flatMap((item) => {
|
|
7505
|
+
const record = recordOf(item);
|
|
7506
|
+
if (record === void 0) return [];
|
|
7507
|
+
const name = textOf(record["name"]).trim();
|
|
7508
|
+
if (name === "") return [];
|
|
7509
|
+
const fallback = record["default"];
|
|
7510
|
+
return [{
|
|
7511
|
+
name,
|
|
7512
|
+
type: textOf(record["type"]) || "string",
|
|
7513
|
+
required: record["required"] === true,
|
|
7514
|
+
positional: record["positional"] === true,
|
|
7515
|
+
valueRequired: record["valueRequired"] === true,
|
|
7516
|
+
choices: stringsOf(record["choices"]),
|
|
7517
|
+
...fallback === void 0 || fallback === null ? {} : { defaultValue: fallback },
|
|
7518
|
+
help: textOf(record["help"])
|
|
7519
|
+
}];
|
|
7520
|
+
});
|
|
7521
|
+
}
|
|
7522
|
+
function parsePluginRows(stdout) {
|
|
7523
|
+
const parsed = parseJsonLoose(stdout);
|
|
7524
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
7525
|
+
const rows = [];
|
|
7526
|
+
for (const item of parsed) {
|
|
7527
|
+
const record = recordOf(item);
|
|
7528
|
+
if (record === void 0) return void 0;
|
|
7529
|
+
const name = textOf(record["name"]).trim();
|
|
7530
|
+
if (name === "") return void 0;
|
|
7531
|
+
rows.push({
|
|
7532
|
+
name,
|
|
7533
|
+
version: textOf(record["version"]),
|
|
7534
|
+
commands: stringsOf(record["commands"])
|
|
7535
|
+
});
|
|
7536
|
+
}
|
|
7537
|
+
return rows;
|
|
7538
|
+
}
|
|
7539
|
+
function parseCommandRows(stdout) {
|
|
7540
|
+
const parsed = parseJsonLoose(stdout);
|
|
7541
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
7542
|
+
const rows = [];
|
|
7543
|
+
for (const item of parsed) {
|
|
7544
|
+
const record = recordOf(item);
|
|
7545
|
+
if (record === void 0) return void 0;
|
|
7546
|
+
const site = textOf(record["site"]).trim();
|
|
7547
|
+
const name = textOf(record["name"]).trim();
|
|
7548
|
+
if (site === "" || name === "") return void 0;
|
|
7549
|
+
rows.push({
|
|
7550
|
+
site,
|
|
7551
|
+
name,
|
|
7552
|
+
description: textOf(record["description"]),
|
|
7553
|
+
access: textOf(record["access"]),
|
|
7554
|
+
...textOf(record["domain"]) === "" ? {} : { domain: textOf(record["domain"]) },
|
|
7555
|
+
args: argsOf(record["args"])
|
|
7556
|
+
});
|
|
7557
|
+
}
|
|
7558
|
+
return rows;
|
|
7559
|
+
}
|
|
7560
|
+
function commandOf(plugin, row) {
|
|
7561
|
+
return {
|
|
7562
|
+
plugin,
|
|
7563
|
+
name: row.name,
|
|
7564
|
+
description: row.description,
|
|
7565
|
+
access: row.access,
|
|
7566
|
+
args: row.args
|
|
7567
|
+
};
|
|
7568
|
+
}
|
|
7569
|
+
function attributeCommands(plugins, rows) {
|
|
7570
|
+
const bySite = /* @__PURE__ */ new Map();
|
|
7571
|
+
for (const row of rows) {
|
|
7572
|
+
const site = bySite.get(row.site) ?? /* @__PURE__ */ new Map();
|
|
7573
|
+
site.set(row.name, row);
|
|
7574
|
+
bySite.set(row.site, site);
|
|
7575
|
+
}
|
|
7576
|
+
const sets = [];
|
|
7577
|
+
const problems = [];
|
|
7578
|
+
if (plugins.length > 0 && rows.length === 0) problems.push(`opencli 报了 ${String(plugins.length)} 个插件,但命令面(opencli list -f json)里一条命令都没有 —— 两份清单对不上`);
|
|
7579
|
+
for (const plugin of plugins) {
|
|
7580
|
+
const found = bySite.get(plugin.name) ?? /* @__PURE__ */ new Map();
|
|
7581
|
+
if (found.size === 0) {
|
|
7582
|
+
problems.push(`「${plugin.name}」在 opencli 插件清单里,但命令面里没有一条 site=${plugin.name} 的命令 —— 归属不了,它这次不合成工具`);
|
|
7583
|
+
continue;
|
|
7584
|
+
}
|
|
7585
|
+
const declared = plugin.commands.length > 0 ? plugin.commands : [...found.keys()];
|
|
7586
|
+
const missing = declared.filter((name) => !found.has(name));
|
|
7587
|
+
if (missing.length > 0) problems.push(`「${plugin.name}」自己的清单里有 ${String(missing.length)} 条命令在命令面里找不到:${missing.join("、")}(插件版本旧或命令面没更新)`);
|
|
7588
|
+
const commands = declared.flatMap((name) => {
|
|
7589
|
+
const row = found.get(name);
|
|
7590
|
+
return row === void 0 ? [] : [commandOf(plugin.name, row)];
|
|
7591
|
+
});
|
|
7592
|
+
if (commands.length === 0) {
|
|
7593
|
+
problems.push(`「${plugin.name}」没有任何一条能归属的命令 —— 它这次不合成工具`);
|
|
7594
|
+
continue;
|
|
7595
|
+
}
|
|
7596
|
+
sets.push({
|
|
7597
|
+
plugin: plugin.name,
|
|
7598
|
+
version: plugin.version,
|
|
7599
|
+
commands
|
|
7600
|
+
});
|
|
7601
|
+
}
|
|
7602
|
+
return {
|
|
7603
|
+
sets,
|
|
7604
|
+
problems
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
function fingerprintOf(plugins) {
|
|
7608
|
+
return plugins.map((plugin) => `${plugin.name}@${plugin.version}:${[...plugin.commands].sort().join(",")}`).sort().join("|");
|
|
7609
|
+
}
|
|
7610
|
+
function messageOf(error) {
|
|
7611
|
+
return error instanceof Error ? error.message : String(error);
|
|
7612
|
+
}
|
|
7613
|
+
function createPluginCatalog(options) {
|
|
7614
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
7615
|
+
let current = {
|
|
7616
|
+
ok: false,
|
|
7617
|
+
at: 0,
|
|
7618
|
+
sets: [],
|
|
7619
|
+
problems: ["还没探过 opencli 的插件命令面"]
|
|
7620
|
+
};
|
|
7621
|
+
let heavy;
|
|
7622
|
+
async function read(line) {
|
|
7623
|
+
const shell = options.shell;
|
|
7624
|
+
if (shell === void 0) return {
|
|
7625
|
+
ok: false,
|
|
7626
|
+
problem: "没有执行缝(shell 服务不可用),探不了这个命令面"
|
|
7627
|
+
};
|
|
7628
|
+
try {
|
|
7629
|
+
const result = await runShell(shell, line, { timeoutMs });
|
|
7630
|
+
if (result.timedOut) return {
|
|
7631
|
+
ok: false,
|
|
7632
|
+
problem: `${line} 超过 ${String(Math.round(timeoutMs / 1e3))} 秒没返回`
|
|
7633
|
+
};
|
|
7634
|
+
if (result.exitCode !== 0) {
|
|
7635
|
+
const tail = result.stderr.trim() || result.stdout.trim();
|
|
7636
|
+
return {
|
|
7637
|
+
ok: false,
|
|
7638
|
+
problem: `${line} 退出码 ${String(result.exitCode)}${tail === "" ? "" : `:${tail.split("\n").slice(-3).join(" ")}`}`
|
|
7639
|
+
};
|
|
7640
|
+
}
|
|
7641
|
+
return {
|
|
7642
|
+
ok: true,
|
|
7643
|
+
stdout: result.stdout
|
|
7644
|
+
};
|
|
7645
|
+
} catch (error) {
|
|
7646
|
+
return {
|
|
7647
|
+
ok: false,
|
|
7648
|
+
problem: `${line} 没跑起来:${messageOf(error)}`
|
|
7649
|
+
};
|
|
7650
|
+
}
|
|
7651
|
+
}
|
|
7652
|
+
function fail(problem) {
|
|
7653
|
+
current = {
|
|
7654
|
+
ok: false,
|
|
7655
|
+
at: Date.now(),
|
|
7656
|
+
sets: current.sets,
|
|
7657
|
+
problems: [problem]
|
|
7658
|
+
};
|
|
7659
|
+
return current;
|
|
7660
|
+
}
|
|
7661
|
+
async function refresh(callOptions = {}) {
|
|
7662
|
+
const pluginsRun = await read(`${options.command} plugin list -f json`);
|
|
7663
|
+
if (!pluginsRun.ok) return fail(pluginsRun.problem);
|
|
7664
|
+
const plugins = parsePluginRows(pluginsRun.stdout);
|
|
7665
|
+
if (plugins === void 0) return fail("opencli plugin list -f json 的输出读不懂(不是带 name 的 JSON 数组)");
|
|
7666
|
+
const fingerprint = fingerprintOf(plugins);
|
|
7667
|
+
if (!callOptions.force && heavy !== void 0 && heavy.fingerprint === fingerprint) {
|
|
7668
|
+
current = {
|
|
7669
|
+
ok: true,
|
|
7670
|
+
at: Date.now(),
|
|
7671
|
+
sets: heavy.sets,
|
|
7672
|
+
problems: heavy.problems
|
|
7673
|
+
};
|
|
7674
|
+
return current;
|
|
7675
|
+
}
|
|
7676
|
+
const commandsRun = await read(`${options.command} list -f json`);
|
|
7677
|
+
if (!commandsRun.ok) return fail(commandsRun.problem);
|
|
7678
|
+
const rows = parseCommandRows(commandsRun.stdout);
|
|
7679
|
+
if (rows === void 0) return fail("opencli list -f json 的输出读不懂(不是带 site / name 的 JSON 数组)");
|
|
7680
|
+
const attributed = attributeCommands(plugins, rows);
|
|
7681
|
+
heavy = {
|
|
7682
|
+
fingerprint,
|
|
7683
|
+
sets: attributed.sets,
|
|
7684
|
+
problems: attributed.problems
|
|
7685
|
+
};
|
|
7686
|
+
current = {
|
|
7687
|
+
ok: true,
|
|
7688
|
+
at: Date.now(),
|
|
7689
|
+
sets: attributed.sets,
|
|
7690
|
+
problems: attributed.problems
|
|
7691
|
+
};
|
|
7692
|
+
return current;
|
|
7693
|
+
}
|
|
7694
|
+
return {
|
|
7695
|
+
refresh,
|
|
7696
|
+
snapshot: () => current
|
|
7697
|
+
};
|
|
7698
|
+
}
|
|
7699
|
+
//#endregion
|
|
7700
|
+
//#region src/shared/api/host/plugin-login-hint.ts
|
|
7701
|
+
var NOT_LOGGED_IN = /未登录|请先登录|not logged in/i;
|
|
7702
|
+
function propertyOf$1(error, key) {
|
|
7703
|
+
return typeof error === "object" && error !== null ? error[key] : void 0;
|
|
7704
|
+
}
|
|
7705
|
+
function helpUrlOfError(error) {
|
|
7706
|
+
const raw = propertyOf$1(error, "helpUrl");
|
|
7707
|
+
if (typeof raw !== "string") return void 0;
|
|
7708
|
+
const trimmed = raw.trim();
|
|
7709
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
7710
|
+
}
|
|
7711
|
+
function isAuthFailure(error, message) {
|
|
7712
|
+
if (NOT_LOGGED_IN.test(message)) return true;
|
|
7713
|
+
return propertyOf$1(error, "code") === "auth-required";
|
|
7714
|
+
}
|
|
7715
|
+
function loginUrlOfDomain(domain) {
|
|
7716
|
+
const bare = (domain ?? "").trim().replace(/\/+$/, "");
|
|
7717
|
+
if (bare === "" || bare.includes("*")) return void 0;
|
|
7718
|
+
return /^https?:\/\//.test(bare) ? bare : `https://${bare}/login`;
|
|
7719
|
+
}
|
|
7720
|
+
function pluginLoginHint(error, domain) {
|
|
7721
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7722
|
+
if (!isAuthFailure(error, message)) return message;
|
|
7723
|
+
const url = helpUrlOfError(error) ?? loginUrlOfDomain(domain);
|
|
7724
|
+
if (url !== void 0) return `${message}\n在 opencli 那扇浏览器窗里打开 ${url} 登一次,然后重试。`;
|
|
7725
|
+
return `${message}\n在 opencli 那扇浏览器窗里登录这个站点(插件没给出具体地址,工作台拼不出来)—— 或跑一次工作台的体检/预热,然后重试。`;
|
|
7726
|
+
}
|
|
7727
|
+
//#endregion
|
|
7728
|
+
//#region src/shared/api/host/plugin-tools.ts
|
|
7729
|
+
var SCHEMA_TYPE = {
|
|
7730
|
+
int: "integer",
|
|
7731
|
+
integer: "integer",
|
|
7732
|
+
number: "number",
|
|
7733
|
+
float: "number",
|
|
7734
|
+
bool: "boolean",
|
|
7735
|
+
boolean: "boolean",
|
|
7736
|
+
string: "string"
|
|
7737
|
+
};
|
|
7738
|
+
var KEY_ARG_LIMIT = 3;
|
|
7739
|
+
function pluginToolName(plugin, command) {
|
|
7740
|
+
return `${plugin}_${command}`.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/_{2,}/g, "_").replace(/^_+|_+$/g, "");
|
|
7741
|
+
}
|
|
7742
|
+
function schemaTypeOf(arg) {
|
|
7743
|
+
return SCHEMA_TYPE[arg.type.toLowerCase()] ?? "string";
|
|
7744
|
+
}
|
|
7745
|
+
function propertyOf(arg) {
|
|
7746
|
+
const type = schemaTypeOf(arg);
|
|
7747
|
+
const enumValues = arg.choices.length > 0 ? arg.choices.map((choice) => choice) : void 0;
|
|
7748
|
+
return {
|
|
7749
|
+
type,
|
|
7750
|
+
...arg.help.trim() === "" ? {} : { description: arg.help.trim() },
|
|
7751
|
+
...enumValues === void 0 ? {} : { enum: enumValues },
|
|
7752
|
+
...arg.defaultValue === void 0 ? {} : { default: arg.defaultValue }
|
|
7753
|
+
};
|
|
7754
|
+
}
|
|
7755
|
+
function pluginToolParameters(command) {
|
|
7756
|
+
const properties = {};
|
|
7757
|
+
for (const arg of command.args) properties[arg.name] = propertyOf(arg);
|
|
7758
|
+
const required = command.args.filter((arg) => arg.required).map((arg) => arg.name);
|
|
7759
|
+
const schema = {
|
|
7760
|
+
type: "object",
|
|
7761
|
+
properties,
|
|
7762
|
+
...required.length === 0 ? {} : { required },
|
|
7763
|
+
additionalProperties: false
|
|
7764
|
+
};
|
|
7765
|
+
return pluginToolWrites(command) ? withApprovalNote(schema, { required: true }) : schema;
|
|
7766
|
+
}
|
|
7767
|
+
function pluginToolDescription(command) {
|
|
7768
|
+
const described = command.description.trim();
|
|
7769
|
+
return described === "" ? `${command.plugin} ${command.name}(opencli 命令)` : described;
|
|
7770
|
+
}
|
|
7771
|
+
function pluginToolWrites(command) {
|
|
7772
|
+
return command.access !== "read";
|
|
7773
|
+
}
|
|
7774
|
+
function valuesOf(raw) {
|
|
7775
|
+
return typeof raw === "object" && raw !== null ? raw : {};
|
|
7776
|
+
}
|
|
7777
|
+
function pluginToolArgv(command, raw) {
|
|
7778
|
+
const values = valuesOf(raw);
|
|
7779
|
+
const argv = [];
|
|
7780
|
+
for (const arg of command.args) {
|
|
7781
|
+
if (!arg.positional) continue;
|
|
7782
|
+
const value = values[arg.name];
|
|
7783
|
+
if (value === void 0 || value === null) continue;
|
|
7784
|
+
argv.push(String(value));
|
|
7785
|
+
}
|
|
7786
|
+
for (const arg of command.args) {
|
|
7787
|
+
if (arg.positional) continue;
|
|
7788
|
+
const value = values[arg.name];
|
|
7789
|
+
if (value === void 0 || value === null) continue;
|
|
7790
|
+
if (typeof value === "boolean") argv.push(`--${arg.name}`, value ? "true" : "false");
|
|
7791
|
+
else if (Array.isArray(value)) argv.push(`--${arg.name}`, value.map((item) => String(item)).join(","));
|
|
7792
|
+
else argv.push(`--${arg.name}`, String(value));
|
|
7793
|
+
}
|
|
7794
|
+
return argv;
|
|
7795
|
+
}
|
|
7796
|
+
function pluginKeyArgs(command, raw) {
|
|
7797
|
+
const values = valuesOf(raw);
|
|
7798
|
+
const ordered = [...command.args.filter((arg) => arg.required), ...command.args.filter((arg) => !arg.required)];
|
|
7799
|
+
const parts = [];
|
|
7800
|
+
for (const arg of ordered) {
|
|
7801
|
+
const value = values[arg.name];
|
|
7802
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
7803
|
+
parts.push(`${arg.name}=${String(value)}`);
|
|
7804
|
+
if (parts.length === KEY_ARG_LIMIT) break;
|
|
7805
|
+
}
|
|
7806
|
+
return parts.length === 0 ? "没有带参数" : parts.join("、");
|
|
7807
|
+
}
|
|
7808
|
+
function pluginApprovalQuestion(command, raw) {
|
|
7809
|
+
const key = pluginKeyArgs(command, raw);
|
|
7810
|
+
return {
|
|
7811
|
+
zh: `允许执行 ${command.plugin} 的 ${command.name}(${key})吗?`,
|
|
7812
|
+
en: `Run ${command.plugin} ${command.name} (${key})?`
|
|
7813
|
+
};
|
|
7814
|
+
}
|
|
7815
|
+
function pluginApprovalFor(command) {
|
|
7816
|
+
const denied = command.access === "read";
|
|
7817
|
+
return (call) => {
|
|
7818
|
+
if (denied) return { kind: "read" };
|
|
7819
|
+
const question = pluginApprovalQuestion(command, call.arguments);
|
|
7820
|
+
return {
|
|
7821
|
+
kind: "ask",
|
|
7822
|
+
reason: command.access === "write" ? `${command.plugin} ${command.name} 在 opencli 里标着 write` : `${command.plugin} ${command.name} 没有拿到 access(fail-safe:按写处理)`,
|
|
7823
|
+
question,
|
|
7824
|
+
fallback: question
|
|
7825
|
+
};
|
|
7826
|
+
};
|
|
7827
|
+
}
|
|
7828
|
+
//#endregion
|
|
7829
|
+
//#region src/shared/api/host/plugin-units.ts
|
|
7830
|
+
var TOOL_OUTPUT_SCHEMA = {
|
|
7831
|
+
type: "object",
|
|
7832
|
+
properties: {},
|
|
7833
|
+
additionalProperties: true
|
|
7834
|
+
};
|
|
7835
|
+
function signatureOf(version, commands) {
|
|
7836
|
+
return [version, ...commands.map((command) => `${command.name}:${command.access}:${command.args.map((arg) => `${arg.name}${arg.required ? "!" : ""}`).join("+")}`)].join("|");
|
|
7837
|
+
}
|
|
7838
|
+
function toolsOf(plugin, commands, taken) {
|
|
7839
|
+
const tools = [];
|
|
7840
|
+
const problems = [];
|
|
7841
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
7842
|
+
for (const command of commands) {
|
|
7843
|
+
const name = pluginToolName(plugin, command.name);
|
|
7844
|
+
if (taken.has(name) || claimed.has(name)) {
|
|
7845
|
+
problems.push(`工具名 ${name}(${plugin} ${command.name})已经被占着 —— 这条命令这次不合成(先来的那份说了算)`);
|
|
7846
|
+
continue;
|
|
7847
|
+
}
|
|
7848
|
+
claimed.add(name);
|
|
7849
|
+
tools.push({
|
|
7850
|
+
name,
|
|
7851
|
+
command
|
|
7852
|
+
});
|
|
7853
|
+
}
|
|
7854
|
+
return {
|
|
7855
|
+
tools,
|
|
7856
|
+
problems
|
|
7857
|
+
};
|
|
7858
|
+
}
|
|
7859
|
+
function planOf(options, registered) {
|
|
7860
|
+
const sets = new Map(options.catalog.snapshot().sets.map((set) => [set.plugin, set]));
|
|
7861
|
+
const mine = new Set([...registered.values()].flatMap((item) => item.tools));
|
|
7862
|
+
const taken = new Set([...options.toolNames?.() ?? []].filter((name) => !mine.has(name)));
|
|
7863
|
+
const units = [];
|
|
7864
|
+
const rows = [];
|
|
7865
|
+
const problems = [];
|
|
7866
|
+
for (const row of options.rows()) {
|
|
7867
|
+
const plugin = row.name?.trim() ?? "";
|
|
7868
|
+
if (plugin === "") {
|
|
7869
|
+
problems.push(`面板上的插件行 ${row.id}(${row.pkg})没有 opencli 插件名 —— 认不出它,这次不给它合成工具`);
|
|
7870
|
+
continue;
|
|
7871
|
+
}
|
|
7872
|
+
const set = sets.get(plugin);
|
|
7873
|
+
if (set === void 0) {
|
|
7874
|
+
problems.push(`已装插件「${plugin}」在 opencli 的插件清单 / 命令面里找不到 —— 归属不了,这次不给它合成工具`);
|
|
7875
|
+
continue;
|
|
7876
|
+
}
|
|
7877
|
+
sets.delete(plugin);
|
|
7878
|
+
const declared = options.declared(plugin);
|
|
7879
|
+
const overlapped = set.commands.filter((command) => declared.has(command.name));
|
|
7880
|
+
if (overlapped.length > 0) problems.push(`「${plugin}」有 ${overlapped.length} 条命令由已装模块自己提供(${overlapped.map((command) => command.name).join("、")})—— 通用桥不重复合成`);
|
|
7881
|
+
const commands = set.commands.filter((command) => !declared.has(command.name));
|
|
7882
|
+
const built = toolsOf(plugin, commands, taken);
|
|
7883
|
+
problems.push(...built.problems);
|
|
7884
|
+
for (const tool of built.tools) taken.add(tool.name);
|
|
7885
|
+
if (built.tools.length === 0 && commands.length === set.commands.length) problems.push(`「${plugin}」的命令一条都没能合成工具`);
|
|
7886
|
+
rows.push({
|
|
7887
|
+
plugin,
|
|
7888
|
+
pkg: row.pkg,
|
|
7889
|
+
version: set.version,
|
|
7890
|
+
commands: set.commands.length,
|
|
7891
|
+
synthesized: built.tools.length,
|
|
7892
|
+
tools: built.tools.map((tool) => tool.name)
|
|
7893
|
+
});
|
|
7894
|
+
if (built.tools.length === 0) continue;
|
|
7895
|
+
units.push({
|
|
7896
|
+
id: row.id,
|
|
7897
|
+
plugin,
|
|
7898
|
+
pkg: row.pkg,
|
|
7899
|
+
version: set.version,
|
|
7900
|
+
signature: signatureOf(set.version, commands),
|
|
7901
|
+
commands,
|
|
7902
|
+
tools: built.tools
|
|
7903
|
+
});
|
|
7904
|
+
}
|
|
7905
|
+
for (const set of sets.values()) problems.push(`opencli 里装着「${set.plugin}」(${String(set.commands.length)} 条命令),但它不在插件台账里(不是从市场装的)—— 面板上没有它的开关,它的命令这次不进工具面`);
|
|
7906
|
+
return {
|
|
7907
|
+
units,
|
|
7908
|
+
rows,
|
|
7909
|
+
problems
|
|
7910
|
+
};
|
|
7911
|
+
}
|
|
7912
|
+
function faceOf(unit, opencliFor) {
|
|
7913
|
+
const byTool = new Map(unit.tools.map((tool) => [tool.name, tool.command]));
|
|
7914
|
+
const manifest = {
|
|
7915
|
+
id: unit.id,
|
|
7916
|
+
card: null,
|
|
7917
|
+
tools: unit.tools.map((tool) => ({
|
|
7918
|
+
name: tool.name,
|
|
7919
|
+
writes: pluginToolWrites(tool.command)
|
|
7920
|
+
})),
|
|
7921
|
+
host: { inject: [] }
|
|
7922
|
+
};
|
|
7923
|
+
return {
|
|
7924
|
+
unit: unit.id,
|
|
7925
|
+
manifest,
|
|
7926
|
+
mount(deps) {
|
|
7927
|
+
const tools = deps.ctx.get("tools");
|
|
7928
|
+
if (tools === void 0) {
|
|
7929
|
+
deps.logger?.info?.(`[dsh-workbench] 没有 tools 服务,跳过 ${unit.plugin} 的命令工具注册`);
|
|
7930
|
+
return {
|
|
7931
|
+
routes: {},
|
|
7932
|
+
registerTools: () => []
|
|
7933
|
+
};
|
|
7934
|
+
}
|
|
7935
|
+
const opencli = opencliFor(unit.plugin);
|
|
7936
|
+
const definitions = unit.tools.map((tool) => ({
|
|
7937
|
+
name: tool.name,
|
|
7938
|
+
description: pluginToolDescription(tool.command),
|
|
7939
|
+
parameters: pluginToolParameters(tool.command),
|
|
7940
|
+
output: {
|
|
7941
|
+
schema: TOOL_OUTPUT_SCHEMA,
|
|
7942
|
+
render: (_args, value) => textOutput(jsonText(value))
|
|
7943
|
+
},
|
|
7944
|
+
async execute(raw, exec) {
|
|
7945
|
+
const workspace = sessionWorkspace(exec);
|
|
7946
|
+
try {
|
|
7947
|
+
return (await opencli.json([tool.command.name, ...pluginToolArgv(tool.command, raw)], workspace === null ? {} : { workdir: workspace })).value;
|
|
7948
|
+
} catch (error) {
|
|
7949
|
+
throw new Error(pluginLoginHint(error, tool.command.domain), { cause: error });
|
|
7950
|
+
}
|
|
7951
|
+
}
|
|
7952
|
+
}));
|
|
7953
|
+
return {
|
|
7954
|
+
routes: {},
|
|
7955
|
+
factsOf: () => [],
|
|
7956
|
+
registerTools: () => definitions.map((definition) => tools.register(definition)),
|
|
7957
|
+
needsApproval: (call) => {
|
|
7958
|
+
const command = call.name === void 0 ? void 0 : byTool.get(call.name);
|
|
7959
|
+
return command === void 0 ? void 0 : pluginApprovalFor(command)(call);
|
|
7960
|
+
}
|
|
7961
|
+
};
|
|
7962
|
+
}
|
|
7963
|
+
};
|
|
7964
|
+
}
|
|
7965
|
+
function reconcile(registered, units, options) {
|
|
7966
|
+
const wanted = new Map(units.map((unit) => [unit.id, unit]));
|
|
7967
|
+
for (const [id, item] of [...registered]) {
|
|
7968
|
+
const next = wanted.get(id);
|
|
7969
|
+
if (next !== void 0 && next.signature === item.signature) continue;
|
|
7970
|
+
item.drop();
|
|
7971
|
+
registered.delete(id);
|
|
7972
|
+
}
|
|
7973
|
+
for (const unit of units) {
|
|
7974
|
+
if (registered.has(unit.id)) continue;
|
|
7975
|
+
registered.set(unit.id, {
|
|
7976
|
+
signature: unit.signature,
|
|
7977
|
+
tools: unit.tools.map((tool) => tool.name),
|
|
7978
|
+
drop: options.registry.registerPlugin(faceOf(unit, options.opencliFor))
|
|
7979
|
+
});
|
|
7980
|
+
}
|
|
7981
|
+
}
|
|
7982
|
+
function createPluginUnits(options) {
|
|
7983
|
+
const registered = /* @__PURE__ */ new Map();
|
|
7984
|
+
let report = {
|
|
7985
|
+
ok: false,
|
|
7986
|
+
at: 0,
|
|
7987
|
+
plugins: [],
|
|
7988
|
+
problems: ["还没探过 opencli 的插件命令面"]
|
|
7989
|
+
};
|
|
7990
|
+
let inflight;
|
|
7991
|
+
let lastTouch = 0;
|
|
7992
|
+
let warned = "";
|
|
7993
|
+
function warn(problems) {
|
|
7994
|
+
const text = [...problems].sort().join("\n");
|
|
7995
|
+
if (text === "" || text === warned) return;
|
|
7996
|
+
warned = text;
|
|
7997
|
+
options.logger?.warn?.(`[dsh-workbench] opencli 插件命令面:\n${text}`);
|
|
7998
|
+
}
|
|
7999
|
+
async function run(force) {
|
|
8000
|
+
const probed = await options.catalog.refresh(force ? { force: true } : {});
|
|
8001
|
+
if (!probed.ok) {
|
|
8002
|
+
const problems = [...probed.problems, "这次探测没成功:上面这些插件行的工具面维持在上一份(不是\"这些插件没有命令\")"];
|
|
8003
|
+
report = {
|
|
8004
|
+
ok: false,
|
|
8005
|
+
at: Date.now(),
|
|
8006
|
+
plugins: report.plugins,
|
|
8007
|
+
problems
|
|
8008
|
+
};
|
|
8009
|
+
warn(problems);
|
|
8010
|
+
return report;
|
|
8011
|
+
}
|
|
8012
|
+
const plan = planOf(options, registered);
|
|
8013
|
+
reconcile(registered, plan.units, options);
|
|
8014
|
+
report = {
|
|
8015
|
+
ok: true,
|
|
8016
|
+
at: Date.now(),
|
|
8017
|
+
plugins: plan.rows,
|
|
8018
|
+
problems: plan.problems
|
|
8019
|
+
};
|
|
8020
|
+
warn(plan.problems);
|
|
8021
|
+
return report;
|
|
8022
|
+
}
|
|
8023
|
+
function refresh(callOptions = {}) {
|
|
8024
|
+
if (inflight !== void 0) return inflight;
|
|
8025
|
+
const task = run(callOptions.force === true).finally(() => {
|
|
8026
|
+
inflight = void 0;
|
|
8027
|
+
});
|
|
8028
|
+
inflight = task;
|
|
8029
|
+
return task;
|
|
8030
|
+
}
|
|
8031
|
+
return {
|
|
8032
|
+
refresh,
|
|
8033
|
+
touch() {
|
|
8034
|
+
const now = Date.now();
|
|
8035
|
+
if (inflight !== void 0) return;
|
|
8036
|
+
if (now - lastTouch < 3e4) return;
|
|
8037
|
+
lastTouch = now;
|
|
8038
|
+
refresh().catch((error) => {
|
|
8039
|
+
options.logger?.warn?.(`[dsh-workbench] 探 opencli 插件命令面时抛了错:${String(error)}`);
|
|
8040
|
+
});
|
|
8041
|
+
},
|
|
8042
|
+
report: () => report,
|
|
8043
|
+
dispose() {
|
|
8044
|
+
for (const item of registered.values()) item.drop();
|
|
8045
|
+
registered.clear();
|
|
8046
|
+
}
|
|
8047
|
+
};
|
|
8048
|
+
}
|
|
8049
|
+
//#endregion
|
|
6584
8050
|
//#region src/shared/api/host/tool-scope.ts
|
|
6585
8051
|
function agentOf(value) {
|
|
6586
8052
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
@@ -6696,6 +8162,7 @@ function seedWorkbenchRuntime(ctx) {
|
|
|
6696
8162
|
function apply(ctx) {
|
|
6697
8163
|
const facts = createFactLog();
|
|
6698
8164
|
const values = createValueHandoff();
|
|
8165
|
+
const panelState = createPanelStateStore();
|
|
6699
8166
|
seedWorkbenchRuntime(ctx);
|
|
6700
8167
|
const opencli = opencliCommandOf(ctx);
|
|
6701
8168
|
const routes = {
|
|
@@ -6708,23 +8175,17 @@ function apply(ctx) {
|
|
|
6708
8175
|
};
|
|
6709
8176
|
const renderers = /* @__PURE__ */ new Map();
|
|
6710
8177
|
const shell = ctx.get("shell");
|
|
6711
|
-
let roster;
|
|
6712
8178
|
const profileDir = profileDirOf(ctx);
|
|
6713
8179
|
const units = createUnitRegistry({
|
|
6714
|
-
declarationOf: (
|
|
6715
|
-
if (roster === void 0) roster = new Map(profileModules(profileDir).map((row) => [row.unit, row.pkg]));
|
|
6716
|
-
const pkg = roster.get(unit);
|
|
6717
|
-
return pkg === void 0 ? void 0 : {
|
|
6718
|
-
pkg,
|
|
6719
|
-
declaration: moduleDeclarationOf(profileDir, pkg)
|
|
6720
|
-
};
|
|
6721
|
-
},
|
|
8180
|
+
declarationOf: createDeclarationLookup(profileDir),
|
|
6722
8181
|
logger: ctx.logger
|
|
6723
8182
|
});
|
|
6724
8183
|
ctx.provide(UNITS_SERVICE, units);
|
|
8184
|
+
ctx.provide(PANEL_STATE_SERVICE, panelState);
|
|
6725
8185
|
routes[UNIT_MANIFESTS_ROUTE] = () => ({
|
|
6726
8186
|
ok: true,
|
|
6727
|
-
manifests: units.manifests()
|
|
8187
|
+
manifests: units.manifests(),
|
|
8188
|
+
installable: installableUnitsOf(units)
|
|
6728
8189
|
});
|
|
6729
8190
|
assembleHostUnits(units);
|
|
6730
8191
|
ctx.inject(["workspaceRegistry"], (scoped) => {
|
|
@@ -6767,6 +8228,23 @@ function apply(ctx) {
|
|
|
6767
8228
|
deniedTools: (workspace) => deniedNow(workspace),
|
|
6768
8229
|
logger: ctx.logger
|
|
6769
8230
|
});
|
|
8231
|
+
const pluginTools = shell === void 0 ? void 0 : createPluginUnits({
|
|
8232
|
+
registry: units,
|
|
8233
|
+
catalog: createPluginCatalog({
|
|
8234
|
+
shell,
|
|
8235
|
+
command: opencli.command
|
|
8236
|
+
}),
|
|
8237
|
+
rows: () => ctx.get("workbenchPanels")?.listPlugins(null) ?? [],
|
|
8238
|
+
declared: (plugin) => declaredPluginCommands(profileDir).get(plugin) ?? /* @__PURE__ */ new Set(),
|
|
8239
|
+
opencliFor: (plugin) => createOpencli({
|
|
8240
|
+
shell,
|
|
8241
|
+
plugin,
|
|
8242
|
+
command: opencli.command,
|
|
8243
|
+
logger: ctx.logger
|
|
8244
|
+
}),
|
|
8245
|
+
toolNames: () => new Set(unitsOfTool().keys()),
|
|
8246
|
+
logger: ctx.logger
|
|
8247
|
+
});
|
|
6770
8248
|
const mounted = /* @__PURE__ */ new Map();
|
|
6771
8249
|
const mountFailures = /* @__PURE__ */ new Map();
|
|
6772
8250
|
const factsOfMap = /* @__PURE__ */ new Map();
|
|
@@ -6781,6 +8259,7 @@ function apply(ctx) {
|
|
|
6781
8259
|
if (item === void 0) return;
|
|
6782
8260
|
mounted.delete(id);
|
|
6783
8261
|
mountFailures.delete(id);
|
|
8262
|
+
units.recovered(id);
|
|
6784
8263
|
for (const dispose of item.toolDisposers) dispose?.();
|
|
6785
8264
|
item.gateOff?.();
|
|
6786
8265
|
item.plan.disposeLongTasks();
|
|
@@ -6799,6 +8278,7 @@ function apply(ctx) {
|
|
|
6799
8278
|
logger: ctx.logger,
|
|
6800
8279
|
manifests: liveManifests,
|
|
6801
8280
|
ownedUnits: () => units.owned(),
|
|
8281
|
+
installable: () => installableUnitsOf(units),
|
|
6802
8282
|
store,
|
|
6803
8283
|
opencliCommand: opencli.command,
|
|
6804
8284
|
probe: (unit, options) => probeOne(unit, options?.force ?? false),
|
|
@@ -6813,6 +8293,7 @@ function apply(ctx) {
|
|
|
6813
8293
|
reason: failureReasonOf(error),
|
|
6814
8294
|
host
|
|
6815
8295
|
});
|
|
8296
|
+
units.fail(id, failureReasonOf(error));
|
|
6816
8297
|
ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(装配期):${failureReasonOf(error)}`);
|
|
6817
8298
|
return;
|
|
6818
8299
|
}
|
|
@@ -6845,6 +8326,7 @@ function apply(ctx) {
|
|
|
6845
8326
|
gateOff: one.gate?.subscribe(() => toolScope.reapply())
|
|
6846
8327
|
});
|
|
6847
8328
|
mountFailures.delete(id);
|
|
8329
|
+
units.recovered(id);
|
|
6848
8330
|
} catch (error) {
|
|
6849
8331
|
for (const route of added) delete routes[route];
|
|
6850
8332
|
for (const kind of one.contextRenderers.keys()) renderers.delete(kind);
|
|
@@ -6856,6 +8338,7 @@ function apply(ctx) {
|
|
|
6856
8338
|
reason: failureReasonOf(error),
|
|
6857
8339
|
host
|
|
6858
8340
|
});
|
|
8341
|
+
units.fail(id, failureReasonOf(error));
|
|
6859
8342
|
ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(注册期):${failureReasonOf(error)}`);
|
|
6860
8343
|
}
|
|
6861
8344
|
}
|
|
@@ -6880,7 +8363,10 @@ function apply(ctx) {
|
|
|
6880
8363
|
function sync() {
|
|
6881
8364
|
const present = new Set(units.all().map((one) => one.unit));
|
|
6882
8365
|
for (const id of [...mounted.keys()]) if (!present.has(id)) unmountUnit(id);
|
|
6883
|
-
for (const id of [...mountFailures.keys()]) if (!present.has(id))
|
|
8366
|
+
for (const id of [...mountFailures.keys()]) if (!present.has(id)) {
|
|
8367
|
+
mountFailures.delete(id);
|
|
8368
|
+
units.recovered(id);
|
|
8369
|
+
}
|
|
6884
8370
|
for (const host of units.all()) {
|
|
6885
8371
|
if (mounted.has(host.unit)) continue;
|
|
6886
8372
|
const already = mountFailures.get(host.unit);
|
|
@@ -6920,6 +8406,23 @@ function apply(ctx) {
|
|
|
6920
8406
|
});
|
|
6921
8407
|
}
|
|
6922
8408
|
sync();
|
|
8409
|
+
if (pluginTools !== void 0) {
|
|
8410
|
+
const health = routes.health;
|
|
8411
|
+
routes.health = async (search) => ({
|
|
8412
|
+
...await health?.(search),
|
|
8413
|
+
pluginTools: pluginTools.report()
|
|
8414
|
+
});
|
|
8415
|
+
ctx.effect(() => {
|
|
8416
|
+
pluginTools.refresh().catch((error) => {
|
|
8417
|
+
warn(ctx, `探 opencli 插件的命令面时抛了错:${String(error)}`);
|
|
8418
|
+
});
|
|
8419
|
+
return () => pluginTools.dispose();
|
|
8420
|
+
}, `${PLUGIN_ID}: 已装且上板的 opencli 插件各合成一个单元(工具面照旧由面板那道闸收窄)`);
|
|
8421
|
+
ctx.effect(() => {
|
|
8422
|
+
const off = ctx.get(PANELS_SERVICE)?.subscribe?.(() => pluginTools.touch());
|
|
8423
|
+
return () => off?.();
|
|
8424
|
+
}, `${PLUGIN_ID}: 面板配置一动就重探插件命令面(装上 / 收起都在那一条链上)`);
|
|
8425
|
+
}
|
|
6923
8426
|
ctx.effect(() => () => {
|
|
6924
8427
|
for (const id of [...mounted.keys()]) unmountUnit(id);
|
|
6925
8428
|
for (const dispose of undoPost) dispose?.();
|
|
@@ -6949,11 +8452,15 @@ function apply(ctx) {
|
|
|
6949
8452
|
return () => off?.();
|
|
6950
8453
|
}, `${PLUGIN_ID}: 事实归属(读工具与写工具同一处声明)`);
|
|
6951
8454
|
ctx.effect(() => {
|
|
6952
|
-
const off = ctx.on?.("agent/created", (payload) =>
|
|
8455
|
+
const off = ctx.on?.("agent/created", (payload) => {
|
|
8456
|
+
toolScope.apply(payload?.agent);
|
|
8457
|
+
pluginTools?.touch();
|
|
8458
|
+
});
|
|
6953
8459
|
return () => off?.();
|
|
6954
8460
|
}, `${PLUGIN_ID}: agent 的工具面按工作区收窄(创建时)`);
|
|
6955
8461
|
ctx.effect(() => {
|
|
6956
8462
|
const off = ctx.on?.("system-prompt/assemble", async (_assembly, context, next) => {
|
|
8463
|
+
pluginTools?.touch();
|
|
6957
8464
|
const resolved = await next();
|
|
6958
8465
|
return toolScope.narrow(resolved, context?.scope);
|
|
6959
8466
|
});
|
|
@@ -7006,6 +8513,7 @@ function apply(ctx) {
|
|
|
7006
8513
|
return () => off?.();
|
|
7007
8514
|
}, `${PLUGIN_ID}: 对话引用注入`);
|
|
7008
8515
|
ctx.effect(() => registerFactStream(ctx, facts), `${PLUGIN_ID}: 变更流`);
|
|
8516
|
+
ctx.effect(() => registerPanelState(ctx, panelState), `${PLUGIN_ID}: 面板运行态上报(浏览器半侧报自己的取数与渲染收尾)`);
|
|
7009
8517
|
ctx.effect(() => registerApi(ctx, routes), `${PLUGIN_ID}: HTTP 投影`);
|
|
7010
8518
|
}
|
|
7011
8519
|
//#endregion
|