harnery 0.42.0 → 0.44.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 (343) hide show
  1. package/README.md +3 -3
  2. package/dist/commander.d.ts +16 -0
  3. package/dist/commander.d.ts.map +1 -1
  4. package/dist/commander.js +18 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +95 -52
  7. package/dist/commands/browse.d.ts.map +1 -1
  8. package/dist/commands/browse.js +38 -11
  9. package/dist/commands/diagnostics.d.ts.map +1 -1
  10. package/dist/commands/diagnostics.js +10 -0
  11. package/dist/commands/doctor.d.ts +12 -0
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +22 -7
  14. package/dist/commands/governor.js +22 -10
  15. package/dist/commands/inbox.d.ts +1 -1
  16. package/dist/commands/inbox.d.ts.map +1 -1
  17. package/dist/commands/inbox.js +55 -2
  18. package/dist/commands/rm.d.ts.map +1 -1
  19. package/dist/commands/rm.js +28 -4
  20. package/dist/commands/servers.d.ts.map +1 -1
  21. package/dist/commands/servers.js +16 -1
  22. package/dist/commands/supervisor.d.ts.map +1 -1
  23. package/dist/commands/supervisor.js +7 -2
  24. package/dist/commands/toc.d.ts +14 -0
  25. package/dist/commands/toc.d.ts.map +1 -1
  26. package/dist/commands/toc.js +7 -4
  27. package/dist/commands/tunnel.d.ts +65 -0
  28. package/dist/commands/tunnel.d.ts.map +1 -1
  29. package/dist/commands/tunnel.js +290 -101
  30. package/dist/commands/web-fetch.d.ts +3 -2
  31. package/dist/commands/web-fetch.d.ts.map +1 -1
  32. package/dist/commands/web-fetch.js +25 -19
  33. package/dist/commands/web.d.ts +7 -1
  34. package/dist/commands/web.d.ts.map +1 -1
  35. package/dist/commands/web.js +9 -3
  36. package/dist/commands/workflow.js +24 -30
  37. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  38. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  39. package/dist/core/agents/live-authority-v3.js +11 -0
  40. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  41. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  42. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  43. package/dist/core/agents/mailbox.d.ts +9 -8
  44. package/dist/core/agents/mailbox.d.ts.map +1 -1
  45. package/dist/core/agents/mailbox.js +24 -17
  46. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  47. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  48. package/dist/core/agents/render/prompt-context.js +19 -17
  49. package/dist/core/agents/render/session-context.d.ts +2 -8
  50. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  51. package/dist/core/agents/render/session-context.js +64 -43
  52. package/dist/core/agents/rules/stop-hook.js +3 -3
  53. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  54. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  55. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  56. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  57. package/dist/core/agents/state/names.d.ts +43 -3
  58. package/dist/core/agents/state/names.d.ts.map +1 -1
  59. package/dist/core/agents/state/names.js +87 -3
  60. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  61. package/dist/core/agents/state/pidmap.js +5 -0
  62. package/dist/core/agents/state/proc-start.d.ts +19 -0
  63. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  64. package/dist/core/agents/state/proc-start.js +41 -0
  65. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  66. package/dist/core/agents/state/stale-sweep.js +180 -70
  67. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  68. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  69. package/dist/core/agents/state/unchanged-file.js +390 -0
  70. package/dist/core/context/index.d.ts +22 -10
  71. package/dist/core/context/index.d.ts.map +1 -1
  72. package/dist/core/context/index.js +381 -119
  73. package/dist/core/context/observation.d.ts +26 -0
  74. package/dist/core/context/observation.d.ts.map +1 -0
  75. package/dist/core/context/observation.js +11 -0
  76. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  77. package/dist/core/diagnostics/bundle.js +25 -14
  78. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  79. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  80. package/dist/core/diagnostics/path-identity.js +12 -0
  81. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  82. package/dist/core/diagnostics/sanitize.js +4 -3
  83. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  84. package/dist/core/events/v3/authority-outbox.js +29 -0
  85. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  86. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  87. package/dist/core/events/v3/generation-custody.js +50 -0
  88. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  89. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  90. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  91. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  92. package/dist/core/events/v3/producers/recorder.js +77 -64
  93. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  94. package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
  95. package/dist/core/governor/planning.d.ts.map +1 -1
  96. package/dist/core/governor/planning.js +14 -1
  97. package/dist/core/governor/service-read.d.ts +10 -2
  98. package/dist/core/governor/service-read.d.ts.map +1 -1
  99. package/dist/core/governor/service-read.js +105 -22
  100. package/dist/core/governor/service.d.ts +20 -2
  101. package/dist/core/governor/service.d.ts.map +1 -1
  102. package/dist/core/governor/service.js +72 -47
  103. package/dist/core/hooks/cli.js +120 -23
  104. package/dist/core/hooks/fork-lineage.d.ts +40 -0
  105. package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
  106. package/dist/core/hooks/fork-lineage.js +62 -0
  107. package/dist/core/hooks/resolve/transcript.d.ts +16 -0
  108. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  109. package/dist/core/hooks/resolve/transcript.js +84 -0
  110. package/dist/core/hooks/shell-rm.d.ts +2 -0
  111. package/dist/core/hooks/shell-rm.d.ts.map +1 -0
  112. package/dist/core/hooks/shell-rm.js +105 -0
  113. package/dist/core/hooks/shell-tokens.d.ts +12 -0
  114. package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
  115. package/dist/core/hooks/shell-tokens.js +97 -0
  116. package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
  117. package/dist/core/hooks/shell-waiter.js +3 -93
  118. package/dist/core/inbox/contract.d.ts +2 -1
  119. package/dist/core/inbox/contract.d.ts.map +1 -1
  120. package/dist/core/inbox/index.d.ts +2 -0
  121. package/dist/core/inbox/index.d.ts.map +1 -1
  122. package/dist/core/inbox/index.js +2 -0
  123. package/dist/core/inbox/peer-messages.d.ts +37 -0
  124. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  125. package/dist/core/inbox/peer-messages.js +72 -0
  126. package/dist/core/inbox/service.d.ts +8 -1
  127. package/dist/core/inbox/service.d.ts.map +1 -1
  128. package/dist/core/inbox/service.js +67 -21
  129. package/dist/core/inbox/targets.d.ts +22 -0
  130. package/dist/core/inbox/targets.d.ts.map +1 -0
  131. package/dist/core/inbox/targets.js +108 -0
  132. package/dist/core/inbox/watcher.js +7 -3
  133. package/dist/core/journal/index.d.ts.map +1 -1
  134. package/dist/core/journal/index.js +17 -2
  135. package/dist/core/resources/contract.d.ts +3 -0
  136. package/dist/core/resources/contract.d.ts.map +1 -1
  137. package/dist/core/resources/service-status.d.ts +14 -1
  138. package/dist/core/resources/service-status.d.ts.map +1 -1
  139. package/dist/core/resources/service-status.js +23 -13
  140. package/dist/core/resources/service.d.ts +9 -1
  141. package/dist/core/resources/service.d.ts.map +1 -1
  142. package/dist/core/resources/service.js +47 -27
  143. package/dist/core/resources/status.d.ts +2 -5
  144. package/dist/core/resources/status.d.ts.map +1 -1
  145. package/dist/core/resources/status.js +36 -7
  146. package/dist/core/semantic/contract.d.ts +5 -5
  147. package/dist/core/servers/index.d.ts +24 -13
  148. package/dist/core/servers/index.d.ts.map +1 -1
  149. package/dist/core/servers/index.js +132 -92
  150. package/dist/core/servers/tunnels.d.ts +3 -5
  151. package/dist/core/servers/tunnels.d.ts.map +1 -1
  152. package/dist/core/servers/tunnels.js +3 -9
  153. package/dist/core/storage/process-log.d.ts +12 -0
  154. package/dist/core/storage/process-log.d.ts.map +1 -1
  155. package/dist/core/storage/process-log.js +10 -2
  156. package/dist/core/supervisor/contract.d.ts +12 -0
  157. package/dist/core/supervisor/contract.d.ts.map +1 -1
  158. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  159. package/dist/core/supervisor/explanations.js +2 -1
  160. package/dist/core/supervisor/findings.d.ts.map +1 -1
  161. package/dist/core/supervisor/findings.js +54 -9
  162. package/dist/core/supervisor/service.d.ts +9 -1
  163. package/dist/core/supervisor/service.d.ts.map +1 -1
  164. package/dist/core/supervisor/service.js +59 -29
  165. package/dist/core/supervisor/services.d.ts.map +1 -1
  166. package/dist/core/supervisor/services.js +22 -1
  167. package/dist/core/supervisor/status.d.ts +17 -2
  168. package/dist/core/supervisor/status.d.ts.map +1 -1
  169. package/dist/core/supervisor/status.js +70 -12
  170. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  171. package/dist/core/supervisor/timeline.js +2 -1
  172. package/dist/core/work/state.js +7 -7
  173. package/dist/core/workflow/engine.d.ts.map +1 -1
  174. package/dist/core/workflow/engine.js +105 -32
  175. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  176. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  177. package/dist/core/workflow/failure-evidence.js +114 -0
  178. package/dist/core/workflow/index.d.ts +1 -1
  179. package/dist/core/workflow/index.d.ts.map +1 -1
  180. package/dist/core/workflow/index.js +1 -1
  181. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  182. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  183. package/dist/core/workflow/live-session-v3.js +126 -5
  184. package/dist/core/workflow/proof.d.ts +5 -5
  185. package/dist/core/workflow/proof.d.ts.map +1 -1
  186. package/dist/core/workflow/proof.js +115 -20
  187. package/dist/core/workflow/run-state.d.ts +3 -1
  188. package/dist/core/workflow/run-state.d.ts.map +1 -1
  189. package/dist/core/workflow/run-state.js +31 -27
  190. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  191. package/dist/core/workflow/spawn-claude.js +19 -0
  192. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  193. package/dist/core/workflow/spawn-codex.js +13 -0
  194. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  195. package/dist/core/workflow/spawn-cursor.js +19 -0
  196. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  197. package/dist/core/workflow/spawn-opencode.js +14 -0
  198. package/dist/core/workflow/types.d.ts +64 -17
  199. package/dist/core/workflow/types.d.ts.map +1 -1
  200. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  201. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  202. package/dist/core/workflow/workspaces/index.js +0 -1
  203. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  204. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  205. package/dist/core/workflow/workspaces/leases.js +30 -16
  206. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  207. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  208. package/dist/core/workflow/workspaces/local-git.js +270 -6
  209. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  210. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  211. package/dist/core/workflow/workspaces/ownership.js +194 -0
  212. package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
  213. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  214. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  215. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  216. package/dist/core/workflow/workspaces/recovery.js +21 -13
  217. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  218. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  219. package/dist/core/workflow/workspaces/state.js +21 -1
  220. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  221. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  222. package/dist/lib/browser/windows-chrome.js +262 -73
  223. package/dist/lib/docs-links.d.ts.map +1 -1
  224. package/dist/lib/docs-links.js +45 -2
  225. package/dist/lib/guarded-remove.d.ts +7 -0
  226. package/dist/lib/guarded-remove.d.ts.map +1 -1
  227. package/dist/lib/guarded-remove.js +106 -12
  228. package/dist/lib/identities/assume.d.ts.map +1 -1
  229. package/dist/lib/identities/assume.js +4 -0
  230. package/dist/lib/tunnel/allowlist.d.ts +44 -0
  231. package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
  232. package/dist/lib/tunnel/allowlist.js +204 -0
  233. package/dist/lib/tunnel/current-address.d.ts +41 -0
  234. package/dist/lib/tunnel/current-address.d.ts.map +1 -0
  235. package/dist/lib/tunnel/current-address.js +123 -0
  236. package/dist/lib/tunnel/gate.js +13 -6
  237. package/dist/lib/tunnel/processes.d.ts +39 -0
  238. package/dist/lib/tunnel/processes.d.ts.map +1 -0
  239. package/dist/lib/tunnel/processes.js +121 -0
  240. package/dist/lib/tunnel/state.d.ts +28 -3
  241. package/dist/lib/tunnel/state.d.ts.map +1 -1
  242. package/dist/lib/tunnel/state.js +98 -27
  243. package/dist/lib/zip/index.d.ts +41 -0
  244. package/dist/lib/zip/index.d.ts.map +1 -0
  245. package/dist/lib/zip/index.js +398 -0
  246. package/package.json +8 -1
  247. package/src/commander.ts +32 -0
  248. package/src/commands/agents.ts +151 -74
  249. package/src/commands/browse.ts +50 -14
  250. package/src/commands/diagnostics.ts +10 -0
  251. package/src/commands/doctor.ts +26 -7
  252. package/src/commands/governor.ts +23 -10
  253. package/src/commands/inbox.ts +52 -2
  254. package/src/commands/rm.ts +43 -4
  255. package/src/commands/servers.ts +16 -4
  256. package/src/commands/supervisor.ts +7 -2
  257. package/src/commands/toc.ts +14 -6
  258. package/src/commands/tunnel.ts +394 -116
  259. package/src/commands/web-fetch.ts +30 -19
  260. package/src/commands/web.ts +9 -3
  261. package/src/commands/workflow.ts +30 -30
  262. package/src/core/agents/live-authority-v3.ts +17 -0
  263. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  264. package/src/core/agents/mailbox.ts +26 -17
  265. package/src/core/agents/render/prompt-context.ts +35 -20
  266. package/src/core/agents/render/session-context.ts +92 -48
  267. package/src/core/agents/rules/stop-hook.ts +3 -3
  268. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  269. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  270. package/src/core/agents/state/names.ts +117 -6
  271. package/src/core/agents/state/pidmap.ts +13 -0
  272. package/src/core/agents/state/proc-start.ts +57 -0
  273. package/src/core/agents/state/stale-sweep.ts +247 -82
  274. package/src/core/agents/state/unchanged-file.ts +456 -0
  275. package/src/core/context/index.ts +446 -136
  276. package/src/core/context/observation.ts +29 -0
  277. package/src/core/diagnostics/bundle.ts +23 -18
  278. package/src/core/diagnostics/path-identity.ts +15 -0
  279. package/src/core/diagnostics/sanitize.ts +4 -3
  280. package/src/core/events/v3/authority-outbox.ts +56 -0
  281. package/src/core/events/v3/generation-custody.ts +59 -0
  282. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  283. package/src/core/events/v3/producers/recorder.ts +94 -68
  284. package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
  285. package/src/core/governor/planning.ts +20 -8
  286. package/src/core/governor/service-read.ts +128 -20
  287. package/src/core/governor/service.ts +119 -42
  288. package/src/core/hooks/cli.ts +139 -22
  289. package/src/core/hooks/fork-lineage.ts +82 -0
  290. package/src/core/hooks/resolve/transcript.ts +88 -0
  291. package/src/core/hooks/shell-rm.ts +103 -0
  292. package/src/core/hooks/shell-tokens.ts +96 -0
  293. package/src/core/hooks/shell-waiter.ts +4 -91
  294. package/src/core/inbox/contract.ts +2 -1
  295. package/src/core/inbox/index.ts +2 -0
  296. package/src/core/inbox/peer-messages.ts +94 -0
  297. package/src/core/inbox/service.ts +88 -22
  298. package/src/core/inbox/targets.ts +148 -0
  299. package/src/core/inbox/watcher.ts +8 -9
  300. package/src/core/journal/index.ts +19 -2
  301. package/src/core/resources/contract.ts +4 -0
  302. package/src/core/resources/service-status.ts +35 -12
  303. package/src/core/resources/service.ts +71 -24
  304. package/src/core/resources/status.ts +57 -8
  305. package/src/core/servers/index.ts +177 -91
  306. package/src/core/servers/tunnels.ts +3 -11
  307. package/src/core/storage/process-log.ts +17 -2
  308. package/src/core/supervisor/contract.ts +13 -0
  309. package/src/core/supervisor/explanations.ts +4 -1
  310. package/src/core/supervisor/findings.ts +68 -10
  311. package/src/core/supervisor/service.ts +88 -26
  312. package/src/core/supervisor/services.ts +26 -8
  313. package/src/core/supervisor/status.ts +89 -12
  314. package/src/core/supervisor/timeline.ts +2 -1
  315. package/src/core/work/state.ts +7 -7
  316. package/src/core/workflow/engine.ts +131 -49
  317. package/src/core/workflow/failure-evidence.ts +141 -0
  318. package/src/core/workflow/index.ts +0 -1
  319. package/src/core/workflow/live-session-v3.ts +146 -5
  320. package/src/core/workflow/proof.ts +134 -26
  321. package/src/core/workflow/run-state.ts +31 -29
  322. package/src/core/workflow/spawn-claude.ts +18 -0
  323. package/src/core/workflow/spawn-codex.ts +13 -0
  324. package/src/core/workflow/spawn-cursor.ts +18 -0
  325. package/src/core/workflow/spawn-opencode.ts +14 -0
  326. package/src/core/workflow/types.ts +51 -17
  327. package/src/core/workflow/workspaces/index.ts +0 -1
  328. package/src/core/workflow/workspaces/leases.ts +48 -17
  329. package/src/core/workflow/workspaces/local-git.ts +315 -5
  330. package/src/core/workflow/workspaces/ownership.ts +232 -0
  331. package/src/core/workflow/workspaces/reclaim.ts +58 -0
  332. package/src/core/workflow/workspaces/recovery.ts +25 -13
  333. package/src/core/workflow/workspaces/state.ts +20 -0
  334. package/src/lib/browser/windows-chrome.ts +346 -86
  335. package/src/lib/docs-links.ts +49 -2
  336. package/src/lib/guarded-remove.ts +132 -14
  337. package/src/lib/identities/assume.ts +4 -0
  338. package/src/lib/tunnel/allowlist.ts +208 -0
  339. package/src/lib/tunnel/current-address.ts +158 -0
  340. package/src/lib/tunnel/gate.ts +16 -8
  341. package/src/lib/tunnel/processes.ts +149 -0
  342. package/src/lib/tunnel/state.ts +123 -27
  343. package/src/lib/zip/index.ts +483 -0
