@dsh-workbench/core 0.2.13 → 0.2.15

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 (42) hide show
  1. package/client.js +700 -124
  2. package/dist/client/client.body.js +700 -124
  3. package/dist/client/index.js +701 -125
  4. package/dist/host/index.js +387 -22
  5. package/dist/types/app/client.d.ts +124 -6
  6. package/dist/types/app/layouts/rightbar/DebugStrip.d.ts +11 -0
  7. package/dist/types/app/layouts/rightbar/kit-preview-controls.d.ts +1 -0
  8. package/dist/types/app/layouts/sidebar/DebugFooterAction.d.ts +13 -0
  9. package/dist/types/app/layouts/sidebar/ProjectSwitcher.d.ts +4 -1
  10. package/dist/types/app/layouts/sidebar/SidebarHeader.d.ts +12 -0
  11. package/dist/types/app/layouts/sidebar/WorkspaceAttentionDot.d.ts +9 -0
  12. package/dist/types/app/layouts/sidebar/WorkspaceAttentionTag.d.ts +7 -0
  13. package/dist/types/app/layouts/sidebar/__tests__/current-workspace.test.d.ts +1 -0
  14. package/dist/types/app/layouts/sidebar/__tests__/debug-ui.test.d.ts +1 -0
  15. package/dist/types/app/layouts/sidebar/current-workspace.d.ts +8 -0
  16. package/dist/types/app/layouts/sidebar/debug-footer.d.ts +4 -0
  17. package/dist/types/app/layouts/sidebar/session-pick.d.ts +2 -1
  18. package/dist/types/app/layouts/sidebar/use-attention.d.ts +16 -0
  19. package/dist/types/app/layouts/sidebar/workspace-items.d.ts +2 -0
  20. package/dist/types/modules/panels/__tests__/diy-harness.d.ts +6 -3
  21. package/dist/types/modules/panels/model/diy-browser.d.ts +3 -0
  22. package/dist/types/modules/panels/model/diy-debug-routes.d.ts +2 -0
  23. package/dist/types/modules/panels/model/diy-debug-routes.test.d.ts +1 -0
  24. package/dist/types/modules/panels/model/diy-debug-tools.d.ts +8 -0
  25. package/dist/types/modules/panels/model/diy-debug-tools.test.d.ts +1 -0
  26. package/dist/types/modules/panels/model/diy-look.d.ts +2 -1
  27. package/dist/types/modules/panels/model/diy-page.d.ts +12 -0
  28. package/dist/types/modules/panels/model/diy-scripts.d.ts +2 -1
  29. package/dist/types/modules/panels/model/diy-tools.d.ts +2 -1
  30. package/dist/types/modules/panels/model/handoff.d.ts +1 -0
  31. package/dist/types/modules/panels/unit.d.ts +7 -2
  32. package/dist/types/modules/sessions/index.d.ts +1 -0
  33. package/dist/types/modules/sessions/model/attention.d.ts +29 -0
  34. package/dist/types/modules/sessions/model/attention.test.d.ts +1 -0
  35. package/dist/types/shared/hooks/use-api-resource.d.ts +1 -0
  36. package/dist/types/shared/hooks/use-diy-debug.d.ts +4 -0
  37. package/package.json +2 -2
  38. package/skills/workbench-module-diy/BUILD.md +1 -1
  39. package/skills/workbench-module-diy/DATASOURCE.md +1 -1
  40. package/skills/workbench-module-diy/KIT.md +22 -5
  41. package/skills/workbench-module-diy/PANEL.md +1 -1
  42. package/skills/workbench-module-diy/REVIEW.md +1 -1
@@ -18,6 +18,7 @@ var OPENCLI_PIN = "1.8.8";
18
18
  var PANELS_SERVICE = "workbenchPanels";
19
19
  var UNITS_SERVICE = "workbenchUnits";
20
20
  var PANEL_STATE_SERVICE = "workbenchPanelState";
21
+ var DIY_DEBUG_SERVICE = "workbenchDiyDebug";
21
22
  var OPENCLI_TIMEOUT_MS = 12e4;
22
23
  var WARM_TIMEOUT_MS = 2e4;
23
24
  var INSTALL_TIMEOUT_MS = 3e5;
@@ -434,6 +435,119 @@ function createPanelStateStore() {
434
435
  }
435
436
  };
436
437
  }
