@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.
@@ -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$6(value, max) {
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$6(record["code"], 120);
264
- const message = textOf$6(record["message"], PANEL_STATE_MAX_TEXT);
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$6(record["workspace"], PANEL_STATE_MAX_TEXT);
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$6(card["unit"], 200);
292
- const route = textOf$6(card["route"], 400);
293
- const phase = textOf$6(card["phase"], 20);
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$6(record["workspace"], PANEL_STATE_MAX_TEXT);
324
- const unit = textOf$6(record["unit"], 200);
325
- const reason = textOf$6(record["reason"], PANEL_STATE_MAX_TEXT);
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$5(value) {
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$5(record["code"]);
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$5(record["message"]);
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$5(record["hint"]);
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$4(value) {
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$4(item["text"]),
829
- tag: textOf$4(item["tag"]),
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$4(error["message"]);
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$4(error["hint"]);
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$4(parsed["url"]);
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$3(value) {
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$3(box["opencliHome"]);
1884
- const pkg = textOf$3(box["package"]);
1885
- const installedAt = textOf$3(box["installedAt"]);
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$3(box["name"]);
1888
- const registry = textOf$3(box["registry"]);
1889
- const releasedAt = textOf$3(box["releasedAt"]);
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$2(withNext)
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$2(answer) {
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$1(value) {
5469
+ function textOf$2(value) {
5470
5470
  return typeof value === "string" ? value.trim() : "";
5471
5471
  }
5472
5472
  function actionOf(call) {
5473
- return textOf$1(call.args["action"]) || "list";
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$1(call.args["unit"]) || textOf$1(call.args["id"]);
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$1(target.path);
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
  }