harnery 0.43.0 → 0.45.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 (323) hide show
  1. package/README.md +3 -3
  2. package/dist/commander.js +1 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +41 -48
  5. package/dist/commands/browse.d.ts.map +1 -1
  6. package/dist/commands/browse.js +38 -11
  7. package/dist/commands/diagnostics.d.ts.map +1 -1
  8. package/dist/commands/diagnostics.js +10 -0
  9. package/dist/commands/doctor.d.ts +12 -0
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +22 -7
  12. package/dist/commands/files.d.ts +2 -2
  13. package/dist/commands/files.d.ts.map +1 -1
  14. package/dist/commands/files.js +7 -4
  15. package/dist/commands/governor.js +22 -10
  16. package/dist/commands/inbox.d.ts +1 -1
  17. package/dist/commands/inbox.d.ts.map +1 -1
  18. package/dist/commands/inbox.js +55 -2
  19. package/dist/commands/rm.d.ts.map +1 -1
  20. package/dist/commands/rm.js +28 -4
  21. package/dist/commands/servers.d.ts.map +1 -1
  22. package/dist/commands/servers.js +26 -17
  23. package/dist/commands/supervisor.d.ts.map +1 -1
  24. package/dist/commands/supervisor.js +7 -2
  25. package/dist/commands/toc.d.ts +14 -0
  26. package/dist/commands/toc.d.ts.map +1 -1
  27. package/dist/commands/toc.js +7 -4
  28. package/dist/commands/tunnel.d.ts +15 -0
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +453 -153
  31. package/dist/commands/turn-rollback.d.ts +5 -0
  32. package/dist/commands/turn-rollback.d.ts.map +1 -0
  33. package/dist/commands/turn-rollback.js +76 -0
  34. package/dist/commands/workflow.d.ts.map +1 -1
  35. package/dist/commands/workflow.js +68 -30
  36. package/dist/core/agents/coord-client.d.ts.map +1 -1
  37. package/dist/core/agents/coord-client.js +18 -0
  38. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  39. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  40. package/dist/core/agents/live-authority-v3.js +11 -0
  41. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  42. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  43. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  44. package/dist/core/agents/mailbox.d.ts +9 -8
  45. package/dist/core/agents/mailbox.d.ts.map +1 -1
  46. package/dist/core/agents/mailbox.js +24 -17
  47. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  48. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/prompt-context.js +8 -12
  50. package/dist/core/agents/render/session-context.d.ts +2 -8
  51. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  52. package/dist/core/agents/render/session-context.js +7 -30
  53. package/dist/core/agents/rules/stop-hook.js +3 -3
  54. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  55. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  56. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  57. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  58. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  59. package/dist/core/agents/state/pidmap.js +5 -0
  60. package/dist/core/agents/state/proc-start.d.ts +19 -1
  61. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  62. package/dist/core/agents/state/proc-start.js +46 -1
  63. package/dist/core/agents/state/process-handle.d.ts +26 -0
  64. package/dist/core/agents/state/process-handle.d.ts.map +1 -0
  65. package/dist/core/agents/state/process-handle.js +121 -0
  66. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  67. package/dist/core/agents/state/stale-sweep.js +180 -70
  68. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  69. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  70. package/dist/core/agents/state/unchanged-file.js +390 -0
  71. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  72. package/dist/core/artifacts/delivery-card.js +2 -1
  73. package/dist/core/context/index.d.ts +22 -10
  74. package/dist/core/context/index.d.ts.map +1 -1
  75. package/dist/core/context/index.js +381 -119
  76. package/dist/core/context/observation.d.ts +26 -0
  77. package/dist/core/context/observation.d.ts.map +1 -0
  78. package/dist/core/context/observation.js +11 -0
  79. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  80. package/dist/core/diagnostics/bundle.js +25 -14
  81. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  82. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  83. package/dist/core/diagnostics/path-identity.js +12 -0
  84. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  85. package/dist/core/diagnostics/sanitize.js +4 -3
  86. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  87. package/dist/core/events/v3/authority-outbox.js +29 -0
  88. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  89. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  90. package/dist/core/events/v3/generation-custody.js +50 -0
  91. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  92. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  93. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  94. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  95. package/dist/core/events/v3/producers/recorder.js +77 -64
  96. package/dist/core/governor/planning.d.ts.map +1 -1
  97. package/dist/core/governor/planning.js +14 -1
  98. package/dist/core/governor/service-read.d.ts +10 -2
  99. package/dist/core/governor/service-read.d.ts.map +1 -1
  100. package/dist/core/governor/service-read.js +105 -22
  101. package/dist/core/governor/service.d.ts +20 -2
  102. package/dist/core/governor/service.d.ts.map +1 -1
  103. package/dist/core/governor/service.js +72 -47
  104. package/dist/core/hooks/cli.js +33 -5
  105. package/dist/core/inbox/contract.d.ts +2 -1
  106. package/dist/core/inbox/contract.d.ts.map +1 -1
  107. package/dist/core/inbox/index.d.ts +2 -0
  108. package/dist/core/inbox/index.d.ts.map +1 -1
  109. package/dist/core/inbox/index.js +2 -0
  110. package/dist/core/inbox/peer-messages.d.ts +37 -0
  111. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  112. package/dist/core/inbox/peer-messages.js +72 -0
  113. package/dist/core/inbox/service.d.ts +8 -1
  114. package/dist/core/inbox/service.d.ts.map +1 -1
  115. package/dist/core/inbox/service.js +67 -21
  116. package/dist/core/inbox/targets.d.ts +22 -0
  117. package/dist/core/inbox/targets.d.ts.map +1 -0
  118. package/dist/core/inbox/targets.js +108 -0
  119. package/dist/core/inbox/watcher.js +7 -3
  120. package/dist/core/journal/index.d.ts.map +1 -1
  121. package/dist/core/journal/index.js +17 -2
  122. package/dist/core/resources/contract.d.ts +3 -0
  123. package/dist/core/resources/contract.d.ts.map +1 -1
  124. package/dist/core/resources/service-status.d.ts +14 -1
  125. package/dist/core/resources/service-status.d.ts.map +1 -1
  126. package/dist/core/resources/service-status.js +23 -13
  127. package/dist/core/resources/service.d.ts +7 -13
  128. package/dist/core/resources/service.d.ts.map +1 -1
  129. package/dist/core/resources/service.js +34 -91
  130. package/dist/core/resources/status.d.ts +2 -5
  131. package/dist/core/resources/status.d.ts.map +1 -1
  132. package/dist/core/resources/status.js +36 -7
  133. package/dist/core/semantic/service.d.ts.map +1 -1
  134. package/dist/core/semantic/service.js +2 -9
  135. package/dist/core/servers/index.d.ts +34 -22
  136. package/dist/core/servers/index.d.ts.map +1 -1
  137. package/dist/core/servers/index.js +297 -144
  138. package/dist/core/servers/tunnels.d.ts +7 -7
  139. package/dist/core/servers/tunnels.d.ts.map +1 -1
  140. package/dist/core/servers/tunnels.js +47 -10
  141. package/dist/core/supervisor/contract.d.ts +12 -0
  142. package/dist/core/supervisor/contract.d.ts.map +1 -1
  143. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  144. package/dist/core/supervisor/explanations.js +2 -1
  145. package/dist/core/supervisor/findings.d.ts.map +1 -1
  146. package/dist/core/supervisor/findings.js +54 -9
  147. package/dist/core/supervisor/service.d.ts +7 -1
  148. package/dist/core/supervisor/service.d.ts.map +1 -1
  149. package/dist/core/supervisor/service.js +59 -29
  150. package/dist/core/supervisor/services.d.ts.map +1 -1
  151. package/dist/core/supervisor/services.js +22 -1
  152. package/dist/core/supervisor/status.d.ts +17 -2
  153. package/dist/core/supervisor/status.d.ts.map +1 -1
  154. package/dist/core/supervisor/status.js +70 -12
  155. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  156. package/dist/core/supervisor/timeline.js +2 -1
  157. package/dist/core/turn-rollback/content.d.ts +53 -0
  158. package/dist/core/turn-rollback/content.d.ts.map +1 -0
  159. package/dist/core/turn-rollback/content.js +396 -0
  160. package/dist/core/turn-rollback/index.d.ts +72 -0
  161. package/dist/core/turn-rollback/index.d.ts.map +1 -0
  162. package/dist/core/turn-rollback/index.js +443 -0
  163. package/dist/core/work/state.js +7 -7
  164. package/dist/core/workflow/engine.d.ts.map +1 -1
  165. package/dist/core/workflow/engine.js +105 -32
  166. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  167. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  168. package/dist/core/workflow/failure-evidence.js +114 -0
  169. package/dist/core/workflow/index.d.ts +5 -3
  170. package/dist/core/workflow/index.d.ts.map +1 -1
  171. package/dist/core/workflow/index.js +3 -2
  172. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  173. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  174. package/dist/core/workflow/live-session-v3.js +126 -5
  175. package/dist/core/workflow/proof.d.ts +5 -5
  176. package/dist/core/workflow/proof.d.ts.map +1 -1
  177. package/dist/core/workflow/proof.js +115 -20
  178. package/dist/core/workflow/recovery-audit.d.ts +30 -0
  179. package/dist/core/workflow/recovery-audit.d.ts.map +1 -0
  180. package/dist/core/workflow/recovery-audit.js +116 -0
  181. package/dist/core/workflow/run-state.d.ts +20 -1
  182. package/dist/core/workflow/run-state.d.ts.map +1 -1
  183. package/dist/core/workflow/run-state.js +141 -32
  184. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  185. package/dist/core/workflow/spawn-claude.js +19 -0
  186. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  187. package/dist/core/workflow/spawn-codex.js +13 -0
  188. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  189. package/dist/core/workflow/spawn-cursor.js +19 -0
  190. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  191. package/dist/core/workflow/spawn-opencode.js +14 -0
  192. package/dist/core/workflow/types.d.ts +64 -17
  193. package/dist/core/workflow/types.d.ts.map +1 -1
  194. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  195. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  196. package/dist/core/workflow/workspaces/index.js +0 -1
  197. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  198. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  199. package/dist/core/workflow/workspaces/leases.js +30 -16
  200. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  201. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  202. package/dist/core/workflow/workspaces/local-git.js +328 -8
  203. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  204. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  205. package/dist/core/workflow/workspaces/ownership.js +194 -0
  206. package/dist/core/workflow/workspaces/reclaim.d.ts +40 -0
  207. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  208. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  209. package/dist/core/workflow/workspaces/recovery.d.ts +2 -0
  210. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  211. package/dist/core/workflow/workspaces/recovery.js +42 -10
  212. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  213. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  214. package/dist/core/workflow/workspaces/state.js +21 -1
  215. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  216. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  217. package/dist/lib/browser/windows-chrome.js +262 -73
  218. package/dist/lib/docs-links.d.ts.map +1 -1
  219. package/dist/lib/docs-links.js +45 -2
  220. package/dist/lib/guarded-remove.d.ts +7 -0
  221. package/dist/lib/guarded-remove.d.ts.map +1 -1
  222. package/dist/lib/guarded-remove.js +106 -12
  223. package/dist/lib/identities/assume.d.ts.map +1 -1
  224. package/dist/lib/identities/assume.js +4 -0
  225. package/dist/lib/tunnel/state.d.ts +13 -6
  226. package/dist/lib/tunnel/state.d.ts.map +1 -1
  227. package/dist/lib/tunnel/state.js +58 -12
  228. package/dist/lib/zip/index.d.ts +41 -0
  229. package/dist/lib/zip/index.d.ts.map +1 -0
  230. package/dist/lib/zip/index.js +398 -0
  231. package/package.json +12 -1
  232. package/src/commander.ts +10 -0
  233. package/src/commands/agents.ts +54 -63
  234. package/src/commands/browse.ts +50 -14
  235. package/src/commands/diagnostics.ts +10 -0
  236. package/src/commands/doctor.ts +26 -7
  237. package/src/commands/files.ts +13 -5
  238. package/src/commands/governor.ts +23 -10
  239. package/src/commands/inbox.ts +52 -2
  240. package/src/commands/rm.ts +43 -4
  241. package/src/commands/servers.ts +27 -26
  242. package/src/commands/supervisor.ts +7 -2
  243. package/src/commands/toc.ts +14 -6
  244. package/src/commands/tunnel.ts +533 -166
  245. package/src/commands/turn-rollback.ts +111 -0
  246. package/src/commands/workflow.ts +89 -30
  247. package/src/core/agents/coord-client.ts +20 -0
  248. package/src/core/agents/live-authority-v3.ts +17 -0
  249. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  250. package/src/core/agents/mailbox.ts +26 -17
  251. package/src/core/agents/render/prompt-context.ts +10 -12
  252. package/src/core/agents/render/session-context.ts +9 -32
  253. package/src/core/agents/rules/stop-hook.ts +3 -3
  254. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  255. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  256. package/src/core/agents/state/pidmap.ts +13 -0
  257. package/src/core/agents/state/proc-start.ts +62 -2
  258. package/src/core/agents/state/process-handle.ts +165 -0
  259. package/src/core/agents/state/stale-sweep.ts +247 -82
  260. package/src/core/agents/state/unchanged-file.ts +456 -0
  261. package/src/core/artifacts/delivery-card.ts +7 -1
  262. package/src/core/context/index.ts +446 -136
  263. package/src/core/context/observation.ts +29 -0
  264. package/src/core/diagnostics/bundle.ts +23 -18
  265. package/src/core/diagnostics/path-identity.ts +15 -0
  266. package/src/core/diagnostics/sanitize.ts +4 -3
  267. package/src/core/events/v3/authority-outbox.ts +56 -0
  268. package/src/core/events/v3/generation-custody.ts +59 -0
  269. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  270. package/src/core/events/v3/producers/recorder.ts +94 -68
  271. package/src/core/governor/planning.ts +20 -8
  272. package/src/core/governor/service-read.ts +128 -20
  273. package/src/core/governor/service.ts +119 -42
  274. package/src/core/hooks/cli.ts +33 -5
  275. package/src/core/inbox/contract.ts +2 -1
  276. package/src/core/inbox/index.ts +2 -0
  277. package/src/core/inbox/peer-messages.ts +94 -0
  278. package/src/core/inbox/service.ts +88 -22
  279. package/src/core/inbox/targets.ts +148 -0
  280. package/src/core/inbox/watcher.ts +8 -9
  281. package/src/core/journal/index.ts +19 -2
  282. package/src/core/resources/contract.ts +4 -0
  283. package/src/core/resources/service-status.ts +35 -12
  284. package/src/core/resources/service.ts +57 -95
  285. package/src/core/resources/status.ts +57 -8
  286. package/src/core/semantic/service.ts +2 -8
  287. package/src/core/servers/index.ts +376 -141
  288. package/src/core/servers/tunnels.ts +57 -13
  289. package/src/core/supervisor/contract.ts +13 -0
  290. package/src/core/supervisor/explanations.ts +4 -1
  291. package/src/core/supervisor/findings.ts +68 -10
  292. package/src/core/supervisor/service.ts +87 -26
  293. package/src/core/supervisor/services.ts +26 -8
  294. package/src/core/supervisor/status.ts +89 -12
  295. package/src/core/supervisor/timeline.ts +2 -1
  296. package/src/core/turn-rollback/content.ts +499 -0
  297. package/src/core/turn-rollback/index.ts +580 -0
  298. package/src/core/work/state.ts +7 -7
  299. package/src/core/workflow/engine.ts +131 -49
  300. package/src/core/workflow/failure-evidence.ts +141 -0
  301. package/src/core/workflow/index.ts +12 -2
  302. package/src/core/workflow/live-session-v3.ts +146 -5
  303. package/src/core/workflow/proof.ts +134 -26
  304. package/src/core/workflow/recovery-audit.ts +181 -0
  305. package/src/core/workflow/run-state.ts +165 -34
  306. package/src/core/workflow/spawn-claude.ts +18 -0
  307. package/src/core/workflow/spawn-codex.ts +13 -0
  308. package/src/core/workflow/spawn-cursor.ts +18 -0
  309. package/src/core/workflow/spawn-opencode.ts +14 -0
  310. package/src/core/workflow/types.ts +51 -17
  311. package/src/core/workflow/workspaces/index.ts +0 -1
  312. package/src/core/workflow/workspaces/leases.ts +48 -17
  313. package/src/core/workflow/workspaces/local-git.ts +388 -7
  314. package/src/core/workflow/workspaces/ownership.ts +232 -0
  315. package/src/core/workflow/workspaces/reclaim.ts +62 -0
  316. package/src/core/workflow/workspaces/recovery.ts +45 -10
  317. package/src/core/workflow/workspaces/state.ts +20 -0
  318. package/src/lib/browser/windows-chrome.ts +346 -86
  319. package/src/lib/docs-links.ts +49 -2
  320. package/src/lib/guarded-remove.ts +132 -14
  321. package/src/lib/identities/assume.ts +4 -0
  322. package/src/lib/tunnel/state.ts +70 -15
  323. package/src/lib/zip/index.ts +483 -0