438
+ function alive(state, now) {
439
+ if (state === null) return null;
440
+ return now - state.at <= 144e5 ? state : null;
441
+ }
442
+ function ownerOf(state, sessionId, now) {
443
+ const held = alive(state, now);
444
+ if (held === null) return "none";
445
+ return held.sessionId === sessionId ? held : "not-owner";
446
+ }
447
+ function createDebugStore() {
448
+ let held = null;
449
+ const live = (now) => alive(held, now);
450
+ return {
451
+ live,
452
+ begin(pact) {
453
+ const current = alive(held, pact.now);
454
+ if (current !== null) return {
455
+ ok: false,
456
+ code: current.sessionId === pact.sessionId ? "already-held" : "held-by-other"
457
+ };
458
+ const state = {
459
+ phase: "debugging",
460
+ sessionId: pact.sessionId,
461
+ workspace: pact.workspace,
462
+ board: pact.board,
463
+ unit: pact.unit,
464
+ name: pact.name,
465
+ startedAt: pact.now,
466
+ at: pact.now
467
+ };
468
+ held = state;
469
+ return {
470
+ ok: true,
471
+ state
472
+ };
473
+ },
474
+ stall(sessionId, now) {
475
+ const found = ownerOf(held, sessionId, now);
476
+ if (typeof found === "string") return {
477
+ ok: false,
478
+ code: found
479
+ };
480
+ if (found.phase === "paused") return {
481
+ ok: false,
482
+ code: "already-paused"
483
+ };
484
+ const state = {
485
+ ...found,
486
+ phase: "paused",
487
+ at: now
488
+ };
489
+ held = state;
490
+ return {
491
+ ok: true,
492
+ state
493
+ };
494
+ },
495
+ pause(sessionId, now) {
496
+ const found = ownerOf(held, sessionId, now);
497
+ if (typeof found === "string") return {
498
+ ok: false,
499
+ code: found
500
+ };
501
+ if (found.phase === "paused") return {
502
+ ok: false,
503
+ code: "already-paused"
504
+ };
505
+ const state = {
506
+ ...found,
507
+ phase: "paused",
508
+ at: now
509
+ };
510
+ held = state;
511
+ return {
512
+ ok: true,
513
+ state
514
+ };
515
+ },
516
+ resume(sessionId, now) {
517
+ const found = ownerOf(held, sessionId, now);
518
+ if (typeof found === "string") return {
519
+ ok: false,
520
+ code: found
521
+ };
522
+ if (found.phase === "debugging") return {
523
+ ok: false,
524
+ code: "already-debugging"
525
+ };
526
+ const state = {
527
+ ...found,
528
+ phase: "debugging",
529
+ at: now
530
+ };
531
+ held = state;
532
+ return {
533
+ ok: true,
534
+ state
535
+ };
536
+ },
537
+ stop(sessionId, now) {
538
+ const found = ownerOf(held, sessionId, now);
539
+ if (typeof found === "string") return {
540
+ ok: false,
541
+ code: found
542
+ };
543
+ held = null;
544
+ return {
545
+ ok: true,
546
+ state: found
547
+ };
548
+ }
549
+ };
550
+ }
437
551
  //#endregion
438
552
  //#region ../shared/dist/host/opencli-window.js
439
553
  var OPENCLI_WINDOW_ENV = "OPENCLI_WINDOW=background";
@@ -2138,6 +2252,10 @@ function sessionWorkspace(exec) {
2138
2252
  const cwd = exec?.agent?.session?.header?.cwd;
2139
2253
  return typeof cwd === "string" && cwd.trim() !== "" ? cwd.trim() : null;
2140
2254
  }
