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
@@ -0,0 +1,97 @@
1
+ /** Shared, deliberately small shell lexer for PreToolUse command guards. */
2
+ /** Keep quoted arguments opaque, including examples and heredoc bodies. */
3
+ export function tokenize(source) {
4
+ const out = [];
5
+ const heredocs = [];
6
+ for (let i = 0; i < source.length;) {
7
+ const ch = source[i];
8
+ if (ch === "\\" && source[i + 1] === "\n") {
9
+ i += 2;
10
+ continue;
11
+ }
12
+ if (ch === "\n") {
13
+ out.push({ value: ";", quoted: false, operator: true });
14
+ i++;
15
+ for (const doc of heredocs.splice(0)) {
16
+ while (i < source.length) {
17
+ const end = source.indexOf("\n", i);
18
+ const stop = end === -1 ? source.length : end;
19
+ const line = source.slice(i, stop);
20
+ i = end === -1 ? source.length : end + 1;
21
+ if ((doc.tabs ? line.replace(/^\t+/, "") : line) === doc.end)
22
+ break;
23
+ }
24
+ }
25
+ continue;
26
+ }
27
+ if (/\s/.test(ch)) {
28
+ i++;
29
+ continue;
30
+ }
31
+ if (ch === "#") {
32
+ const end = source.indexOf("\n", i);
33
+ i = end === -1 ? source.length : end;
34
+ continue;
35
+ }
36
+ if (";|&(){}<>".includes(ch)) {
37
+ const op = source.slice(i).match(/^(?:<<-?|>>|&&|\|\||[;|&(){}<>])/)[0];
38
+ out.push({ value: op, quoted: false, operator: true });
39
+ i += op.length;
40
+ continue;
41
+ }
42
+ let value = "";
43
+ let quoted = false;
44
+ while (i < source.length && !/[\s;|&(){}<>]/.test(source[i])) {
45
+ const next = source[i++];
46
+ if (next === "'" || next === '"' || next === "`") {
47
+ quoted = true;
48
+ while (i < source.length && source[i] !== next) {
49
+ if (source[i] === "\\" && next !== "'" && i + 1 < source.length)
50
+ i++;
51
+ value += source[i++];
52
+ }
53
+ if (source[i] === next)
54
+ i++;
55
+ }
56
+ else if (next === "\\" && i < source.length) {
57
+ const escaped = source[i++];
58
+ if (escaped !== "\n")
59
+ value += escaped;
60
+ }
61
+ else {
62
+ value += next;
63
+ }
64
+ }
65
+ const previous = out.at(-1)?.value;
66
+ if (previous === "<<" || previous === "<<-") {
67
+ heredocs.push({ end: value, tabs: previous === "<<-" });
68
+ }
69
+ out.push({ value, quoted });
70
+ }
71
+ return out;
72
+ }
73
+ export function basename(value) {
74
+ return value.split(/[\\/]/).at(-1) ?? value;
75
+ }
76
+ /** The command string of a Bash-style tool call, or null for any other tool. */
77
+ export function shellCommandFromTool(toolName, toolInput) {
78
+ if (typeof toolName !== "string")
79
+ return null;
80
+ const name = toolName.toLowerCase().replace(/^functions\./, "");
81
+ if (!["bash", "shell", "shell_command", "exec_command"].includes(name))
82
+ return null;
83
+ let input = toolInput;
84
+ if (typeof input === "string") {
85
+ try {
86
+ input = JSON.parse(input);
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ if (!input || typeof input !== "object")
93
+ return null;
94
+ const args = input;
95
+ const command = args.command ?? args.cmd;
96
+ return typeof command === "string" ? command : null;
97
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"shell-waiter.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-waiter.ts"],"names":[],"mappings":"AAqJA,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgBtF"}
1
+ {"version":3,"file":"shell-waiter.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-waiter.ts"],"names":[],"mappings":"AA2EA,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAGtF"}
@@ -1,83 +1,10 @@
1
+ import { basename, shellCommandFromTool, tokenize } from "./shell-tokens.js";
1
2
  /** Recognize inline waiters; this is not a proof of arbitrary shell termination. */
2
3
  const REASON = "Inline shell waiter blocked: polling loops and delayed log-only checks can outlive the job " +
3
4
  "and accumulate as background tasks. Read the existing task output now, or wait on its " +