@@ -14,30 +14,30 @@
14
14
  * for `gc` once its starting agent has ended or been unobserved for a long
15
15
  * time AND nobody has used the server for the idle window.
16
16
  *
17
- * Fail-closed rules: a record whose pid was recycled is treated as dead, never
18
- * signalled; an unreadable coordination view makes every owner `unknown`,
17
+ * Fail-closed rules: a record whose pid was recycled is never signalled;
18
+ * an unreadable start token or coordination view is `unknown`,
19
19
  * which `gc` never stops; a record without an owner is never stopped
20
20
  * automatically.
21
21
  */
22
- import { spawnSync } from "node:child_process";
23
- import { createHash } from "node:crypto";
22
+ import { createHash, randomUUID } from "node:crypto";
24
23
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
25
24
  import { hostname } from "node:os";
26
25
  import { basename, isAbsolute, join, relative, resolve } from "node:path";
27
- import { listStates } from "../../lib/tunnel/state.js";
28
26
  import { resolveCoordRoot } from "../agents/coord-client.js";
29
27
  import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
30
- import { checkPidToken, processStartToken } from "../agents/state/proc-start.js";
28
+ import { processStartToken, signalIfSameGeneration, verifyProcessGeneration, } from "../agents/state/proc-start.js";
31
29
  import { serverPortRange } from "../config.js";
