@dsh-workbench/core 0.2.14 → 0.2.16

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.
Files changed (36) hide show
  1. package/client.js +630 -142
  2. package/dist/client/client.body.js +630 -142
  3. package/dist/client/index.js +3524 -421
  4. package/dist/host/index.js +571 -44
  5. package/dist/types/app/client.d.ts +121 -6
  6. package/dist/types/app/layouts/rightbar/__tests__/board-fact.test.d.ts +1 -0
  7. package/dist/types/app/layouts/rightbar/board-fact.d.ts +5 -0
  8. package/dist/types/app/layouts/sidebar/DebugFooterAction.d.ts +17 -0
  9. package/dist/types/app/layouts/sidebar/ProjectSwitcher.d.ts +2 -1
  10. package/dist/types/app/layouts/sidebar/__tests__/debug-ui.test.d.ts +1 -0
  11. package/dist/types/app/layouts/sidebar/debug-footer.d.ts +4 -0
  12. package/dist/types/app/layouts/sidebar/session-pick.d.ts +2 -1
  13. package/dist/types/modules/panels/__tests__/diy-harness.d.ts +6 -3
  14. package/dist/types/modules/panels/model/diy-browser-act.d.ts +1 -1
  15. package/dist/types/modules/panels/model/diy-browser.d.ts +4 -0
  16. package/dist/types/modules/panels/model/diy-debug-routes.d.ts +2 -0
  17. package/dist/types/modules/panels/model/diy-debug-routes.test.d.ts +1 -0
  18. package/dist/types/modules/panels/model/diy-debug-tools.d.ts +8 -0
  19. package/dist/types/modules/panels/model/diy-debug-tools.test.d.ts +1 -0
  20. package/dist/types/modules/panels/model/diy-look.d.ts +2 -1
  21. package/dist/types/modules/panels/model/diy-page.d.ts +14 -0
  22. package/dist/types/modules/panels/model/diy-scripts.d.ts +2 -1
  23. package/dist/types/modules/panels/model/diy-tools.d.ts +2 -1
  24. package/dist/types/modules/panels/model/handoff.d.ts +1 -0
  25. package/dist/types/modules/panels/unit.d.ts +7 -2
  26. package/dist/types/modules/sessions/index.d.ts +1 -1
  27. package/dist/types/modules/sessions/model/attention.d.ts +7 -0
  28. package/dist/types/shared/__tests__/host-import-barrel.test.d.ts +1 -0
  29. package/dist/types/shared/api/host/__tests__/diy-debug-act.test.d.ts +1 -0
  30. package/dist/types/shared/api/host/__tests__/host-args.test.d.ts +1 -0
  31. package/dist/types/shared/api/host/diy-debug-act.d.ts +31 -0
  32. package/dist/types/shared/api/host/host-args.d.ts +3 -0
  33. package/dist/types/shared/hooks/use-api-resource.d.ts +1 -0
  34. package/dist/types/shared/hooks/use-diy-debug.d.ts +8 -0
  35. package/package.json +2 -2
  36. package/skills/workbench-module-diy/KIT.md +18 -3
@@ -13,11 +13,15 @@ var PREFS_PREFIX = `${API_PREFIX}/prefs`;
13
13
  var EVENTS_PATH = "/workbench/events";
14
14
  var ACTIONS_PREFIX = `${API_PREFIX}/actions`;
15
15
  var PROVISION_PREFIX = `${API_PREFIX}/provision`;
16
- var PANEL_STATE_PREFIX = `${API_PREFIX}/panel-state`;
16
+ var PANEL_STATE_PATH = "panel-state";
17
+ var DIY_DEBUG_PATH = "diy-debug";
18
+ `${DIY_DEBUG_PATH}`;
19
+ var PANEL_STATE_PREFIX = `${API_PREFIX}/${PANEL_STATE_PATH}`;
17
20
  var OPENCLI_PIN = "1.8.8";
18
21
  var PANELS_SERVICE = "workbenchPanels";
19
22
  var UNITS_SERVICE = "workbenchUnits";
20
23
  var PANEL_STATE_SERVICE = "workbenchPanelState";
24
+ var DIY_DEBUG_SERVICE = "workbenchDiyDebug";
21
25
  var OPENCLI_TIMEOUT_MS = 12e4;
22
26
  var WARM_TIMEOUT_MS = 2e4;
23
27
  var INSTALL_TIMEOUT_MS = 3e5;
@@ -435,6 +439,125 @@ function createPanelStateStore() {
435
439
  };
436
440
  }
437
441
  //#endregion
442
+ //#region ../shared/dist/lib/board-fact.js
443
+ function insideWorkshop$1(root, target) {
444
+ if (target === root) return true;
445
+ const base = root.endsWith("/") ? root : `${root}/`;
446
+ return target.startsWith(base);
447
+ }
448
+ function boardFactOf(probe, workshop) {
449
+ if (!probe.workbench) return "unknown";
450
+ if (!probe.bar || !probe.open || !probe.drawn) return "hidden";
451
+ if (probe.board === "" || workshop === null) return "unknown";
452
+ return insideWorkshop$1(workshop, probe.board) ? "workshop" : "other";
453
+ }
454
+ function boardSituation(probe, workshop) {
455
+ if (!probe.workbench || workshop === null) return "unknown";
456
+ if (probe.bar && probe.open && probe.drawn) {
457
+ if (probe.board === "") return "unknown";
458
+ return insideWorkshop$1(workshop, probe.board) ? "ok" : "other-board";
459
+ }
460
+ if (probe.where !== "" && !insideWorkshop$1(workshop, probe.where)) return "off-workspace";
461
+ return "panel-closed";
462
+ }
463
+ //#endregion
464
+ //#region ../shared/dist/lib/diy-debug.js
465
+ function parseDebugIntent(value) {
466
+ return value === "pause" || value === "resume" || value === "stop" ? value : null;
467
+ }
468
+ function alive(state, now) {
469
+ if (state === null) return null;
470
+ return now - state.at <= 144e5 ? state : null;
471
+ }
472
+ function ownerOf(state, sessionId, now) {
473
+ const held = alive(state, now);
474
+ if (held === null) return "none";
475
+ return held.sessionId === sessionId ? held : "not-owner";
476
+ }
477
+ function createDebugStore() {
478
+ let held = null;
479
+ const live = (now) => alive(held, now);
480
+ return {
481
+ live,
482
+ begin(pact) {
483
+ const current = alive(held, pact.now);
484
+ if (current !== null) return {
485
+ ok: false,
486
+ code: current.sessionId === pact.sessionId ? "already-held" : "held-by-other"
487
+ };
488
+ const state = {
489
+ phase: "debugging",
490
+ sessionId: pact.sessionId,
491
+ workspace: pact.workspace,
492
+ board: pact.board,
493
+ unit: pact.unit,
494
+ name: pact.name,
495
+ startedAt: pact.now,
496
+ at: pact.now
497
+ };
498
+ held = state;
499
+ return {
500
+ ok: true,
501
+ state
502
+ };
503
+ },
504
+ pause(sessionId, now) {
505
+ const found = ownerOf(held, sessionId, now);
506
+ if (typeof found === "string") return {
507
+ ok: false,
508
+ code: found
509
+ };
510
+ if (found.phase === "paused") return {
511
+ ok: false,
512
+ code: "already-paused"
513
+ };
514
+ const state = {
515
+ ...found,
516
+ phase: "paused",
517
+ at: now
518
+ };
519
+ held = state;
520
+ return {
521
+ ok: true,
522
+ state
523
+ };
524
+ },
525
+ resume(sessionId, now) {
526
+ const found = ownerOf(held, sessionId, now);
527
+ if (typeof found === "string") return {
528
+ ok: false,
529
+ code: found
530
+ };
531
+ if (found.phase === "debugging") return {
532
+ ok: false,
533
+ code: "already-debugging"
534
+ };
535
+ const state = {
536
+ ...found,
537
+ phase: "debugging",
538
+ at: now
539
+ };
540
+ held = state;
541
+ return {
542
+ ok: true,
543
+ state
544
+ };
545
+ },
546
+ stop(sessionId, now) {
547
+ const found = ownerOf(held, sessionId, now);
548
+ if (typeof found === "string") return {
549
+ ok: false,
550
+ code: found
551
+ };
552
+ held = null;
553
+ return {
554
+ ok: true,
555
+ state: found
556
+ };
557
+ }
558
+ };
559
+ }
560
+ //#endregion
438
561
  //#region ../shared/dist/host/opencli-window.js
