harnery 0.41.0 → 0.43.0

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 (269) hide show
  1. package/dist/commander.d.ts +16 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +21 -0
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +63 -5
  6. package/dist/commands/artifacts.d.ts.map +1 -1
  7. package/dist/commands/artifacts.js +113 -12
  8. package/dist/commands/browse.d.ts +164 -0
  9. package/dist/commands/browse.d.ts.map +1 -1
  10. package/dist/commands/browse.js +263 -51
  11. package/dist/commands/claude-desktop.d.ts.map +1 -1
  12. package/dist/commands/claude-desktop.js +52 -0
  13. package/dist/commands/disk.d.ts +6 -0
  14. package/dist/commands/disk.d.ts.map +1 -0
  15. package/dist/commands/disk.js +80 -0
  16. package/dist/commands/doctor.d.ts +8 -0
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +48 -1
  19. package/dist/commands/files.d.ts +16 -0
  20. package/dist/commands/files.d.ts.map +1 -1
  21. package/dist/commands/files.js +54 -2
  22. package/dist/commands/init.d.ts +2 -2
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +43 -9
  25. package/dist/commands/rm.d.ts +4 -0
  26. package/dist/commands/rm.d.ts.map +1 -0
  27. package/dist/commands/rm.js +87 -0
  28. package/dist/commands/servers.d.ts +7 -0
  29. package/dist/commands/servers.d.ts.map +1 -0
  30. package/dist/commands/servers.js +394 -0
  31. package/dist/commands/tunnel.d.ts +65 -0
  32. package/dist/commands/tunnel.d.ts.map +1 -1
  33. package/dist/commands/tunnel.js +372 -103
  34. package/dist/commands/web-fetch.d.ts +3 -2
  35. package/dist/commands/web-fetch.d.ts.map +1 -1
  36. package/dist/commands/web-fetch.js +25 -19
  37. package/dist/commands/web.d.ts +7 -1
  38. package/dist/commands/web.d.ts.map +1 -1
  39. package/dist/commands/web.js +9 -3
  40. package/dist/core/adapters/profiles.d.ts +9 -0
  41. package/dist/core/adapters/profiles.d.ts.map +1 -1
  42. package/dist/core/adapters/profiles.js +22 -1
  43. package/dist/core/adapters/types.d.ts +9 -0
  44. package/dist/core/adapters/types.d.ts.map +1 -1
  45. package/dist/core/agents/cli.js +13 -6
  46. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  47. package/dist/core/agents/render/prompt-context.js +11 -5
  48. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/session-context.js +59 -13
  50. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  51. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  52. package/dist/core/agents/state/live-coordination-view.js +60 -0
  53. package/dist/core/agents/state/names.d.ts +43 -3
  54. package/dist/core/agents/state/names.d.ts.map +1 -1
  55. package/dist/core/agents/state/names.js +87 -3
  56. package/dist/core/artifacts/delivery-card.js +9 -2
  57. package/dist/core/artifacts/index.d.ts +140 -4
  58. package/dist/core/artifacts/index.d.ts.map +1 -1
  59. package/dist/core/artifacts/index.js +522 -49
  60. package/dist/core/config.d.ts +68 -11
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +128 -3
  63. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  64. package/dist/core/diagnostics/bundle.js +2 -2
  65. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  66. package/dist/core/events/v3/producers/recorder.js +16 -0
  67. package/dist/core/events/v3/projection.js +9 -1
  68. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  69. package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
  70. package/dist/core/hooks/adapter/events.d.ts +17 -0
  71. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  72. package/dist/core/hooks/adapter/events.js +5 -0
  73. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  74. package/dist/core/hooks/adapter/wiring.d.ts +37 -7
  75. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  76. package/dist/core/hooks/adapter/wiring.js +62 -4
  77. package/dist/core/hooks/cli.js +131 -23
  78. package/dist/core/hooks/fork-lineage.d.ts +40 -0
  79. package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
  80. package/dist/core/hooks/fork-lineage.js +62 -0
  81. package/dist/core/hooks/resolve/transcript.d.ts +16 -0
  82. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  83. package/dist/core/hooks/resolve/transcript.js +84 -0
  84. package/dist/core/hooks/run-markers.d.ts +37 -0
  85. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  86. package/dist/core/hooks/run-markers.js +109 -0
  87. package/dist/core/hooks/shell-rm.d.ts +2 -0
  88. package/dist/core/hooks/shell-rm.d.ts.map +1 -0
  89. package/dist/core/hooks/shell-rm.js +105 -0
  90. package/dist/core/hooks/shell-tokens.d.ts +12 -0
  91. package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
  92. package/dist/core/hooks/shell-tokens.js +97 -0
  93. package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
  94. package/dist/core/hooks/shell-waiter.js +3 -93
  95. package/dist/core/semantic/contract.d.ts +5 -5
  96. package/dist/core/servers/index.d.ts +211 -0
  97. package/dist/core/servers/index.d.ts.map +1 -0
  98. package/dist/core/servers/index.js +560 -0
  99. package/dist/core/servers/net.d.ts +58 -0
  100. package/dist/core/servers/net.d.ts.map +1 -0
  101. package/dist/core/servers/net.js +270 -0
  102. package/dist/core/servers/tunnels.d.ts +14 -0
  103. package/dist/core/servers/tunnels.d.ts.map +1 -0
  104. package/dist/core/servers/tunnels.js +33 -0
  105. package/dist/core/storage/builtins.js +1 -0
  106. package/dist/core/storage/process-log.d.ts +12 -0
  107. package/dist/core/storage/process-log.d.ts.map +1 -1
  108. package/dist/core/storage/process-log.js +10 -2
  109. package/dist/core/workflow/engine.d.ts.map +1 -1
  110. package/dist/core/workflow/engine.js +30 -1
  111. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  112. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  113. package/dist/core/workflow/sandbox-projection.js +23 -0
  114. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  115. package/dist/core/workflow/spawn-claude.js +6 -1
  116. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  117. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  118. package/dist/core/workflow/spawn-codex.js +20 -1
  119. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  120. package/dist/core/workflow/spawn-cursor.js +6 -1
  121. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  122. package/dist/core/workflow/spawn-opencode.js +4 -1
  123. package/dist/core/workflow/types.d.ts +47 -2
  124. package/dist/core/workflow/types.d.ts.map +1 -1
  125. package/dist/core/workflow/worker-access.d.ts +92 -0
  126. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  127. package/dist/core/workflow/worker-access.js +450 -0
  128. package/dist/lib/browser/batch-steps.d.ts +18 -0
  129. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  130. package/dist/lib/browser/batch-steps.js +82 -0
  131. package/dist/lib/browser/collect/collector.d.ts +99 -0
  132. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  133. package/dist/lib/browser/collect/collector.js +202 -0
  134. package/dist/lib/browser/collect/fields.d.ts +31 -0
  135. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  136. package/dist/lib/browser/collect/fields.js +75 -0
  137. package/dist/lib/browser/collect/index.d.ts +6 -0
  138. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  139. package/dist/lib/browser/collect/index.js +5 -0
  140. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  141. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  142. package/dist/lib/browser/collect/page-driver.js +306 -0
  143. package/dist/lib/browser/collect/presets.d.ts +52 -0
  144. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  145. package/dist/lib/browser/collect/presets.js +176 -0
  146. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  147. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  148. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  149. package/dist/lib/browser/index.d.ts +1 -1
  150. package/dist/lib/browser/index.d.ts.map +1 -1
  151. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  152. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  153. package/dist/lib/browser/page-review-judge.js +13 -0
  154. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  155. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  156. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  157. package/dist/lib/browser/qa-run.js +1 -0
  158. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  159. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  160. package/dist/lib/browser/windows-chrome.js +179 -0
  161. package/dist/lib/claude-desktop-share.d.ts +136 -0
  162. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  163. package/dist/lib/claude-desktop-share.js +394 -0
  164. package/dist/lib/disk-usage.d.ts +50 -0
  165. package/dist/lib/disk-usage.d.ts.map +1 -0
  166. package/dist/lib/disk-usage.js +303 -0
  167. package/dist/lib/docs-lint.d.ts +6 -0
  168. package/dist/lib/docs-lint.d.ts.map +1 -1
  169. package/dist/lib/docs-lint.js +8 -3
  170. package/dist/lib/guarded-remove.d.ts +34 -0
  171. package/dist/lib/guarded-remove.d.ts.map +1 -0
  172. package/dist/lib/guarded-remove.js +310 -0
  173. package/dist/lib/instructions/templates.js +8 -8
  174. package/dist/lib/tunnel/allowlist.d.ts +44 -0
  175. package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
  176. package/dist/lib/tunnel/allowlist.js +204 -0
  177. package/dist/lib/tunnel/current-address.d.ts +41 -0
  178. package/dist/lib/tunnel/current-address.d.ts.map +1 -0
  179. package/dist/lib/tunnel/current-address.js +123 -0
  180. package/dist/lib/tunnel/error-page.d.ts +1 -1
  181. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  182. package/dist/lib/tunnel/error-page.js +19 -8
  183. package/dist/lib/tunnel/gate.js +33 -7
  184. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  185. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  186. package/dist/lib/tunnel/path-scope.js +62 -0
  187. package/dist/lib/tunnel/processes.d.ts +39 -0
  188. package/dist/lib/tunnel/processes.d.ts.map +1 -0
  189. package/dist/lib/tunnel/processes.js +121 -0
  190. package/dist/lib/tunnel/state.d.ts +33 -3
  191. package/dist/lib/tunnel/state.d.ts.map +1 -1
  192. package/dist/lib/tunnel/state.js +115 -30
  193. package/package.json +7 -1
  194. package/schemas/config.schema.json +91 -0
  195. package/src/commander.ts +51 -0
  196. package/src/commands/agents.ts +111 -11
  197. package/src/commands/artifacts.ts +171 -19
  198. package/src/commands/browse.ts +400 -57
  199. package/src/commands/claude-desktop.ts +61 -0
  200. package/src/commands/disk.ts +114 -0
  201. package/src/commands/doctor.ts +52 -1
  202. package/src/commands/files.ts +93 -2
  203. package/src/commands/init.ts +55 -5
  204. package/src/commands/rm.ts +115 -0
  205. package/src/commands/servers.ts +506 -0
  206. package/src/commands/tunnel.ts +494 -116
  207. package/src/commands/web-fetch.ts +30 -19
  208. package/src/commands/web.ts +9 -3
  209. package/src/core/adapters/profiles.ts +22 -1
  210. package/src/core/adapters/types.ts +10 -0
  211. package/src/core/agents/cli.ts +14 -6
  212. package/src/core/agents/render/prompt-context.ts +25 -8
  213. package/src/core/agents/render/session-context.ts +84 -16
  214. package/src/core/agents/state/live-coordination-view.ts +73 -0
  215. package/src/core/agents/state/names.ts +117 -6
  216. package/src/core/artifacts/delivery-card.ts +10 -2
  217. package/src/core/artifacts/index.ts +697 -44
  218. package/src/core/config.ts +183 -4
  219. package/src/core/diagnostics/bundle.ts +2 -1
  220. package/src/core/events/v3/producers/recorder.ts +16 -0
  221. package/src/core/events/v3/projection.ts +11 -1
  222. package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
  223. package/src/core/hooks/adapter/events.ts +19 -0
  224. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  225. package/src/core/hooks/adapter/wiring.ts +116 -8
  226. package/src/core/hooks/cli.ts +152 -22
  227. package/src/core/hooks/fork-lineage.ts +82 -0
  228. package/src/core/hooks/resolve/transcript.ts +88 -0
  229. package/src/core/hooks/run-markers.ts +132 -0
  230. package/src/core/hooks/shell-rm.ts +103 -0
  231. package/src/core/hooks/shell-tokens.ts +96 -0
  232. package/src/core/hooks/shell-waiter.ts +4 -91
  233. package/src/core/servers/index.ts +712 -0
  234. package/src/core/servers/net.ts +283 -0
  235. package/src/core/servers/tunnels.ts +39 -0
  236. package/src/core/storage/builtins.ts +1 -0
  237. package/src/core/storage/process-log.ts +17 -2
  238. package/src/core/workflow/engine.ts +38 -1
  239. package/src/core/workflow/sandbox-projection.ts +38 -2
  240. package/src/core/workflow/spawn-claude.ts +5 -1
  241. package/src/core/workflow/spawn-codex.ts +18 -1
  242. package/src/core/workflow/spawn-cursor.ts +5 -1
  243. package/src/core/workflow/spawn-opencode.ts +4 -1
  244. package/src/core/workflow/types.ts +62 -2
  245. package/src/core/workflow/worker-access.ts +525 -0
  246. package/src/lib/browser/batch-steps.ts +79 -0
  247. package/src/lib/browser/collect/collector.ts +306 -0
  248. package/src/lib/browser/collect/fields.ts +87 -0
  249. package/src/lib/browser/collect/index.ts +47 -0
  250. package/src/lib/browser/collect/page-driver.ts +327 -0
  251. package/src/lib/browser/collect/presets.ts +237 -0
  252. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  253. package/src/lib/browser/index.ts +1 -0
  254. package/src/lib/browser/page-review-judge.ts +16 -0
  255. package/src/lib/browser/qa-run-contracts.ts +11 -0
  256. package/src/lib/browser/qa-run.ts +1 -0
  257. package/src/lib/browser/windows-chrome.ts +227 -0
  258. package/src/lib/claude-desktop-share.ts +511 -0
  259. package/src/lib/disk-usage.ts +347 -0
  260. package/src/lib/docs-lint.ts +8 -3
  261. package/src/lib/guarded-remove.ts +376 -0
  262. package/src/lib/instructions/templates.ts +8 -8
  263. package/src/lib/tunnel/allowlist.ts +208 -0
  264. package/src/lib/tunnel/current-address.ts +158 -0
  265. package/src/lib/tunnel/error-page.ts +25 -9
  266. package/src/lib/tunnel/gate.ts +38 -9
  267. package/src/lib/tunnel/path-scope.ts +73 -0
  268. package/src/lib/tunnel/processes.ts +149 -0
  269. package/src/lib/tunnel/state.ts +143 -30
