@dsh-workbench/core 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/bin/dsh-workbench +10 -43
- package/client.js +794 -237
- package/dist/client/client.body.js +794 -237
- package/dist/client/index.js +795 -238
- package/dist/host/index.js +380 -47
- package/dist/types/app/client.d.ts +309 -24
- package/dist/types/app/layouts/config-sync/ConfigSyncSection.d.ts +5 -0
- package/dist/types/app/layouts/sidebar/workspace-groups.d.ts +4 -1
- package/dist/types/modules/sessions/index.d.ts +2 -1
- package/dist/types/modules/sessions/model/scroll-edges.d.ts +10 -0
- package/dist/types/modules/sessions/model/scroll-edges.test.d.ts +1 -0
- package/dist/types/modules/sessions/model/session-node.d.ts +2 -0
- package/dist/types/shared/api/config-sync.d.ts +37 -0
- package/dist/types/shared/api/config-sync.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/config-sync.test.d.ts +1 -0
- package/dist/types/shared/api/host/config-sync.d.ts +46 -0
- package/docs/HANDOFF.md +17 -12
- package/docs/OPERATIONS.md +8 -6
- package/package.json +2 -2
- package/scripts/seed-profile.sh +12 -85
package/dist/host/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import path, { delimiter, dirname, join, resolve, sep } from "node:path";
|
|
2
|
+
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path, { basename, delimiter, dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -234,6 +234,9 @@ function parseJsonLoose(text) {
|
|
|
234
234
|
return;
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
var DAY = 864e5;
|
|
238
|
+
30 * DAY;
|
|
239
|
+
365 * DAY;
|
|
237
240
|
var PANEL_STATE_MAX_TEXT = 2e3;
|
|
238
241
|
var PHASES = [
|
|
239
242
|
"idle",
|
|
@@ -244,7 +247,7 @@ var PHASES = [
|
|
|
244
247
|
function asRecord$10(value) {
|
|
245
248
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
246
249
|
}
|
|
247
|
-
function textOf$
|
|
250
|
+
function textOf$7(value, max) {
|
|
248
251
|
if (typeof value !== "string") return "";
|
|
249
252
|
const trimmed = value.trim();
|
|
250
253
|
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
@@ -257,8 +260,8 @@ function atOf(value) {
|
|
|
257
260
|
function errorOf(raw) {
|
|
258
261
|
const record = asRecord$10(raw);
|
|
259
262
|
if (record === void 0) return void 0;
|
|
260
|
-
const code = textOf$
|
|
261
|
-
const message = textOf$
|
|
263
|
+
const code = textOf$7(record["code"], 120);
|
|
264
|
+
const message = textOf$7(record["message"], PANEL_STATE_MAX_TEXT);
|
|
262
265
|
if (code === "" && message === "") return void 0;
|
|
263
266
|
return {
|
|
264
267
|
code: code === "" ? "unknown" : code,
|
|
@@ -271,7 +274,7 @@ function parseCardsReport(payload) {
|
|
|
271
274
|
ok: false,
|
|
272
275
|
message: "请求体要是一个对象"
|
|
273
276
|
};
|
|
274
|
-
const workspace = textOf$
|
|
277
|
+
const workspace = textOf$7(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
275
278
|
if (workspace === "") return {
|
|
276
279
|
ok: false,
|
|
277
280
|
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
@@ -285,9 +288,9 @@ function parseCardsReport(payload) {
|
|
|
285
288
|
for (const item of raw.slice(0, 32)) {
|
|
286
289
|
const card = asRecord$10(item);
|
|
287
290
|
if (card === void 0) continue;
|
|
288
|
-
const unit = textOf$
|
|
289
|
-
const route = textOf$
|
|
290
|
-
const phase = textOf$
|
|
291
|
+
const unit = textOf$7(card["unit"], 200);
|
|
292
|
+
const route = textOf$7(card["route"], 400);
|
|
293
|
+
const phase = textOf$7(card["phase"], 20);
|
|
291
294
|
const at = atOf(card["at"]);
|
|
292
295
|
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
293
296
|
const error = errorOf(card["error"]);
|
|
@@ -317,9 +320,9 @@ function parseCrashReport(payload) {
|
|
|
317
320
|
ok: false,
|
|
318
321
|
message: "请求体要是一个对象"
|
|
319
322
|
};
|
|
320
|
-
const workspace = textOf$
|
|
321
|
-
const unit = textOf$
|
|
322
|
-
const reason = textOf$
|
|
323
|
+
const workspace = textOf$7(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
324
|
+
const unit = textOf$7(record["unit"], 200);
|
|
325
|
+
const reason = textOf$7(record["reason"], PANEL_STATE_MAX_TEXT);
|
|
323
326
|
if (workspace === "") return {
|
|
324
327
|
ok: false,
|
|
325
328
|
message: "workspace 不能是空的"
|
|
@@ -565,20 +568,20 @@ var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
|
565
568
|
function asRecord$9(value) {
|
|
566
569
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
567
570
|
}
|
|
568
|
-
function textOf$
|
|
571
|
+
function textOf$6(value) {
|
|
569
572
|
return typeof value === "string" ? value.trim() : "";
|
|
570
573
|
}
|
|
571
574
|
function codeOf(page) {
|
|
572
575
|
const record = asRecord$9(page);
|
|
573
|
-
return record === void 0 ? "" : textOf$
|
|
576
|
+
return record === void 0 ? "" : textOf$6(record["code"]);
|
|
574
577
|
}
|
|
575
578
|
function messageOf$2(page) {
|
|
576
579
|
const record = asRecord$9(page);
|
|
577
|
-
return record === void 0 ? "" : textOf$
|
|
580
|
+
return record === void 0 ? "" : textOf$6(record["message"]);
|
|
578
581
|
}
|
|
579
582
|
function hintOf$1(page) {
|
|
580
583
|
const record = asRecord$9(page);
|
|
581
|
-
const hint = record === void 0 ? "" : textOf$
|
|
584
|
+
const hint = record === void 0 ? "" : textOf$6(record["hint"]);
|
|
582
585
|
return hint === "" ? void 0 : hint;
|
|
583
586
|
}
|
|
584
587
|
function pageFailed(page) {
|
|
@@ -809,7 +812,7 @@ var DEFAULT_TEXT_MAX = 200;
|
|
|
809
812
|
function asRecord$8(value) {
|
|
810
813
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
811
814
|
}
|
|
812
|
-
function textOf$
|
|
815
|
+
function textOf$5(value) {
|
|
813
816
|
return typeof value === "string" ? value.trim() : "";
|
|
814
817
|
}
|
|
815
818
|
function entriesOf(value) {
|
|
@@ -822,8 +825,8 @@ function entriesOf(value) {
|
|
|
822
825
|
if (item === void 0) continue;
|
|
823
826
|
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
824
827
|
entries.push({
|
|
825
|
-
text: textOf$
|
|
826
|
-
tag: textOf$
|
|
828
|
+
text: textOf$5(item["text"]),
|
|
829
|
+
tag: textOf$5(item["tag"]),
|
|
827
830
|
nth
|
|
828
831
|
});
|
|
829
832
|
}
|
|
@@ -832,7 +835,7 @@ function entriesOf(value) {
|
|
|
832
835
|
function reasonOf(stdout, stderr) {
|
|
833
836
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
834
837
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
835
|
-
const message = error === void 0 ? "" : textOf$
|
|
838
|
+
const message = error === void 0 ? "" : textOf$5(error["message"]);
|
|
836
839
|
if (message !== "") return message;
|
|
837
840
|
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
838
841
|
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
@@ -840,7 +843,7 @@ function reasonOf(stdout, stderr) {
|
|
|
840
843
|
function hintOf(stdout) {
|
|
841
844
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
842
845
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
843
|
-
const hint = error === void 0 ? "" : textOf$
|
|
846
|
+
const hint = error === void 0 ? "" : textOf$5(error["hint"]);
|
|
844
847
|
return hint === "" ? void 0 : hint;
|
|
845
848
|
}
|
|
846
849
|
function sameOrigin(left, right) {
|
|
@@ -854,7 +857,7 @@ function urlOf(stdout) {
|
|
|
854
857
|
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
855
858
|
if (bare.startsWith("{")) {
|
|
856
859
|
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
857
|
-
const url = parsed === void 0 ? "" : textOf$
|
|
860
|
+
const url = parsed === void 0 ? "" : textOf$5(parsed["url"]);
|
|
858
861
|
if (url !== "") return url;
|
|
859
862
|
}
|
|
860
863
|
return bare;
|
|
@@ -1869,7 +1872,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
1869
1872
|
function ledgerKeyOf(entry) {
|
|
1870
1873
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1871
1874
|
}
|
|
1872
|
-
function textOf$
|
|
1875
|
+
function textOf$4(value) {
|
|
1873
1876
|
if (typeof value !== "string") return void 0;
|
|
1874
1877
|
const trimmed = value.trim();
|
|
1875
1878
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -1877,13 +1880,13 @@ function textOf$3(value) {
|
|
|
1877
1880
|
function entryOf(value) {
|
|
1878
1881
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1879
1882
|
const box = value;
|
|
1880
|
-
const opencliHome = textOf$
|
|
1881
|
-
const pkg = textOf$
|
|
1882
|
-
const installedAt = textOf$
|
|
1883
|
+
const opencliHome = textOf$4(box["opencliHome"]);
|
|
1884
|
+
const pkg = textOf$4(box["package"]);
|
|
1885
|
+
const installedAt = textOf$4(box["installedAt"]);
|
|
1883
1886
|
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
1884
|
-
const name = textOf$
|
|
1885
|
-
const registry = textOf$
|
|
1886
|
-
const releasedAt = textOf$
|
|
1887
|
+
const name = textOf$4(box["name"]);
|
|
1888
|
+
const registry = textOf$4(box["registry"]);
|
|
1889
|
+
const releasedAt = textOf$4(box["releasedAt"]);
|
|
1887
1890
|
return {
|
|
1888
1891
|
opencliHome,
|
|
1889
1892
|
package: pkg,
|
|
@@ -3269,7 +3272,7 @@ function reconOf(commands, plugins, keywords) {
|
|
|
3269
3272
|
};
|
|
3270
3273
|
return {
|
|
3271
3274
|
...withNext,
|
|
3272
|
-
text: textOf$
|
|
3275
|
+
text: textOf$3(withNext)
|
|
3273
3276
|
};
|
|
3274
3277
|
}
|
|
3275
3278
|
function nextOf(answer) {
|
|
@@ -3282,7 +3285,7 @@ function nextOf(answer) {
|
|
|
3282
3285
|
if (plugin !== void 0) return `这台机器另装的插件 ${plugin.name} 里有匹配:opencli ${plugin.name} --help 看全部命令`;
|
|
3283
3286
|
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:workbench_diy action=probe(script 读接口 / css 读 DOM)";
|
|
3284
3287
|
}
|
|
3285
|
-
function textOf$
|
|
3288
|
+
function textOf$3(answer) {
|
|
3286
3289
|
const lines = [];
|
|
3287
3290
|
if (answer.sites.length > 0) {
|
|
3288
3291
|
lines.push(`opencli 自带这 ${String(answer.sites.length)} 组能力(不用装、不用点头):`);
|
|
@@ -5463,16 +5466,16 @@ function applyPanelsPref(panels, payload) {
|
|
|
5463
5466
|
//#region src/modules/panels/model/handoff.ts
|
|
5464
5467
|
var PANELS_FACT = "panels";
|
|
5465
5468
|
var CAPABILITY_FACT = "capability";
|
|
5466
|
-
function textOf$
|
|
5469
|
+
function textOf$2(value) {
|
|
5467
5470
|
return typeof value === "string" ? value.trim() : "";
|
|
5468
5471
|
}
|
|
5469
5472
|
function actionOf(call) {
|
|
5470
|
-
return textOf$
|
|
5473
|
+
return textOf$2(call.args["action"]) || "list";
|
|
5471
5474
|
}
|
|
5472
5475
|
function requestOf(call) {
|
|
5473
5476
|
const action = actionOf(call);
|
|
5474
5477
|
if (!PROVISION_ACTIONS.includes(action)) return void 0;
|
|
5475
|
-
const target = textOf$
|
|
5478
|
+
const target = textOf$2(call.args["unit"]) || textOf$2(call.args["id"]);
|
|
5476
5479
|
if (target === "") return void 0;
|
|
5477
5480
|
const wanted = action;
|
|
5478
5481
|
return isDependencyAction(wanted) ? {
|
|
@@ -5484,7 +5487,7 @@ function requestOf(call) {
|
|
|
5484
5487
|
};
|
|
5485
5488
|
}
|
|
5486
5489
|
function panelsHandoffEntry(target = {}) {
|
|
5487
|
-
const path = textOf$
|
|
5490
|
+
const path = textOf$2(target.path);
|
|
5488
5491
|
return path === "" ? void 0 : {
|
|
5489
5492
|
fact: PANELS_FACT,
|
|
5490
5493
|
key: `path:${path}`
|
|
@@ -7625,7 +7628,7 @@ function bindRosterFacts(options) {
|
|
|
7625
7628
|
function recordOf(value) {
|
|
7626
7629
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
7627
7630
|
}
|
|
7628
|
-
function textOf(value) {
|
|
7631
|
+
function textOf$1(value) {
|
|
7629
7632
|
return typeof value === "string" ? value : "";
|
|
7630
7633
|
}
|
|
7631
7634
|
function stringsOf(value) {
|
|
@@ -7636,18 +7639,18 @@ function argsOf(raw) {
|
|
|
7636
7639
|
return raw.flatMap((item) => {
|
|
7637
7640
|
const record = recordOf(item);
|
|
7638
7641
|
if (record === void 0) return [];
|
|
7639
|
-
const name = textOf(record["name"]).trim();
|
|
7642
|
+
const name = textOf$1(record["name"]).trim();
|
|
7640
7643
|
if (name === "") return [];
|
|
7641
7644
|
const fallback = record["default"];
|
|
7642
7645
|
return [{
|
|
7643
7646
|
name,
|
|
7644
|
-
type: textOf(record["type"]) || "string",
|
|
7647
|
+
type: textOf$1(record["type"]) || "string",
|
|
7645
7648
|
required: record["required"] === true,
|
|
7646
7649
|
positional: record["positional"] === true,
|
|
7647
7650
|
valueRequired: record["valueRequired"] === true,
|
|
7648
7651
|
choices: stringsOf(record["choices"]),
|
|
7649
7652
|
...fallback === void 0 || fallback === null ? {} : { defaultValue: fallback },
|
|
7650
|
-
help: textOf(record["help"])
|
|
7653
|
+
help: textOf$1(record["help"])
|
|
7651
7654
|
}];
|
|
7652
7655
|
});
|
|
7653
7656
|
}
|
|
@@ -7658,11 +7661,11 @@ function parsePluginRows(stdout) {
|
|
|
7658
7661
|
for (const item of parsed) {
|
|
7659
7662
|
const record = recordOf(item);
|
|
7660
7663
|
if (record === void 0) return void 0;
|
|
7661
|
-
const name = textOf(record["name"]).trim();
|
|
7664
|
+
const name = textOf$1(record["name"]).trim();
|
|
7662
7665
|
if (name === "") return void 0;
|
|
7663
7666
|
rows.push({
|
|
7664
7667
|
name,
|
|
7665
|
-
version: textOf(record["version"]),
|
|
7668
|
+
version: textOf$1(record["version"]),
|
|
7666
7669
|
commands: stringsOf(record["commands"])
|
|
7667
7670
|
});
|
|
7668
7671
|
}
|
|
@@ -7675,17 +7678,17 @@ function parseCommandRows(stdout) {
|
|
|
7675
7678
|
for (const item of parsed) {
|
|
7676
7679
|
const record = recordOf(item);
|
|
7677
7680
|
if (record === void 0) return void 0;
|
|
7678
|
-
const site = textOf(record["site"]).trim();
|
|
7679
|
-
const name = textOf(record["name"]).trim();
|
|
7681
|
+
const site = textOf$1(record["site"]).trim();
|
|
7682
|
+
const name = textOf$1(record["name"]).trim();
|
|
7680
7683
|
if (site === "" || name === "") return void 0;
|
|
7681
7684
|
rows.push({
|
|
7682
7685
|
site,
|
|
7683
7686
|
name,
|
|
7684
|
-
description: textOf(record["description"]),
|
|
7685
|
-
access: textOf(record["access"]),
|
|
7687
|
+
description: textOf$1(record["description"]),
|
|
7688
|
+
access: textOf$1(record["access"]),
|
|
7686
7689
|
browser: record["browser"] === true,
|
|
7687
|
-
siteSession: textOf(record["siteSession"]),
|
|
7688
|
-
...textOf(record["domain"]) === "" ? {} : { domain: textOf(record["domain"]) },
|
|
7690
|
+
siteSession: textOf$1(record["siteSession"]),
|
|
7691
|
+
...textOf$1(record["domain"]) === "" ? {} : { domain: textOf$1(record["domain"]) },
|
|
7689
7692
|
args: argsOf(record["args"])
|
|
7690
7693
|
});
|
|
7691
7694
|
}
|
|
@@ -8218,6 +8221,326 @@ function createPluginUnits(options) {
|
|
|
8218
8221
|
};
|
|
8219
8222
|
}
|
|
8220
8223
|
//#endregion
|
|
8224
|
+
//#region src/shared/api/config-sync.ts
|
|
8225
|
+
var CONFIG_SYNC_ROUTE = "config-sync";
|
|
8226
|
+
var CONFIG_SYNC_RESOURCE = "configSync";
|
|
8227
|
+
//#endregion
|
|
8228
|
+
//#region src/shared/api/host/config-sync.ts
|
|
8229
|
+
var ID_LINE = /^- id:[ \t]*"?([^"\s#]+)/;
|
|
8230
|
+
var INSERT_LINE = /^- insert:[ \t]*$/;
|
|
8231
|
+
var NESTED_ID = /^[ \t]+-[ \t]*id:[ \t]*"?([^"\s#]+)/;
|
|
8232
|
+
var NAME_LINE = /^[ \t]*-?[ \t]*name:[ \t]*['"]?([^'"\s#]+)/;
|
|
8233
|
+
var COMMENT_LINE = /^[ \t]*#/;
|
|
8234
|
+
function trimmedTail(lines) {
|
|
8235
|
+
let end = lines.length;
|
|
8236
|
+
while (end > 0 && (lines[end - 1] ?? "").trim() === "") end -= 1;
|
|
8237
|
+
return lines.slice(0, end);
|
|
8238
|
+
}
|
|
8239
|
+
function matched(text, pattern) {
|
|
8240
|
+
const found = pattern.exec(text);
|
|
8241
|
+
return found === null ? void 0 : found[1];
|
|
8242
|
+
}
|
|
8243
|
+
function blockOf(body, index) {
|
|
8244
|
+
const lines = body.split("\n");
|
|
8245
|
+
const first = lines[0] ?? "";
|
|
8246
|
+
const id = matched(first, ID_LINE);
|
|
8247
|
+
const nested = lines.flatMap((line) => {
|
|
8248
|
+
const one = matched(line, NESTED_ID);
|
|
8249
|
+
return one === void 0 ? [] : [one];
|
|
8250
|
+
});
|
|
8251
|
+
const insert = id === void 0 && INSERT_LINE.test(first);
|
|
8252
|
+
const label = id ?? nested.join(", ");
|
|
8253
|
+
const key = id !== void 0 ? `id:${id}` : insert ? `insert:${nested.join(", ")}` : `block:${String(index)}`;
|
|
8254
|
+
const names = lines.flatMap((line) => {
|
|
8255
|
+
const one = matched(line, NAME_LINE);
|
|
8256
|
+
return one === void 0 ? [] : [one];
|
|
8257
|
+
});
|
|
8258
|
+
return {
|
|
8259
|
+
key,
|
|
8260
|
+
id: label === "" ? key : label,
|
|
8261
|
+
insert,
|
|
8262
|
+
body,
|
|
8263
|
+
names
|
|
8264
|
+
};
|
|
8265
|
+
}
|
|
8266
|
+
function spannedBlocks(text) {
|
|
8267
|
+
const lines = text.split("\n");
|
|
8268
|
+
const starts = [];
|
|
8269
|
+
lines.forEach((line, index) => {
|
|
8270
|
+
if (/^- /.test(line) || line === "-") starts.push(index);
|
|
8271
|
+
});
|
|
8272
|
+
const blocks = [];
|
|
8273
|
+
for (let i = 0; i < starts.length; i += 1) {
|
|
8274
|
+
const from = starts[i] ?? 0;
|
|
8275
|
+
const to = i + 1 < starts.length ? starts[i + 1] ?? lines.length : lines.length;
|
|
8276
|
+
const body = trimmedTail(lines.slice(from, to)).join("\n");
|
|
8277
|
+
if (body.trim() === "") continue;
|
|
8278
|
+
blocks.push({
|
|
8279
|
+
...blockOf(body, blocks.length),
|
|
8280
|
+
from,
|
|
8281
|
+
to
|
|
8282
|
+
});
|
|
8283
|
+
}
|
|
8284
|
+
return blocks;
|
|
8285
|
+
}
|
|
8286
|
+
function patchBlocks(text) {
|
|
8287
|
+
return spannedBlocks(text).map(({ from: _from, to: _to, ...block }) => block);
|
|
8288
|
+
}
|
|
8289
|
+
function normalizeBlock(body) {
|
|
8290
|
+
return body.split("\n").map((line) => line.replace(/\s+$/, "")).filter((line) => line.trim() !== "" && !COMMENT_LINE.test(line)).join("\n");
|
|
8291
|
+
}
|
|
8292
|
+
function syncRows(source, own, missing) {
|
|
8293
|
+
const mine = new Map(own.map((block) => [block.key, block]));
|
|
8294
|
+
const rows = [];
|
|
8295
|
+
for (const block of source) {
|
|
8296
|
+
const here = mine.get(block.key);
|
|
8297
|
+
if (here !== void 0 && normalizeBlock(here.body) === normalizeBlock(block.body)) continue;
|
|
8298
|
+
rows.push({
|
|
8299
|
+
key: block.key,
|
|
8300
|
+
id: block.id,
|
|
8301
|
+
insert: block.insert,
|
|
8302
|
+
state: here === void 0 ? "new" : "differs",
|
|
8303
|
+
body: block.body,
|
|
8304
|
+
ownBody: here === void 0 ? null : here.body,
|
|
8305
|
+
missing: missing(block)
|
|
8306
|
+
});
|
|
8307
|
+
}
|
|
8308
|
+
return rows;
|
|
8309
|
+
}
|
|
8310
|
+
function replaceBlocks(text, blocks) {
|
|
8311
|
+
const wanted = new Map(blocks.map((block) => [block.key, block]));
|
|
8312
|
+
const spans = spannedBlocks(text).filter((span) => wanted.has(span.key));
|
|
8313
|
+
const lines = text.split("\n");
|
|
8314
|
+
for (const span of [...spans].reverse()) {
|
|
8315
|
+
const replacement = (wanted.get(span.key)?.body ?? "").split("\n");
|
|
8316
|
+
lines.splice(span.from, span.to - span.from, ...replacement);
|
|
8317
|
+
}
|
|
8318
|
+
return lines.join("\n");
|
|
8319
|
+
}
|
|
8320
|
+
function appendBlocks(text, blocks) {
|
|
8321
|
+
const kept = text.split("\n").filter((line) => line.trim() !== "[]").join("\n").replace(/\s+$/, "");
|
|
8322
|
+
let out = kept === "" ? "" : `${kept}\n`;
|
|
8323
|
+
for (const block of blocks) out += `\n${block.body}\n`;
|
|
8324
|
+
return out;
|
|
8325
|
+
}
|
|
8326
|
+
function mergeBlocks(ownText, incoming, overwrite) {
|
|
8327
|
+
const own = patchBlocks(ownText);
|
|
8328
|
+
const mine = new Set(own.map((block) => block.key));
|
|
8329
|
+
const written = [];
|
|
8330
|
+
const skipped = [];
|
|
8331
|
+
const take = [];
|
|
8332
|
+
for (const block of incoming) {
|
|
8333
|
+
if (!mine.has(block.key) || overwrite.has(block.key)) {
|
|
8334
|
+
take.push(block);
|
|
8335
|
+
written.push(block.id);
|
|
8336
|
+
continue;
|
|
8337
|
+
}
|
|
8338
|
+
skipped.push(block.id);
|
|
8339
|
+
}
|
|
8340
|
+
if (take.length === 0) return {
|
|
8341
|
+
ok: true,
|
|
8342
|
+
text: ownText,
|
|
8343
|
+
written,
|
|
8344
|
+
skipped
|
|
8345
|
+
};
|
|
8346
|
+
const text = appendBlocks(replaceBlocks(ownText, take.filter((block) => mine.has(block.key))), take.filter((block) => !mine.has(block.key)));
|
|
8347
|
+
const check = patchBlocks(text);
|
|
8348
|
+
const expected = /* @__PURE__ */ new Set([...mine, ...take.map((block) => block.key)]);
|
|
8349
|
+
if (check.length !== expected.size || new Set(check.map((block) => block.key)).size !== expected.size) return {
|
|
8350
|
+
ok: false,
|
|
8351
|
+
reason: "写入前的自检没过:行的 id 会重复或丢行"
|
|
8352
|
+
};
|
|
8353
|
+
return {
|
|
8354
|
+
ok: true,
|
|
8355
|
+
text,
|
|
8356
|
+
written,
|
|
8357
|
+
skipped
|
|
8358
|
+
};
|
|
8359
|
+
}
|
|
8360
|
+
function readPatchBlocks(path) {
|
|
8361
|
+
try {
|
|
8362
|
+
const text = readFileSync(path, "utf8");
|
|
8363
|
+
return {
|
|
8364
|
+
ok: true,
|
|
8365
|
+
text,
|
|
8366
|
+
blocks: patchBlocks(text)
|
|
8367
|
+
};
|
|
8368
|
+
} catch (error) {
|
|
8369
|
+
return {
|
|
8370
|
+
ok: false,
|
|
8371
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
8372
|
+
};
|
|
8373
|
+
}
|
|
8374
|
+
}
|
|
8375
|
+
function patchMode(path) {
|
|
8376
|
+
try {
|
|
8377
|
+
return statSync(path).mode & 511;
|
|
8378
|
+
} catch {
|
|
8379
|
+
return 420;
|
|
8380
|
+
}
|
|
8381
|
+
}
|
|
8382
|
+
function writePatchFile(path, text) {
|
|
8383
|
+
const mode = patchMode(path);
|
|
8384
|
+
const tmp = `${path}.wb-sync-${String(process.pid)}`;
|
|
8385
|
+
writeFileSync(tmp, text, { mode });
|
|
8386
|
+
try {
|
|
8387
|
+
renameSync(tmp, path);
|
|
8388
|
+
} catch (error) {
|
|
8389
|
+
try {
|
|
8390
|
+
unlinkSync(tmp);
|
|
8391
|
+
} catch {
|
|
8392
|
+
throw error;
|
|
8393
|
+
}
|
|
8394
|
+
throw error;
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
8397
|
+
function configSyncHandler(deps) {
|
|
8398
|
+
return (search) => ({
|
|
8399
|
+
ok: true,
|
|
8400
|
+
...configSyncSnapshot(deps, search.get("source") ?? void 0)
|
|
8401
|
+
});
|
|
8402
|
+
}
|
|
8403
|
+
function textOf(value) {
|
|
8404
|
+
return typeof value === "string" ? value : "";
|
|
8405
|
+
}
|
|
8406
|
+
function profileDependencies(dir) {
|
|
8407
|
+
try {
|
|
8408
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
8409
|
+
return new Set(Object.keys(manifest.dependencies ?? {}));
|
|
8410
|
+
} catch {
|
|
8411
|
+
return /* @__PURE__ */ new Set();
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8414
|
+
function profileNames(profilesDir) {
|
|
8415
|
+
try {
|
|
8416
|
+
return readdirSync(profilesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => entry.name).filter((name) => existsSync(join(profilesDir, name, "package.json"))).sort((a, b) => a.localeCompare(b));
|
|
8417
|
+
} catch {
|
|
8418
|
+
return [];
|
|
8419
|
+
}
|
|
8420
|
+
}
|
|
8421
|
+
function configSyncSnapshot(deps, asked) {
|
|
8422
|
+
const profilesDir = join(deps.home, "profiles");
|
|
8423
|
+
const others = profileNames(profilesDir).filter((name) => name !== deps.self);
|
|
8424
|
+
const base = {
|
|
8425
|
+
self: deps.self,
|
|
8426
|
+
home: deps.home,
|
|
8427
|
+
restart: true
|
|
8428
|
+
};
|
|
8429
|
+
if (deps.profileDir === null) return {
|
|
8430
|
+
...base,
|
|
8431
|
+
source: "",
|
|
8432
|
+
profiles: [],
|
|
8433
|
+
rows: [],
|
|
8434
|
+
emptyProviders: false,
|
|
8435
|
+
reason: "拿不到当前 profile 目录"
|
|
8436
|
+
};
|
|
8437
|
+
const own = readPatchBlocks(join(deps.profileDir, "cordis.patch.yml"));
|
|
8438
|
+
const emptyProviders = own.ok && !own.blocks.some((block) => block.key.startsWith("id:llm-"));
|
|
8439
|
+
const ownBlocks = own.ok ? own.blocks : [];
|
|
8440
|
+
const profiles = others.map((name) => {
|
|
8441
|
+
const read = readPatchBlocks(join(profilesDir, name, "cordis.patch.yml"));
|
|
8442
|
+
return {
|
|
8443
|
+
name,
|
|
8444
|
+
readable: read.ok,
|
|
8445
|
+
candidates: read.ok ? syncRows(read.blocks, ownBlocks, () => []).length : 0
|
|
8446
|
+
};
|
|
8447
|
+
});
|
|
8448
|
+
if (others.length === 0) return {
|
|
8449
|
+
...base,
|
|
8450
|
+
source: "",
|
|
8451
|
+
profiles,
|
|
8452
|
+
rows: [],
|
|
8453
|
+
emptyProviders,
|
|
8454
|
+
reason: "这台机器上只有当前这个 profile,没有可同步的来源"
|
|
8455
|
+
};
|
|
8456
|
+
const source = others.includes(asked ?? "") ? asked ?? "" : others.includes("web") ? "web" : others[0] ?? "";
|
|
8457
|
+
const sourceDir = join(profilesDir, source);
|
|
8458
|
+
const read = readPatchBlocks(join(sourceDir, "cordis.patch.yml"));
|
|
8459
|
+
if (!read.ok) return {
|
|
8460
|
+
...base,
|
|
8461
|
+
source,
|
|
8462
|
+
profiles,
|
|
8463
|
+
rows: [],
|
|
8464
|
+
emptyProviders,
|
|
8465
|
+
reason: read.reason
|
|
8466
|
+
};
|
|
8467
|
+
if (!own.ok) return {
|
|
8468
|
+
...base,
|
|
8469
|
+
source,
|
|
8470
|
+
profiles,
|
|
8471
|
+
rows: [],
|
|
8472
|
+
emptyProviders,
|
|
8473
|
+
reason: own.reason
|
|
8474
|
+
};
|
|
8475
|
+
const declared = profileDependencies(sourceDir);
|
|
8476
|
+
const modulesDir = join(deps.profileDir, "node_modules");
|
|
8477
|
+
const rows = syncRows(read.blocks, own.blocks, (block) => block.names.filter((name) => declared.has(name) && !existsSync(join(modulesDir, name))));
|
|
8478
|
+
return {
|
|
8479
|
+
...base,
|
|
8480
|
+
source,
|
|
8481
|
+
profiles,
|
|
8482
|
+
rows,
|
|
8483
|
+
emptyProviders
|
|
8484
|
+
};
|
|
8485
|
+
}
|
|
8486
|
+
function configSyncApply(deps, payload) {
|
|
8487
|
+
if (deps.profileDir === null) return {
|
|
8488
|
+
ok: false,
|
|
8489
|
+
text: "拿不到当前 profile 目录,这次没写任何东西"
|
|
8490
|
+
};
|
|
8491
|
+
const body = typeof payload === "object" && payload !== null ? payload : {};
|
|
8492
|
+
const source = textOf(body["source"]);
|
|
8493
|
+
const keys = new Set((Array.isArray(body["keys"]) ? body["keys"] : []).map((item) => textOf(item)).filter((item) => item !== ""));
|
|
8494
|
+
const overwrite = new Set((Array.isArray(body["overwrite"]) ? body["overwrite"] : []).map((item) => textOf(item)));
|
|
8495
|
+
if (source === "" || keys.size === 0) return {
|
|
8496
|
+
ok: false,
|
|
8497
|
+
text: "没说清从哪个 profile 搬哪几行"
|
|
8498
|
+
};
|
|
8499
|
+
const profilesDir = join(deps.home, "profiles");
|
|
8500
|
+
if (!profileNames(profilesDir).includes(source)) return {
|
|
8501
|
+
ok: false,
|
|
8502
|
+
text: `没有这个 profile:${source}`
|
|
8503
|
+
};
|
|
8504
|
+
if (source === deps.self) return {
|
|
8505
|
+
ok: false,
|
|
8506
|
+
text: "来源不能是工作台自己"
|
|
8507
|
+
};
|
|
8508
|
+
const read = readPatchBlocks(join(profilesDir, source, "cordis.patch.yml"));
|
|
8509
|
+
if (!read.ok) return {
|
|
8510
|
+
ok: false,
|
|
8511
|
+
text: `读不到 ${source} 的配置:${read.reason}`
|
|
8512
|
+
};
|
|
8513
|
+
const own = readPatchBlocks(join(deps.profileDir, "cordis.patch.yml"));
|
|
8514
|
+
if (!own.ok) return {
|
|
8515
|
+
ok: false,
|
|
8516
|
+
text: `读不到工作台自己的配置:${own.reason}`
|
|
8517
|
+
};
|
|
8518
|
+
const picked = read.blocks.filter((block) => keys.has(block.key));
|
|
8519
|
+
if (picked.length === 0) return {
|
|
8520
|
+
ok: false,
|
|
8521
|
+
text: "选中的行在来源里已经找不到了,重新读一遍再试"
|
|
8522
|
+
};
|
|
8523
|
+
const merged = mergeBlocks(own.text, picked, overwrite);
|
|
8524
|
+
if (!merged.ok) return {
|
|
8525
|
+
ok: false,
|
|
8526
|
+
text: merged.reason
|
|
8527
|
+
};
|
|
8528
|
+
try {
|
|
8529
|
+
writePatchFile(join(deps.profileDir, "cordis.patch.yml"), merged.text);
|
|
8530
|
+
} catch (error) {
|
|
8531
|
+
return {
|
|
8532
|
+
ok: false,
|
|
8533
|
+
text: `写不进去:${error instanceof Error ? error.message : String(error)}`
|
|
8534
|
+
};
|
|
8535
|
+
}
|
|
8536
|
+
return {
|
|
8537
|
+
ok: true,
|
|
8538
|
+
written: merged.written,
|
|
8539
|
+
skipped: merged.skipped,
|
|
8540
|
+
restart: true
|
|
8541
|
+
};
|
|
8542
|
+
}
|
|
8543
|
+
//#endregion
|
|
8221
8544
|
//#region src/shared/api/host/tool-scope.ts
|
|
8222
8545
|
function agentOf(value) {
|
|
8223
8546
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
@@ -8347,6 +8670,12 @@ function apply(ctx) {
|
|
|
8347
8670
|
const renderers = /* @__PURE__ */ new Map();
|
|
8348
8671
|
const shell = ctx.get("shell");
|
|
8349
8672
|
const profileDir = profileDirOf(ctx);
|
|
8673
|
+
const configSync = {
|
|
8674
|
+
home: dshHome(),
|
|
8675
|
+
self: profileDir === null ? "" : basename(profileDir),
|
|
8676
|
+
profileDir
|
|
8677
|
+
};
|
|
8678
|
+
routes[CONFIG_SYNC_ROUTE] = configSyncHandler(configSync);
|
|
8350
8679
|
const units = createUnitRegistry({
|
|
8351
8680
|
declarationOf: createDeclarationLookup(profileDir),
|
|
8352
8681
|
logger: ctx.logger
|
|
@@ -8684,6 +9013,10 @@ function apply(ctx) {
|
|
|
8684
9013
|
return () => off?.();
|
|
8685
9014
|
}, `${PLUGIN_ID}: 对话引用注入`);
|
|
8686
9015
|
ctx.effect(() => registerFactStream(ctx, facts), `${PLUGIN_ID}: 变更流`);
|
|
9016
|
+
ctx.effect(() => registerActions(ctx, [{
|
|
9017
|
+
resource: CONFIG_SYNC_RESOURCE,
|
|
9018
|
+
handle: (payload) => configSyncApply(configSync, payload)
|
|
9019
|
+
}]), `${PLUGIN_ID}: 配置同步那一跳(把来源 profile 的行写进工作台自己的 patch)`);
|
|
8687
9020
|
ctx.effect(() => registerPanelState(ctx, panelState), `${PLUGIN_ID}: 面板运行态上报(浏览器半侧报自己的取数与渲染收尾)`);
|
|
8688
9021
|
ctx.effect(() => registerApi(ctx, routes), `${PLUGIN_ID}: HTTP 投影`);
|
|
8689
9022
|
}
|