4
5
  "existing task handle. If polling is necessary, use a reviewed script with a hard deadline, " +
5
6
  "nonzero timeout exit, and producer-exit/failure detection. Do not create another waiter " +
6
7
  "to check progress or infer completion from a broad pgrep -f pattern.";
7
- /** Keep quoted arguments opaque, including examples and heredoc bodies. */
8
- function tokenize(source) {
9
- const out = [];
10
- const heredocs = [];
11
- for (let i = 0; i < source.length;) {
12
- const ch = source[i];
13
- if (ch === "\\" && source[i + 1] === "\n") {
14
- i += 2;
15
- continue;
16
- }
17
- if (ch === "\n") {
18
- out.push({ value: ";", quoted: false, operator: true });
19
- i++;
20
- for (const doc of heredocs.splice(0)) {
21
- while (i < source.length) {
22
- const end = source.indexOf("\n", i);
23
- const stop = end === -1 ? source.length : end;
24
- const line = source.slice(i, stop);
25
- i = end === -1 ? source.length : end + 1;
26
- if ((doc.tabs ? line.replace(/^\t+/, "") : line) === doc.end)
27
- break;
28
- }
29
- }
30
- continue;
31
- }
32
- if (/\s/.test(ch)) {
33
- i++;
34
- continue;
35
- }
36
- if (ch === "#") {
37
- const end = source.indexOf("\n", i);
38
- i = end === -1 ? source.length : end;
39
- continue;
40
- }
41
- if (";|&(){}<>".includes(ch)) {
42
- const op = source.slice(i).match(/^(?:<<-?|>>|&&|\|\||[;|&(){}<>])/)[0];
43
- out.push({ value: op, quoted: false, operator: true });
44
- i += op.length;
45
- continue;
46
- }
47
- let value = "";
48
- let quoted = false;
49
- while (i < source.length && !/[\s;|&(){}<>]/.test(source[i])) {
50
- const next = source[i++];
51
- if (next === "'" || next === '"' || next === "`") {
52
- quoted = true;
53
- while (i < source.length && source[i] !== next) {
54
- if (source[i] === "\\" && next !== "'" && i + 1 < source.length)
55
- i++;
56
- value += source[i++];
57
- }
58
- if (source[i] === next)
59
- i++;
60
- }
61
- else if (next === "\\" && i < source.length) {
62
- const escaped = source[i++];
63
- if (escaped !== "\n")
64
- value += escaped;
65
- }
66
- else {
67
- value += next;
68
- }
69
- }
70
- const previous = out.at(-1)?.value;
71
- if (previous === "<<" || previous === "<<-") {
72
- heredocs.push({ end: value, tabs: previous === "<<-" });
73
- }
74
- out.push({ value, quoted });
75
- }
76
- return out;
77
- }
78
- function basename(value) {
79
- return value.split(/[\\/]/).at(-1) ?? value;
80
- }
81
8
  function isWaiter(source, depth = 0) {
82
9
  if (depth > 8)
83
10
  return false;
@@ -151,23 +78,6 @@ function isWaiter(source, depth = 0) {
151
78
  commands.every((name) => harmless.has(name)));
152
79
  }
153
80
  export function shellWaiterReason(toolName, toolInput) {
154
- if (typeof toolName !== "string")
155
- return null;
156
- const name = toolName.toLowerCase().replace(/^functions\./, "");
157
- if (!["bash", "shell", "shell_command", "exec_command"].includes(name))
158
- return null;
159
- let input = toolInput;
160
- if (typeof input === "string") {
161
- try {
162
- input = JSON.parse(input);
163
- }
164
- catch {
165
- return null;
166
- }
167
- }
168
- if (!input || typeof input !== "object")
169
- return null;
170
- const args = input;
171
- const command = args.command ?? args.cmd;
172
- return typeof command === "string" && isWaiter(command) ? REASON : null;
81
+ const command = shellCommandFromTool(toolName, toolInput);
82
+ return command !== null && isWaiter(command) ? REASON : null;
173
83
  }