@@ -331,13 +331,20 @@ export interface NameHistoryRow {
331
331
  ts: string;
332
332
  /** Durable persona UUID. Present after `agents identity assume`. */
333
333
  agent_id?: string;
334
- /** Audit marker distinguishing an explicit role adoption from pool assignment. */
335
- source?: "pool" | "identity.assume";
334
+ /** Audit marker distinguishing an explicit role adoption from pool assignment,
335
+ * and a lineage row appended after the name was already assigned. */
336
+ source?: "pool" | "identity.assume" | "fork.detected";
336
337
  previous_name?: string;
337
338
  /** Instance this session was forked/branched from (recorded fork lineage).
338
339
  * Stamped only on the row that first assigns this instance, when the adapter
339
340
  * layer detected or supplied a parent conversation. */
340
341
  forked_from?: string;
342
+ /** The adapter declared or inherited-history proved this instance is a fork,
343
+ * even when its parent could not be resolved yet. */
344
+ fork?: true;
345
+ /** Fork detection has run against this instance's transcript; the tool-path
346
+ * heal skips instances carrying it so the scan runs once per instance. */
347
+ fork_checked?: true;
341
348
  }
342
349
 
343
350
  function atomicWrite(path: string, content: string): void {
@@ -516,7 +523,13 @@ export function assignName(
516
523
  coordRoot: string,
517
524
  instanceId: string,
518
525
  kind: NameKind,
519
- opts: { freshnessSecs: number; forkedFrom?: string; nowMs?: number },
526
+ opts: {
527
+ freshnessSecs: number;
528
+ forkedFrom?: string;
529
+ fork?: boolean;
530
+ forkChecked?: boolean;
531
+ nowMs?: number;
532
+ },
520
533
  ): string {
521
534
  // Check 1: existing history row → original name. A resume re-enters here,
522
535
  // which also makes fork stamping naturally idempotent: lineage lands only on
@@ -558,13 +571,111 @@ export function assignName(
558
571
  kind,
559
572
  source: "pool",
560
573
  ...(forkedFrom && forkedFrom !== instanceId ? { forked_from: forkedFrom } : {}),
574
+ ...(opts.fork || (forkedFrom && forkedFrom !== instanceId) ? { fork: true as const } : {}),
575
+ ...(opts.forkChecked ? { fork_checked: true as const } : {}),
561
576
  ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
562
577
  });
563
578
  return name;
564
579
  }