@@ -7,7 +7,6 @@
7
7
  * string.
8
8
  */
9
9
  import { statSync } from "node:fs";
10
- import { type MailboxMessageV1 } from "../mailbox.js";
11
10
  export interface RenderOpts {
12
11
  coordRoot: string;
13
12
  instanceId: string;
@@ -16,19 +15,14 @@ export interface RenderOpts {
16
15
  /** Adapter label rendered in parens after the self-name (e.g. "Cursor", "Codex").
17
16
  * Claude Code omits it. */
18
17
  platformLabel?: string;
18
+ /** The emitter acknowledges exactly these IDs only after output succeeds. */
19
+ onInboxPrepared?: (messageIds: readonly string[]) => void;
19
20
  }
20
21
  /**
21
22
  * Build the combined SessionStart systemMessage. Returns the additionalContext
22
23
  * string (or "" if there's nothing to say).
23
24
  */
24
25
  export declare function renderSessionContext(opts: RenderOpts): string;
25
- /**
26
- * Write delivered peer messages into the recipient's own journal, so they
27
- * survive context compaction the way any other coordination breadcrumb does.
28
- * A message the sender already journaled (the recipient was live when it was
29
- * sent) is skipped to avoid a duplicate entry.
30
- */
31
- export declare function recordDeliveredMessages(instanceId: string, messages: readonly MailboxMessageV1[]): void;
32
26
  /**
33
27
  * Returns a list of commit-guard wiring issues (empty when wired). Portable
34
28
  * across host projects: it asserts the FUNCTIONAL property ("does this repo's
@@ -1 +1 @@
1
- {"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAyC,QAAQ,EAAE,MAAM,SAAS,CAAC;AAM1E,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAoB3F,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+BAC2B;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAyF7D;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GACpC,IAAI,CAUN;AAkID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBvD;AA0DD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiDlF;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,iBAAW,CAAC"}
1
+ {"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAyC,QAAQ,EAAE,MAAM,SAAS,CAAC;AAyB1E,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+BAC2B;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;CAC3D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA8F7D;AA6LD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBvD;AA0DD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiDlF;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,iBAAW,CAAC"}
@@ -11,11 +11,10 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { resolveBinName, resolveHooksSetupHint } from "../../config.js";
13
13
  import { harneryVersion, loadAdapterWiring } from "../../hooks/adapter/wiring.js";
14
- import { appendEntry } from "../../journal/index.js";
14
+ import { formatPeerInbox, peerInbox } from "../../inbox/peer-messages.js";
15
15
  import { readRemoteMachines } from "../../presence/index.js";
16
- import { drainMailbox, formatMailboxDelivery } from "../mailbox.js";
17
16
  import { readLiveCoordinationRows } from "../state/live-coordination-view.js";
18
- import { readForkParent } from "../state/names.js";
17
+ import { forkRelationLabels, readForkState } from "../state/names.js";
19
18
  /**
20
19
  * Build the combined SessionStart systemMessage. Returns the additionalContext
21
20
  * string (or "" if there's nothing to say).
@@ -25,7 +24,13 @@ export function renderSessionContext(opts) {
25
24
  const messages = [];
26
25
  // 1. Self-name line + peer table (folded if peers present)
27
26
  const peers = readActivePeers(coordRoot, instanceId);
28
- const localTable = formatPeerTable(peers, sessionId);
27
+ const forkState = readForkState(coordRoot, instanceId);
28
+ const forkParent = forkState?.parent ?? null;
29
+ const parentRow = forkParent
30
+ ? peers.find((peer) => peer.instance_id === forkParent.instance_id)
31
+ : undefined;
32
+ const relations = forkRelationLabels(coordRoot, instanceId, new Set(peers.map((peer) => peer.instance_id ?? "")));
33
+ const localTable = formatPeerTable(peers, sessionId, relations);
29
34
  // Cross-machine presence (ADR 0016): sessions on other machines (advisory).
30
35
  const peerTable = [localTable, formatRemoteMachines(coordRoot)].filter(Boolean).join("\n\n");
31
36
  if (agentName) {
@@ -35,13 +40,11 @@ export function renderSessionContext(opts) {
35
40
  // asserts the parent's name, claims, and task. When recorded fork lineage
36
41
  // (.name-history forked_from) names the parent, say so specifically — the
37
42
  // fork is about to read a transcript full of that exact name. Otherwise
38
- // fall back to the generic clause, which needs no fork detection.
39
- const forkParent = readForkParent(coordRoot, instanceId);
40
- const authority = forkParent?.name
41
- ? `This conversation was branched from agent-${forkParent.name}'s session: earlier context showing agent-${forkParent.name}'s name, task, or file claims belongs to the pre-fork session, not to you.`
42
- : forkParent
43
- ? `This conversation was branched from another session (${forkParent.instance_id}); any agent name in earlier context belongs to that session, not to you.`
44
- : "Any different agent name in earlier context was inherited from another session; this one is authoritative.";
43
+ // fall back to the generic clause, which needs no fork detection. A live
44
+ // parent is the dangerous case (2026-10-07: a fork read its running
45
+ // parent as its own earlier self and offered to close it), so the notice
46
+ // names what the parent is doing and forbids ending it.
47
+ const authority = forkAuthorityClause(forkParent, forkState?.fork === true, parentRow);
45
48
  const selfLine = `You are agent-${agentName}${suffix}. ${authority}`;
46
49
  messages.push(peerTable ? `${selfLine}\n\n${peerTable}` : selfLine);
47
50
  }
@@ -52,15 +55,12 @@ export function renderSessionContext(opts) {
52
55
  if (isLinkedWorktree(coordRoot)) {
53
56
  messages.push(`Running inside worktree ${process.cwd()}. The coord layer is scoped to this worktree only; use \`${resolveBinName(coordRoot)} worktree diff\` to check for conflicts against sibling worktrees.`);
54
57
  }
55
- // 3. Peer messages queued for this name while no session held it. Draining
56
- // here is what makes `agents ping` safe to use on a dormant agent: the sender
57
- // never has to know whether the recipient is running.
58
- if (agentName) {
59
- const delivered = drainMailbox(coordRoot, agentName);
60
- if (delivered.length > 0) {
61
- recordDeliveredMessages(instanceId, delivered);
62
- messages.push(formatMailboxDelivery(coordRoot, delivered));
63
- }
58
+ // 3. Rendering never consumes a message. The native emitter acknowledges the
59
+ // prepared instance-bound batch only after it has flushed the output.
60
+ const delivered = peerInbox(coordRoot).prepareSurface(instanceId);
61
+ if (delivered.length > 0) {
62
+ opts.onInboxPrepared?.(delivered.map(({ message_id }) => message_id));
63
+ messages.push(formatPeerInbox(coordRoot, delivered));
64
64
  }
65
65
  // 4. Council invites
66
66
  if (agentName) {
@@ -103,25 +103,6 @@ export function renderSessionContext(opts) {
103
103
  }
104
104
  return messages.join("\n\n");
105
105
  }
106
- /**
107
- * Write delivered peer messages into the recipient's own journal, so they
108
- * survive context compaction the way any other coordination breadcrumb does.
109
- * A message the sender already journaled (the recipient was live when it was
110
- * sent) is skipped to avoid a duplicate entry.
111
- */
112
- export function recordDeliveredMessages(instanceId, messages) {
113
- for (const m of messages) {
114
- if (m.journaled)
115
- continue;
116
- try {
117
- appendEntry(instanceId, "handoff", `from agent-${m.from_name}: ${m.body}`);
118
- }
119
- catch {
120
- // A journal write failure must never cost the delivery itself; the
121
- // message is already in the rendered context the model reads.
122
- }
123
- }
124
- }
125
106
  /** One-line-per-agent view of sessions on other machines (presence refs). */
126
107
  function formatRemoteMachines(coordRoot) {
127
108
  try {
@@ -151,13 +132,48 @@ function formatRemoteMachines(coordRoot) {
151
132
  function readActivePeers(coordRoot, selfInstanceId) {
152
133
  return readLiveCoordinationRows(coordRoot).filter((heartbeat) => heartbeat.instance_id && heartbeat.instance_id !== selfInstanceId);
153
134
  }
135
+ const DO_NOT_END = "Do not end, release, or finalize that session unless the operator explicitly tells you to.";
136
+ function forkAuthorityClause(parent, fork, parentRow) {
137
+ if (parent?.name && parentRow) {
138
+ const files = parentRow.files_touched ?? [];
139
+ const task = parentRow.task ? ` on "${parentRow.task.slice(0, 80)}"` : "";
140
+ const holds = files.length > 0
141
+ ? ` and holds ${files.length} file${files.length === 1 ? "" : "s"} (${fmtFileList(files)})`
142
+ : "";
143
+ const owned = files.length > 0
144
+ ? parentRow.task
145
+ ? "That name, task, and those files"
146
+ : "That name and those files"
147
+ : parentRow.task
148
+ ? "That name and task"
149
+ : "That name";
150
+ return (`This conversation was forked from agent-${parent.name}, which is still running as a separate session${task}${holds}. ` +
151
+ `${owned} belong${owned === "That name" ? "s" : ""} to agent-${parent.name}, not to you. ${DO_NOT_END}`);
152
+ }
153
+ if (parent?.name) {
154
+ return `This conversation was branched from agent-${parent.name}'s session: earlier context showing agent-${parent.name}'s name, task, or file claims belongs to the pre-fork session, not to you.`;
155
+ }
156
+ if (parent) {
157
+ return `This conversation was branched from another session (${parent.instance_id}); any agent name in earlier context belongs to that session, not to you.`;
158
+ }
159
+ if (fork) {
160
+ return `This conversation was forked from another session, which may still be running. Any agent name, task, or file claim in earlier context belongs to that session, not to you. ${DO_NOT_END}`;
161
+ }
162
+ return "Any different agent name in earlier context was inherited from another session; this one is authoritative.";
163
+ }
164
+ function fmtFileList(files) {
165
+ const sorted = [...files].sort();
166
+ return sorted.length <= 3
167
+ ? sorted.join(", ")
168
+ : `${sorted.slice(0, 3).join(", ")}, +${sorted.length - 3} more`;
169
+ }
154
170
  /**
155
171
  * Renders the peer table as
156
172
  * two subsections: "Other agent groups active" (blocking) and "Your group"
157
173
  * (subagents/siblings, no mutual block). Folds transient subagents' files
158
174
  * into their parent session.
159
175
  */
160
- function formatPeerTable(peers, mySessionId) {
176
+ function formatPeerTable(peers, mySessionId, relations = new Map()) {
161
177
  if (peers.length === 0)
162
178
  return "";
163
179
  const nowSec = Math.floor(Date.now() / 1000);
@@ -174,9 +190,13 @@ function formatPeerTable(peers, mySessionId) {
174
190
  .map((p) => {
175
191
  const folded = fold[p.instance_id ?? ""] ?? [];
176
192
  const display = Array.from(new Set([...(p.files_touched ?? []), ...folded])).sort();
177
- return { ...p, display_files: display };
193
+ const relation = relations.get(p.instance_id ?? "");
194
+ return { ...p, display_files: display, ...(relation ? { relation } : {}) };
178
195
  });
179
- const blocking = rows.filter((p) => p.session_id !== mySessionId).sort(byStartedAt);
196
+ // Related sessions sort first so the parent never hides in "+N more".
197
+ const blocking = rows
198
+ .filter((p) => p.session_id !== mySessionId)
199
+ .sort((a, b) => Number(!!b.relation) - Number(!!a.relation) || byStartedAt(a, b));
180
200
  const group = rows.filter((p) => p.session_id === mySessionId).sort(byStartedAt);
181
201
  const sections = [];
182
202
  const blockingSection = renderSubtable(blocking, "Other agent groups active (their files block you):", nowSec);
@@ -204,7 +224,8 @@ function formatRow(r, nowSec) {
204
224
  const reason = lifecycle === "blocked" && r.task_state_reason ? `: ${r.task_state_reason.slice(0, 80)}` : "";
205
225
  const ageFrom = fmtAge(nowSec - parseIsoSec(r.started_at));
206
226
  const filesPart = fmtFiles(r.display_files);
207
- return ` - agent-${r.name ?? "unknown"}${taskPart} (activity=${activity}, lifecycle=${lifecycle}${reason}, ${ageFrom}, ${filesPart})`;
227
+ const relation = r.relation ? ` [${r.relation}]` : "";
228
+ return ` - agent-${r.name ?? "unknown"}${relation}${taskPart} (activity=${activity}, lifecycle=${lifecycle}${reason}, ${ageFrom}, ${filesPart})`;
208
229
  }
209
230
  function fmtFiles(files) {
210
231
  if (files.length === 0)
@@ -331,9 +331,9 @@ function sessionNameBlock(coordRoot, instanceId) {
331
331
  catch {
332
332
  // The ledger evidence still authorizes the block; the cache is UX only.
333
333
  }
334
- const instruction = name
335
- ? `Reproduce it by itself in a fenced code block: ${name}`
336
- : `Run \`${resolveBinName(coordRoot)} agents suggest-name\`, then reproduce its output by itself in a fenced code block.`;
334
+ // The first post-mint reply was already rejected. Repeating its title later
335
+ // cannot change that evidence; the JSON retry result opens a new window.
336
+ const instruction = `Run \`${resolveBinName(coordRoot)} agents suggest-name --json\` as your only tool call to create a fresh verification boundary, then display its suggested_session_name verbatim by itself in a fenced code block${name ? `: ${name}` : "."}`;
337
337
  return {
338
338
  allow: false,
339
339
  exit_code: 2,
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAaH,OAAO,EAAE,KAAK,SAAS,EAAgC,MAAM,uBAAuB,CAAC;AACrF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,KAAK,SAAS,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAatE,kFAAkF;AAClF,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,SAAS,EAChB,MAAM,CAAC,EAAE,MAAM,EACf,oBAAoB,CAAC,EAAE,MAAM,GAC5B,SAAS,GAAG,IAAI,CAQlB;AAeD,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAoB7F;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CAOlB;AAED;;;;6BAI6B;AAC7B,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,SAAS,GAAG,UAAU,GAAG,WAAuB,GACrD,SAAS,GAAG,IAAI,CAelB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EAAE,GACzB;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKvD;AAED,2EAA2E;AAC3E,wBAAgB,2BAA2B,CACzC,kBAAkB,EAAE,MAAM,GAAG,SAAS,EACtC,KAAK,EAAE,SAAS,GACf,MAAM,GAAG,IAAI,CAOf;AAED;;;;4EAI4E;AAC5E,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,MAAM,GACZ,SAAS,GAAG,IAAI,CAMlB;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAOlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAOlB;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAQlB;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAWlB;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,eAAe,EAAE,CA6BnB;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CASrF;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAiBnF"}
1
+ {"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,8 @@
15
15
  */
16
16
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
17
17
  import { dirname, join } from "node:path";
18
+ import { assertCurrentGenerationV3, withGenerationCustodyV3, } from "../../events/v3/generation-custody.js";
19
+ import { liveInstanceIdV3 } from "../../events/v3/live-routing.js";
18
20
  import { canonicalClaimPath } from "../claim-path.js";
19
21
  import { heartbeatPath, readHeartbeat } from "./heartbeat-reader.js";
20
22
  export { readHeartbeat } from "./heartbeat-reader.js";
@@ -38,13 +40,16 @@ export function setLifecycleCache(coordRoot, instanceId, state, reason, suggeste
38
40
  }));
39
41
  }
40
42
  function mutate(coordRoot, instanceId, fn) {
41
- const hb = readHeartbeat(coordRoot, instanceId);
42
- if (!hb)
43
- return null;
44
- const next = fn(hb);
45
- next.last_heartbeat = nowIsoSeconds();
46
- atomicWrite(heartbeatPath(coordRoot, instanceId), JSON.stringify(next, null, 2));
47
- return next;
43
+ return withGenerationCustodyV3(coordRoot, [liveInstanceIdV3(instanceId)], () => {
44
+ const hb = readHeartbeat(coordRoot, instanceId);
45
+ if (!hb)
46
+ return null;
47
+ const next = fn(hb);
48
+ next.last_heartbeat = nowIsoSeconds();
49
+ assertCurrentGenerationV3(coordRoot, liveInstanceIdV3(instanceId), hb.v3_generation_id);
50
+ atomicWrite(heartbeatPath(coordRoot, instanceId), JSON.stringify(next, null, 2));
51
+ return next;
52
+ });
48
53
  }
49
54
  export function setTask(coordRoot, instanceId, task) {
50
55
  return mutate(coordRoot, instanceId, (hb) => {
@@ -68,13 +73,16 @@ export function setTask(coordRoot, instanceId, task) {
68
73
  }
69
74
  /** Update the disposable identity cache after a canonical V3 attestation. */
70
75
  export function setIdentityCache(coordRoot, instanceId, name, agentId) {
71
- const heartbeat = readHeartbeat(coordRoot, instanceId);
72
- if (!heartbeat)
73
- return null;
74
- heartbeat.name = name;
75
- heartbeat.agent_id = agentId;
76
- atomicWrite(heartbeatPath(coordRoot, instanceId), JSON.stringify(heartbeat, null, 2));
77
- return heartbeat;
76
+ return withGenerationCustodyV3(coordRoot, [liveInstanceIdV3(instanceId)], () => {
77
+ const heartbeat = readHeartbeat(coordRoot, instanceId);
78
+ if (!heartbeat)
79
+ return null;
80
+ heartbeat.name = name;
81
+ heartbeat.agent_id = agentId;
82
+ assertCurrentGenerationV3(coordRoot, liveInstanceIdV3(instanceId), heartbeat.v3_generation_id);
83
+ atomicWrite(heartbeatPath(coordRoot, instanceId), JSON.stringify(heartbeat, null, 2));
84
+ return heartbeat;
85
+ });
78
86
  }
79
87
  /** Restore the pool-assigned display name on a generation that was
80
88
  * materialized before the adapter's normal SessionStart naming path ran.
@@ -248,6 +256,9 @@ export function clearCoordinationCache(coordRoot, instanceId) {
248
256
  }
249
257
  }
250
258
  export function healPidmap(coordRoot, instanceId, pid) {
259
+ withGenerationCustodyV3(coordRoot, [liveInstanceIdV3(instanceId)], () => healPidmapUnderCustody(coordRoot, instanceId, pid));
260
+ }
261
+ function healPidmapUnderCustody(coordRoot, instanceId, pid) {
251
262
  const dir = join(coordRoot, ".harnery", "pid-map");
252
263
  mkdirSync(dir, { recursive: true });
253
264
  const hb = readHeartbeat(coordRoot, instanceId);
@@ -1 +1 @@
1
- {"version":3,"file":"live-coordination-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/live-coordination-writer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAMhD,OAAO,EAAE,KAAK,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AAUtE;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,SAAS,GAAG,IAAI,CASlB;AAED,wFAAwF;AACxF,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,OAAO,EAChB,KAAK,CAAC,EAAE,MAAM,GACb,SAAS,GAAG,IAAI,CASlB"}
1
+ {"version":3,"file":"live-coordination-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/live-coordination-writer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAUhD,OAAO,EAAE,KAAK,SAAS,EAAiB,MAAM,uBAAuB,CAAC;AAUtE;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,OAAO,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,SAAS,GAAG,IAAI,CASlB;AAED,wFAAwF;AACxF,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,OAAO,EAChB,KAAK,CAAC,EAAE,MAAM,GACb,SAAS,GAAG,IAAI,CASlB"}
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { readCoordinationViewV3, requireAuthoritySafeCoordinationViewV3, } from "../../events/v3/coordination-view.js";
4
+ import { assertCurrentGenerationV3, withGenerationCustodyV3, } from "../../events/v3/generation-custody.js";
4
5
  import { liveInstanceIdV3, resolveLiveEventLedgerRouteV3 } from "../../events/v3/live-routing.js";
5
6
  import { readHeartbeat } from "./heartbeat-reader.js";
6
7
  import { isAdapterV3, isCurrentV3HeartbeatCache, observedAdapterV3, projectHeartbeatV3, } from "./live-coordination-view.js";
@@ -19,6 +20,9 @@ export function repairLiveCoordinationHeartbeat(coordRoot, nativeInstanceId, nat
19
20
  return materializeLiveCoordinationHeartbeat(coordRoot, nativeInstanceId, nativeSessionId, adapter, model, true);
20
21
  }
21
22
  function materializeLiveCoordinationHeartbeat(coordRoot, nativeInstanceId, nativeSessionId, _adapter, model, force) {
23
+ return withGenerationCustodyV3(coordRoot, [liveInstanceIdV3(nativeInstanceId)], () => materializeHeartbeatUnderCustody(coordRoot, nativeInstanceId, nativeSessionId, _adapter, model, force));
24
+ }
25
+ function materializeHeartbeatUnderCustody(coordRoot, nativeInstanceId, nativeSessionId, _adapter, model, force) {
22
26
  const route = resolveLiveEventLedgerRouteV3(coordRoot);
23
27
  if (route.state === "blocked")
24
28
  throw new Error(`event_v3_coordination_view:${route.reason}`);
@@ -85,6 +89,7 @@ function materializeLiveCoordinationHeartbeat(coordRoot, nativeInstanceId, nativ
85
89
  v3_projection_event_id: generation.last_event_id,
86
90
  v3_task_state: generation.task_state === "set" ? "set" : "cleared",
87
91
  };
92
+ assertCurrentGenerationV3(coordRoot, liveInstanceIdV3(nativeInstanceId), generation.generation_id);
88
93
  writeHeartbeatCache(coordRoot, nativeInstanceId, materialized);
89
94
  return materialized;
90
95
  }
@@ -43,13 +43,20 @@ export interface NameHistoryRow {
43
43
  ts: string;
44
44
  /** Durable persona UUID. Present after `agents identity assume`. */
45
45
  agent_id?: string;
46
- /** Audit marker distinguishing an explicit role adoption from pool assignment. */
47
- source?: "pool" | "identity.assume";
46
+ /** Audit marker distinguishing an explicit role adoption from pool assignment,
47
+ * and a lineage row appended after the name was already assigned. */
48
+ source?: "pool" | "identity.assume" | "fork.detected";
48
49
  previous_name?: string;
49
50
  /** Instance this session was forked/branched from (recorded fork lineage).
50
51
  * Stamped only on the row that first assigns this instance, when the adapter
51
52
  * layer detected or supplied a parent conversation. */
52
53
  forked_from?: string;
54
+ /** The adapter declared or inherited-history proved this instance is a fork,
55
+ * even when its parent could not be resolved yet. */
56
+ fork?: true;
57
+ /** Fork detection has run against this instance's transcript; the tool-path
58
+ * heal skips instances carrying it so the scan runs once per instance. */
59
+ fork_checked?: true;
53
60
  }
54
61
  /**
55
62
  * Resolve (name, kind) for an existing owner without consuming a counter
@@ -112,10 +119,43 @@ export declare function readLiveNames(coordRoot: string, opts: {
112
119
  export declare function assignName(coordRoot: string, instanceId: string, kind: NameKind, opts: {
113
120
  freshnessSecs: number;
114
121
  forkedFrom?: string;
122
+ fork?: boolean;
123
+ forkChecked?: boolean;
115
124
  nowMs?: number;
116
125
  }): string;
126
+ export interface ForkState {
127
+ /** True when the instance is a recorded fork, parent known or not. */
128
+ fork: boolean;
129
+ /** Detection already ran for this instance. */
130
+ checked: boolean;
131
+ parent: {
132
+ instance_id: string;
133
+ name: string | null;
134
+ } | null;
135
+ }
136
+ /** Recorded fork state for <instanceId>, or null when it has no history row. */
137
+ export declare function readForkState(coordRoot: string, instanceId: string): ForkState | null;
138
+ /**
139
+ * Record fork detection that ran after the instance was already named (the
140
+ * tool-path heal). Appends a copy of the latest identity row, so the name,
141
+ * kind, and persona are unchanged, plus `fork_checked` and any lineage found.
142
+ * A no-op for an instance with no history row or one already checked.
143
+ */
144
+ export declare function recordForkDetection(coordRoot: string, instanceId: string, detection: {
145
+ fork: boolean;
146
+ forkedFrom?: string;
147
+ }): boolean;
148
+ /**
149
+ * Peer labels for recorded fork relations, keyed by instance id: this
150
+ * session's fork parent and its direct forks. `liveIds` marks which of them
151
+ * are running, so a fork can tell its live parent from its own earlier self.
152
+ */
153
+ export declare function forkRelationLabels(coordRoot: string, instanceId: string, liveIds: Set<string>): Map<string, string>;
154
+ /** Instance ids recorded as direct forks of <instanceId>. */
155
+ export declare function readForkChildren(coordRoot: string, instanceId: string): Set<string>;
117
156
  /** One step of recorded fork lineage: the latest row for <instanceId> that
118
- * carries `forked_from` (latest-row-wins, matching resolveName). */
157
+ * carries `forked_from`. Later rows without lineage (an identity assumption)
158
+ * do not erase it: a session never stops being a fork. */
119
159
  export declare function readForkParent(coordRoot: string, instanceId: string): {
120
160
  instance_id: string;
121
161
  name: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAYH,qBAAqB;AACrB,eAAO,MAAM,WAAW,85EA+Qd,CAAC;AAMX,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAE/E,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;2DAEuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsCD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4B5D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,QAAoB,GACzB,oBAAoB,CAoBtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,GAAG,CAAC,MAAM,CAAC,CA4Bb;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACnE,MAAM,CA4CR;AAED;oEACoE;AACpE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAUrD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAYrD"}
1
+ {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"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"}
@@ -496,19 +496,103 @@ export function assignName(coordRoot, instanceId, kind, opts) {
496
496
  kind,
497
497
  source: "pool",
498
498
  ...(forkedFrom && forkedFrom !== instanceId ? { forked_from: forkedFrom } : {}),
499
+ ...(opts.fork || (forkedFrom && forkedFrom !== instanceId) ? { fork: true } : {}),
500
+ ...(opts.forkChecked ? { fork_checked: true } : {}),
499
501
  ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
500
502
  });
501
503
  return name;
502
504
  }
505
+ /** Recorded fork state for <instanceId>, or null when it has no history row. */
506
+ export function readForkState(coordRoot, instanceId) {
507
+ const history = readHistory(coordRoot);
508
+ let seen = false;
509
+ let fork = false;
510
+ let checked = false;
511
+ let parentId;
512
+ for (const row of history) {
513
+ if (row.instance_id !== instanceId)
514
+ continue;
515
+ seen = true;
516
+ if (row.fork || row.forked_from)
517
+ fork = true;
518
+ if (row.fork_checked)
519
+ checked = true;
520
+ if (row.forked_from && row.forked_from !== instanceId)
521
+ parentId = row.forked_from;
522
+ }
523
+ if (!seen)
524
+ return null;
525
+ const parent = parentId
526
+ ? { instance_id: parentId, name: resolveName(coordRoot, parentId)?.name ?? null }
527
+ : null;
528
+ return { fork, checked, parent };
529
+ }
530
+ /**
531
+ * Record fork detection that ran after the instance was already named (the
532
+ * tool-path heal). Appends a copy of the latest identity row, so the name,
533
+ * kind, and persona are unchanged, plus `fork_checked` and any lineage found.
534
+ * A no-op for an instance with no history row or one already checked.
535
+ */
536
+ export function recordForkDetection(coordRoot, instanceId, detection) {
537
+ const history = readHistory(coordRoot);
538
+ let latest;
539
+ for (const row of history) {
540
+ if (row.instance_id !== instanceId)
541
+ continue;
542
+ if (row.fork_checked)
543
+ return false;
544
+ latest = row;
545
+ }
546
+ if (!latest)
547
+ return false;
548
+ const forkedFrom = detection.forkedFrom && detection.forkedFrom !== instanceId ? detection.forkedFrom : undefined;
549
+ appendHistory(coordRoot, {
550
+ instance_id: instanceId,
551
+ name: latest.name,
552
+ kind: latest.kind,
553
+ ...(latest.agent_id ? { agent_id: latest.agent_id } : {}),
554
+ source: "fork.detected",
555
+ ...(forkedFrom ? { forked_from: forkedFrom } : {}),
556
+ ...(detection.fork || forkedFrom || latest.fork ? { fork: true } : {}),
557
+ fork_checked: true,
558
+ ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
559
+ });
560
+ return true;
561
+ }
562
+ /**
563
+ * Peer labels for recorded fork relations, keyed by instance id: this
564
+ * session's fork parent and its direct forks. `liveIds` marks which of them
565
+ * are running, so a fork can tell its live parent from its own earlier self.
566
+ */
567
+ export function forkRelationLabels(coordRoot, instanceId, liveIds) {
568
+ const out = new Map();
569
+ const parent = readForkParent(coordRoot, instanceId);
570
+ if (parent) {
571
+ out.set(parent.instance_id, liveIds.has(parent.instance_id) ? "your fork parent, live" : "your fork parent");
572
+ }
573
+ for (const child of readForkChildren(coordRoot, instanceId))
574
+ out.set(child, "your fork");
575
+ return out;
576
+ }
577
+ /** Instance ids recorded as direct forks of <instanceId>. */
578
+ export function readForkChildren(coordRoot, instanceId) {
579
+ const out = new Set();
580
+ for (const row of readHistory(coordRoot)) {
581
+ if (row.forked_from === instanceId && row.instance_id !== instanceId)
582
+ out.add(row.instance_id);
583
+ }
584
+ return out;
585
+ }
503
586
  /** One step of recorded fork lineage: the latest row for <instanceId> that
504
- * carries `forked_from` (latest-row-wins, matching resolveName). */
587
+ * carries `forked_from`. Later rows without lineage (an identity assumption)
588
+ * do not erase it: a session never stops being a fork. */
505
589
  export function readForkParent(coordRoot, instanceId) {
506
590
  const history = readHistory(coordRoot);
507
591
  for (let i = history.length - 1; i >= 0; i--) {
508
592
  const row = history[i];
509
- if (row.instance_id !== instanceId)
593
+ if (row.instance_id !== instanceId || !row.forked_from)
510
594
  continue;
511
- if (!row.forked_from)
595
+ if (row.forked_from === instanceId)
512
596
  return null;
513
597
  const parent = resolveName(coordRoot, row.forked_from);
514
598
  return { instance_id: row.forked_from, name: parent?.name ?? null };
@@ -1 +1 @@
1
- {"version":3,"file":"pidmap.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/pidmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAyBH,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAGA;AAwCD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CA8B7D;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,IAAI,CA6BN;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAmBjF;AAED,yFAAyF;AACzF,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoBzF"}
1
+ {"version":3,"file":"pidmap.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/pidmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA2BH,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAGA;AAwCD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CA8B7D;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,IAAI,CAIN;AAsCD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAmBjF;AAED,yFAAyF;AACzF,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoBzF"}
@@ -15,6 +15,8 @@
15
15
  */
16
16
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
17
17
  import { dirname, join } from "node:path";
18
+ import { withGenerationCustodyV3 } from "../../events/v3/generation-custody.js";
19
+ import { liveInstanceIdV3 } from "../../events/v3/live-routing.js";
18
20
  import { checkPidToken, processStartToken } from "./proc-start.js";
19
21
  /**
20
22
  * Row count that triggers a dead-row sweep on the next write.
@@ -110,6 +112,9 @@ export function prunePidmapDeadRows(coordRoot) {
110
112
  return removed;
111
113
  }
112
114
  export function writePidmapRow(coordRoot, pid, instanceId, platform) {
115
+ withGenerationCustodyV3(coordRoot, [liveInstanceIdV3(instanceId)], () => writePidmapRowUnderCustody(coordRoot, pid, instanceId, platform));
116
+ }
117
+ function writePidmapRowUnderCustody(coordRoot, pid, instanceId, platform) {
113
118
  const dir = join(coordRoot, ".harnery", "pid-map");
114
119
  const path = join(dir, String(pid));
115
120
  // Stamped at write time, when the pid provably belongs to the process we
@@ -68,6 +68,24 @@ export declare function psStartToken(pid: number, run?: (pid: number) => {
68
68
  * will not say.
69
69
  */
70
70
  export declare function processStartToken(pid: number): string | null;
71
+ export type ProcessGenerationVerdict = "same" | "different" | "unknown";
72
+ type ReadProcessStartToken = (pid: number) => string | null;
73
+ /** A missing or unreadable token never establishes process identity. */
74
+ export declare function verifyProcessGeneration(pid: number, recordedToken: string | null | undefined, readToken?: ReadProcessStartToken): ProcessGenerationVerdict;
75
+ export interface ProcessGenerationSignalDependencies {
76
+ readToken?: ReadProcessStartToken;
77
+ kill?: (pid: number, signal: NodeJS.Signals | number) => unknown;
78
+ }
79
+ /**
80
+ * Recheck the process generation immediately before sending the signal.
81
+ *
82
+ * The process can still exit and its PID can be reused between the token read
83
+ * and the signal. Eliminating that race requires a generation-bound OS handle.
84
+ */
85
+ export declare function signalIfSameGeneration(pid: number, recordedToken: string | null | undefined, signal: NodeJS.Signals | number, dependencies?: ProcessGenerationSignalDependencies): {
86
+ sent: boolean;
87
+ verdict: ProcessGenerationVerdict;
88
+ };
71
89
  /**
72
90
  * Do two tokens describe the same run?
73
91
  *
@@ -92,4 +110,5 @@ export declare function tokensAgree(recorded: string, current: string): boolean;
92
110
  * treated every row previously: trust it if the pid is alive.
93
111
  */
94
112
  export declare function checkPidToken(pid: number, recorded: string | undefined): "match" | "mismatch" | "unverified";
113
+ export {};
95
114
  //# sourceMappingURL=proc-start.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"proc-start.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/proc-start.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,IAAI,CAAC;AAc9C;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,eAAe,CAOjD;AAyBD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc3D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAiB,GAC7E,MAAM,GAAG,IAAI,CAUf;AAWD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQtE;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,GAAG,UAAU,GAAG,YAAY,CAKrC"}
1
+ {"version":3,"file":"proc-start.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/proc-start.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,IAAI,CAAC;AAc9C;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,eAAe,CAOjD;AAyBD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc3D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAiB,GAC7E,MAAM,GAAG,IAAI,CAUf;AAWD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;AAExE,KAAK,qBAAqB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAE5D,wEAAwE;AACxE,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,SAAS,GAAE,qBAAyC,GACnD,wBAAwB,CAkB1B;AAED,MAAM,WAAW,mCAAmC;IAClD,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,KAAK,OAAO,CAAC;CAClE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,EAC/B,YAAY,GAAE,mCAAwC,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,wBAAwB,CAAA;CAAE,CAUtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQtE;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,GAAG,UAAU,GAAG,YAAY,CAKrC"}