@@ -17,7 +17,8 @@ export interface HarneryInboxMessageRecordV1 {
17
17
  schema: typeof HARNERY_INBOX_RECORD_SCHEMA;
18
18
  kind: "message";
19
19
  message_id: string;
20
- sender_instance_id: string;
20
+ /** A required null identifies an operator; strings identify real agent instances. */
21
+ sender_instance_id: string | null;
21
22
  sender_display_name: string;
22
23
  recipient_instance_id: string;
23
24
  recipient_display_name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/contract.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAC9E,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG,4BAA4B,CAAC;AAE9F,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,0BAA0B,GAClC,oBAAoB,GACpB,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,CAAC;AAE1B,qBAAa,yBAA0B,SAAQ,KAAK;IAEhD,QAAQ,CAAC,WAAW,EAAE,0BAA0B;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAFb,WAAW,EAAE,0BAA0B,EACvC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM;CAMzB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAC;IAChD,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;IAChC,6BAA6B,EAAE,IAAI,CAAC;CACrC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/contract.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAC9E,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG,4BAA4B,CAAC;AAE9F,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,0BAA0B,GAClC,oBAAoB,GACpB,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,CAAC;AAE1B,qBAAa,yBAA0B,SAAQ,KAAK;IAEhD,QAAQ,CAAC,WAAW,EAAE,0BAA0B;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAFb,WAAW,EAAE,0BAA0B,EACvC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM;CAMzB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAC;IAChD,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;IAChC,6BAA6B,EAAE,IAAI,CAAC;CACrC"}
@@ -1,5 +1,7 @@
1
1
  export * from "./contract.js";
2
2
  export * from "./lifecycle.js";
3
+ export * from "./peer-messages.js";
3
4
  export * from "./service.js";
5
+ export * from "./targets.js";
4
6
  export * from "./watcher.js";
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC"}
@@ -1,4 +1,6 @@
1
1
  export * from "./contract.js";
2
2
  export * from "./lifecycle.js";
3
+ export * from "./peer-messages.js";
3
4
  export * from "./service.js";
5
+ export * from "./targets.js";
4
6
  export * from "./watcher.js";
@@ -0,0 +1,37 @@
1
+ import type { HarneryInboxLimits, HarneryInboxMessageRecordV1 } from "./contract.js";
2
+ import { HarneryInboxService } from "./service.js";
3
+ import { type MessageTargetInput } from "./targets.js";
4
+ /** Preserve the existing ping body/count bounds. Pending messages have no TTL;
5
+ * the lifecycle planner protects them through the idle finalization window. */
6
+ export declare const PEER_INBOX_LIMITS: HarneryInboxLimits;
7
+ export declare function peerInbox(coordRoot: string): HarneryInboxService;
8
+ /** CLI and web use this one path. A receipt proves a synced append, never that
9
+ * a process is awake or that the recipient has noticed the message. */
10
+ export declare function sendPeerMessage(input: {
11
+ coordRoot: string;
12
+ target: MessageTargetInput;
13
+ senderInstanceId: string | null;
14
+ senderName: string;
15
+ body: string;
16
+ }): {
17
+ ok: false;
18
+ code: "no_peer" | "ambiguous_peer" | "not_live" | "authority_unsafe";
19
+ message: string;
20
+ candidates?: string[];
21
+ } | {
22
+ state: "queued" | "written-live";
23
+ message_id: string;
24
+ body_bytes: number;
25
+ synced: true;
26
+ ok: true;
27
+ target: {
28
+ instance_id: string;
29
+ name: string;
30
+ delivery: "written-live" | "queued";
31
+ };
32
+ };
33
+ export declare function formatPeerInbox(coordRoot: string, messages: readonly HarneryInboxMessageRecordV1[]): string;
34
+ /** Wait for every earlier stdout write to finish before acknowledging context.
35
+ * If output fails, the caller throws and leaves the batch pending. */
36
+ export declare function acknowledgeEmittedPeerInbox(coordRoot: string, instanceId: string, messageIds: readonly string[]): Promise<void>;
37
+ //# sourceMappingURL=peer-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-messages.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/peer-messages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,KAAK,kBAAkB,EAAwB,MAAM,cAAc,CAAC;AAI7E;+EAC+E;AAC/E,eAAO,MAAM,iBAAiB,EAAE,kBAY/B,CAAC;AAEF,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAEhE;AAED;uEACuE;AACvE,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;;;;;;;;;;;;;;;;EAWA;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,2BAA2B,EAAE,GAC/C,MAAM,CA4BR;AAED;sEACsE;AACtE,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -0,0 +1,72 @@
1
+ import { resolveBinName } from "../config.js";
2
+ import { HarneryInboxService } from "./service.js";
3
+ import { resolveMessageTarget } from "./targets.js";
4
+ const DAY = 24 * 60 * 60_000;
5
+ /** Preserve the existing ping body/count bounds. Pending messages have no TTL;
6
+ * the lifecycle planner protects them through the idle finalization window. */
7
+ export const PEER_INBOX_LIMITS = {
8
+ max_message_body_bytes: 4_000,
9
+ max_pending_count: 25,
10
+ max_pending_bytes: 100_000,
11
+ max_history_bytes: 1024 * 1024,
12
+ max_history_records: 10_000,
13
+ warning_pressure_ratio: 0.8,
14
+ max_surface_count: 10,
15
+ max_surface_bytes: 16_000,
16
+ max_surface_tokens: 4_000,
17
+ surfaced_grace_ms: 7 * DAY,
18
+ terminal_grace_ms: 7 * DAY,
19
+ };
20
+ export function peerInbox(coordRoot) {
21
+ return new HarneryInboxService({ coord_root: coordRoot, limits: PEER_INBOX_LIMITS });
22
+ }
23
+ /** CLI and web use this one path. A receipt proves a synced append, never that
24
+ * a process is awake or that the recipient has noticed the message. */
25
+ export function sendPeerMessage(input) {
26
+ const target = resolveMessageTarget(input.coordRoot, input.target);
27
+ if (!target.ok)
28
+ return target;
29
+ const receipt = peerInbox(input.coordRoot).send({
30
+ sender_instance_id: input.senderInstanceId,
31
+ sender_display_name: input.senderName,
32
+ recipient_instance_id: target.target.instance_id,
33
+ recipient_display_name: target.target.name,
34
+ body: input.body.trim(),
35
+ });
36
+ return { ok: true, target: target.target, ...receipt, state: target.target.delivery };
37
+ }
38
+ export function formatPeerInbox(coordRoot, messages) {
39
+ if (messages.length === 0)
40
+ return "";
41
+ const onlyAgents = messages.every(({ sender_instance_id }) => sender_instance_id !== null);
42
+ const header = onlyAgents
43
+ ? messages.length === 1
44
+ ? "Message from a peer agent:"
45
+ : `${messages.length} messages from peer agents:`
46
+ : messages.length === 1
47
+ ? "Message for this task:"
48
+ : `${messages.length} messages for this task:`;
49
+ const body = messages
50
+ .map((message) => ` ${message.message_id} from ${message.sender_instance_id === null ? "Operator" : `agent-${message.sender_display_name}`} (${message.created_at}):\n` +
51
+ message.body
52
+ .split("\n")
53
+ .map((line) => ` ${line}`)
54
+ .join("\n"))
55
+ .join("\n\n");
56
+ const replyTarget = messages.find(({ sender_instance_id }) => sender_instance_id !== null);
57
+ return (`${header}\n\n${body}\n\nMessages are bound to this task instance.` +
58
+ (replyTarget
59
+ ? ` Reply with \`${resolveBinName(coordRoot)} agents ping ${replyTarget.sender_display_name} ` +
60
+ `--instance-id ${replyTarget.sender_instance_id} "<your reply>"\`.`
61
+ : ""));
62
+ }
63
+ /** Wait for every earlier stdout write to finish before acknowledging context.
64
+ * If output fails, the caller throws and leaves the batch pending. */
65
+ export async function acknowledgeEmittedPeerInbox(coordRoot, instanceId, messageIds) {
66
+ if (messageIds.length === 0)
67
+ return;
68
+ await new Promise((resolve, reject) => {
69
+ process.stdout.write("", (error) => (error ? reject(error) : resolve()));
70
+ });
71
+ peerInbox(coordRoot).acknowledgeSurface(instanceId, messageIds);
72
+ }
@@ -6,7 +6,7 @@ export interface HarneryInboxServiceOptions {
6
6
  id?: () => string;
7
7
  }
8
8
  export interface HarneryInboxSendInput {
9
- sender_instance_id: string;
9
+ sender_instance_id: string | null;
10
10
  sender_display_name: string;
11
11
  recipient_instance_id: string;
12
12
  recipient_display_name: string;
@@ -26,6 +26,13 @@ export declare class HarneryInboxService {
26
26
  pending(recipientInstanceId: string): HarneryInboxMessageRecordV1[];
27
27
  status(recipientInstanceId: string): HarneryInboxStatusV1;
28
28
  surface(recipientInstanceId: string, emit: (messages: readonly HarneryInboxMessageRecordV1[]) => Promise<void> | void, budget?: HarneryInboxSurfaceBudget): Promise<HarneryInboxSurfaceResult>;
29
+ /** Preparing prompt text is read-only. A failed or abandoned emission must
30
+ * leave the same messages pending for the recipient's next prompt. */
31
+ prepareSurface(recipientInstanceId: string, budget?: HarneryInboxSurfaceBudget): HarneryInboxMessageRecordV1[];
32
+ /** Call only after the selected IDs have actually been emitted. A message
33
+ * appended while output is in flight remains pending rather than being
34
+ * included in an acknowledgement of an older batch. */
35
+ acknowledgeSurface(recipientInstanceId: string, messageIds: readonly string[]): void;
29
36
  }
30
37
  export declare function inboxPath(coordRoot: string, recipientInstanceId: string): string;
31
38
  export declare function project(records: readonly HarneryInboxRecordV1[]): {
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/service.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,QAAQ,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,IAAI,CAAC;CACd;AAED,qBAAa,mBAAmB;;gBAGlB,OAAO,EAAE,0BAA0B;IAK/C,IAAI,CAAC,KAAK,EAAE,qBAAqB,GAAG,uBAAuB;IA2C3D,OAAO,CAAC,mBAAmB,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAO5D,OAAO,CAAC,mBAAmB,EAAE,MAAM,GAAG,2BAA2B,EAAE;IAInE,MAAM,CAAC,mBAAmB,EAAE,MAAM,GAAG,oBAAoB;IA0BnD,OAAO,CACX,mBAAmB,EAAE,MAAM,EAC3B,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS,2BAA2B,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAChF,MAAM,GAAE,yBAIP,GACA,OAAO,CAAC,yBAAyB,CAAC;CAoCtC;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,SAAS,oBAAoB,EAAE,GAAG;IACjE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACvC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAsBA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/service.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,QAAQ,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,IAAI,CAAC;CACd;AAED,qBAAa,mBAAmB;;gBAGlB,OAAO,EAAE,0BAA0B;IAK/C,IAAI,CAAC,KAAK,EAAE,qBAAqB,GAAG,uBAAuB;IA2C3D,OAAO,CAAC,mBAAmB,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAS5D,OAAO,CAAC,mBAAmB,EAAE,MAAM,GAAG,2BAA2B,EAAE;IAInE,MAAM,CAAC,mBAAmB,EAAE,MAAM,GAAG,oBAAoB;IA0BnD,OAAO,CACX,mBAAmB,EAAE,MAAM,EAC3B,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS,2BAA2B,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAChF,MAAM,GAAE,yBAIP,GACA,OAAO,CAAC,yBAAyB,CAAC;IAiBrC;0EACsE;IACtE,cAAc,CACZ,mBAAmB,EAAE,MAAM,EAC3B,MAAM,GAAE,yBAIP,GACA,2BAA2B,EAAE;IAKhC;;2DAEuD;IACvD,kBAAkB,CAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;CA2BrF;AA4BD,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,SAAS,oBAAoB,EAAE,GAAG;IACjE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACvC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAsBA"}
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync, statSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
- import { readCrashSafeJsonlFile, updateCrashSafeJsonlFile } from "../storage/durable-history.js";
4
+ import { readCrashSafeJsonlFile, updateCrashSafeJsonlFile, } from "../storage/durable-history.js";
5
5
  import { HARNERY_INBOX_RECORD_SCHEMA, HARNERY_INBOX_STATUS_SCHEMA, HarneryInboxCapacityError, } from "./contract.js";
6
6
  export class HarneryInboxService {
7
7
  #options;
@@ -20,7 +20,7 @@ export class HarneryInboxService {
20
20
  schema: HARNERY_INBOX_RECORD_SCHEMA,
21
21
  kind: "message",
22
22
  message_id: `msg_${this.#options.id?.() ?? randomUUID()}`,
23
- sender_instance_id: opaqueId(input.sender_instance_id, "sender instance"),
23
+ sender_instance_id: senderId(input.sender_instance_id),
24
24
  sender_display_name: boundedDisplay(input.sender_display_name),
25
25
  recipient_instance_id: recipient,
26
26
  recipient_display_name: boundedDisplay(input.recipient_display_name),
@@ -29,7 +29,7 @@ export class HarneryInboxService {
29
29
  body_bytes: bodyBytes,
30
30
  };
31
31
  const path = inboxPath(this.#options.coord_root, recipient);
32
- return updateCrashSafeJsonlFile(path, readOptions(limits), (records) => {
32
+ return updateInboxHistory(path, readOptions(limits), (records) => {
33
33
  const projection = project(records);
34
34
  capacityCheck(projection, record, path, limits);
35
35
  return {
@@ -44,7 +44,10 @@ export class HarneryInboxService {
44
44
  });
45
45
  }
46
46
  records(recipientInstanceId) {
47
- return readCrashSafeJsonlFile(inboxPath(this.#options.coord_root, opaqueId(recipientInstanceId, "recipient instance")), readOptions(this.#options.limits));
47
+ const records = readCrashSafeJsonlFile(inboxPath(this.#options.coord_root, opaqueId(recipientInstanceId, "recipient instance")), readOptions(this.#options.limits));
48
+ for (const record of records)
49
+ validateRecord(record);
50
+ return records;
48
51
  }
49
52
  pending(recipientInstanceId) {
50
53
  return project(this.records(recipientInstanceId)).pending;
@@ -75,18 +78,42 @@ export class HarneryInboxService {
75
78
  max_bytes: this.#options.limits.max_surface_bytes,
76
79
  max_tokens: this.#options.limits.max_surface_tokens,
77
80
  }) {
78
- validateSurfaceBudget(budget);
79
81
  const recipient = opaqueId(recipientInstanceId, "recipient instance");
80
- const selected = selectOldestFitting(this.pending(recipient), budget);
82
+ const selected = this.prepareSurface(recipient, budget);
81
83
  await emit(selected);
82
- if (selected.length > 0) {
84
+ this.acknowledgeSurface(recipient, selected.map(({ message_id }) => message_id));
85
+ const remaining = this.pending(recipient);
86
+ return {
87
+ emitted: selected,
88
+ remaining_pending_count: remaining.length,
89
+ remaining_pending_bytes: remaining.reduce((sum, record) => sum + record.body_bytes, 0),
90
+ repeated_after_crash_possible: true,
91
+ };
92
+ }
93
+ /** Preparing prompt text is read-only. A failed or abandoned emission must
94
+ * leave the same messages pending for the recipient's next prompt. */
95
+ prepareSurface(recipientInstanceId, budget = {
96
+ max_count: this.#options.limits.max_surface_count,
97
+ max_bytes: this.#options.limits.max_surface_bytes,
98
+ max_tokens: this.#options.limits.max_surface_tokens,
99
+ }) {
100
+ validateSurfaceBudget(budget);
101
+ return selectOldestFitting(this.pending(recipientInstanceId), budget);
102
+ }
103
+ /** Call only after the selected IDs have actually been emitted. A message
104
+ * appended while output is in flight remains pending rather than being
105
+ * included in an acknowledgement of an older batch. */
106
+ acknowledgeSurface(recipientInstanceId, messageIds) {
107
+ const recipient = opaqueId(recipientInstanceId, "recipient instance");
108
+ const selected = new Set(messageIds);
109
+ if (selected.size > 0) {
83
110
  const surfacedAt = now(this.#options).toISOString();
84
- updateCrashSafeJsonlFile(inboxPath(this.#options.coord_root, recipient), readOptions(this.#options.limits), (records) => {
111
+ updateInboxHistory(inboxPath(this.#options.coord_root, recipient), readOptions(this.#options.limits), (records) => {
85
112
  const stillPending = new Set(project(records).pending.map(({ message_id }) => message_id));
86
113
  return {
87
- append: selected
88
- .filter(({ message_id }) => stillPending.has(message_id))
89
- .map(({ message_id }) => ({
114
+ append: [...selected]
115
+ .filter((message_id) => stillPending.has(message_id))
116
+ .map((message_id) => ({
90
117
  schema: HARNERY_INBOX_RECORD_SCHEMA,
91
118
  kind: "surfaced",
92
119
  message_id,
@@ -96,13 +123,26 @@ export class HarneryInboxService {
96
123
  };
97
124
  });
98
125
  }
99
- const remaining = this.pending(recipient);
100
- return {
101
- emitted: selected,
102
- remaining_pending_count: remaining.length,
103
- remaining_pending_bytes: remaining.reduce((sum, record) => sum + record.body_bytes, 0),
104
- repeated_after_crash_possible: true,
105
- };
126
+ }
127
+ }
128
+ /** The shared writer refuses an occupied lease before invoking the update.
129
+ * Briefly retry only that exact refusal so concurrent peer senders both get
130
+ * a chance to append. All validation, authority and I/O failures still fail
131
+ * immediately, and an occupied live lease is never removed or overridden. */
132
+ function updateInboxHistory(path, options, update) {
133
+ const deadline = performance.now() + 2_000;
134
+ const pause = new Int32Array(new SharedArrayBuffer(4));
135
+ while (true) {
136
+ try {
137
+ return updateCrashSafeJsonlFile(path, options, update);
138
+ }
139
+ catch (error) {
140
+ if (!(error instanceof Error) ||
141
+ error.message !== `durable history lease busy: ${path}.lease` ||
142
+ performance.now() >= deadline)
143
+ throw error;
144
+ Atomics.wait(pause, 0, 0, Math.min(10, deadline - performance.now()));
145
+ }
106
146
  }
107
147
  }
108
148
  export function inboxPath(coordRoot, recipientInstanceId) {
@@ -174,10 +214,13 @@ function now(options) {
174
214
  return options.now?.() ?? new Date();
175
215
  }
176
216
  function opaqueId(value, field) {
177
- if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(value))
217
+ if (typeof value !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(value))
178
218
  throw new Error(`invalid ${field} id`);
179
219
  return value;
180
220
  }
221
+ function senderId(value) {
222
+ return value === null ? null : opaqueId(value, "sender instance");
223
+ }
181
224
  function boundedDisplay(value) {
182
225
  const result = value.trim();
183
226
  if (result.length === 0 || Buffer.byteLength(result) > 256)
@@ -187,8 +230,11 @@ function boundedDisplay(value) {
187
230
  function validateRecord(record) {
188
231
  if (record.schema !== HARNERY_INBOX_RECORD_SCHEMA)
189
232
  throw new Error("unknown inbox record schema");
190
- if (record.kind === "message" && Buffer.byteLength(record.body) !== record.body_bytes) {
191
- throw new Error(`inbox message ${record.message_id} has invalid body_bytes`);
233
+ if (record.kind === "message") {
234
+ senderId(record.sender_instance_id);
235
+ if (Buffer.byteLength(record.body) !== record.body_bytes) {
236
+ throw new Error(`inbox message ${record.message_id} has invalid body_bytes`);
237
+ }
192
238
  }
193
239
  }
194
240
  function validateLimits(limits) {
@@ -0,0 +1,22 @@
1
+ export interface MessageTargetInput {
2
+ name?: string;
3
+ instanceId?: string;
4
+ requireLive?: boolean;
5
+ now?: Date;
6
+ }
7
+ export type MessageTargetResult = {
8
+ ok: true;
9
+ target: {
10
+ instance_id: string;
11
+ name: string;
12
+ delivery: "written-live" | "queued";
13
+ };
14
+ } | {
15
+ ok: false;
16
+ code: "no_peer" | "ambiguous_peer" | "not_live" | "authority_unsafe";
17
+ message: string;
18
+ candidates?: string[];
19
+ };
20
+ /** Resolve one immutable recipient without extending presence or claiming attention. */
21
+ export declare function resolveMessageTarget(coordRoot: string, input: MessageTargetInput): MessageTargetResult;
22
+ //# sourceMappingURL=targets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"targets.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/targets.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,mBAAmB,GAC3B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,cAAc,GAAG,QAAQ,CAAA;KAAE,CAAC;CACpF,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,SAAS,GAAG,gBAAgB,GAAG,UAAU,GAAG,kBAAkB,CAAC;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEN,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,kBAAkB,GACxB,mBAAmB,CAwFrB"}