439
562
  var OPENCLI_WINDOW_ENV = "OPENCLI_WINDOW=background";
440
563
  function backgroundPrefix() {
@@ -1661,6 +1784,15 @@ function registerPanelState(ctx, store) {
1661
1784
  }]
1662
1785
  });
1663
1786
  }
1787
+ function registerDiyDebugAct(ctx, resource, log) {
1788
+ return registerPost(ctx, {
1789
+ prefix: `${API_PREFIX}/${DIY_DEBUG_PATH}`,
1790
+ what: "工作台 DIY 调试动作",
1791
+ refusedCode: "action-refused",
1792
+ resources: [resource],
1793
+ wrote: () => log?.bump(["diy-debug"])
1794
+ });
1795
+ }
1664
1796
  //#endregion
1665
1797
  //#region ../shared/dist/host/long-task.js
1666
1798
  var LIVE = /* @__PURE__ */ new Set(["running", "needs-login"]);
@@ -2138,6 +2270,10 @@ function sessionWorkspace(exec) {
2138
2270
  const cwd = exec?.agent?.session?.header?.cwd;
2139
2271
  return typeof cwd === "string" && cwd.trim() !== "" ? cwd.trim() : null;
2140
2272
  }
2273
+ function sessionIdOf(exec) {
2274
+ const id = exec?.agent?.session?.id;
2275
+ return typeof id === "string" && id.trim() !== "" ? id.trim() : null;
2276
+ }
2141
2277
  function textOutput(text) {
2142
2278
  return [{
2143
2279
  type: "text",
@@ -2799,9 +2935,7 @@ function workshopPackagesDir(ctx) {
2799
2935
  return root === null ? null : join(root, "packages");
2800
2936
  }
2801
2937
  function insideWorkshop(root, target) {
2802
- const base = existsSync(root) ? realpathSync(root) : resolve(root);
2803
- const real = existsSync(target) ? realpathSync(target) : resolve(target);
2804
- return real === base || real.startsWith(base + sep);
2938
+ return insideWorkshop$1(existsSync(root) ? realpathSync(root) : resolve(root), existsSync(target) ? realpathSync(target) : resolve(target));
2805
2939
  }
2806
2940
  function workshopModules(packagesDir, installed = []) {
2807
2941
  if (!existsSync(packagesDir)) return [];
@@ -2871,6 +3005,10 @@ var DIY_NET_STOP_TOOL = "workbench_diy_net_stop";
2871
3005
  var DIY_LOGS_WATCH_TOOL = "workbench_diy_logs_watch";
2872
3006
  var DIY_LOGS_READ_TOOL = "workbench_diy_logs_read";
2873
3007
  var DIY_LOGS_STOP_TOOL = "workbench_diy_logs_stop";
3008
+ var DIY_DEBUG_START_TOOL = "workbench_diy_debug_start";
3009
+ var DIY_DEBUG_PAUSE_TOOL = "workbench_diy_debug_pause";
3010
+ var DIY_DEBUG_RESUME_TOOL = "workbench_diy_debug_resume";
3011
+ var DIY_DEBUG_STOP_TOOL = "workbench_diy_debug_stop";
2874
3012
  var READ_TOOLS = [
2875
3013
  PANELS_LIST_TOOL,
2876
3014
  MODULES_LIST_TOOL,
@@ -2911,7 +3049,11 @@ var WRITE_TOOLS = [
2911
3049
  MODULES_UNINSTALL_PLUGIN_TOOL,
2912
3050
  MODULES_UPDATE_WORKBENCH_TOOL,
2913
3051
  DIY_INSTALL_TOOL,
2914
- DIY_SHOW_TOOL
3052
+ DIY_SHOW_TOOL,
3053
+ DIY_DEBUG_START_TOOL,
3054
+ DIY_DEBUG_PAUSE_TOOL,
3055
+ DIY_DEBUG_RESUME_TOOL,
3056
+ DIY_DEBUG_STOP_TOOL
2915
3057
  ];
2916
3058
  var DIY_TOOLS = [
2917
3059
  DIY_LIST_TOOL,
@@ -2938,6 +3080,12 @@ var DIY_TOOLS = [
2938
3080
  DIY_LOGS_READ_TOOL,
2939
3081
  DIY_LOGS_STOP_TOOL
2940
3082
  ];
3083
+ var DIY_DEBUG_TOOLS = [
3084
+ DIY_DEBUG_START_TOOL,
3085
+ DIY_DEBUG_PAUSE_TOOL,
3086
+ DIY_DEBUG_RESUME_TOOL,
3087
+ DIY_DEBUG_STOP_TOOL
3088
+ ];
2941
3089
  var panelsUnit = {
2942
3090
  id: "panels",
2943
3091
  card: null,
@@ -3031,6 +3179,13 @@ function askOf(reason, question, fallback) {
3031
3179
  };
3032
3180
  }
3033
3181
  var panelsApproval = (call) => {
3182
+ if (call.name === "workbench_diy_debug_start" || call.name === "workbench_diy_debug_resume") {
3183
+ const starting = call.name === DIY_DEBUG_START_TOOL;
3184
+ const unit = argsOf$1(call)["unit"];
3185
+ const target = typeof unit === "string" && unit !== "" ? ` [${unit}]` : "";
3186
+ return askOf(`${call.name}${target} takes over the user's screen to debug a workbench module`, pair(starting ? "要在「工作台DIY」那块板上开始调试 —— 调试中请留在那一屏,允许吗?" : "要接着调试 —— 请回到「工作台DIY」那一屏,允许吗?", starting ? "it takes over your screen to debug on the workshop board — please stay on that screen. Allow?" : "it needs your screen again to keep debugging — please go back to the workshop board. Allow?"), pair(starting ? "agent 要占用你的屏幕开始调试工作台模块,允许吗?" : "agent 要接着占用你的屏幕调试,允许吗?", starting ? "The agent wants to take over your screen to start debugging a workbench module. Allow?" : "The agent wants your screen again to keep debugging. Allow?"));
3187
+ }
3188
+ if (call.name === "workbench_diy_debug_pause" || call.name === "workbench_diy_debug_stop") return { kind: "allow" };
3034
3189
  if (call.name !== void 0 && DIY_TOOLS.includes(call.name)) return { kind: "allow" };
3035
3190
  const raw = argsOf$1(call);
3036
3191
  const text = (key) => {
@@ -3591,11 +3746,28 @@ function numberIn$1(value, fallback) {
3591
3746
  }
3592
3747
  var PAGE_PROBE = `(async () => {
3593
3748
  const answer = await fetch('/workbench/api/health').then((one) => one.json()).catch(() => null);
3749
+ const root = document.querySelector('.wb-root');
3750
+ const where = root === null ? '' : (root.getAttribute('data-wb-where') ?? '');
3751
+ const bar = document.querySelector('.wb-rightbar');
3752
+ const board = bar === null ? '' : (bar.getAttribute('data-wb-board') ?? '');
3753
+ const open = bar === null ? null : bar.hasAttribute('data-wb-open');
3754
+ const style = bar === null ? null : getComputedStyle(bar);
3755
+ const box = bar === null ? null : bar.getBoundingClientRect();
3756
+ const drawn =
3757
+ bar !== null &&
3758
+ style.visibility !== 'hidden' &&
3759
+ style.display !== 'none' &&
3760
+ box.width > 0 &&
3761
+ box.height > 0;
3594
3762
  return JSON.stringify({
3595
3763
  plugin: answer === null ? null : answer.plugin,
3596
3764
  pid: answer === null ? null : answer.pid,
3597
3765
  url: location.href,
3598
3766
  title: document.title,
3767
+ where,
3768
+ board,
3769
+ open,
3770
+ shown: drawn,
3599
3771
  });
3600
3772
  })()`;
3601
3773
  function cardSelectorOf(unit) {
@@ -3657,6 +3829,7 @@ var NO_PICK = {
3657
3829
  var EMPTY_CARD = {
3658
3830
  ok: false,
3659
3831
  anchored: false,
3832
+ visible: false,
3660
3833
  width: 0,
3661
3834
  height: 0,
3662
3835
  head: "",
@@ -3703,10 +3876,13 @@ function measureScriptOf(unit, pick) {
3703
3876
  };
3704
3877
  const anchor = document.querySelector(${JSON.stringify(cardSelectorOf(unit))});
3705
3878
  const all = [...document.querySelectorAll('.wb-card')];
3706
- if (anchor === null) return JSON.stringify({ ok: false, cards: all.length, anchored: false });
3879
+ if (anchor === null) return JSON.stringify({ ok: false, cards: all.length, anchored: false, visible: false });
3707
3880
  const card = anchor.classList.contains('wb-card') ? anchor : anchor.querySelector('.wb-card');
3708
- if (card === null) return JSON.stringify({ ok: false, cards: all.length, anchored: true });
3881
+ if (card === null) return JSON.stringify({ ok: false, cards: all.length, anchored: true, visible: false });
3709
3882
  const box = card.getBoundingClientRect();
3883
+ const painted = getComputedStyle(card);
3884
+ const visible =
3885
+ painted.visibility !== 'hidden' && painted.display !== 'none' && box.width > 0 && box.height > 0;
3710
3886
  const head = card.querySelector('.wb-cardTitle');
3711
3887
  const text = scrub(card.textContent);
3712
3888
  const shown = scrub(card.innerText);
@@ -3749,6 +3925,7 @@ function measureScriptOf(unit, pick) {
3749
3925
  return JSON.stringify({
3750
3926
  ok: true,
3751
3927
  cards: all.length,
3928
+ visible,
3752
3929
  width: Math.ceil(box.width),
3753
3930
  height: Math.ceil(box.height),
3754
3931
  x: Math.round(box.x),
@@ -3900,12 +4077,21 @@ async function workbenchPageOf(shell, command, session) {
3900
4077
  if (parsed === void 0) return {
3901
4078
  ok: false,
3902
4079
  url: "",
3903
- title: ""
4080
+ title: "",
4081
+ where: "",
4082
+ board: "",
4083
+ open: null,
4084
+ shown: false
3904
4085
  };
4086
+ const raw = parsed["open"];
3905
4087
  return {
3906
4088
  ok: textOf$4(parsed["plugin"]) === "@dsh-workbench/core",
3907
4089
  url: textOf$4(parsed["url"]),
3908
- title: textOf$4(parsed["title"])
4090
+ title: textOf$4(parsed["title"]),
4091
+ where: textOf$4(parsed["where"]),
4092
+ board: textOf$4(parsed["board"]),
4093
+ open: typeof raw === "boolean" ? raw : null,
4094
+ shown: parsed["shown"] === true
3909
4095
  };
3910
4096
  }
3911
4097
  async function writeShotsDir(shell, dir) {
@@ -3951,13 +4137,15 @@ async function measureCard(shell, command, session, unit, pick) {
3951
4137
  if (parsed === void 0 || parsed["ok"] !== true) return {
3952
4138
  ...EMPTY_CARD,
3953
4139
  cards: numberIn(parsed?.["cards"], 0),
3954
- anchored: parsed?.["anchored"] === true
4140
+ anchored: parsed?.["anchored"] === true,
4141
+ visible: parsed?.["visible"] === true
3955
4142
  };
3956
4143
  const width = numberIn(parsed["width"], 0);
3957
4144
  const height = numberIn(parsed["height"], 0);
3958
4145
  return {
3959
4146
  ok: true,
3960
4147
  anchored: true,
4148
+ visible: parsed["visible"] === true,
3961
4149
  width,
3962
4150
  height,
3963
4151
  head: textOf$4(parsed["head"]),
@@ -4036,6 +4224,35 @@ async function shootCard(shell, command, session, unit, file, view) {
4036
4224
  }
4037
4225
  //#endregion
4038
4226
  //#region src/modules/panels/model/diy-page.ts
4227
+ function debugPausedText(held) {
4228
+ return `调试已暂停(${held.unit})—— 动作族关闸,不再拿屏当目标。要接着看先 workbench_diy_debug_resume(会再要用户点头一次);这之前可以改源码 / build / 跑门禁。`;
4229
+ }
4230
+ function debugForeignPactText(held) {
4231
+ return `另一场调试正占着那块屏(${held.sessionId} 在调试 ${held.unit})—— 一台机器同时只允许一场:让它先 workbench_diy_debug_stop,或等它收工。`;
4232
+ }
4233
+ function debugOffBoardText(held, situation = "other-board") {
4234
+ const what = situation === "off-workspace" ? "用户现在不在 DIY 工作区(外壳选中的是别的工作区)" : situation === "panel-closed" ? "工作台面板没画在屏上(右栏收着,或那一列让给了别的面板)" : "你现在这一页画的不是工坊那块板";
4235
+ const next = situation === "off-workspace" ? "让用户在左栏切回「工作台DIY」那个工作区" : situation === "panel-closed" ? "让用户把「工作台面板」切回来(或把右栏展开)" : "让用户把那一页切回「工作台DIY」";
4236
+ return `${what} —— 看 / 动 / 网络这一族现在发不出去,我不拿别的板当目标。${held.unit} 这场调试仍然**进行中**(我没有自动替你落 paused):${next};要停下来就自己 workbench_diy_debug_pause(不问审批),页面切回来了就接着看。`;
4237
+ }
4238
+ function boardProbeOf(page) {
4239
+ return {
4240
+ workbench: page.ok,
4241
+ where: page.where,
4242
+ bar: page.open !== null,
4243
+ board: page.board,
4244
+ open: page.open === true,
4245
+ drawn: page.shown
4246
+ };
4247
+ }
4248
+ function boardStateOf(page, workshop) {
4249
+ return boardFactOf(boardProbeOf(page), workshop);
4250
+ }
4251
+ function boardGateText(situation, unit, page) {
4252
+ if (situation === "off-workspace") return `用户现在不在 DIY 工作区 —— 外壳选中的是 ${page.where || "(问不出来是哪一块)"}。工坊那块板没在屏上,${unit} 的这张卡我看不到,也不拿别的板当目标。让用户在左栏切回「工作台DIY」;这之前我可以继续改源码 / build / 跑门禁。`;
4253
+ if (situation === "panel-closed") return `工作台面板没画在屏上(右栏收着,或者这一页让给了别的面板:文件预览、模块市场等)。让用户把「工作台面板」切回来(或把右栏展开),我再接着看 ${unit}。`;
4254
+ return `你现在这一页画的是 ${page.board || "(不知道哪一块)"} 的面板,工坊那块板没在这一屏 —— ${unit} 的这张卡不在屏上,我不拿别的板当目标。请用户在左栏切回「工作台DIY」;这之前我可以继续改源码 / build / 跑门禁。`;
4255
+ }
4039
4256
  async function pageAt(ctx, exec, raw, options = {}) {
4040
4257
  const { packagesDir } = workshopOf(ctx, exec);
4041
4258
  const args = asRecord$3(raw);
@@ -4053,6 +4270,17 @@ async function pageAt(ctx, exec, raw, options = {}) {
4053
4270
  const { page } = handshake;
4054
4271
  throw new Error(`你现在这一页不是工作台(${page.title || "(没有标题)"} ${page.url})—— 把工作台那一页切到前台,再喊我一次。我只看用户正在看的那一页。`);
4055
4272
  }
4273
+ const workshop = workshopPathOf(ctx);
4274
+ const board = boardStateOf(handshake.page, workshop);
4275
+ const situation = boardSituation(boardProbeOf(handshake.page), workshop);
4276
+ const held = ctx.get("workbenchDiyDebug")?.live(Date.now()) ?? null;
4277
+ const sessionId = sessionIdOf(exec);
4278
+ if (options.requirePact !== false && held !== null) {
4279
+ if (held.sessionId !== sessionId) throw new Error(debugForeignPactText(held));
4280
+ if (held.phase === "paused") throw new Error(debugPausedText(held));
4281
+ if (board === "other" || board === "hidden") throw new Error(debugOffBoardText(held, situation));
4282
+ }
4283
+ if (options.requireBoard !== false && (board === "other" || board === "hidden")) throw new Error(boardGateText(situation, wanted, handshake.page));
4056
4284
  return {
4057
4285
  packagesDir,
4058
4286
  unit: wanted,
@@ -4060,10 +4288,155 @@ async function pageAt(ctx, exec, raw, options = {}) {
4060
4288
  shell,
4061
4289
  command,
4062
4290
  session,
4063
- bound: handshake.bound
4291
+ bound: handshake.bound,
4292
+ board,
4293
+ boardPath: handshake.page.board
4064
4294
  };
4065
4295
  }
4066
4296
  //#endregion
4297
+ //#region src/modules/panels/model/diy-debug-tools.ts
4298
+ var EMPTY = {
4299
+ type: "object",
4300
+ properties: {},
4301
+ additionalProperties: false
4302
+ };
4303
+ function debugRefusalText(code, held) {
4304
+ switch (code) {
4305
+ case "held-by-other": return `另一场调试还占着那块屏(${held === void 0 ? "另一个会话" : held.sessionId})—— 一台机器同时只允许一场:让它先 workbench_diy_debug_stop,或等它收工。`;
4306
+ case "already-held": return `你这个会话已经持有一场调试(${held === void 0 ? "" : `${held.unit},${held.phase}`})—— 要换目标先 workbench_diy_debug_stop;暂停着要接着看用 workbench_diy_debug_resume。`;
4307
+ case "not-owner": return "这个调试态不是你这个会话的 —— 谁开始的谁收工。";
4308
+ case "none": return "现在没有调试态。";
4309
+ case "already-paused": return "已经暂停了 —— 要接着看用 workbench_diy_debug_resume(会再要用户点头)。";
4310
+ case "already-debugging": return "正在调试中,不用再开一次。";
4311
+ }
4312
+ }
4313
+ function renderDebugStart(state) {
4314
+ return [`开始调试 ${state.unit}(声明的目标:${state.board === "" ? "用户那一屏" : state.board})。`, "现在看 / 动 / 网络那一族才把屏当目标 —— 收工前记得 workbench_diy_debug_stop。"].join("\n");
4315
+ }
4316
+ function renderDebugPause(state) {
4317
+ return [`调试暂停了(${state.unit})—— 动作族关闸,不再拿屏当目标。`, "改源码 / build / 跑门禁照常;要看屏再 workbench_diy_debug_resume(会再要用户点头一次)。"].join("\n");
4318
+ }
4319
+ function renderDebugResume(state) {
4320
+ return [`继续调试 ${state.unit} —— 请用户回到「工作台DIY」那一屏。`, "卡不在屏上时我会当场拒(不拿别的板当目标)。"].join("\n");
4321
+ }
4322
+ function renderDebugStop(state) {
4323
+ return `收工了:${state.unit} 的调试态清掉了(占用解除)。`;
4324
+ }
4325
+ function moveOrThrow(move, held) {
4326
+ if (move.ok) return move.state;
4327
+ throw new Error(debugRefusalText(move.code, held ?? void 0));
4328
+ }
4329
+ function registerDebugTools(ctx, tools, store) {
4330
+ const startTool = {
4331
+ name: DIY_DEBUG_START_TOOL,
4332
+ description: "开始调试:占用用户那一屏(要点头一次)。声明 unit 之后,看 / 动那一族才把屏当目标。",
4333
+ parameters: withApprovalNote({
4334
+ type: "object",
4335
+ properties: { unit: {
4336
+ type: "string",
4337
+ description: "工坊里的 unit(workbench_diy_list 会给)。"
4338
+ } },
4339
+ required: ["unit"],
4340
+ additionalProperties: false
4341
+ }, { required: true }),
4342
+ output: OUTPUT$2,
4343
+ async execute(raw, exec) {
4344
+ const { packagesDir, workspace } = workshopOf(ctx, exec);
4345
+ const unit = asText$1(asRecord$3(raw)["unit"]);
4346
+ if (unit === "") {
4347
+ const { modules } = modulesOf(ctx, packagesDir);
4348
+ throw new Error(`要一个 unit(工坊里:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
4349
+ }
4350
+ const held = store.live(Date.now());
4351
+ const page = await pageAt(ctx, exec, raw, {
4352
+ requireBoard: false,
4353
+ requirePact: false
4354
+ });
4355
+ const state = moveOrThrow(store.begin({
4356
+ sessionId: sessionIdOf(exec) ?? "",
4357
+ workspace: workspace ?? "",
4358
+ board: page.boardPath,
4359
+ unit,
4360
+ name: modulesOf(ctx, packagesDir).modules.find((one) => one.unit === unit)?.name ?? unit,
4361
+ now: Date.now()
4362
+ }), held);
4363
+ return plainJson({
4364
+ workshop: packagesDir,
4365
+ unit,
4366
+ board: state.board,
4367
+ phase: state.phase,
4368
+ state,
4369
+ text: renderDebugStart(state)
4370
+ });
4371
+ }
4372
+ };
4373
+ const pauseTool = {
4374
+ name: DIY_DEBUG_PAUSE_TOOL,
4375
+ description: "暂停调试(不审批):动作族关闸,不再拿屏当目标。要看屏再 debug_resume。",
4376
+ parameters: EMPTY,
4377
+ output: OUTPUT$2,
4378
+ async execute(_raw, exec) {
4379
+ const { packagesDir } = workshopOf(ctx, exec);
4380
+ const held = store.live(Date.now());
4381
+ const state = moveOrThrow(store.pause(sessionIdOf(exec) ?? "", Date.now()), held);
4382
+ return plainJson({
4383
+ workshop: packagesDir,
4384
+ unit: state.unit,
4385
+ phase: state.phase,
4386
+ state,
4387
+ text: renderDebugPause(state)
4388
+ });
4389
+ }
4390
+ };
4391
+ const resumeTool = {
4392
+ name: DIY_DEBUG_RESUME_TOOL,
4393
+ description: "继续调试(要点头一次):请用户回到「工作台DIY」那一屏。",
4394
+ parameters: withApprovalNote(EMPTY, { required: true }),
4395
+ output: OUTPUT$2,
4396
+ async execute(_raw, exec) {
4397
+ const { packagesDir } = workshopOf(ctx, exec);
4398
+ const held = store.live(Date.now());
4399
+ const page = await pageAt(ctx, exec, { unit: held?.unit ?? "" }, {
4400
+ requireBoard: false,
4401
+ requirePact: false,
4402
+ requireUnit: false
4403
+ });
4404
+ const state = moveOrThrow(store.resume(sessionIdOf(exec) ?? "", Date.now()), held);
4405
+ return plainJson({
4406
+ workshop: packagesDir,
4407
+ unit: state.unit,
4408
+ board: page.boardPath,
4409
+ phase: state.phase,
4410
+ state,
4411
+ text: renderDebugResume(state)
4412
+ });
4413
+ }
4414
+ };
4415
+ const stopTool = {
4416
+ name: DIY_DEBUG_STOP_TOOL,
4417
+ description: "结束调试(不审批):清掉\"占用用户那一屏\"这件事。收工用它。",
4418
+ parameters: EMPTY,
4419
+ output: OUTPUT$2,
4420
+ async execute(_raw, exec) {
4421
+ const { packagesDir } = workshopOf(ctx, exec);
4422
+ const held = store.live(Date.now());
4423
+ const state = moveOrThrow(store.stop(sessionIdOf(exec) ?? "", Date.now()), held);
4424
+ return plainJson({
4425
+ workshop: packagesDir,
4426
+ unit: state.unit,
4427
+ phase: "idle",
4428
+ text: renderDebugStop(state)
4429
+ });
4430
+ }
4431
+ };
4432
+ return [
4433
+ tools.register(startTool),
4434
+ tools.register(pauseTool),
4435
+ tools.register(resumeTool),
4436
+ tools.register(stopTool)
4437
+ ];
4438
+ }
4439
+ //#endregion
4067
4440
  //#region src/modules/panels/model/diy-browser-act.ts
4068
4441
  var ACT_TIMEOUT_MS = 15e3;
4069
4442
  function actTargetOf(raw) {
@@ -4180,12 +4553,11 @@ async function scrollIntoView(shell, command, session, unit, selector) {
4180
4553
  async function reloadPage(shell, command, session) {
4181
4554
  return actBrowser(shell, command, session, ["eval", `(() => { setTimeout(() => location.reload(), 30); return JSON.stringify({ ok: true }); })()`]);
4182
4555
  }
4183
- async function shootAtWidth(shell, command, session, file, width, height) {
4556
+ async function shootAtWidth(shell, command, session, file, width = 0, height = 0) {
4184
4557
  const step = await actBrowser(shell, command, session, [
4185
4558
  "screenshot",
4186
4559
  file,
4187
- "--width",
4188
- String(width),
4560
+ ...width > 0 ? ["--width", String(width)] : [],
4189
4561
  ...height > 0 ? ["--height", String(height)] : []
4190
4562
  ], LOOK_TIMEOUT_MS);
4191
4563
  return {
@@ -4321,16 +4693,20 @@ function registerLookTools(ctx, tools) {
4321
4693
  },
4322
4694
  output: OUTPUT$2,
4323
4695
  async execute(raw, exec) {
4324
- const { packagesDir, unit, shell, command, session } = await pageAt(ctx, exec, raw);
4696
+ const { packagesDir, unit, shell, command, session, board } = await pageAt(ctx, exec, raw);
4325
4697
  const pick = pickOf(asRecord$3(raw));
4326
4698
  let view = await measureCard(shell, command, session, unit, pick);
4327
- if (!view.ok) return plainJson({
4328
- workshop: packagesDir,
4329
- unit,
4330
- ok: false,
4331
- cards: view.cards,
4332
- text: renderMissingCard(unit, view.cards, view.anchored)
4333
- });
4699
+ if (!view.ok || !view.visible) {
4700
+ const state = view.ok ? "hidden" : board;
4701
+ return plainJson({
4702
+ workshop: packagesDir,
4703
+ unit,
4704
+ ok: false,
4705
+ board: state,
4706
+ cards: view.cards,
4707
+ text: renderMissingCard(unit, view, state)
4708
+ });
4709
+ }
4334
4710
  if (!insideViewportOf(view)) {
4335
4711
  await scrollIntoView(shell, command, session, unit, "");
4336
4712
  view = await measureCard(shell, command, session, unit, pick);
@@ -4431,9 +4807,12 @@ function renderLook(unit, view, shot, pick) {
4431
4807
  ...view.clipped ? ["有内容被裁掉了(某一格比它自己的宽度还宽)—— 这是要改的信号"] : []
4432
4808
  ].join("\n");
4433
4809
  }
4434
- function renderMissingCard(unit, cards, anchored) {
4435
- if (anchored) return `面板里给 ${unit} 留了位置(锚点在),但里面没有卡片 —— 它的 View 没画出来:要么渲染时抛了(看 workbench_diy_panel 的运行态上报与 workbench_diy_status),要么它返回了空 / 没套卡壳。你现在这一页面板里一共 ${String(cards)} 张卡。`;
4436
- return `你现在这一页的面板里没有 ${unit} 这张卡(面板里现在有 ${String(cards)} 张卡)。三件事,按顺序试:① workbench_diy_show(unit=${unit})把它上板;② 右栏可能没停在「工作台面板」那一列 —— 让用户切回来;③ 页面上跑的外壳可能还是旧的一份(锚点由外壳盖)—— 让用户刷新一次页面。`;
4810
+ function renderMissingCard(unit, view, board) {
4811
+ if (board === "other") return `你现在这一页画的是别的工作区的面板,工坊那块板没在这一屏 —— ${unit} 的卡不在屏上,我不拿别的板当目标。请用户在左栏切回「工作台DIY」,或把这张卡上到他正看着的那块板上。`;
4812
+ if (board === "hidden") return `右栏收着 —— 面板没画在屏上(锚点还在页面里,但看不见它),量不到也不该拍 ${unit}。让用户把面板展开、或切回「工作台DIY」,我再接着看。`;
4813
+ if (view.anchored) return `面板里给 ${unit} 留了位置(锚点在),但里面没有卡片 —— 它的 View 没画出来:要么渲染时抛了(看 workbench_diy_panel 的运行态上报与 workbench_diy_status),要么它返回了空 / 没套卡壳。你现在这一页面板里一共 ${String(view.cards)} 张卡。`;
4814
+ const stale = board === "unknown" ? "③ 页面上跑的外壳可能还是旧的一份(锚点由外壳盖)—— 让用户刷新一次页面。" : "";
4815
+ return `你现在这一页的面板里没有 ${unit} 这张卡(面板里现在有 ${String(view.cards)} 张卡)。按顺序试:① workbench_diy_show(unit=${unit})把它上板;② 右栏可能没停在「工作台面板」那一列 —— 让用户切回来。${stale}`;
4437
4816
  }
4438
4817
  //#endregion
4439
4818
  //#region src/modules/panels/model/diy-logs.ts
@@ -5089,24 +5468,26 @@ function registerActTools(ctx, tools) {
5089
5468
  };
5090
5469
  const viewportTool = {
5091
5470
  name: DIY_VIEWPORT_TOOL,
5092
- description: "按某个宽度重拍一张整屏图(看窄屏/宽屏下这张卡挤不挤)—— 只在这一拍里换视口。",
5471
+ description: "拍整屏图。不给宽高 = 按用户当前窗口拍;给了才换视口 —— 会回流一次。",
5093
5472
  parameters: pageSchema({
5094
5473
  width: {
5095
5474
  type: "number",
5096
- description: "视口宽度(CSS px,例如 390)。"
5475
+ description: "视口宽度(CSS px,如 390);不填按当前窗口。"
5097
5476
  },
5098
5477
  height: {
5099
5478
  type: "number",
5100
5479
  description: "视口高度(可不要)。"
5101
5480
  }
5102
- }, ["width"]),
5481
+ }, []),
5103
5482
  output: OUTPUT$2,
5104
5483
  async execute(raw, exec) {
5105
5484
  const page = await pageAt(ctx, exec, raw, { requireUnit: false });
5106
- const width = asNumber(page.args["width"]);
5107
- if (width === void 0 || width <= 0) throw new Error("多宽?给 width(CSS px)。");
5485
+ const width = asNumber(page.args["width"]) ?? 0;
5486
+ if (width < 0) throw new Error("width 得是正的 CSS px;不给就按用户当前窗口拍。");
5108
5487
  const height = asNumber(page.args["height"]) ?? 0;
5109
- const file = join(dirname(page.packagesDir), "shots", `${page.unit || "page"}-${String(width)}-${String(Date.now())}.png`);
5488
+ const overrode = width > 0 || height > 0;
5489
+ const stamp = overrode ? `${String(width)}x${String(height)}` : "now";
5490
+ const file = join(dirname(page.packagesDir), "shots", `${page.unit || "page"}-${stamp}-${String(Date.now())}.png`);
5110
5491
  await writeShotsDir(page.shell, dirname(file));
5111
5492
  const shot = await shootAtWidth(page.shell, page.command, page.session, file, width, height);
5112
5493
  return plainJson({
@@ -5114,12 +5495,13 @@ function registerActTools(ctx, tools) {
5114
5495
  unit: page.unit,
5115
5496
  ok: shot.ok,
5116
5497
  file: shot.file,
5117
- width,
5118
- viewport: {
5498
+ overrode,
5499
+ width: overrode ? width : null,
5500
+ viewport: overrode ? {
5119
5501
  width,
5120
5502
  height
5121
- },
5122
- text: shot.ok ? `按 ${String(width)}px 宽重拍了一张:${shot.file}(页面按新视口回流,只在这一拍里生效)。` : `没拍成:${shot.reason}`
5503
+ } : null,
5504
+ text: shot.ok ? overrode ? `重拍了一张:${shot.file}(临时换了视口${width > 0 ? ` 宽 ${String(width)}px` : ""}${height > 0 ? ` 高 ${String(height)}px` : ""},你那一屏会回流一次、拍完就恢复)。` : `按你当前窗口拍了一张:${shot.file}(没动你的视口)。` : `没拍成:${shot.reason}`
5123
5505
  });
5124
5506
  }
5125
5507
  };
@@ -5483,7 +5865,7 @@ function renderList(packagesDir, modules) {
5483
5865
  }
5484
5866
  //#endregion
5485
5867
  //#region src/modules/panels/model/diy-tools.ts
5486
- function registerDiyTools(ctx, market, panels) {
5868
+ function registerDiyTools(ctx, market, panels, debug) {
5487
5869
  const tools = ctx.get("tools");
5488
5870
  if (tools === void 0) {
5489
5871
  ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过 DIY 工坊工具注册");
@@ -5831,11 +6213,20 @@ function registerDiyTools(ctx, market, panels) {
5831
6213
  ...registerLogTools(ctx, tools),
5832
6214
  ...registerNetTools(ctx, tools),
5833
6215
  ...registerActTools(ctx, tools),
6216
+ ...registerDebugTools(ctx, tools, debug),
5834
6217
  tools.register(showTool),
5835
6218
  tools.register(installTool)
5836
6219
  ];
5837
6220
  }
5838
6221
  //#endregion
6222
+ //#region src/modules/panels/model/diy-debug-routes.ts
6223
+ function diyDebugRoutes(store) {
6224
+ return { "panels/diy-debug": () => ({
6225
+ ok: true,
6226
+ state: store.live(Date.now())
6227
+ }) };
6228
+ }
6229
+ //#endregion
5839
6230
  //#region src/shared/api/host/release.ts
5840
6231
  function parseLedgerVersion(stdout, name) {
5841
6232
  const parsed = parseJsonLoose(stdout);
@@ -7869,7 +8260,14 @@ function applyPanelsPref(panels, payload) {
7869
8260
  //#endregion
7870
8261
  //#region src/modules/panels/model/handoff.ts
7871
8262
  var PANELS_FACT = "panels";
8263
+ var DIY_DEBUG_FACT = "diy-debug";
7872
8264
  var CAPABILITY_FACT = "capability";
8265
+ var DEBUG_TOOLS = [
8266
+ DIY_DEBUG_START_TOOL,
8267
+ DIY_DEBUG_PAUSE_TOOL,
8268
+ DIY_DEBUG_RESUME_TOOL,
8269
+ DIY_DEBUG_STOP_TOOL
8270
+ ];
7873
8271
  var PROVISION_TOOLS = {
7874
8272
  [MODULES_INSTALL_TOOL]: "install",
7875
8273
  [MODULES_UPDATE_TOOL]: "update",
@@ -7905,6 +8303,7 @@ function panelsFactsOf(call, manifests) {
7905
8303
  if (call.source === "workbench_modules_list") return [PANELS_FACT, CAPABILITY_FACT];
7906
8304
  if (call.source === "workbench_modules_update_workbench") return [PANELS_FACT, CAPABILITY_FACT];
7907
8305
  if (call.source === "workbench_diy_list") return [PANELS_FACT];
8306
+ if (DEBUG_TOOLS.includes(call.source)) return [DIY_DEBUG_FACT];
7908
8307
  const request = requestOf(call);
7909
8308
  if (request === void 0) return void 0;
7910
8309
  return [
@@ -8479,6 +8878,8 @@ var OWNED = [{
8479
8878
  logger
8480
8879
  });
8481
8880
  ctx.provide(PANELS_SERVICE, panels);
8881
+ const debug = createDebugStore();
8882
+ ctx.provide(DIY_DEBUG_SERVICE, debug);
8482
8883
  const profileDir = profileDirOf(ctx);
8483
8884
  const shell = ctx.get("shell");
8484
8885
  const moduleManifests = () => {
@@ -8526,12 +8927,13 @@ var OWNED = [{
8526
8927
  ...pluginRoutes({
8527
8928
  ledger,
8528
8929
  ...shell === void 0 ? {} : { shell }
8529
- })
8930
+ }),
8931
+ ...diyDebugRoutes(debug)
8530
8932
  },
8531
8933
  registerTools: () => [
8532
8934
  ...registerPanelTools(ctx, panels, values),
8533
8935
  ...registerMarketTools(ctx, market),
8534
- ...registerDiyTools(ctx, market, panels)
8936
+ ...registerDiyTools(ctx, market, panels, debug)
8535
8937
  ],
8536
8938
  factsOf: (call) => panelsFactsOf(call, manifests()),
8537
8939
  needsApproval: panelsApproval,
@@ -8542,7 +8944,7 @@ var OWNED = [{
8542
8944
  return [...manifests().flatMap((manifest) => owned.has(manifest.id) || resolveScope({
8543
8945
  id: manifest.id,
8544
8946
  boards
8545
- }) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [...DIY_TOOLS]];
8947
+ }) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [...DIY_TOOLS, ...DIY_DEBUG_TOOLS]];
8546
8948
  },
8547
8949
  subscribe: (listener) => panels.subscribe(listener)
8548
8950
  },
@@ -8875,6 +9277,122 @@ function createPostChannels(deps) {
8875
9277
  };
8876
9278
  }
8877
9279
  //#endregion
9280
+ //#region src/shared/api/host/diy-debug-act.ts
9281
+ var DEBUG_NOTICE_KIND = "workbench-diy";
9282
+ var INTENT_WORD = {
9283
+ pause: "暂停",
9284
+ resume: "继续",
9285
+ stop: "结束"
9286
+ };
9287
+ function debugNoticeText(intent, state) {
9288
+ const head = `[工作台 DIY]用户按了「${INTENT_WORD[intent]}」:`;
9289
+ if (intent === "pause") return `${head}${state.unit} 这场调试暂停了 —— 现在别再把那一屏当目标(看 / 动 / 网络那一族发不出去)。要接着看屏就等用户按「继续」;在那之前照常改源码 / build / 跑门禁。`;
9290
+ if (intent === "resume") return `${head}${state.unit} 这场调试重新开始了 —— 那一屏又归你看。屏 / 板不对的话照旧会被拒,拒了就照实说清是哪种不对。`;
9291
+ return `${head}${state.unit} 这场调试收工了(占用解除)。要再调试就重新 start 一场。`;
9292
+ }
9293
+ function debugNoticeSummary(intent, state) {
9294
+ return `工作台 DIY:用户按了${INTENT_WORD[intent]}(${state.unit})`;
9295
+ }
9296
+ function debugNotice(intent, state) {
9297
+ const summary = debugNoticeSummary(intent, state);
9298
+ return {
9299
+ summary,
9300
+ message: {
9301
+ role: "user",
9302
+ id: randomUUID(),
9303
+ content: [{
9304
+ type: "text",
9305
+ text: debugNoticeText(intent, state)
9306
+ }],
9307
+ source: {
9308
+ kind: DEBUG_NOTICE_KIND,
9309
+ form: "notice",
9310
+ summary
9311
+ }
9312
+ }
9313
+ };
9314
+ }
9315
+ var NOTICE_MODE = {
9316
+ resume: "wake",
9317
+ pause: "hold",
9318
+ stop: "drop"
9319
+ };
9320
+ var held = /* @__PURE__ */ new Map();
9321
+ function holdNotice(sessionId, message) {
9322
+ const list = held.get(sessionId);
9323
+ if (list === void 0) held.set(sessionId, [message]);
9324
+ else list.push(message);
9325
+ }
9326
+ function takeHeldNotices(sessionId) {
9327
+ const list = held.get(sessionId) ?? [];
9328
+ held.delete(sessionId);
9329
+ return list;
9330
+ }
9331
+ function deliverDebugNotice(agents, sessionId, message, mode = "wake") {
9332
+ const agent = agents?.get(sessionId);
9333
+ if (agent === void 0) {
9334
+ if (mode === "drop") return "none";
9335
+ holdNotice(sessionId, message);
9336
+ return "held";
9337
+ }
9338
+ if (agent.status !== "idle") {
9339
+ agent.inject(message);
9340
+ return "inject";
9341
+ }
9342
+ if (mode === "wake") {
9343
+ agent.followup(message);
9344
+ return "followup";
9345
+ }
9346
+ if (mode === "hold") {
9347
+ holdNotice(sessionId, message);
9348
+ return "held";
9349
+ }
9350
+ return "skipped-idle";
9351
+ }
9352
+ var REFUSAL = {
9353
+ "already-paused": "这一场已经是暂停态了",
9354
+ "already-debugging": "这一场本来就在调试中",
9355
+ none: "现在没有调试态",
9356
+ "not-owner": "这场调试不是这个会话的"
9357
+ };
9358
+ function debugActResource(deps) {
9359
+ const now = () => deps.now?.() ?? Date.now();
9360
+ return {
9361
+ resource: "act",
9362
+ handle(payload) {
9363
+ const intent = parseDebugIntent(payload?.intent);
9364
+ if (intent === null) return {
9365
+ ok: false,
9366
+ text: "不认识的意图(只认 pause / resume / stop)"
9367
+ };
9368
+ const store = deps.store();
9369
+ if (store === void 0) return {
9370
+ ok: false,
9371
+ text: "现在没有调试态 —— 没什么可暂停 / 继续 / 结束的"
9372
+ };
9373
+ const at = now();
9374
+ const held = store.live(at);
9375
+ if (held === null) return {
9376
+ ok: false,
9377
+ text: "现在没有调试态 —— 没什么可暂停 / 继续 / 结束的"
9378
+ };
9379
+ const move = intent === "pause" ? store.pause(held.sessionId, at) : intent === "resume" ? store.resume(held.sessionId, at) : store.stop(held.sessionId, at);
9380
+ if (!move.ok) return {
9381
+ ok: false,
9382
+ text: REFUSAL[move.code] ?? `按不动:${move.code}`
9383
+ };
9384
+ const notice = deps.notice?.(intent, move.state) ?? debugNotice(intent, move.state);
9385
+ return {
9386
+ ok: true,
9387
+ intent,
9388
+ delivered: deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE[intent]),
9389
+ summary: notice.summary,
9390
+ state: store.live(now())
9391
+ };
9392
+ }
9393
+ };
9394
+ }
9395
+ //#endregion
8878
9396
  //#region src/shared/api/host/events.ts
8879
9397
  var HEARTBEAT_MS = 25e3;
8880
9398
  function frame(payload) {
@@ -11004,15 +11522,17 @@ function createToolScope(options) {
11004
11522
  };
11005
11523
  }
11006
11524
  //#endregion
11007
- //#region src/app/host.ts
11008
- var name = "dsh-workbench";
11009
- var inject = ["webServer"];
11525
+ //#region src/shared/api/host/host-args.ts
11010
11526
  function asArgs(value) {
11011
11527
  return typeof value === "object" && value !== null ? value : {};
11012
11528
  }
11013
11529
  function warn(ctx, message) {
11014
11530
  ctx.logger?.warn?.(`[dsh-workbench] ${message}`);
11015
11531
  }
11532
+ //#endregion
11533
+ //#region src/app/host.ts
11534
+ var name = "dsh-workbench";
11535
+ var inject = ["webServer"];
11016
11536
  function seedWorkbenchRuntime(ctx) {
11017
11537
  const root = runtimeSeedRootOf(ctx);
11018
11538
  if (root === null) return;
@@ -11384,9 +11904,10 @@ function apply(ctx) {
11384
11904
  if (decision.kind === "reject") return decision;
11385
11905
  try {
11386
11906
  const message = await injector.inject(decision.messages ?? [], payload?.agent);
11387
- return message === void 0 ? decision : {
11907
+ const extra = [...takeHeldNotices(String((payload?.agent)?.id ?? "")), ...message === void 0 ? [] : [message]];
11908
+ return extra.length === 0 ? decision : {
11388
11909
  ...decision,
11389
- messages: [...decision.messages ?? [], message]
11910
+ messages: [...decision.messages ?? [], ...extra]
11390
11911
  };
11391
11912
  } catch (error) {
11392
11913
  ctx.logger?.warn?.(`[dsh-workbench] 对话引用注入失败:${String(error)}`);
@@ -11397,6 +11918,12 @@ function apply(ctx) {
11397
11918
  }, `${PLUGIN_ID}: 对话引用注入`);
11398
11919
  ctx.effect(() => registerFactStream(ctx, facts), `${PLUGIN_ID}: 变更流`);
11399
11920
  ctx.effect(() => registerPanelState(ctx, panelState), `${PLUGIN_ID}: 面板运行态上报(浏览器半侧报自己的取数与渲染收尾)`);
11921
+ ctx.effect(() => {
11922
+ return registerDiyDebugAct(ctx, debugActResource({
11923
+ store: () => ctx.get(DIY_DEBUG_SERVICE),
11924
+ agents: () => ctx.get("agents")
11925
+ }), { bump: (names) => facts.bump(names) });
11926
+ }, `${PLUGIN_ID}: DIY 调试的三个键(用户按的)`);
11400
11927
  ctx.effect(() => registerApi(ctx, routes), `${PLUGIN_ID}: HTTP 投影`);
11401
11928
  }
11402
11929
  //#endregion