32
30
  import { readCoordinationViewV3, requireAuthoritySafeCoordinationViewV3, } from "../events/v3/coordination-view.js";
33
31
  import { liveInstanceIdV3 } from "../events/v3/live-route-observer.js";
34
32
  import { resolveOwner } from "../hooks/resolve/owner.js";
35
33
  import { writePrivateJsonAtomic } from "../storage/atomic-json.js";
34
+ import { acquireNoClobberLease } from "../workflow/workspaces/leases.js";
36
35
  import { ancestry, establishedConnectionCounts, listListeningPorts, listListeningSockets, processStartedAt, readProcessInfo, scanSupport, } from "./net.js";
37
- import { tunnelServerId, tunnelServerInput } from "./tunnels.js";
36
+ export { processStartToken, signalIfSameGeneration, verifyProcessGeneration, } from "../agents/state/proc-start.js";
37
+ export { acquireNoClobberLease } from "../workflow/workspaces/leases.js";
38
38
  export { ancestry, establishedConnectionCounts, listListeningPorts, listListeningSockets, lsofConnectionCounts, lsofListeningSockets, parseElapsed, parseLsofListen, parseProcNetTcp, processStartedAt, psProcessInfo, readProcessInfo, scanSupport, } from "./net.js";
39
- export { tunnelServerId, tunnelServerInput } from "./tunnels.js";
40
- export const SERVER_RECORD_SCHEMA_VERSION = 1;
39
+ export { createTunnelReadinessVerifier, tunnelServerId, tunnelServerInput } from "./tunnels.js";
40
+ export const SERVER_RECORD_SCHEMA_VERSION = 2;
41
41
  export const SERVER_DEFAULT_IDLE_HOURS = 2;