565
580
 
581
+ export interface ForkState {
582
+ /** True when the instance is a recorded fork, parent known or not. */
583
+ fork: boolean;
584
+ /** Detection already ran for this instance. */
585
+ checked: boolean;
586
+ parent: { instance_id: string; name: string | null } | null;
587
+ }
588
+
589
+ /** Recorded fork state for <instanceId>, or null when it has no history row. */
590
+ export function readForkState(coordRoot: string, instanceId: string): ForkState | null {
591
+ const history = readHistory(coordRoot);
592
+ let seen = false;
593
+ let fork = false;
594
+ let checked = false;
595
+ let parentId: string | undefined;
596
+ for (const row of history) {
597
+ if (row.instance_id !== instanceId) continue;
598
+ seen = true;
599
+ if (row.fork || row.forked_from) fork = true;
600
+ if (row.fork_checked) checked = true;
601
+ if (row.forked_from && row.forked_from !== instanceId) parentId = row.forked_from;
602
+ }
603
+ if (!seen) return null;
604
+ const parent = parentId
605
+ ? { instance_id: parentId, name: resolveName(coordRoot, parentId)?.name ?? null }
606
+ : null;
607
+ return { fork, checked, parent };
608
+ }
609
+
610
+ /**
611
+ * Record fork detection that ran after the instance was already named (the
612
+ * tool-path heal). Appends a copy of the latest identity row, so the name,
613
+ * kind, and persona are unchanged, plus `fork_checked` and any lineage found.
614
+ * A no-op for an instance with no history row or one already checked.
615
+ */
616
+ export function recordForkDetection(
617
+ coordRoot: string,
618
+ instanceId: string,
619
+ detection: { fork: boolean; forkedFrom?: string },
620
+ ): boolean {
621
+ const history = readHistory(coordRoot);
622
+ let latest: NameHistoryRow | undefined;
623
+ for (const row of history) {
624
+ if (row.instance_id !== instanceId) continue;
625
+ if (row.fork_checked) return false;
626
+ latest = row;
627
+ }
628
+ if (!latest) return false;
629
+ const forkedFrom =
630
+ detection.forkedFrom && detection.forkedFrom !== instanceId ? detection.forkedFrom : undefined;
631
+ appendHistory(coordRoot, {
632
+ instance_id: instanceId,
633
+ name: latest.name,
634
+ kind: latest.kind,
635
+ ...(latest.agent_id ? { agent_id: latest.agent_id } : {}),
636
+ source: "fork.detected",
637
+ ...(forkedFrom ? { forked_from: forkedFrom } : {}),
638
+ ...(detection.fork || forkedFrom || latest.fork ? { fork: true as const } : {}),
639
+ fork_checked: true,
640
+ ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
641
+ });
642
+ return true;
643
+ }
644
+
645
+ /**
646
+ * Peer labels for recorded fork relations, keyed by instance id: this
647
+ * session's fork parent and its direct forks. `liveIds` marks which of them
648
+ * are running, so a fork can tell its live parent from its own earlier self.
649
+ */
650
+ export function forkRelationLabels(
651
+ coordRoot: string,
652
+ instanceId: string,
653
+ liveIds: Set<string>,
654
+ ): Map<string, string> {
655
+ const out = new Map<string, string>();
656
+ const parent = readForkParent(coordRoot, instanceId);
657
+ if (parent) {
658
+ out.set(
659
+ parent.instance_id,
660
+ liveIds.has(parent.instance_id) ? "your fork parent, live" : "your fork parent",
661
+ );
662
+ }
663
+ for (const child of readForkChildren(coordRoot, instanceId)) out.set(child, "your fork");
664
+ return out;
665
+ }
666
+
667
+ /** Instance ids recorded as direct forks of <instanceId>. */
668
+ export function readForkChildren(coordRoot: string, instanceId: string): Set<string> {
669
+ const out = new Set<string>();
670
+ for (const row of readHistory(coordRoot)) {
671
+ if (row.forked_from === instanceId && row.instance_id !== instanceId) out.add(row.instance_id);
672
+ }
673
+ return out;
674
+ }
675
+
566
676
  /** One step of recorded fork lineage: the latest row for <instanceId> that
567
- * carries `forked_from` (latest-row-wins, matching resolveName). */
677
+ * carries `forked_from`. Later rows without lineage (an identity assumption)
678
+ * do not erase it: a session never stops being a fork. */
568
679
  export function readForkParent(
569
680
  coordRoot: string,
570
681
  instanceId: string,
@@ -572,8 +683,8 @@ export function readForkParent(
572
683
  const history = readHistory(coordRoot);
573
684
  for (let i = history.length - 1; i >= 0; i--) {
574
685
  const row = history[i]!;
575
- if (row.instance_id !== instanceId) continue;
576
- if (!row.forked_from) return null;
686
+ if (row.instance_id !== instanceId || !row.forked_from) continue;
687
+ if (row.forked_from === instanceId) return null;
577
688
  const parent = resolveName(coordRoot, row.forked_from);
578
689
  return { instance_id: row.forked_from, name: parent?.name ?? null };
579
690
  }
@@ -12,7 +12,7 @@ import {
12
12
  } from "node:fs";
13
13
  import { isAbsolute, relative, resolve, sep } from "node:path";
14
14
  import { encodeLinkSafeComponent } from "../../lib/local-file-url.ts";
15
- import { findLiveTunnelForOrigin } from "../../lib/tunnel/state.ts";
15
+ import { findLiveTunnelForOrigin, tunnelServesPaths } from "../../lib/tunnel/state.ts";
16
16
  import { resolveWebPort } from "../config.ts";
17
17
  import { stateFileMode } from "../storage/modes.ts";
18
18
  import { ARTIFACT_MANIFEST } from "./constants.ts";
@@ -326,9 +326,17 @@ function deliveryLinkBase(repoRoot: string, environment: DisplayEnvironment): st
326
326
  // dashboard's own origin will answer them. A tunnel sharing this upstream
327
327
  // port under another Host serves a different site and would 400 on /browse
328
328
  // and /files, so fall back to the local URL rather than publish that host.
329
+ // The same holds for a dashboard tunnel whose path scope leaves out either
330
+ // route: its gate would refuse every link on the card.
331
+ const tunnel =
332
+ environment.tunnelUrl === undefined
333
+ ? findLiveTunnelForOrigin(webPort, `localhost:${webPort}`, repoRoot)
334
+ : null;
329
335
  const tunnelUrl =
330
336
  environment.tunnelUrl === undefined
331
- ? findLiveTunnelForOrigin(webPort, `localhost:${webPort}`, repoRoot)?.url
337
+ ? tunnel && tunnelServesPaths(tunnel, ["/browse", "/files"])
338
+ ? tunnel.url
339
+ : undefined
332
340
  : environment.tunnelUrl;
333
341
  return (tunnelUrl ?? `http://localhost:${webPort}`).replace(/\/+$/, "");
334
342
  }