@dsh-workbench/core 0.2.5 → 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 +546 -198
- package/dist/client/client.body.js +546 -198
- package/dist/client/index.js +547 -199
- package/dist/host/index.js +377 -47
- package/dist/types/app/client.d.ts +210 -6
- 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 +1 -1
- package/dist/types/modules/sessions/model/session-node.d.ts +1 -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";
|
|
@@ -247,7 +247,7 @@ var PHASES = [
|
|
|
247
247
|
function asRecord$10(value) {
|
|
248
248
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
249
249
|
}
|
|
250
|
-
function textOf$
|
|
250
|
+
function textOf$7(value, max) {
|
|
251
251
|
if (typeof value !== "string") return "";
|
|
252
252
|
const trimmed = value.trim();
|
|
253
253
|
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
@@ -260,8 +260,8 @@ function atOf(value) {
|
|
|
260
260
|
function errorOf(raw) {
|
|
261
261
|
const record = asRecord$10(raw);
|
|
262
262
|
if (record === void 0) return void 0;
|
|
263
|
-
const code = textOf$
|
|
264
|
-
const message = textOf$
|
|
263
|
+
const code = textOf$7(record["code"], 120);
|
|
264
|
+
const message = textOf$7(record["message"], PANEL_STATE_MAX_TEXT);
|
|
265
265
|
if (code === "" && message === "") return void 0;
|
|
266
266
|
return {
|
|
267
267
|
code: code === "" ? "unknown" : code,
|
|
@@ -274,7 +274,7 @@ function parseCardsReport(payload) {
|
|
|
274
274
|
ok: false,
|
|
275
275
|
message: "请求体要是一个对象"
|
|
276
276
|
};
|
|
277
|
-
const workspace = textOf$
|
|
277
|
+
const workspace = textOf$7(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
278
278
|
if (workspace === "") return {
|
|
279
279
|
ok: false,
|
|
280
280
|
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
@@ -288,9 +288,9 @@ function parseCardsReport(payload) {
|
|
|
288
288
|
for (const item of raw.slice(0, 32)) {
|
|
289
289
|
const card = asRecord$10(item);
|
|
290
290
|
if (card === void 0) continue;
|
|
291
|
-
const unit = textOf$
|
|
292
|
-
const route = textOf$
|
|
293
|
-
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);
|
|
294
294
|
const at = atOf(card["at"]);
|
|
295
295
|
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
296
296
|
const error = errorOf(card["error"]);
|
|
@@ -320,9 +320,9 @@ function parseCrashReport(payload) {
|
|
|
320
320
|
ok: false,
|
|
321
321
|
message: "请求体要是一个对象"
|
|
322
322
|
};
|
|
323
|
-
const workspace = textOf$
|
|
324
|
-
const unit = textOf$
|
|
325
|
-
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);
|
|
326
326
|
if (workspace === "") return {
|
|
327
327
|
ok: false,
|
|
328
328
|
message: "workspace 不能是空的"
|
|
@@ -568,20 +568,20 @@ var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
|
568
568
|
function asRecord$9(value) {
|
|
569
569
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
570
570
|
}
|
|
571
|
-
function textOf$
|
|
571
|
+
function textOf$6(value) {
|
|
572
572
|
return typeof value === "string" ? value.trim() : "";
|
|
573
573
|
}
|
|
574
574
|
function codeOf(page) {
|
|
575
575
|
const record = asRecord$9(page);
|
|
576
|
-
return record === void 0 ? "" : textOf$
|
|
576
|
+
return record === void 0 ? "" : textOf$6(record["code"]);
|
|
577
577
|
}
|
|
578
578
|
function messageOf$2(page) {
|
|
579
579
|
const record = asRecord$9(page);
|
|
580
|
-
return record === void 0 ? "" : textOf$
|
|
580
|
+
return record === void 0 ? "" : textOf$6(record["message"]);
|
|
581
581
|
}
|
|
582
582
|
function hintOf$1(page) {
|
|
583
583
|
const record = asRecord$9(page);
|
|
584
|
-
const hint = record === void 0 ? "" : textOf$
|
|
584
|
+
const hint = record === void 0 ? "" : textOf$6(record["hint"]);
|
|
585
585
|
return hint === "" ? void 0 : hint;
|
|
586
586
|
}
|
|
587
587
|
function pageFailed(page) {
|
|
@@ -812,7 +812,7 @@ var DEFAULT_TEXT_MAX = 200;
|
|
|
812
812
|
function asRecord$8(value) {
|
|
813
813
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
814
814
|
}
|
|
815
|
-
function textOf$
|
|
815
|
+
function textOf$5(value) {
|
|
816
816
|
return typeof value === "string" ? value.trim() : "";
|
|
817
817
|
}
|
|
818
818
|
function entriesOf(value) {
|
|
@@ -825,8 +825,8 @@ function entriesOf(value) {
|
|
|
825
825
|
if (item === void 0) continue;
|
|
826
826
|
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
827
827
|
entries.push({
|
|
828
|
-
text: textOf$
|
|
829
|
-
tag: textOf$
|
|
828
|
+
text: textOf$5(item["text"]),
|
|
829
|
+
tag: textOf$5(item["tag"]),
|
|
830
830
|
nth
|
|
831
831
|
});
|
|
832
832
|
}
|
|
@@ -835,7 +835,7 @@ function entriesOf(value) {
|
|
|
835
835
|
function reasonOf(stdout, stderr) {
|
|
836
836
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
837
837
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
838
|
-
const message = error === void 0 ? "" : textOf$
|
|
838
|
+
const message = error === void 0 ? "" : textOf$5(error["message"]);
|
|
839
839
|
if (message !== "") return message;
|
|
840
840
|
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
841
841
|
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
@@ -843,7 +843,7 @@ function reasonOf(stdout, stderr) {
|
|
|
843
843
|
function hintOf(stdout) {
|
|
844
844
|
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
845
845
|
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
846
|
-
const hint = error === void 0 ? "" : textOf$
|
|
846
|
+
const hint = error === void 0 ? "" : textOf$5(error["hint"]);
|
|
847
847
|
return hint === "" ? void 0 : hint;
|
|
848
848
|
}
|
|
849
849
|
function sameOrigin(left, right) {
|
|
@@ -857,7 +857,7 @@ function urlOf(stdout) {
|
|
|
857
857
|
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
858
858
|
if (bare.startsWith("{")) {
|
|
859
859
|
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
860
|
-
const url = parsed === void 0 ? "" : textOf$
|
|
860
|
+
const url = parsed === void 0 ? "" : textOf$5(parsed["url"]);
|
|
861
861
|
if (url !== "") return url;
|
|
862
862
|
}
|
|
863
863
|
return bare;
|
|
@@ -1872,7 +1872,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
1872
1872
|
function ledgerKeyOf(entry) {
|
|
1873
1873
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1874
1874
|
}
|
|
1875
|
-
function textOf$
|
|
1875
|
+
function textOf$4(value) {
|
|
1876
1876
|
if (typeof value !== "string") return void 0;
|
|
1877
1877
|
const trimmed = value.trim();
|
|
1878
1878
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -1880,13 +1880,13 @@ function textOf$3(value) {
|
|
|
1880
1880
|
function entryOf(value) {
|
|
1881
1881
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1882
1882
|
const box = value;
|
|
1883
|
-
const opencliHome = textOf$
|
|
1884
|
-
const pkg = textOf$
|
|
1885
|
-
const installedAt = textOf$
|
|
1883
|
+
const opencliHome = textOf$4(box["opencliHome"]);
|
|
1884
|
+
const pkg = textOf$4(box["package"]);
|
|
1885
|
+
const installedAt = textOf$4(box["installedAt"]);
|
|
1886
1886
|
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
1887
|
-
const name = textOf$
|
|
1888
|
-
const registry = textOf$
|
|
1889
|
-
const releasedAt = textOf$
|
|
1887
|
+
const name = textOf$4(box["name"]);
|
|
1888
|
+
const registry = textOf$4(box["registry"]);
|
|
1889
|
+
const releasedAt = textOf$4(box["releasedAt"]);
|
|
1890
1890
|
return {
|
|
1891
1891
|
opencliHome,
|
|
1892
1892
|
package: pkg,
|
|
@@ -3272,7 +3272,7 @@ function reconOf(commands, plugins, keywords) {
|
|
|
3272
3272
|
};
|
|
3273
3273
|
return {
|
|
3274
3274
|
...withNext,
|
|
3275
|
-
text: textOf$
|
|
3275
|
+
text: textOf$3(withNext)
|
|
3276
3276
|
};
|
|
3277
3277
|
}
|
|
3278
3278
|
function nextOf(answer) {
|
|
@@ -3285,7 +3285,7 @@ function nextOf(answer) {
|
|
|
3285
3285
|
if (plugin !== void 0) return `这台机器另装的插件 ${plugin.name} 里有匹配:opencli ${plugin.name} --help 看全部命令`;
|
|
3286
3286
|
return "这台机器上没有现成的 —— 两条路:① 给这个站点写 adapter:opencli skills read opencli-adapter-author;② 只读一次:workbench_diy action=probe(script 读接口 / css 读 DOM)";
|
|
3287
3287
|
}
|
|
3288
|
-
function textOf$
|
|
3288
|
+
function textOf$3(answer) {
|
|
3289
3289
|
const lines = [];
|
|
3290
3290
|
if (answer.sites.length > 0) {
|
|
3291
3291
|
lines.push(`opencli 自带这 ${String(answer.sites.length)} 组能力(不用装、不用点头):`);
|
|
@@ -5466,16 +5466,16 @@ function applyPanelsPref(panels, payload) {
|
|
|
5466
5466
|
//#region src/modules/panels/model/handoff.ts
|
|
5467
5467
|
var PANELS_FACT = "panels";
|
|
5468
5468
|
var CAPABILITY_FACT = "capability";
|
|
5469
|
-
function textOf$
|
|
5469
|
+
function textOf$2(value) {
|
|
5470
5470
|
return typeof value === "string" ? value.trim() : "";
|
|
5471
5471
|
}
|
|
5472
5472
|
function actionOf(call) {
|
|
5473
|
-
return textOf$
|
|
5473
|
+
return textOf$2(call.args["action"]) || "list";
|
|
5474
5474
|
}
|
|
5475
5475
|
function requestOf(call) {
|
|
5476
5476
|
const action = actionOf(call);
|
|
5477
5477
|
if (!PROVISION_ACTIONS.includes(action)) return void 0;
|
|
5478
|
-
const target = textOf$
|
|
5478
|
+
const target = textOf$2(call.args["unit"]) || textOf$2(call.args["id"]);
|
|
5479
5479
|
if (target === "") return void 0;
|
|
5480
5480
|
const wanted = action;
|
|
5481
5481
|
return isDependencyAction(wanted) ? {
|
|
@@ -5487,7 +5487,7 @@ function requestOf(call) {
|
|
|
5487
5487
|
};
|
|
5488
5488
|
}
|
|
5489
5489
|
function panelsHandoffEntry(target = {}) {
|
|
5490
|
-
const path = textOf$
|
|
5490
|
+
const path = textOf$2(target.path);
|
|
5491
5491
|
return path === "" ? void 0 : {
|
|
5492
5492
|
fact: PANELS_FACT,
|
|
5493
5493
|
key: `path:${path}`
|
|
@@ -7628,7 +7628,7 @@ function bindRosterFacts(options) {
|
|
|
7628
7628
|
function recordOf(value) {
|
|
7629
7629
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
7630
7630
|
}
|
|
7631
|
-
function textOf(value) {
|
|
7631
|
+
function textOf$1(value) {
|
|
7632
7632
|
return typeof value === "string" ? value : "";
|
|
7633
7633
|
}
|
|
7634
7634
|
function stringsOf(value) {
|
|
@@ -7639,18 +7639,18 @@ function argsOf(raw) {
|
|
|
7639
7639
|
return raw.flatMap((item) => {
|
|
7640
7640
|
const record = recordOf(item);
|
|
7641
7641
|
if (record === void 0) return [];
|
|
7642
|
-
const name = textOf(record["name"]).trim();
|
|
7642
|
+
const name = textOf$1(record["name"]).trim();
|
|
7643
7643
|
if (name === "") return [];
|
|
7644
7644
|
const fallback = record["default"];
|
|
7645
7645
|
return [{
|
|
7646
7646
|
name,
|
|
7647
|
-
type: textOf(record["type"]) || "string",
|
|
7647
|
+
type: textOf$1(record["type"]) || "string",
|
|
7648
7648
|
required: record["required"] === true,
|
|
7649
7649
|
positional: record["positional"] === true,
|
|
7650
7650
|
valueRequired: record["valueRequired"] === true,
|
|
7651
7651
|
choices: stringsOf(record["choices"]),
|
|
7652
7652
|
...fallback === void 0 || fallback === null ? {} : { defaultValue: fallback },
|
|
7653
|
-
help: textOf(record["help"])
|
|
7653
|
+
help: textOf$1(record["help"])
|
|
7654
7654
|
}];
|
|
7655
7655
|
});
|
|
7656
7656
|
}
|
|
@@ -7661,11 +7661,11 @@ function parsePluginRows(stdout) {
|
|
|
7661
7661
|
for (const item of parsed) {
|
|
7662
7662
|
const record = recordOf(item);
|
|
7663
7663
|
if (record === void 0) return void 0;
|
|
7664
|
-
const name = textOf(record["name"]).trim();
|
|
7664
|
+
const name = textOf$1(record["name"]).trim();
|
|
7665
7665
|
if (name === "") return void 0;
|
|
7666
7666
|
rows.push({
|
|
7667
7667
|
name,
|
|
7668
|
-
version: textOf(record["version"]),
|
|
7668
|
+
version: textOf$1(record["version"]),
|
|
7669
7669
|
commands: stringsOf(record["commands"])
|
|
7670
7670
|
});
|
|
7671
7671
|
}
|
|
@@ -7678,17 +7678,17 @@ function parseCommandRows(stdout) {
|
|
|
7678
7678
|
for (const item of parsed) {
|
|
7679
7679
|
const record = recordOf(item);
|
|
7680
7680
|
if (record === void 0) return void 0;
|
|
7681
|
-
const site = textOf(record["site"]).trim();
|
|
7682
|
-
const name = textOf(record["name"]).trim();
|
|
7681
|
+
const site = textOf$1(record["site"]).trim();
|
|
7682
|
+
const name = textOf$1(record["name"]).trim();
|
|
7683
7683
|
if (site === "" || name === "") return void 0;
|
|
7684
7684
|
rows.push({
|
|
7685
7685
|
site,
|
|
7686
7686
|
name,
|
|
7687
|
-
description: textOf(record["description"]),
|
|
7688
|
-
access: textOf(record["access"]),
|
|
7687
|
+
description: textOf$1(record["description"]),
|
|
7688
|
+
access: textOf$1(record["access"]),
|
|
7689
7689
|
browser: record["browser"] === true,
|
|
7690
|
-
siteSession: textOf(record["siteSession"]),
|
|
7691
|
-
...textOf(record["domain"]) === "" ? {} : { domain: textOf(record["domain"]) },
|
|
7690
|
+
siteSession: textOf$1(record["siteSession"]),
|
|
7691
|
+
...textOf$1(record["domain"]) === "" ? {} : { domain: textOf$1(record["domain"]) },
|
|
7692
7692
|
args: argsOf(record["args"])
|
|
7693
7693
|
});
|
|
7694
7694
|
}
|
|
@@ -8221,6 +8221,326 @@ function createPluginUnits(options) {
|
|
|
8221
8221
|
};
|
|
8222
8222
|
}
|
|
8223
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
|
|
8224
8544
|
//#region src/shared/api/host/tool-scope.ts
|
|
8225
8545
|
function agentOf(value) {
|
|
8226
8546
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
@@ -8350,6 +8670,12 @@ function apply(ctx) {
|
|
|
8350
8670
|
const renderers = /* @__PURE__ */ new Map();
|
|
8351
8671
|
const shell = ctx.get("shell");
|
|
8352
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);
|
|
8353
8679
|
const units = createUnitRegistry({
|
|
8354
8680
|
declarationOf: createDeclarationLookup(profileDir),
|
|
8355
8681
|
logger: ctx.logger
|
|
@@ -8687,6 +9013,10 @@ function apply(ctx) {
|
|
|
8687
9013
|
return () => off?.();
|
|
8688
9014
|
}, `${PLUGIN_ID}: 对话引用注入`);
|
|
8689
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)`);
|
|
8690
9020
|
ctx.effect(() => registerPanelState(ctx, panelState), `${PLUGIN_ID}: 面板运行态上报(浏览器半侧报自己的取数与渲染收尾)`);
|
|
8691
9021
|
ctx.effect(() => registerApi(ctx, routes), `${PLUGIN_ID}: HTTP 投影`);
|
|
8692
9022
|
}
|