42
42
  export const SERVER_DEFAULT_OWNER_STALE_HOURS = 24;
43
43
  const TOUCH_INTERVAL_MS = 60_000;
@@ -86,34 +86,54 @@ export function registerServer(input, options) {
86
86
  !(Number.isInteger(input.port) && input.port > 0 && input.port < 65536))
87
87
  throw new Error(`invalid server port: ${input.port}`);
88
88
  const id = input.id ?? serverId(input.type, input.scope);
89
- const now = new Date().toISOString();
90
- const startToken = processStartToken(pid);
91
- const owner = input.owner === undefined ? callingOwner(coordRoot) : (input.owner ?? undefined);
92
- const record = {
93
- schema_version: SERVER_RECORD_SCHEMA_VERSION,
94
- id,
95
- kind: input.kind,
96
- type: input.type,
97
- label: input.label ?? id,
98
- ...(input.url ? { url: input.url } : {}),
99
- ...(input.port ? { port: input.port } : {}),
100
- pid,
101
- ...(startToken ? { start_token: startToken } : {}),
102
- ...(input.pids?.length
103
- ? { pids: input.pids.filter((p) => Number.isSafeInteger(p) && p > 0) }
104
- : {}),
105
- host: hostname(),
106
- ...(input.scope ? { scope: resolve(input.scope) } : {}),
107
- ...(input.cwd ? { cwd: resolve(input.cwd) } : {}),
108
- ...(input.log ? { log: resolve(input.log) } : {}),
109
- ...(input.stop_argv?.length ? { stop_argv: input.stop_argv } : {}),
110
- ...(owner ? { owner } : {}),
111
- started_at: input.started_at ?? now,
112
- registered_at: now,
113
- last_active_at: now,
114
- };
115
- writePrivateJsonAtomic(recordPath(coordRoot, id), record);
116
- return record;
89
+ return withServerMutation(coordRoot, id, () => {
90
+ const path = recordPath(coordRoot, id);
91
+ const now = new Date().toISOString();
92
+ const startToken = input.start_token ?? processStartToken(pid);
93
+ const pids = [...new Set(input.pids?.filter((p) => Number.isSafeInteger(p) && p > 0))];
94
+ const pidsStartTokens = {};
95
+ for (const auxiliaryPid of pids) {
96
+ const token = processStartToken(auxiliaryPid);
97
+ if (token)
98
+ pidsStartTokens[auxiliaryPid] = token;
99
+ }
100
+ const owner = input.owner === undefined ? callingOwner(coordRoot) : (input.owner ?? undefined);
101
+ const record = {
102
+ schema_version: SERVER_RECORD_SCHEMA_VERSION,
103
+ id,
104
+ registration_id: randomUUID(),
105
+ kind: input.kind,
106
+ type: input.type,
107
+ label: input.label ?? id,
108
+ ...(input.url ? { url: input.url } : {}),
109
+ ...(input.port ? { port: input.port } : {}),
110
+ pid,
111
+ ...(startToken ? { start_token: startToken } : {}),
112
+ ...(pids.length ? { pids, pids_start_tokens: pidsStartTokens } : {}),
113
+ host: hostname(),
114
+ ...(input.scope ? { scope: resolve(input.scope) } : {}),
115
+ ...(input.cwd ? { cwd: resolve(input.cwd) } : {}),
116
+ ...(input.log ? { log: resolve(input.log) } : {}),
117
+ ...(owner ? { owner } : {}),
118
+ started_at: input.started_at ?? now,
119
+ registered_at: now,
120
+ last_active_at: now,
121
+ };
122
+ if (input.start_token !== undefined &&
123
+ verifyProcessGeneration(pid, input.start_token) !== "same")
124
+ throw new Error(`pid ${pid} generation could not be verified before registration`);
125
+ if (existsSync(path)) {
126
+ const existing = readRecord(path);
127
+ if (!existing)
128
+ throw new Error(`registration ${id} custody is unknown: invalid or older server record retained`);
129
+ if (sameProcessCustody(existing, record) && serverState(existing) === "running")
130
+ return existing;
131
+ if (existing.host !== hostname() || !allProcessesAbsent(existing))
132
+ throw new Error(`registration ${id} custody is held by a live or unverifiable server`);
133
+ }
134
+ writePrivateJsonAtomic(path, record);
135
+ return record;
136
+ });
117
137
  }