2255
+ function sessionIdOf(exec) {
2256
+ const id = exec?.agent?.session?.id;
2257
+ return typeof id === "string" && id.trim() !== "" ? id.trim() : null;
2258
+ }
2141
2259
  function textOutput(text) {
2142
2260
  return [{
2143
2261
  type: "text",
@@ -2871,6 +2989,10 @@ var DIY_NET_STOP_TOOL = "workbench_diy_net_stop";
2871
2989
  var DIY_LOGS_WATCH_TOOL = "workbench_diy_logs_watch";
2872
2990
  var DIY_LOGS_READ_TOOL = "workbench_diy_logs_read";
2873
2991
  var DIY_LOGS_STOP_TOOL = "workbench_diy_logs_stop";
2992
+ var DIY_DEBUG_START_TOOL = "workbench_diy_debug_start";
2993
+ var DIY_DEBUG_PAUSE_TOOL = "workbench_diy_debug_pause";
2994
+ var DIY_DEBUG_RESUME_TOOL = "workbench_diy_debug_resume";
2995
+ var DIY_DEBUG_STOP_TOOL = "workbench_diy_debug_stop";
2874
2996
  var READ_TOOLS = [
2875
2997
  PANELS_LIST_TOOL,
2876
2998
  MODULES_LIST_TOOL,
@@ -2911,7 +3033,11 @@ var WRITE_TOOLS = [
2911
3033
  MODULES_UNINSTALL_PLUGIN_TOOL,
2912
3034
  MODULES_UPDATE_WORKBENCH_TOOL,
2913
3035
  DIY_INSTALL_TOOL,
2914
- DIY_SHOW_TOOL
3036
+ DIY_SHOW_TOOL,
3037
+ DIY_DEBUG_START_TOOL,
3038
+ DIY_DEBUG_PAUSE_TOOL,
3039
+ DIY_DEBUG_RESUME_TOOL,
3040
+ DIY_DEBUG_STOP_TOOL
2915
3041
  ];
2916
3042
  var DIY_TOOLS = [
2917
3043
  DIY_LIST_TOOL,
@@ -2938,6 +3064,12 @@ var DIY_TOOLS = [
2938
3064
  DIY_LOGS_READ_TOOL,
2939
3065
  DIY_LOGS_STOP_TOOL
2940
3066
  ];
3067
+ var DIY_DEBUG_TOOLS = [
3068
+ DIY_DEBUG_START_TOOL,
3069
+ DIY_DEBUG_PAUSE_TOOL,
3070
+ DIY_DEBUG_RESUME_TOOL,
3071
+ DIY_DEBUG_STOP_TOOL
3072
+ ];
2941
3073
  var panelsUnit = {
2942
3074
  id: "panels",
2943
3075
  card: null,
@@ -3031,6 +3163,13 @@ function askOf(reason, question, fallback) {
3031
3163
  };
3032
3164
  }
3033
3165
  var panelsApproval = (call) => {
3166
+ if (call.name === "workbench_diy_debug_start" || call.name === "workbench_diy_debug_resume") {
3167
+ const starting = call.name === DIY_DEBUG_START_TOOL;
3168
+ const unit = argsOf$1(call)["unit"];
3169
+ const target = typeof unit === "string" && unit !== "" ? ` [${unit}]` : "";
3170
+ 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?"));
3171
+ }
3172
+ if (call.name === "workbench_diy_debug_pause" || call.name === "workbench_diy_debug_stop") return { kind: "allow" };
3034
3173
  if (call.name !== void 0 && DIY_TOOLS.includes(call.name)) return { kind: "allow" };
3035
3174
  const raw = argsOf$1(call);
3036
3175
  const text = (key) => {
@@ -3591,11 +3730,18 @@ function numberIn$1(value, fallback) {
3591
3730
  }
3592
3731
  var PAGE_PROBE = `(async () => {
3593
3732
  const answer = await fetch('/workbench/api/health').then((one) => one.json()).catch(() => null);
3733
+ const bar = document.querySelector('.wb-rightbar');
3734
+ const board = bar === null ? '' : (bar.getAttribute('data-wb-board') ?? '');
3735
+ const open = bar === null ? null : bar.hasAttribute('data-wb-open');
3736
+ const shown = bar === null ? false : getComputedStyle(bar).visibility !== 'hidden';
3594
3737
  return JSON.stringify({
3595
3738
  plugin: answer === null ? null : answer.plugin,
3596
3739
  pid: answer === null ? null : answer.pid,
3597
3740
  url: location.href,
3598
3741
  title: document.title,
3742
+ board,
3743
+ open,
3744
+ shown,
3599
3745
  });
3600
3746
  })()`;
3601
3747
  function cardSelectorOf(unit) {
@@ -3657,6 +3803,7 @@ var NO_PICK = {
3657
3803
  var EMPTY_CARD = {
3658
3804
  ok: false,
3659
3805
  anchored: false,
3806
+ visible: false,
3660
3807
  width: 0,
3661
3808
  height: 0,
3662
3809
  head: "",
@@ -3703,10 +3850,13 @@ function measureScriptOf(unit, pick) {
3703
3850
  };
3704
3851
  const anchor = document.querySelector(${JSON.stringify(cardSelectorOf(unit))});
3705
3852
  const all = [...document.querySelectorAll('.wb-card')];
3706
- if (anchor === null) return JSON.stringify({ ok: false, cards: all.length, anchored: false });
3853
+ if (anchor === null) return JSON.stringify({ ok: false, cards: all.length, anchored: false, visible: false });
3707
3854
  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 });
3855
+ if (card === null) return JSON.stringify({ ok: false, cards: all.length, anchored: true, visible: false });
3709
3856
  const box = card.getBoundingClientRect();
3857
+ const painted = getComputedStyle(card);
3858
+ const visible =
3859
+ painted.visibility !== 'hidden' && painted.display !== 'none' && box.width > 0 && box.height > 0;
3710
3860
  const head = card.querySelector('.wb-cardTitle');
3711
3861
  const text = scrub(card.textContent);
3712
3862
  const shown = scrub(card.innerText);
@@ -3749,6 +3899,7 @@ function measureScriptOf(unit, pick) {
3749
3899
  return JSON.stringify({
3750
3900
  ok: true,
3751
3901
  cards: all.length,
3902
+ visible,
3752
3903
  width: Math.ceil(box.width),
3753
3904
  height: Math.ceil(box.height),
3754
3905
  x: Math.round(box.x),
@@ -3900,12 +4051,19 @@ async function workbenchPageOf(shell, command, session) {
3900
4051
  if (parsed === void 0) return {
3901
4052
  ok: false,
3902
4053
  url: "",
3903
- title: ""
4054
+ title: "",
4055
+ board: "",
4056
+ open: null,
4057
+ shown: false
3904
4058
  };
4059
+ const raw = parsed["open"];
3905
4060
  return {
3906
4061
  ok: textOf$4(parsed["plugin"]) === "@dsh-workbench/core",
3907
4062
  url: textOf$4(parsed["url"]),
3908
- title: textOf$4(parsed["title"])
4063
+ title: textOf$4(parsed["title"]),
4064
+ board: textOf$4(parsed["board"]),
4065
+ open: typeof raw === "boolean" ? raw : null,
4066
+ shown: parsed["shown"] === true
3909
4067
  };
3910
4068
  }
3911
4069
  async function writeShotsDir(shell, dir) {
@@ -3951,13 +4109,15 @@ async function measureCard(shell, command, session, unit, pick) {
3951
4109
  if (parsed === void 0 || parsed["ok"] !== true) return {
3952
4110
  ...EMPTY_CARD,
3953
4111
  cards: numberIn(parsed?.["cards"], 0),
3954
- anchored: parsed?.["anchored"] === true
4112
+ anchored: parsed?.["anchored"] === true,
4113
+ visible: parsed?.["visible"] === true
3955
4114
  };
3956
4115
  const width = numberIn(parsed["width"], 0);
3957
4116
  const height = numberIn(parsed["height"], 0);
3958
4117
  return {
3959
4118
  ok: true,
3960
4119
  anchored: true,
4120
+ visible: parsed["visible"] === true,
3961
4121
  width,
3962
4122
  height,
3963
4123
  head: textOf$4(parsed["head"]),
@@ -4036,6 +4196,25 @@ async function shootCard(shell, command, session, unit, file, view) {
4036
4196
  }
4037
4197
  //#endregion
4038
4198
  //#region src/modules/panels/model/diy-page.ts
4199
+ function debugPausedText(held) {
4200
+ return `调试已暂停(${held.unit})—— 动作族关闸,不再拿屏当目标。要接着看先 workbench_diy_debug_resume(会再要用户点头一次);这之前可以改源码 / build / 跑门禁。`;
4201
+ }
4202
+ function debugForeignPactText(held) {
4203
+ return `另一场调试正占着那块屏(${held.sessionId} 在调试 ${held.unit})—— 一台机器同时只允许一场:让它先 workbench_diy_debug_stop,或等它收工。`;
4204
+ }
4205
+ function debugStalledText(held) {
4206
+ return `你现在这一页画的不是工坊那块板 —— ${held.unit} 这场调试算**搁置**(已自动落 paused):说好别离开那一屏。请用户在左栏切回「工作台DIY」;回到那一屏之后再 workbench_diy_debug_resume 接着看。`;
4207
+ }
4208
+ function boardStateOf(page, workshop) {
4209
+ if (page.open === null) return "unknown";
4210
+ if (!page.open || !page.shown) return "hidden";
4211
+ if (page.board === "" || workshop === null) return "unknown";
4212
+ return insideWorkshop(workshop, page.board) ? "workshop" : "other";
4213
+ }
4214
+ function boardGateText(board, unit, page, workshop) {
4215
+ if (board === "hidden") return `右栏收着 —— 面板没画在屏上(锚点还在页面里,但看不见它)。让用户把面板展开、或切回「工作台DIY」,我再接着看 ${unit}。`;
4216
+ return `你现在这一页画的是 ${page.board || "(不知道哪一块)"} 的面板,工坊那块板(${workshop ?? "(不知道)"})没在这一屏 —— ${unit} 的这张卡不在屏上,我不拿别的板当目标。请用户在左栏切回「工作台DIY」;这之前我可以继续改源码 / build / 跑门禁。`;
4217
+ }
4039
4218
  async function pageAt(ctx, exec, raw, options = {}) {
4040
4219
  const { packagesDir } = workshopOf(ctx, exec);
4041
4220
  const args = asRecord$3(raw);
@@ -4053,6 +4232,20 @@ async function pageAt(ctx, exec, raw, options = {}) {
4053
4232
  const { page } = handshake;
4054
4233
  throw new Error(`你现在这一页不是工作台(${page.title || "(没有标题)"} ${page.url})—— 把工作台那一页切到前台,再喊我一次。我只看用户正在看的那一页。`);
4055
4234
  }
4235
+ const workshop = workshopPathOf(ctx);
4236
+ const board = boardStateOf(handshake.page, workshop);
4237
+ const store = ctx.get(DIY_DEBUG_SERVICE);
4238
+ const held = store?.live(Date.now()) ?? null;
4239
+ const sessionId = sessionIdOf(exec);
4240
+ if (options.requirePact !== false && held !== null) {
4241
+ if (held.sessionId !== sessionId) throw new Error(debugForeignPactText(held));
4242
+ if (held.phase === "paused") throw new Error(debugPausedText(held));
4243
+ if (board === "other" || board === "hidden") {
4244
+ store?.stall(sessionId ?? "", Date.now());
4245
+ throw new Error(debugStalledText(held));
4246
+ }
4247
+ }
4248
+ if (options.requireBoard !== false && (board === "other" || board === "hidden")) throw new Error(boardGateText(board, wanted, handshake.page, workshop));
4056
4249
  return {
4057
4250
  packagesDir,
4058
4251
  unit: wanted,
@@ -4060,10 +4253,155 @@ async function pageAt(ctx, exec, raw, options = {}) {
4060
4253
  shell,
4061
4254
  command,
4062
4255
  session,
4063
- bound: handshake.bound
4256
+ bound: handshake.bound,
4257
+ board,
4258
+ boardPath: handshake.page.board
4064
4259
  };
4065
4260
  }
4066
4261
  //#endregion
4262
+ //#region src/modules/panels/model/diy-debug-tools.ts
4263
+ var EMPTY = {
4264
+ type: "object",
4265
+ properties: {},
4266
+ additionalProperties: false
4267
+ };
4268
+ function debugRefusalText(code, held) {
4269
+ switch (code) {
4270
+ case "held-by-other": return `另一场调试还占着那块屏(${held === void 0 ? "另一个会话" : held.sessionId})—— 一台机器同时只允许一场:让它先 workbench_diy_debug_stop,或等它收工。`;
4271
+ case "already-held": return `你这个会话已经持有一场调试(${held === void 0 ? "" : `${held.unit},${held.phase}`})—— 要换目标先 workbench_diy_debug_stop;暂停着要接着看用 workbench_diy_debug_resume。`;
4272
+ case "not-owner": return "这个调试态不是你这个会话的 —— 谁开始的谁收工。";
4273
+ case "none": return "现在没有调试态。";
4274
+ case "already-paused": return "已经暂停了 —— 要接着看用 workbench_diy_debug_resume(会再要用户点头)。";
4275
+ case "already-debugging": return "正在调试中,不用再开一次。";
4276
+ }
4277
+ }
4278
+ function renderDebugStart(state) {
4279
+ return [`开始调试 ${state.unit}(声明的目标:${state.board === "" ? "用户那一屏" : state.board})。`, "现在看 / 动 / 网络那一族才把屏当目标 —— 收工前记得 workbench_diy_debug_stop。"].join("\n");
4280
+ }
4281
+ function renderDebugPause(state) {
4282
+ return [`调试暂停了(${state.unit})—— 动作族关闸,不再拿屏当目标。`, "改源码 / build / 跑门禁照常;要看屏再 workbench_diy_debug_resume(会再要用户点头一次)。"].join("\n");
4283
+ }
4284
+ function renderDebugResume(state) {
4285
+ return [`继续调试 ${state.unit} —— 请用户回到「工作台DIY」那一屏。`, "卡不在屏上时我会当场拒(不拿别的板当目标)。"].join("\n");
4286
+ }
4287
+ function renderDebugStop(state) {
4288
+ return `收工了:${state.unit} 的调试态清掉了(占用解除)。`;
4289
+ }
4290
+ function moveOrThrow(move, held) {
4291
+ if (move.ok) return move.state;
4292
+ throw new Error(debugRefusalText(move.code, held ?? void 0));
4293
+ }
4294
+ function registerDebugTools(ctx, tools, store) {
4295
+ const startTool = {
4296
+ name: DIY_DEBUG_START_TOOL,
4297
+ description: "开始调试:占用用户那一屏(要点头一次)。声明 unit 之后,看 / 动那一族才把屏当目标。",
4298
+ parameters: withApprovalNote({
4299
+ type: "object",
4300
+ properties: { unit: {
4301
+ type: "string",
4302
+ description: "工坊里的 unit(workbench_diy_list 会给)。"
4303
+ } },
4304
+ required: ["unit"],
4305
+ additionalProperties: false
4306
+ }, { required: true }),
4307
+ output: OUTPUT$2,
4308
+ async execute(raw, exec) {
4309
+ const { packagesDir, workspace } = workshopOf(ctx, exec);
4310
+ const unit = asText$1(asRecord$3(raw)["unit"]);
4311
+ if (unit === "") {
4312
+ const { modules } = modulesOf(ctx, packagesDir);
4313
+ throw new Error(`要一个 unit(工坊里:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
4314
+ }
4315
+ const held = store.live(Date.now());
4316
+ const page = await pageAt(ctx, exec, raw, {
4317
+ requireBoard: false,
4318
+ requirePact: false
4319
+ });
4320
+ const state = moveOrThrow(store.begin({
4321
+ sessionId: sessionIdOf(exec) ?? "",
4322
+ workspace: workspace ?? "",
4323
+ board: page.boardPath,
4324
+ unit,
4325
+ name: modulesOf(ctx, packagesDir).modules.find((one) => one.unit === unit)?.name ?? unit,
4326
+ now: Date.now()
4327
+ }), held);
4328
+ return plainJson({
4329
+ workshop: packagesDir,
4330
+ unit,
4331
+ board: state.board,
4332
+ phase: state.phase,
4333
+ state,
4334
+ text: renderDebugStart(state)
4335
+ });
4336
+ }
4337
+ };
4338
+ const pauseTool = {
4339
+ name: DIY_DEBUG_PAUSE_TOOL,
4340
+ description: "暂停调试(不审批):动作族关闸,不再拿屏当目标。要看屏再 debug_resume。",
4341
+ parameters: EMPTY,
4342
+ output: OUTPUT$2,
4343
+ async execute(_raw, exec) {
4344
+ const { packagesDir } = workshopOf(ctx, exec);
4345
+ const held = store.live(Date.now());
4346
+ const state = moveOrThrow(store.pause(sessionIdOf(exec) ?? "", Date.now()), held);
4347
+ return plainJson({
4348
+ workshop: packagesDir,
4349
+ unit: state.unit,
4350
+ phase: state.phase,
4351
+ state,
4352
+ text: renderDebugPause(state)
4353
+ });
4354
+ }
4355
+ };
4356
+ const resumeTool = {
4357
+ name: DIY_DEBUG_RESUME_TOOL,
4358
+ description: "继续调试(要点头一次):请用户回到「工作台DIY」那一屏。",
4359
+ parameters: withApprovalNote(EMPTY, { required: true }),
4360
+ output: OUTPUT$2,
4361
+ async execute(_raw, exec) {
4362
+ const { packagesDir } = workshopOf(ctx, exec);
4363
+ const held = store.live(Date.now());
4364
+ const page = await pageAt(ctx, exec, { unit: held?.unit ?? "" }, {
4365
+ requireBoard: false,
4366
+ requirePact: false,
4367
+ requireUnit: false
4368
+ });
4369
+ const state = moveOrThrow(store.resume(sessionIdOf(exec) ?? "", Date.now()), held);
4370
+ return plainJson({
4371
+ workshop: packagesDir,
4372
+ unit: state.unit,
4373
+ board: page.boardPath,
4374
+ phase: state.phase,
4375
+ state,
4376
+ text: renderDebugResume(state)
4377
+ });
4378
+ }
4379
+ };
4380
+ const stopTool = {
4381
+ name: DIY_DEBUG_STOP_TOOL,
4382
+ description: "结束调试(不审批):清掉\"占用用户那一屏\"这件事。收工用它。",
4383
+ parameters: EMPTY,
4384
+ output: OUTPUT$2,
4385
+ async execute(_raw, exec) {
4386
+ const { packagesDir } = workshopOf(ctx, exec);
4387
+ const held = store.live(Date.now());
4388
+ const state = moveOrThrow(store.stop(sessionIdOf(exec) ?? "", Date.now()), held);
4389
+ return plainJson({
4390
+ workshop: packagesDir,
4391
+ unit: state.unit,
4392
+ phase: "idle",
4393
+ text: renderDebugStop(state)
4394
+ });
4395
+ }
4396
+ };
4397
+ return [
4398
+ tools.register(startTool),
4399
+ tools.register(pauseTool),
4400
+ tools.register(resumeTool),
4401
+ tools.register(stopTool)
4402
+ ];
4403
+ }
4404
+ //#endregion
4067
4405
  //#region src/modules/panels/model/diy-browser-act.ts
4068
4406
  var ACT_TIMEOUT_MS = 15e3;
4069
4407
  function actTargetOf(raw) {
@@ -4321,16 +4659,20 @@ function registerLookTools(ctx, tools) {
4321
4659
  },
4322
4660
  output: OUTPUT$2,
4323
4661
  async execute(raw, exec) {
4324
- const { packagesDir, unit, shell, command, session } = await pageAt(ctx, exec, raw);
4662
+ const { packagesDir, unit, shell, command, session, board } = await pageAt(ctx, exec, raw);
4325
4663
  const pick = pickOf(asRecord$3(raw));
4326
4664
  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
- });
4665
+ if (!view.ok || !view.visible) {
4666
+ const state = view.ok ? "hidden" : board;
4667
+ return plainJson({
4668
+ workshop: packagesDir,
4669
+ unit,
4670
+ ok: false,
4671
+ board: state,
4672
+ cards: view.cards,
4673
+ text: renderMissingCard(unit, view, state)
4674
+ });
4675
+ }
4334
4676
  if (!insideViewportOf(view)) {
4335
4677
  await scrollIntoView(shell, command, session, unit, "");
4336
4678
  view = await measureCard(shell, command, session, unit, pick);
@@ -4431,9 +4773,12 @@ function renderLook(unit, view, shot, pick) {
4431
4773
  ...view.clipped ? ["有内容被裁掉了(某一格比它自己的宽度还宽)—— 这是要改的信号"] : []
4432
4774
  ].join("\n");
4433
4775
  }
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})把它上板;② 右栏可能没停在「工作台面板」那一列 —— 让用户切回来;③ 页面上跑的外壳可能还是旧的一份(锚点由外壳盖)—— 让用户刷新一次页面。`;
4776
+ function renderMissingCard(unit, view, board) {
4777
+ if (board === "other") return `你现在这一页画的是别的工作区的面板,工坊那块板没在这一屏 —— ${unit} 的卡不在屏上,我不拿别的板当目标。请用户在左栏切回「工作台DIY」,或把这张卡上到他正看着的那块板上。`;
4778
+ if (board === "hidden") return `右栏收着 —— 面板没画在屏上(锚点还在页面里,但看不见它),量不到也不该拍 ${unit}。让用户把面板展开、或切回「工作台DIY」,我再接着看。`;
4779
+ if (view.anchored) return `面板里给 ${unit} 留了位置(锚点在),但里面没有卡片 —— 它的 View 没画出来:要么渲染时抛了(看 workbench_diy_panel 的运行态上报与 workbench_diy_status),要么它返回了空 / 没套卡壳。你现在这一页面板里一共 ${String(view.cards)} 张卡。`;
4780
+ const stale = board === "unknown" ? "③ 页面上跑的外壳可能还是旧的一份(锚点由外壳盖)—— 让用户刷新一次页面。" : "";
4781
+ return `你现在这一页的面板里没有 ${unit} 这张卡(面板里现在有 ${String(view.cards)} 张卡)。按顺序试:① workbench_diy_show(unit=${unit})把它上板;② 右栏可能没停在「工作台面板」那一列 —— 让用户切回来。${stale}`;
4437
4782
  }
4438
4783
  //#endregion
4439
4784
  //#region src/modules/panels/model/diy-logs.ts
@@ -5483,7 +5828,7 @@ function renderList(packagesDir, modules) {
5483
5828
  }
5484
5829
  //#endregion
5485
5830
  //#region src/modules/panels/model/diy-tools.ts
5486
- function registerDiyTools(ctx, market, panels) {
5831
+ function registerDiyTools(ctx, market, panels, debug) {
5487
5832
  const tools = ctx.get("tools");
5488
5833
  if (tools === void 0) {
5489
5834
  ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过 DIY 工坊工具注册");
@@ -5831,11 +6176,20 @@ function registerDiyTools(ctx, market, panels) {
5831
6176
  ...registerLogTools(ctx, tools),
5832
6177
  ...registerNetTools(ctx, tools),
5833
6178
  ...registerActTools(ctx, tools),
6179
+ ...registerDebugTools(ctx, tools, debug),
5834
6180
  tools.register(showTool),
5835
6181
  tools.register(installTool)
5836
6182
  ];
5837
6183
  }
5838
6184
  //#endregion
6185
+ //#region src/modules/panels/model/diy-debug-routes.ts
6186
+ function diyDebugRoutes(store) {
6187
+ return { "panels/diy-debug": () => ({
6188
+ ok: true,
6189
+ state: store.live(Date.now())
6190
+ }) };
6191
+ }
6192
+ //#endregion
5839
6193
  //#region src/shared/api/host/release.ts
5840
6194
  function parseLedgerVersion(stdout, name) {
5841
6195
  const parsed = parseJsonLoose(stdout);
@@ -7869,7 +8223,14 @@ function applyPanelsPref(panels, payload) {
7869
8223
  //#endregion
7870
8224
  //#region src/modules/panels/model/handoff.ts
7871
8225
  var PANELS_FACT = "panels";
8226
+ var DIY_DEBUG_FACT = "diy-debug";
7872
8227
  var CAPABILITY_FACT = "capability";
8228
+ var DEBUG_TOOLS = [
8229
+ DIY_DEBUG_START_TOOL,
8230
+ DIY_DEBUG_PAUSE_TOOL,
8231
+ DIY_DEBUG_RESUME_TOOL,
8232
+ DIY_DEBUG_STOP_TOOL
8233
+ ];
7873
8234
  var PROVISION_TOOLS = {
7874
8235
  [MODULES_INSTALL_TOOL]: "install",
7875
8236
  [MODULES_UPDATE_TOOL]: "update",
@@ -7905,6 +8266,7 @@ function panelsFactsOf(call, manifests) {
7905
8266
  if (call.source === "workbench_modules_list") return [PANELS_FACT, CAPABILITY_FACT];
7906
8267
  if (call.source === "workbench_modules_update_workbench") return [PANELS_FACT, CAPABILITY_FACT];
7907
8268
  if (call.source === "workbench_diy_list") return [PANELS_FACT];
8269
+ if (DEBUG_TOOLS.includes(call.source)) return [DIY_DEBUG_FACT];
7908
8270
  const request = requestOf(call);
7909
8271
  if (request === void 0) return void 0;
7910
8272
  return [
@@ -8479,6 +8841,8 @@ var OWNED = [{
8479
8841
  logger
8480
8842
  });
8481
8843
  ctx.provide(PANELS_SERVICE, panels);
8844
+ const debug = createDebugStore();
8845
+ ctx.provide(DIY_DEBUG_SERVICE, debug);
8482
8846
  const profileDir = profileDirOf(ctx);
8483
8847
  const shell = ctx.get("shell");
8484
8848
  const moduleManifests = () => {
@@ -8526,12 +8890,13 @@ var OWNED = [{
8526
8890
  ...pluginRoutes({
8527
8891
  ledger,
8528
8892
  ...shell === void 0 ? {} : { shell }
8529
- })
8893
+ }),
8894
+ ...diyDebugRoutes(debug)
8530
8895
  },
8531
8896
  registerTools: () => [
8532
8897
  ...registerPanelTools(ctx, panels, values),
8533
8898
  ...registerMarketTools(ctx, market),
8534
- ...registerDiyTools(ctx, market, panels)
8899
+ ...registerDiyTools(ctx, market, panels, debug)
8535
8900
  ],
8536
8901
  factsOf: (call) => panelsFactsOf(call, manifests()),
8537
8902
  needsApproval: panelsApproval,
@@ -8542,7 +8907,7 @@ var OWNED = [{
8542
8907
  return [...manifests().flatMap((manifest) => owned.has(manifest.id) || resolveScope({
8543
8908
  id: manifest.id,
8544
8909
  boards
8545
- }) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [...DIY_TOOLS]];
8910
+ }) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [...DIY_TOOLS, ...DIY_DEBUG_TOOLS]];
8546
8911
  },
8547
8912
  subscribe: (listener) => panels.subscribe(listener)
8548
8913
  },