118
138
  /** Best-effort registration that never throws (for server start paths). */
119
139
  export function tryRegisterServer(input, options) {
@@ -136,43 +156,106 @@ function callingOwner(coordRoot) {
136
156
  return undefined;
137
157
  }
138
158
  }
139
- /**
140
- * Remove a server's record. With `pid`, only a record still naming that pid
141
- * is removed, so a replacement that re-registered the same id survives.
142
- */
143
- export function unregisterServer(id, options) {
159
+ /** Remove only the exact registration receipt observed by the caller. */
160
+ export function unregisterServer(record, options) {
144
161
  const coordRoot = rootOf(options);
145
- if (!coordRoot || !isValidServerId(id))
162
+ if (!coordRoot || !record || !isValidServerId(record.id))
146
163
  return false;
147
- const path = recordPath(coordRoot, id);
148
- if (options?.pid !== undefined) {
149
- const record = readRecord(path);
150
- if (record && record.pid !== options.pid)
151
- return false;
164
+ return withServerMutation(coordRoot, record.id, () => removeServerReceipt(coordRoot, record));
165
+ }
166
+ export function withServerRegistrationCustody(record, options, effect) {
167
+ const coordRoot = rootOf(options);
168
+ if (!coordRoot)
169
+ throw new Error("registration custody root is unavailable");
170
+ const lease = serverMutationLease(coordRoot, record.id);
171
+ try {
172
+ const current = readServer(record.id, { coordRoot });
173
+ if (!current ||
174
+ current.registration_id !== record.registration_id ||
175
+ !sameProcessCustody(current, record))
176
+ throw new Error(`registration ${record.id} custody changed`);
177
+ const result = effect();
178
+ if (result !== null &&
179
+ (typeof result === "object" || typeof result === "function") &&
180
+ typeof result.then === "function")
181
+ return Promise.resolve(result).finally(() => lease.release());
182
+ lease.release();
183
+ return result;
184
+ }
185
+ catch (error) {
186
+ lease.release();
187
+ throw error;
152
188
  }
153
- if (!existsSync(path))
189
+ }
190
+ function removeServerReceipt(coordRoot, record) {
191
+ const path = recordPath(coordRoot, record.id);
192
+ const current = readRecord(path);
193
+ if (!current ||
194
+ current.registration_id !== record.registration_id ||
195
+ !sameProcessCustody(current, record))
154
196
  return false;
155
- rmSync(path, { force: true });
197
+ rmSync(path);
156
198
  return true;
157
199
  }
200
+ function serverMutationLease(coordRoot, id) {
201
+ recordPath(coordRoot, id);
202
+ const scope = `server-registration-${id}`;
203
+ return acquireNoClobberLease({
204
+ path: join(serversDir(coordRoot), ".custody", id),
205
+ scope,
206
+ authoritySha256: createHash("sha256").update(scope).digest("hex"),
207
+ staleAfterMs: 1,
208
+ });
209
+ }
210
+ function withServerMutation(coordRoot, id, operation) {
211
+ const lease = serverMutationLease(coordRoot, id);
212
+ try {
213
+ return operation();
214
+ }
215
+ finally {
216
+ lease.release();
217
+ }
218
+ }
219
+ function sameProcessCustody(a, b) {
220
+ const helpers = (record) => [...new Set(record.pids ?? [])]
221
+ .sort((x, y) => x - y)
222
+ .map((pid) => [pid, record.pids_start_tokens?.[pid] ?? null]);
223
+ return (a.schema_version === SERVER_RECORD_SCHEMA_VERSION &&
224
+ b.schema_version === SERVER_RECORD_SCHEMA_VERSION &&
225
+ a.id === b.id &&
226
+ a.pid === b.pid &&
227
+ a.start_token === b.start_token &&
228
+ a.host === b.host &&
229
+ a.kind === b.kind &&
230
+ a.type === b.type &&
231
+ a.scope === b.scope &&
232
+ a.owner?.instance_id === b.owner?.instance_id &&
233
+ JSON.stringify(helpers(a)) === JSON.stringify(helpers(b)));
234
+ }
158
235
  export function readServer(id, options) {
159
236
  const coordRoot = rootOf(options);
160
237
  if (!coordRoot || !isValidServerId(id))
161
238
  return null;
162
- return readRecord(recordPath(coordRoot, id));
239
+ const path = recordPath(coordRoot, id);
240
+ const record = readRecord(path);
241
+ if (!record && existsSync(path))
242
+ throw new Error(`server record ${id} is invalid or uses an older schema; retained without custody`);
243
+ return record;
163
244
  }
164
245
  /** Mark a server as used now. */
165
246
  export function touchServer(id, options) {
166
247
  const coordRoot = rootOf(options);
167
248
  if (!coordRoot || !isValidServerId(id))
168
249
  return;
169
- const path = recordPath(coordRoot, id);
170
- const record = readRecord(path);
171
- if (!record)
172
- return;
173
- writePrivateJsonAtomic(path, {
174
- ...record,
175
- last_active_at: (options?.now ?? new Date()).toISOString(),
250
+ withServerMutation(coordRoot, id, () => {
251
+ const path = recordPath(coordRoot, id);
252
+ const record = readRecord(path);
253
+ if (!record)
254
+ return;
255
+ writePrivateJsonAtomic(path, {
256
+ ...record,
257
+ last_active_at: (options?.now ?? new Date()).toISOString(),
258
+ });
176
259
  });
177
260
  }
178
261
  /**
@@ -201,6 +284,8 @@ function readRecord(path) {
201
284
  const value = JSON.parse(readFileSync(path, "utf8"));
202
285
  if (value?.schema_version !== SERVER_RECORD_SCHEMA_VERSION ||
203
286
  typeof value.id !== "string" ||
287
+ typeof value.registration_id !== "string" ||
288
+ !/^[0-9a-f-]{36}$/.test(value.registration_id) ||
204
289
  !Number.isSafeInteger(value.pid) ||
205
290
  (value.kind !== "service" && value.kind !== "session"))
206
291
  return null;
@@ -219,25 +304,42 @@ export function readServerRecords(options) {
219
304
  return [];
220
305
  return readdirSync(dir)
221
306
  .filter((name) => name.endsWith(".json"))
222
- .map((name) => readRecord(join(dir, name)))
223
- .filter((record) => record !== null)
307
+ .map((name) => {
308
+ const record = readRecord(join(dir, name));
309
+ if (!record)
310
+ throw new Error(`server record ${name} is invalid or uses an older schema; retained without custody`);
311
+ return record;
312
+ })
224
313
  .sort((a, b) => a.kind.localeCompare(b.kind) || a.type.localeCompare(b.type) || a.id.localeCompare(b.id));
225
314
  }
226
315
  export function pidAlive(pid) {
316
+ return processPresence(pid) !== "absent";
317
+ }
318
+ function processPresence(pid, probePid) {
227
319
  try {
320
+ if (probePid)
321
+ return probePid(pid) ? "present" : "absent";
228
322
  process.kill(pid, 0);
229
- return true;
323
+ return "present";
230
324
  }
231
325
  catch (error) {
232
- return error.code === "EPERM";
326
+ const code = error.code;
327
+ return code === "ESRCH" ? "absent" : code === "EPERM" ? "present" : "unknown";
233
328
  }
234
329
  }
235
- export function serverState(record) {
330
+ function allProcessesAbsent(record, probePid) {
331
+ return [record.pid, ...(record.pids ?? [])].every((pid) => processPresence(pid, probePid) === "absent");
332
+ }
333
+ export function serverState(record, readToken, probePid) {
236
334
  if (record.host !== hostname())
237
335
  return "other-host";
238
- if (!pidAlive(record.pid))
239
- return "dead";
240
- return checkPidToken(record.pid, record.start_token) === "mismatch" ? "dead" : "running";
336
+ const presence = processPresence(record.pid, probePid);
337
+ if (presence === "absent")
338
+ return allProcessesAbsent(record, probePid) ? "dead" : "unknown";
339
+ if (presence === "unknown")
340
+ return "unknown";
341
+ const verdict = verifyProcessGeneration(record.pid, record.start_token, readToken);
342
+ return verdict === "same" ? "running" : verdict === "different" ? "dead" : "unknown";
241
343
  }
242
344
  function ownerReader(coordRoot, ownerStaleMs, nowMs) {
243
345
  let view = null;
@@ -268,7 +370,6 @@ export function listServers(options) {
268
370
  const now = options?.now ?? new Date();
269
371
  const nowMs = now.getTime();
270
372
  const ownerStaleMs = hours(options?.ownerStaleHours ?? SERVER_DEFAULT_OWNER_STALE_HOURS);
271
- adoptRunningTunnels(coordRoot);
272
373
  const records = readServerRecords({ coordRoot });
273
374
  const ownerOf = ownerReader(coordRoot, ownerStaleMs, nowMs);
274
375
  const states = records.map((record) => serverState(record));
@@ -278,8 +379,8 @@ export function listServers(options) {
278
379
  const servers = [];
279
380
  records.forEach((record, i) => {
280
381
  const state = states[i];
281
- if (state === "dead" && options?.prune !== false) {
282
- unregisterServer(record.id, { coordRoot, pid: record.pid });
382
+ if (state === "dead" && allProcessesAbsent(record) && options?.prune !== false) {
383
+ unregisterServer(record, { coordRoot });
283
384
  pruned.push(record.id);
284
385
  return;
285
386
  }
@@ -316,27 +417,6 @@ export function listServers(options) {
316
417
  pruned,
317
418
  };
318
419
  }
319
- /** Register running tunnels that have no current record. Returns adopted ids. */
320
- export function adoptRunningTunnels(coordRoot) {
321
- const adopted = [];
322
- let states;
323
- try {
324
- states = listStates(coordRoot);
325
- }
326
- catch {
327
- return adopted;
328
- }
329
- for (const state of states) {
330
- if (!state.gate_pid || !pidAlive(state.gate_pid))
331
- continue;
332
- const id = tunnelServerId(state.name);
333
- if (readServer(id, { coordRoot })?.pid === state.gate_pid)
334
- continue;
335
- if (tryRegisterServer(tunnelServerInput(state, coordRoot), { coordRoot }))
336
- adopted.push(id);
337
- }
338
- return adopted;
339
- }
340
420
  /** Processes that browser automation and editors start; never project servers. */
341
421
  const IGNORED_LISTENER_COMMANDS = [
342
422
  /(^|\/)(chrome|chromium|chrome-headless-shell|headless_shell|msedge|firefox)( |$)/i,
@@ -350,10 +430,10 @@ const IGNORED_LISTENER_COMMANDS = [
350
430
  * a recorded port.
351
431
  */
352
432
  export function findUnregisteredListeners(coordRoot, servers) {
353
- const running = servers.filter((server) => server.state === "running");
433
+ const registered = servers.filter((server) => server.record.host === hostname());
354
434
  const covered = new Set();
355
435
  const ports = new Set();
356
- for (const { record } of running) {
436
+ for (const { record } of registered) {
357
437
  covered.add(record.pid);
358
438
  for (const pid of record.pids ?? [])
359
439
  covered.add(pid);
@@ -363,8 +443,9 @@ export function findUnregisteredListeners(coordRoot, servers) {
363
443
  const results = [];
364
444
  const seenPids = new Map();
365
445
  for (const socket of listListeningSockets()) {
366
- if (ports.has(socket.port) || socket.pid === process.pid)
446
+ if (ports.has(socket.port) || covered.has(socket.pid) || socket.pid === process.pid)
367
447
  continue;
448
+ const startToken = processStartToken(socket.pid);
368
449
  let inside = seenPids.get(socket.pid);
369
450
  const info = readProcessInfo(socket.pid);
370
451
  if (!info)
@@ -380,6 +461,7 @@ export function findUnregisteredListeners(coordRoot, servers) {
380
461
  continue;
381
462
  results.push({
382
463
  pid: socket.pid,
464
+ ...(startToken ? { start_token: startToken } : {}),
383
465
  port: socket.port,
384
466
  address: socket.address,
385
467
  command: info.command.slice(0, 300),
@@ -430,72 +512,138 @@ export function planServerGc(views, policy) {
430
512
  });
431
513
  }
432
514
  /**
433
- * Stop one server: its graceful stop command first when recorded, then
434
- * SIGTERM to its process group, then SIGKILL after the grace period. A pid
435
- * whose start token no longer matches is never signalled.
515
+ * Stop a verified server with SIGTERM, then SIGKILL after the grace period.
516
+ * Every signal targets one retained process handle, including auxiliary processes.
517
+ * Each target requires a fresh generation check. Unknown or different generations retain the record.
436
518
  */
437
519
  export async function stopServer(record, options) {
438
520
  const coordRoot = rootOf(options);
521
+ if (!coordRoot)
522
+ return {
523
+ id: record.id,
524
+ stopped: false,
525
+ method: "refused",
526
+ generation: "unknown",
527
+ detail: "registration custody root is unavailable",
528
+ };
529
+ let lease;
530
+ try {
531
+ lease = serverMutationLease(coordRoot, record.id);
532
+ }
533
+ catch {
534
+ return {
535
+ id: record.id,
536
+ stopped: false,
537
+ method: "refused",
538
+ generation: "unknown",
539
+ detail: "registration custody is unavailable",
540
+ };
541
+ }
542
+ try {
543
+ const current = readServer(record.id, { coordRoot });
544
+ if (!current || current.registration_id !== record.registration_id)
545
+ return {
546
+ id: record.id,
547
+ stopped: false,
548
+ method: "refused",
549
+ generation: "unknown",
550
+ detail: "registration custody changed; retained current server",
551
+ };
552
+ return await stopServerWithCustody(record, coordRoot, options);
553
+ }
554
+ finally {
555
+ lease.release();
556
+ }
557
+ }
558
+ async function stopServerWithCustody(record, coordRoot, options) {
439
559
  const graceMs = options?.graceMs ?? 5_000;
560
+ const dependencies = options?.signalDependencies ?? {};
561
+ const targets = [
562
+ { pid: record.pid, token: record.start_token },
563
+ ...[...new Set(record.pids ?? [])]
564
+ .filter((pid) => pid !== record.pid)
565
+ .map((pid) => ({ pid, token: record.pids_start_tokens?.[pid] })),
566
+ ];
567
+ const refuse = (pid, generation) => ({
568
+ id: record.id,
569
+ stopped: false,
570
+ method: "refused",
571
+ generation,
572
+ detail: `pid ${pid} generation is ${generation}; left the server record in place`,
573
+ });
440
574
  const finish = (result) => {
441
575
  if (result.stopped || result.method === "not-running")
442
- unregisterServer(record.id, { coordRoot, pid: record.pid });
576
+ removeServerReceipt(coordRoot, record);
443
577
  return result;
444
578
  };
445
- const state = serverState(record);
446
- if (state === "other-host")
579
+ if (record.host !== hostname())
447
580
  return { id: record.id, stopped: false, method: "refused", detail: `runs on ${record.host}` };
448
- if (state === "dead")
449
- return finish({ id: record.id, stopped: false, method: "not-running" });
450
- if (record.stop_argv?.length) {
451
- const [command, ...args] = record.stop_argv;
452
- spawnSync(command, args, { stdio: "ignore", timeout: 30_000, cwd: record.cwd });
453
- if (await exited(record, graceMs))
454
- return finish({ id: record.id, stopped: true, method: "stop-command" });
581
+ const leaderPresence = processPresence(record.pid, dependencies.probePid);
582
+ if (leaderPresence === "unknown" ||
583
+ (leaderPresence === "absent" && !allProcessesAbsent(record, dependencies.probePid)))
584
+ return refuse(record.pid, "unknown");
585
+ // Check every recorded live process before sending the first signal.
586
+ for (const target of targets) {
587
+ const presence = processPresence(target.pid, dependencies.probePid);
588
+ if (presence === "absent")
589
+ continue;
590
+ if (presence === "unknown")
591
+ return refuse(target.pid, "unknown");
592
+ const verdict = verifyProcessGeneration(target.pid, target.token, dependencies.readToken);
593
+ if (verdict !== "same")
594
+ return refuse(target.pid, verdict);
455
595
  }
456
- signal(record.pid, "SIGTERM");
457
- for (const pid of record.pids ?? [])
458
- signal(pid, "SIGTERM");
459
- if (await exited(record, graceMs))
460
- return finish({ id: record.id, stopped: true, method: "signal" });
461
- if (serverState(record) === "running") {
462
- signal(record.pid, "SIGKILL");
463
- for (const pid of record.pids ?? [])
464
- signal(pid, "SIGKILL");
596
+ if (targets.every((target) => processPresence(target.pid, dependencies.probePid) === "absent"))
597
+ return finish({ id: record.id, stopped: false, method: "not-running" });
598
+ const current = readServer(record.id, { coordRoot });
599
+ if (!current || !sameProcessCustody(current, record))
600
+ return refuse(record.pid, "unknown");
601
+ for (const name of ["SIGTERM", "SIGKILL"]) {
602
+ for (const target of targets) {
603
+ const presence = processPresence(target.pid, dependencies.probePid);
604
+ if (presence === "absent")
605
+ continue;
606
+ if (presence === "unknown")
607
+ return refuse(target.pid, "unknown");
608
+ const result = signalIfSameGeneration(target.pid, target.token, name, dependencies);
609
+ if (!result.sent || result.verdict !== "same")
610
+ return refuse(target.pid, result.verdict === "same" ? "unknown" : result.verdict);
611
+ }
612
+ const observed = await waitForExit(targets, name === "SIGTERM" ? graceMs : 2_000, dependencies);
613
+ if (observed.refused)
614
+ return refuse(observed.refused.pid, observed.refused.verdict);
615
+ if (observed.exited)
616
+ return finish({ id: record.id, stopped: true, method: "signal", generation: "same" });
465
617
  }
466
- const stopped = await exited(record, 2_000);
467
- return finish({
618
+ return {
468
619
  id: record.id,
469
- stopped,
620
+ stopped: false,
470
621
  method: "signal",
471
- ...(stopped ? {} : { detail: "process survived SIGKILL" }),
472
- });
473
- }
474
- function signal(pid, name) {
475
- // Detached servers lead their own process group; signal the group so a
476
- // wrapper (npm, bunx) does not orphan the real listener.
477
- try {
478
- process.kill(-pid, name);
479
- return;
480
- }
481
- catch {
482
- // Not a group leader, or already gone.
483
- }
484
- try {
485
- process.kill(pid, name);
486
- }
487
- catch {
488
- // Already gone.
489
- }
622
+ generation: "same",
623
+ detail: "process survived SIGKILL",
624
+ };
490
625
  }
491
- async function exited(record, waitMs) {
626
+ async function waitForExit(targets, waitMs, dependencies) {
492
627
  const deadline = Date.now() + waitMs;
493
- while (Date.now() < deadline) {
494
- if (serverState(record) !== "running")
495
- return true;
628
+ while (true) {
629
+ let live = false;
630
+ for (const target of targets) {
631
+ const presence = processPresence(target.pid, dependencies.probePid);
632
+ if (presence === "absent")
633
+ continue;
634
+ if (presence === "unknown")
635
+ return { exited: false, refused: { pid: target.pid, verdict: "unknown" } };
636
+ const verdict = verifyProcessGeneration(target.pid, target.token, dependencies.readToken);
637
+ if (verdict !== "same")
638
+ return { exited: false, refused: { pid: target.pid, verdict } };
639
+ live = true;
640
+ }
641
+ if (!live)
642
+ return { exited: true };
643
+ if (Date.now() >= deadline)
644
+ return { exited: false };
496
645
  await new Promise((r) => setTimeout(r, 100));
497
646
  }
498
- return serverState(record) !== "running";
499
647
  }
500
648
  /**
501
649
  * The first free port in the configured range for `type`
@@ -510,7 +658,8 @@ export function allocateServerPort(type, options) {
510
658
  return null;
511
659
  const taken = listListeningPorts();
512
660
  for (const record of readServerRecords({ coordRoot })) {
513
- if (record.port && serverState(record) === "running")
661
+ const state = serverState(record);
662
+ if (record.port && (state === "running" || state === "unknown"))
514
663
  taken.add(record.port);
515
664
  }
516
665
  for (let port = range[0]; port <= range[1]; port++)
@@ -526,12 +675,15 @@ export function adoptServer(pid, options) {
526
675
  const coordRoot = rootOf(options);
527
676
  if (!coordRoot)
528
677
  throw new Error("no Harnery project found (coordination root did not resolve)");
529
- const report = listServers({ coordRoot, sample: false });
678
+ const report = (options?.dependencies?.listServers ?? listServers)({ coordRoot, sample: false });
530
679
  const listeners = report.unregistered.filter((item) => item.pid === pid);
531
680
  if (!listeners.length)
532
681
  throw new Error(`pid ${pid} is not an unregistered listener inside this project`);
533
682
  const ports = listeners.map((item) => item.port).sort((a, b) => a - b);
534
683
  const first = listeners[0];
684
+ const generation = verifyProcessGeneration(pid, first.start_token, options?.dependencies?.readToken);
685
+ if (generation !== "same")
686
+ throw new Error(`pid ${pid} generation is ${generation}; adoption refused`);
535
687
  const type = options?.type ?? "adopted";
536
688
  const scope = options?.scope ?? first.cwd ?? undefined;
537
689
  const started = processStartedAt(pid);
@@ -543,6 +695,7 @@ export function adoptServer(pid, options) {
543
695
  url: `http://127.0.0.1:${ports[0]}/`,
544
696
  port: ports[0],
545
697
  pid,
698
+ start_token: first.start_token,
546
699
  ...(scope ? { scope } : {}),
547
700
  ...(first.cwd ? { cwd: first.cwd } : {}),
548
701
  ...(options?.owner === "none" ? { owner: null } : {}),
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Tunnels as registry services.
3
3
  *
4
- * `tunnel up` and `tunnel reload` register the instance; `tunnel down`
5
- * removes it. Tunnels started before the registry existed are adopted the next
6
- * time the registry is read, so a long-lived tunnel never shows as an
7
- * unregistered listener.
4
+ * `tunnel up` and `tunnel reload` register the new gate; `tunnel down` removes
5
+ * its record. Missing original witnesses remain unknown and cannot authorize actions.
8
6
  */
9
- import { type TunnelState } from "../../lib/tunnel/state.js";
10
- import type { RegisterServerInput } from "./index.js";
7
+ import { type TunnelReadinessVerifier, type TunnelState } from "../../lib/tunnel/state.js";
8
+ import { type RegisterServerInput, type ServerOptions } from "./index.js";
9
+ /** Verify only witnesses saved by the original tunnel registration. */
10
+ export declare function createTunnelReadinessVerifier(options: ServerOptions): TunnelReadinessVerifier;
11
11
  export declare function tunnelServerId(name: string): string;
12
- /** The registry input for one tunnel instance. */
12
+ /** Register the new gate; saved helper PIDs have no generation witnesses. */
13
13
  export declare function tunnelServerInput(state: TunnelState, coordRoot: string): RegisterServerInput;
14
14
  //# sourceMappingURL=tunnels.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tunnels.d.ts","sourceRoot":"","sources":["../../../src/core/servers/tunnels.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAmB5F"}
1
+ {"version":3,"file":"tunnels.d.ts","sourceRoot":"","sources":["../../../src/core/servers/tunnels.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,aAAa,EAGnB,MAAM,YAAY,CAAC;AAEpB,uEAAuE;AACvE,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,aAAa,GAAG,uBAAuB,CAiC7F;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAmB5F"}