@geminixiang/mikan 1.0.0-beta.2 → 1.0.0-beta.21

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 (497) hide show
  1. package/CHANGELOG.md +388 -0
  2. package/README.md +49 -16
  3. package/dist/adapter.d.ts +1 -1
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js.map +1 -1
  6. package/dist/adapters/buffered-responder.d.ts +60 -0
  7. package/dist/adapters/buffered-responder.d.ts.map +1 -0
  8. package/dist/adapters/buffered-responder.js +201 -0
  9. package/dist/adapters/buffered-responder.js.map +1 -0
  10. package/dist/adapters/discord/bot.d.ts +1 -0
  11. package/dist/adapters/discord/bot.d.ts.map +1 -1
  12. package/dist/adapters/discord/bot.js +36 -67
  13. package/dist/adapters/discord/bot.js.map +1 -1
  14. package/dist/adapters/discord/context.d.ts.map +1 -1
  15. package/dist/adapters/discord/context.js +44 -204
  16. package/dist/adapters/discord/context.js.map +1 -1
  17. package/dist/adapters/github/bot.d.ts +86 -0
  18. package/dist/adapters/github/bot.d.ts.map +1 -0
  19. package/dist/adapters/github/bot.js +640 -0
  20. package/dist/adapters/github/bot.js.map +1 -0
  21. package/dist/adapters/github/client.d.ts +122 -0
  22. package/dist/adapters/github/client.d.ts.map +1 -0
  23. package/dist/adapters/github/client.js +314 -0
  24. package/dist/adapters/github/client.js.map +1 -0
  25. package/dist/adapters/github/cloudbuild.d.ts +24 -0
  26. package/dist/adapters/github/cloudbuild.d.ts.map +1 -0
  27. package/dist/adapters/github/cloudbuild.js +71 -0
  28. package/dist/adapters/github/cloudbuild.js.map +1 -0
  29. package/dist/adapters/github/context.d.ts +9 -0
  30. package/dist/adapters/github/context.d.ts.map +1 -0
  31. package/dist/adapters/github/context.js +103 -0
  32. package/dist/adapters/github/context.js.map +1 -0
  33. package/dist/adapters/github/gcp-auth.d.ts +31 -0
  34. package/dist/adapters/github/gcp-auth.d.ts.map +1 -0
  35. package/dist/adapters/github/gcp-auth.js +196 -0
  36. package/dist/adapters/github/gcp-auth.js.map +1 -0
  37. package/dist/adapters/github/github-ops.d.ts +90 -0
  38. package/dist/adapters/github/github-ops.d.ts.map +1 -0
  39. package/dist/adapters/github/github-ops.js +405 -0
  40. package/dist/adapters/github/github-ops.js.map +1 -0
  41. package/dist/adapters/github/ids.d.ts +34 -0
  42. package/dist/adapters/github/ids.d.ts.map +1 -0
  43. package/dist/adapters/github/ids.js +53 -0
  44. package/dist/adapters/github/ids.js.map +1 -0
  45. package/dist/adapters/github/repo.d.ts +32 -0
  46. package/dist/adapters/github/repo.d.ts.map +1 -0
  47. package/dist/adapters/github/repo.js +141 -0
  48. package/dist/adapters/github/repo.js.map +1 -0
  49. package/dist/adapters/github/tool-pack.d.ts +14 -0
  50. package/dist/adapters/github/tool-pack.d.ts.map +1 -0
  51. package/dist/adapters/github/tool-pack.js +56 -0
  52. package/dist/adapters/github/tool-pack.js.map +1 -0
  53. package/dist/adapters/github/tools/checks.d.ts +24 -0
  54. package/dist/adapters/github/tools/checks.d.ts.map +1 -0
  55. package/dist/adapters/github/tools/checks.js +105 -0
  56. package/dist/adapters/github/tools/checks.js.map +1 -0
  57. package/dist/adapters/github/tools/issue.d.ts +22 -0
  58. package/dist/adapters/github/tools/issue.d.ts.map +1 -0
  59. package/dist/adapters/github/tools/issue.js +48 -0
  60. package/dist/adapters/github/tools/issue.js.map +1 -0
  61. package/dist/adapters/github/tools/pr.d.ts +22 -0
  62. package/dist/adapters/github/tools/pr.d.ts.map +1 -0
  63. package/dist/adapters/github/tools/pr.js +48 -0
  64. package/dist/adapters/github/tools/pr.js.map +1 -0
  65. package/dist/adapters/github/tools/read.d.ts +22 -0
  66. package/dist/adapters/github/tools/read.d.ts.map +1 -0
  67. package/dist/adapters/github/tools/read.js +141 -0
  68. package/dist/adapters/github/tools/read.js.map +1 -0
  69. package/dist/adapters/github/tools/review-reply.d.ts +19 -0
  70. package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
  71. package/dist/adapters/github/tools/review-reply.js +38 -0
  72. package/dist/adapters/github/tools/review-reply.js.map +1 -0
  73. package/dist/adapters/github/tools/sync.d.ts +17 -0
  74. package/dist/adapters/github/tools/sync.d.ts.map +1 -0
  75. package/dist/adapters/github/tools/sync.js +34 -0
  76. package/dist/adapters/github/tools/sync.js.map +1 -0
  77. package/dist/adapters/github/types.d.ts +350 -0
  78. package/dist/adapters/github/types.d.ts.map +1 -0
  79. package/dist/adapters/github/types.js +2 -0
  80. package/dist/adapters/github/types.js.map +1 -0
  81. package/dist/adapters/intake.d.ts +16 -5
  82. package/dist/adapters/intake.d.ts.map +1 -1
  83. package/dist/adapters/intake.js +79 -22
  84. package/dist/adapters/intake.js.map +1 -1
  85. package/dist/adapters/slack/bot.d.ts +8 -6
  86. package/dist/adapters/slack/bot.d.ts.map +1 -1
  87. package/dist/adapters/slack/bot.js +82 -134
  88. package/dist/adapters/slack/bot.js.map +1 -1
  89. package/dist/adapters/slack/context.d.ts.map +1 -1
  90. package/dist/adapters/slack/context.js +3 -14
  91. package/dist/adapters/slack/context.js.map +1 -1
  92. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  93. package/dist/adapters/slack/response-lifecycle.js +14 -17
  94. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  95. package/dist/adapters/slack/session.d.ts.map +1 -1
  96. package/dist/adapters/slack/session.js +5 -8
  97. package/dist/adapters/slack/session.js.map +1 -1
  98. package/dist/adapters/streaming.d.ts +4 -0
  99. package/dist/adapters/streaming.d.ts.map +1 -1
  100. package/dist/adapters/streaming.js +11 -0
  101. package/dist/adapters/streaming.js.map +1 -1
  102. package/dist/adapters/telegram/bot.d.ts +1 -2
  103. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  104. package/dist/adapters/telegram/bot.js +27 -62
  105. package/dist/adapters/telegram/bot.js.map +1 -1
  106. package/dist/adapters/telegram/context.d.ts.map +1 -1
  107. package/dist/adapters/telegram/context.js +37 -174
  108. package/dist/adapters/telegram/context.js.map +1 -1
  109. package/dist/adapters/types.d.ts +28 -2
  110. package/dist/adapters/types.d.ts.map +1 -1
  111. package/dist/adapters/types.js.map +1 -1
  112. package/dist/agent.d.ts +37 -14
  113. package/dist/agent.d.ts.map +1 -1
  114. package/dist/agent.js +359 -229
  115. package/dist/agent.js.map +1 -1
  116. package/dist/cli/arg-grammar.d.ts +25 -0
  117. package/dist/cli/arg-grammar.d.ts.map +1 -0
  118. package/dist/cli/arg-grammar.js +59 -0
  119. package/dist/cli/arg-grammar.js.map +1 -0
  120. package/dist/cli/boot.d.ts +16 -0
  121. package/dist/cli/boot.d.ts.map +1 -0
  122. package/dist/cli/boot.js +119 -0
  123. package/dist/cli/boot.js.map +1 -0
  124. package/dist/cli/download.d.ts.map +1 -0
  125. package/dist/cli/download.js.map +1 -0
  126. package/dist/cli/ext-git.d.ts +22 -0
  127. package/dist/cli/ext-git.d.ts.map +1 -0
  128. package/dist/cli/ext-git.js +76 -0
  129. package/dist/cli/ext-git.js.map +1 -0
  130. package/dist/cli/ext.d.ts +2 -0
  131. package/dist/cli/ext.d.ts.map +1 -0
  132. package/dist/cli/ext.js +215 -0
  133. package/dist/cli/ext.js.map +1 -0
  134. package/dist/commands/admin.d.ts.map +1 -1
  135. package/dist/commands/admin.js +2 -1
  136. package/dist/commands/admin.js.map +1 -1
  137. package/dist/commands/auto-reply.d.ts.map +1 -1
  138. package/dist/commands/auto-reply.js +2 -6
  139. package/dist/commands/auto-reply.js.map +1 -1
  140. package/dist/commands/extensions.d.ts.map +1 -1
  141. package/dist/commands/extensions.js +18 -8
  142. package/dist/commands/extensions.js.map +1 -1
  143. package/dist/commands/login.d.ts +2 -1
  144. package/dist/commands/login.d.ts.map +1 -1
  145. package/dist/commands/login.js +35 -3
  146. package/dist/commands/login.js.map +1 -1
  147. package/dist/commands/manifest.d.ts +72 -0
  148. package/dist/commands/manifest.d.ts.map +1 -0
  149. package/dist/commands/manifest.js +121 -0
  150. package/dist/commands/manifest.js.map +1 -0
  151. package/dist/commands/model.d.ts.map +1 -1
  152. package/dist/commands/model.js +11 -9
  153. package/dist/commands/model.js.map +1 -1
  154. package/dist/commands/new.d.ts.map +1 -1
  155. package/dist/commands/new.js +2 -1
  156. package/dist/commands/new.js.map +1 -1
  157. package/dist/commands/sandbox.d.ts.map +1 -1
  158. package/dist/commands/sandbox.js +23 -17
  159. package/dist/commands/sandbox.js.map +1 -1
  160. package/dist/commands/text.d.ts +2 -0
  161. package/dist/commands/text.d.ts.map +1 -0
  162. package/dist/commands/text.js +16 -0
  163. package/dist/commands/text.js.map +1 -0
  164. package/dist/commands/types.d.ts +14 -1
  165. package/dist/commands/types.d.ts.map +1 -1
  166. package/dist/commands/types.js.map +1 -1
  167. package/dist/config.d.ts +1 -3
  168. package/dist/config.d.ts.map +1 -1
  169. package/dist/config.js +98 -95
  170. package/dist/config.js.map +1 -1
  171. package/dist/env-manifest.d.ts +35 -0
  172. package/dist/env-manifest.d.ts.map +1 -0
  173. package/dist/env-manifest.js +225 -0
  174. package/dist/env-manifest.js.map +1 -0
  175. package/dist/events.d.ts.map +1 -1
  176. package/dist/events.js +9 -73
  177. package/dist/events.js.map +1 -1
  178. package/dist/execution-resolver.d.ts +4 -2
  179. package/dist/execution-resolver.d.ts.map +1 -1
  180. package/dist/execution-resolver.js +54 -41
  181. package/dist/execution-resolver.js.map +1 -1
  182. package/dist/harness/event-format.d.ts +66 -0
  183. package/dist/harness/event-format.d.ts.map +1 -0
  184. package/dist/harness/event-format.js +112 -0
  185. package/dist/harness/event-format.js.map +1 -0
  186. package/dist/harness/extensions/loader.d.ts +44 -11
  187. package/dist/harness/extensions/loader.d.ts.map +1 -1
  188. package/dist/harness/extensions/loader.js +273 -84
  189. package/dist/harness/extensions/loader.js.map +1 -1
  190. package/dist/harness/extensions/registry.d.ts +48 -1
  191. package/dist/harness/extensions/registry.d.ts.map +1 -1
  192. package/dist/harness/extensions/registry.js +146 -0
  193. package/dist/harness/extensions/registry.js.map +1 -1
  194. package/dist/harness/extensions/types.d.ts +165 -22
  195. package/dist/harness/extensions/types.d.ts.map +1 -1
  196. package/dist/harness/extensions/types.js.map +1 -1
  197. package/dist/harness/index.d.ts +6 -5
  198. package/dist/harness/index.d.ts.map +1 -1
  199. package/dist/harness/index.js +3 -2
  200. package/dist/harness/index.js.map +1 -1
  201. package/dist/harness/models.d.ts +5 -0
  202. package/dist/harness/models.d.ts.map +1 -1
  203. package/dist/harness/models.js +10 -0
  204. package/dist/harness/models.js.map +1 -1
  205. package/dist/harness/runner.d.ts +37 -2
  206. package/dist/harness/runner.d.ts.map +1 -1
  207. package/dist/harness/runner.js +107 -23
  208. package/dist/harness/runner.js.map +1 -1
  209. package/dist/harness/session-store.d.ts +4 -1
  210. package/dist/harness/session-store.d.ts.map +1 -1
  211. package/dist/harness/session-store.js +11 -2
  212. package/dist/harness/session-store.js.map +1 -1
  213. package/dist/harness/subagent-runner.d.ts +34 -0
  214. package/dist/harness/subagent-runner.d.ts.map +1 -0
  215. package/dist/harness/subagent-runner.js +339 -0
  216. package/dist/harness/subagent-runner.js.map +1 -0
  217. package/dist/harness/types.d.ts +53 -0
  218. package/dist/harness/types.d.ts.map +1 -1
  219. package/dist/harness/types.js.map +1 -1
  220. package/dist/index.d.ts +3 -3
  221. package/dist/index.d.ts.map +1 -1
  222. package/dist/index.js +2 -2
  223. package/dist/index.js.map +1 -1
  224. package/dist/main.js +260 -109
  225. package/dist/main.js.map +1 -1
  226. package/dist/observability/instrument.js +6 -4
  227. package/dist/observability/instrument.js.map +1 -1
  228. package/dist/observability/sentry.d.ts.map +1 -1
  229. package/dist/observability/sentry.js +3 -2
  230. package/dist/observability/sentry.js.map +1 -1
  231. package/dist/provisioner.d.ts.map +1 -1
  232. package/dist/provisioner.js +2 -5
  233. package/dist/provisioner.js.map +1 -1
  234. package/dist/runtime/conversation-runtime.d.ts +1 -1
  235. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  236. package/dist/runtime/conversation-runtime.js +276 -43
  237. package/dist/runtime/conversation-runtime.js.map +1 -1
  238. package/dist/runtime/types.d.ts +36 -5
  239. package/dist/runtime/types.d.ts.map +1 -1
  240. package/dist/runtime/types.js.map +1 -1
  241. package/dist/sandbox/cloudflare.d.ts +3 -0
  242. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  243. package/dist/sandbox/cloudflare.js +10 -0
  244. package/dist/sandbox/cloudflare.js.map +1 -1
  245. package/dist/sandbox/container.d.ts +4 -0
  246. package/dist/sandbox/container.d.ts.map +1 -1
  247. package/dist/sandbox/container.js +11 -2
  248. package/dist/sandbox/container.js.map +1 -1
  249. package/dist/sandbox/firecracker.d.ts +3 -0
  250. package/dist/sandbox/firecracker.d.ts.map +1 -1
  251. package/dist/sandbox/firecracker.js +10 -1
  252. package/dist/sandbox/firecracker.js.map +1 -1
  253. package/dist/sandbox/gondolin-bootstrap.d.ts +26 -0
  254. package/dist/sandbox/gondolin-bootstrap.d.ts.map +1 -0
  255. package/dist/sandbox/gondolin-bootstrap.js +27 -0
  256. package/dist/sandbox/gondolin-bootstrap.js.map +1 -0
  257. package/dist/sandbox/gondolin-contract.d.ts +125 -0
  258. package/dist/sandbox/gondolin-contract.d.ts.map +1 -0
  259. package/dist/sandbox/gondolin-contract.js +155 -0
  260. package/dist/sandbox/gondolin-contract.js.map +1 -0
  261. package/dist/sandbox/gondolin-fleet.d.ts +104 -0
  262. package/dist/sandbox/gondolin-fleet.d.ts.map +1 -0
  263. package/dist/sandbox/gondolin-fleet.js +330 -0
  264. package/dist/sandbox/gondolin-fleet.js.map +1 -0
  265. package/dist/sandbox/gondolin-gateway.d.ts +86 -0
  266. package/dist/sandbox/gondolin-gateway.d.ts.map +1 -0
  267. package/dist/sandbox/gondolin-gateway.js +407 -0
  268. package/dist/sandbox/gondolin-gateway.js.map +1 -0
  269. package/dist/sandbox/gondolin-inventory.d.ts +75 -0
  270. package/dist/sandbox/gondolin-inventory.d.ts.map +1 -0
  271. package/dist/sandbox/gondolin-inventory.js +257 -0
  272. package/dist/sandbox/gondolin-inventory.js.map +1 -0
  273. package/dist/sandbox/gondolin-join.d.ts +62 -0
  274. package/dist/sandbox/gondolin-join.d.ts.map +1 -0
  275. package/dist/sandbox/gondolin-join.js +261 -0
  276. package/dist/sandbox/gondolin-join.js.map +1 -0
  277. package/dist/sandbox/gondolin-nfs-advisory.d.ts +15 -0
  278. package/dist/sandbox/gondolin-nfs-advisory.d.ts.map +1 -0
  279. package/dist/sandbox/gondolin-nfs-advisory.js +60 -0
  280. package/dist/sandbox/gondolin-nfs-advisory.js.map +1 -0
  281. package/dist/sandbox/gondolin-placement.d.ts +30 -0
  282. package/dist/sandbox/gondolin-placement.d.ts.map +1 -0
  283. package/dist/sandbox/gondolin-placement.js +77 -0
  284. package/dist/sandbox/gondolin-placement.js.map +1 -0
  285. package/dist/sandbox/gondolin-recovery.d.ts +33 -0
  286. package/dist/sandbox/gondolin-recovery.d.ts.map +1 -0
  287. package/dist/sandbox/gondolin-recovery.js +39 -0
  288. package/dist/sandbox/gondolin-recovery.js.map +1 -0
  289. package/dist/sandbox/gondolin-remote.d.ts +70 -0
  290. package/dist/sandbox/gondolin-remote.d.ts.map +1 -0
  291. package/dist/sandbox/gondolin-remote.js +325 -0
  292. package/dist/sandbox/gondolin-remote.js.map +1 -0
  293. package/dist/sandbox/gondolin-worker-client.d.ts +135 -0
  294. package/dist/sandbox/gondolin-worker-client.d.ts.map +1 -0
  295. package/dist/sandbox/gondolin-worker-client.js +323 -0
  296. package/dist/sandbox/gondolin-worker-client.js.map +1 -0
  297. package/dist/sandbox/gondolin-worker-main.d.ts +2 -0
  298. package/dist/sandbox/gondolin-worker-main.d.ts.map +1 -0
  299. package/dist/sandbox/gondolin-worker-main.js +22 -0
  300. package/dist/sandbox/gondolin-worker-main.js.map +1 -0
  301. package/dist/sandbox/gondolin-worker.d.ts +17 -0
  302. package/dist/sandbox/gondolin-worker.d.ts.map +1 -0
  303. package/dist/sandbox/gondolin-worker.js +201 -0
  304. package/dist/sandbox/gondolin-worker.js.map +1 -0
  305. package/dist/sandbox/gondolin.d.ts +53 -0
  306. package/dist/sandbox/gondolin.d.ts.map +1 -0
  307. package/dist/sandbox/gondolin.js +429 -0
  308. package/dist/sandbox/gondolin.js.map +1 -0
  309. package/dist/sandbox/host.d.ts +3 -0
  310. package/dist/sandbox/host.d.ts.map +1 -1
  311. package/dist/sandbox/host.js +14 -0
  312. package/dist/sandbox/host.js.map +1 -1
  313. package/dist/sandbox/identity.d.ts +21 -0
  314. package/dist/sandbox/identity.d.ts.map +1 -0
  315. package/dist/sandbox/identity.js +36 -0
  316. package/dist/sandbox/identity.js.map +1 -0
  317. package/dist/sandbox/index.d.ts +13 -4
  318. package/dist/sandbox/index.d.ts.map +1 -1
  319. package/dist/sandbox/index.js +19 -3
  320. package/dist/sandbox/index.js.map +1 -1
  321. package/dist/sandbox/types.d.ts +31 -1
  322. package/dist/sandbox/types.d.ts.map +1 -1
  323. package/dist/sandbox/types.js.map +1 -1
  324. package/dist/sandbox/utils.d.ts +12 -0
  325. package/dist/sandbox/utils.d.ts.map +1 -1
  326. package/dist/sandbox/utils.js +38 -0
  327. package/dist/sandbox/utils.js.map +1 -1
  328. package/dist/sessions/chat-history-sync.d.ts +32 -0
  329. package/dist/sessions/chat-history-sync.d.ts.map +1 -0
  330. package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +40 -113
  331. package/dist/sessions/chat-history-sync.js.map +1 -0
  332. package/dist/sessions/conversation-log.d.ts +13 -0
  333. package/dist/sessions/conversation-log.d.ts.map +1 -0
  334. package/dist/sessions/conversation-log.js +47 -0
  335. package/dist/sessions/conversation-log.js.map +1 -0
  336. package/dist/sessions/history-line.d.ts +20 -0
  337. package/dist/sessions/history-line.d.ts.map +1 -0
  338. package/dist/sessions/history-line.js +24 -0
  339. package/dist/sessions/history-line.js.map +1 -0
  340. package/dist/sessions/policy.d.ts.map +1 -1
  341. package/dist/sessions/policy.js +5 -3
  342. package/dist/sessions/policy.js.map +1 -1
  343. package/dist/sessions/rotation.d.ts +2 -0
  344. package/dist/sessions/rotation.d.ts.map +1 -0
  345. package/dist/sessions/rotation.js +39 -0
  346. package/dist/sessions/rotation.js.map +1 -0
  347. package/dist/sessions/session-key.d.ts +31 -0
  348. package/dist/sessions/session-key.d.ts.map +1 -0
  349. package/dist/sessions/session-key.js +44 -0
  350. package/dist/sessions/session-key.js.map +1 -0
  351. package/dist/sessions/store.d.ts.map +1 -1
  352. package/dist/sessions/store.js +6 -12
  353. package/dist/sessions/store.js.map +1 -1
  354. package/dist/sessions/types.d.ts +2 -2
  355. package/dist/sessions/types.d.ts.map +1 -1
  356. package/dist/sessions/types.js.map +1 -1
  357. package/dist/settings-mutation.d.ts +23 -0
  358. package/dist/settings-mutation.d.ts.map +1 -0
  359. package/dist/settings-mutation.js +42 -0
  360. package/dist/settings-mutation.js.map +1 -0
  361. package/dist/tools/bash.d.ts +3 -1
  362. package/dist/tools/bash.d.ts.map +1 -1
  363. package/dist/tools/bash.js +24 -17
  364. package/dist/tools/bash.js.map +1 -1
  365. package/dist/tools/edit.d.ts.map +1 -1
  366. package/dist/tools/edit.js +9 -12
  367. package/dist/tools/edit.js.map +1 -1
  368. package/dist/tools/event.d.ts +1 -1
  369. package/dist/tools/event.d.ts.map +1 -1
  370. package/dist/tools/event.js +53 -43
  371. package/dist/tools/event.js.map +1 -1
  372. package/dist/tools/generate-image.d.ts +17 -0
  373. package/dist/tools/generate-image.d.ts.map +1 -0
  374. package/dist/tools/generate-image.js +63 -0
  375. package/dist/tools/generate-image.js.map +1 -0
  376. package/dist/tools/host-fn-tool.d.ts +21 -0
  377. package/dist/tools/host-fn-tool.d.ts.map +1 -0
  378. package/dist/tools/host-fn-tool.js +32 -0
  379. package/dist/tools/host-fn-tool.js.map +1 -0
  380. package/dist/tools/index.d.ts +17 -2
  381. package/dist/tools/index.d.ts.map +1 -1
  382. package/dist/tools/index.js +28 -3
  383. package/dist/tools/index.js.map +1 -1
  384. package/dist/tools/react.d.ts +15 -0
  385. package/dist/tools/react.d.ts.map +1 -0
  386. package/dist/tools/react.js +29 -0
  387. package/dist/tools/react.js.map +1 -0
  388. package/dist/tools/read.d.ts.map +1 -1
  389. package/dist/tools/read.js +7 -26
  390. package/dist/tools/read.js.map +1 -1
  391. package/dist/tools/sandbox.d.ts +2 -2
  392. package/dist/tools/sandbox.d.ts.map +1 -1
  393. package/dist/tools/sandbox.js +13 -9
  394. package/dist/tools/sandbox.js.map +1 -1
  395. package/dist/tools/subagent-slots.d.ts +28 -0
  396. package/dist/tools/subagent-slots.d.ts.map +1 -0
  397. package/dist/tools/subagent-slots.js +57 -0
  398. package/dist/tools/subagent-slots.js.map +1 -0
  399. package/dist/tools/subagent.d.ts +74 -0
  400. package/dist/tools/subagent.d.ts.map +1 -0
  401. package/dist/tools/subagent.js +335 -0
  402. package/dist/tools/subagent.js.map +1 -0
  403. package/dist/tools/types.d.ts +40 -6
  404. package/dist/tools/types.d.ts.map +1 -1
  405. package/dist/tools/types.js.map +1 -1
  406. package/dist/tools/write.d.ts.map +1 -1
  407. package/dist/tools/write.js +1 -8
  408. package/dist/tools/write.js.map +1 -1
  409. package/dist/trigger.d.ts.map +1 -1
  410. package/dist/trigger.js +1 -2
  411. package/dist/trigger.js.map +1 -1
  412. package/dist/types.d.ts +144 -31
  413. package/dist/types.d.ts.map +1 -1
  414. package/dist/types.js.map +1 -1
  415. package/dist/utils/date.d.ts +1 -1
  416. package/dist/utils/date.js +1 -1
  417. package/dist/utils/date.js.map +1 -1
  418. package/dist/vault/disabled.d.ts +9 -0
  419. package/dist/vault/disabled.d.ts.map +1 -0
  420. package/dist/vault/disabled.js +22 -0
  421. package/dist/vault/disabled.js.map +1 -0
  422. package/dist/vault/index.d.ts +0 -2
  423. package/dist/vault/index.d.ts.map +1 -1
  424. package/dist/vault/index.js +1 -32
  425. package/dist/vault/index.js.map +1 -1
  426. package/dist/vault/injection.d.ts +20 -0
  427. package/dist/vault/injection.d.ts.map +1 -0
  428. package/dist/vault/injection.js +27 -0
  429. package/dist/vault/injection.js.map +1 -0
  430. package/dist/vault/policy.d.ts +20 -0
  431. package/dist/vault/policy.d.ts.map +1 -0
  432. package/dist/vault/policy.js +20 -0
  433. package/dist/vault/policy.js.map +1 -0
  434. package/dist/vault/types.d.ts +0 -2
  435. package/dist/vault/types.d.ts.map +1 -1
  436. package/dist/vault/types.js.map +1 -1
  437. package/dist/web/admin/portal.d.ts +27 -0
  438. package/dist/web/admin/portal.d.ts.map +1 -1
  439. package/dist/web/admin/portal.js +117 -154
  440. package/dist/web/admin/portal.js.map +1 -1
  441. package/dist/web/admin/types.d.ts +6 -0
  442. package/dist/web/admin/types.d.ts.map +1 -1
  443. package/dist/web/admin/types.js.map +1 -1
  444. package/dist/web/agent-events/portal.d.ts.map +1 -1
  445. package/dist/web/agent-events/portal.js +2 -1
  446. package/dist/web/agent-events/portal.js.map +1 -1
  447. package/dist/web/login/oauth.d.ts +2 -3
  448. package/dist/web/login/oauth.d.ts.map +1 -1
  449. package/dist/web/login/oauth.js +0 -31
  450. package/dist/web/login/oauth.js.map +1 -1
  451. package/dist/web/login/portal.js +1 -1
  452. package/dist/web/login/portal.js.map +1 -1
  453. package/dist/web/login/types.d.ts +0 -11
  454. package/dist/web/login/types.d.ts.map +1 -1
  455. package/dist/web/login/types.js.map +1 -1
  456. package/dist/web/portal-shell.d.ts +4 -0
  457. package/dist/web/portal-shell.d.ts.map +1 -0
  458. package/dist/{portal-shell.js → web/portal-shell.js} +2 -2
  459. package/dist/web/portal-shell.js.map +1 -0
  460. package/dist/web/server.d.ts.map +1 -1
  461. package/dist/web/server.js +7 -0
  462. package/dist/web/server.js.map +1 -1
  463. package/dist/web/session-view/command.d.ts.map +1 -1
  464. package/dist/web/session-view/command.js +4 -1
  465. package/dist/web/session-view/command.js.map +1 -1
  466. package/dist/web/session-view/portal.d.ts.map +1 -1
  467. package/dist/web/session-view/portal.js +4 -3
  468. package/dist/web/session-view/portal.js.map +1 -1
  469. package/dist/web/session-view/service.d.ts.map +1 -1
  470. package/dist/web/session-view/service.js +2 -1
  471. package/dist/web/session-view/service.js.map +1 -1
  472. package/dist/web/session-view/types.d.ts +2 -1
  473. package/dist/web/session-view/types.d.ts.map +1 -1
  474. package/dist/web/session-view/types.js.map +1 -1
  475. package/package.json +7 -4
  476. package/dist/download.d.ts.map +0 -1
  477. package/dist/download.js.map +0 -1
  478. package/dist/model-registry.d.ts +0 -4
  479. package/dist/model-registry.d.ts.map +0 -1
  480. package/dist/model-registry.js +0 -7
  481. package/dist/model-registry.js.map +0 -1
  482. package/dist/portal-shell.d.ts +0 -4
  483. package/dist/portal-shell.d.ts.map +0 -1
  484. package/dist/portal-shell.js.map +0 -1
  485. package/dist/runtime/agent-run-controller.d.ts +0 -31
  486. package/dist/runtime/agent-run-controller.d.ts.map +0 -1
  487. package/dist/runtime/agent-run-controller.js +0 -195
  488. package/dist/runtime/agent-run-controller.js.map +0 -1
  489. package/dist/sessions/agent-memory-file-manager.d.ts +0 -19
  490. package/dist/sessions/agent-memory-file-manager.d.ts.map +0 -1
  491. package/dist/sessions/agent-memory-file-manager.js.map +0 -1
  492. package/dist/vault/routing.d.ts +0 -3
  493. package/dist/vault/routing.d.ts.map +0 -1
  494. package/dist/vault/routing.js +0 -13
  495. package/dist/vault/routing.js.map +0 -1
  496. /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
  497. /package/dist/{download.js → cli/download.js} +0 -0
@@ -0,0 +1,257 @@
1
+ import { readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as log from "../log.js";
4
+ import { ensureDirExists, readJsonFileIfExists } from "../utils/file-guards.js";
5
+ import { execSimple } from "./utils.js";
6
+ import { isGondolinRuntimeRecord } from "./gondolin-contract.js";
7
+ async function defaultGcSessions() {
8
+ const { gcSessions } = await import("@earendil-works/gondolin");
9
+ return gcSessions();
10
+ }
11
+ class GondolinRuntimeInventory {
12
+ constructor() {
13
+ this.execFileImpl = execSimple;
14
+ this.killImpl = (pid, signal) => process.kill(pid, signal);
15
+ this.gcSessionsImpl = defaultGcSessions;
16
+ this.killWaitMs = 2000;
17
+ this.killPollIntervalMs = 100;
18
+ this.liveRecords = new Map();
19
+ }
20
+ configure(dir, overrides) {
21
+ this.dir = dir;
22
+ this.execFileImpl = overrides?.execFile ?? execSimple;
23
+ this.killImpl = overrides?.kill ?? ((pid, signal) => process.kill(pid, signal));
24
+ this.gcSessionsImpl = overrides?.gcSessions ?? defaultGcSessions;
25
+ this.killWaitMs = overrides?.killWaitMs ?? 2000;
26
+ this.killPollIntervalMs = overrides?.killPollIntervalMs ?? 100;
27
+ this.liveRecords.clear();
28
+ }
29
+ record(entry) {
30
+ if (!this.dir)
31
+ return;
32
+ const record = {
33
+ ...entry,
34
+ ownerPid: process.pid,
35
+ createdAt: new Date().toISOString(),
36
+ };
37
+ this.liveRecords.set(entry.sessionId, record);
38
+ this.write(record);
39
+ }
40
+ /** Re-persist a runtime whose runner pid changed (late boot, runner restart). */
41
+ refresh(sessionId, runnerPid) {
42
+ const record = this.liveRecords.get(sessionId);
43
+ if (!record || record.runnerPid === runnerPid)
44
+ return;
45
+ record.runnerPid = runnerPid;
46
+ this.write(record);
47
+ }
48
+ release(sessionId) {
49
+ this.liveRecords.delete(sessionId);
50
+ if (!this.dir)
51
+ return;
52
+ try {
53
+ rmSync(this.recordPath(sessionId), { force: true });
54
+ }
55
+ catch (err) {
56
+ log.logWarning(`Failed to release Gondolin runtime record '${sessionId}'`, err instanceof Error ? err.message : String(err));
57
+ }
58
+ }
59
+ /**
60
+ * Newest worker-owned record for a session key whose owner is still alive —
61
+ * a candidate for adoption instead of spawning a fresh worker. Records left
62
+ * by dead owners are reaped on the spot.
63
+ */
64
+ async findAdoptable(instanceId) {
65
+ let newest;
66
+ for (const { record, path } of this.readRecords()) {
67
+ if (record.instanceId !== instanceId || !record.socketPath)
68
+ continue;
69
+ if (!this.isPidAlive(record.ownerPid)) {
70
+ await this.reapRecord(record, path);
71
+ continue;
72
+ }
73
+ if (!newest || record.createdAt > newest.createdAt)
74
+ newest = record;
75
+ }
76
+ return newest;
77
+ }
78
+ /** Reap one session's record if its owner is gone (e.g. after force-killing a worker). */
79
+ async reapSession(sessionId) {
80
+ for (const { record, path } of this.readRecords()) {
81
+ if (record.sessionId !== sessionId)
82
+ continue;
83
+ if (!this.isPidAlive(record.ownerPid))
84
+ await this.reapRecord(record, path);
85
+ return;
86
+ }
87
+ }
88
+ /** Worker-owned records with live owners (runtimes surviving from a previous mikan). */
89
+ listWorkerRecords() {
90
+ return this.readRecords()
91
+ .map(({ record }) => record)
92
+ .filter((record) => record.socketPath !== undefined && this.isPidAlive(record.ownerPid));
93
+ }
94
+ /**
95
+ * mikan liveness signal for workers: a worker whose heartbeat goes stale
96
+ * knows every mikan that could adopt it is gone and shuts itself down.
97
+ */
98
+ touchHeartbeat() {
99
+ if (!this.dir)
100
+ return;
101
+ try {
102
+ ensureDirExists(this.dir);
103
+ writeFileSync(this.heartbeatPath(), new Date().toISOString() + "\n");
104
+ }
105
+ catch (err) {
106
+ log.logWarning("Failed to touch Gondolin heartbeat", err instanceof Error ? err.message : String(err));
107
+ }
108
+ }
109
+ heartbeatPath() {
110
+ return this.dir ? join(this.dir, "heartbeat") : undefined;
111
+ }
112
+ directory() {
113
+ return this.dir;
114
+ }
115
+ /**
116
+ * Startup reconciliation: stop VM runners orphaned by a previous owner
117
+ * process that died without cleanup, drop stale records, and let Gondolin
118
+ * collect its own stale session registry entries. Runtimes whose owner is
119
+ * still alive (workers surviving a mikan restart) are left for adoption.
120
+ * Idempotent — a second run finds an empty inventory.
121
+ */
122
+ async reconcile() {
123
+ if (!this.dir)
124
+ return;
125
+ ensureDirExists(this.dir);
126
+ let killed = 0;
127
+ let stale = 0;
128
+ let skipped = 0;
129
+ for (const { record, path } of this.readRecords((badPath) => {
130
+ rmSync(badPath, { force: true });
131
+ stale += 1;
132
+ })) {
133
+ if (record.ownerPid === process.pid)
134
+ continue;
135
+ if (this.isPidAlive(record.ownerPid)) {
136
+ skipped += 1;
137
+ continue;
138
+ }
139
+ const outcome = await this.reapRecord(record, path);
140
+ if (outcome === "killed")
141
+ killed += 1;
142
+ else if (outcome !== "alive")
143
+ stale += 1;
144
+ }
145
+ try {
146
+ await this.gcSessionsImpl();
147
+ }
148
+ catch (err) {
149
+ log.logWarning("Failed to collect Gondolin session registry", err instanceof Error ? err.message : String(err));
150
+ }
151
+ log.logInfo(`Reconciled Gondolin runtimes (killed=${killed}, stale=${stale}, liveOwner=${skipped})`);
152
+ }
153
+ /**
154
+ * Reap one dead-owner record: stop its orphaned runner if it is still the
155
+ * process we launched, then drop the record (kept only while the runner
156
+ * refuses to die, so the next reconcile retries).
157
+ */
158
+ async reapRecord(record, path = this.recordPath(record.sessionId)) {
159
+ const outcome = record.runnerPid === null ? "gone" : await this.stopOrphan(record);
160
+ if (outcome === "alive") {
161
+ log.logWarning(`Orphaned Gondolin runner ${record.runnerPid} survived SIGKILL; keeping record '${record.sessionId}'`);
162
+ return outcome;
163
+ }
164
+ rmSync(path, { force: true });
165
+ return outcome;
166
+ }
167
+ readRecords(onMalformed) {
168
+ if (!this.dir)
169
+ return [];
170
+ ensureDirExists(this.dir);
171
+ const entries = [];
172
+ for (const file of readdirSync(this.dir)) {
173
+ if (!file.endsWith(".json"))
174
+ continue;
175
+ const path = join(this.dir, file);
176
+ let record;
177
+ try {
178
+ record = readJsonFileIfExists(path, isGondolinRuntimeRecord, (detail) => `Malformed Gondolin runtime record at ${path}: ${detail}`);
179
+ }
180
+ catch (err) {
181
+ log.logWarning("Dropping malformed Gondolin runtime record", err instanceof Error ? err.message : String(err));
182
+ }
183
+ if (record)
184
+ entries.push({ record, path });
185
+ else
186
+ onMalformed?.(path);
187
+ }
188
+ return entries;
189
+ }
190
+ async stopOrphan(record) {
191
+ const pid = record.runnerPid;
192
+ if (!this.isPidAlive(pid))
193
+ return "gone";
194
+ // Guard against pid reuse: only kill processes that still look like a
195
+ // Gondolin runner (its guest assets and overlay disk live under paths
196
+ // containing "gondolin").
197
+ let command;
198
+ try {
199
+ command = await this.execFileImpl("ps", ["-p", String(pid), "-o", "command="]);
200
+ }
201
+ catch {
202
+ return "gone";
203
+ }
204
+ if (!command.toLowerCase().includes("gondolin")) {
205
+ log.logWarning(`Pid ${pid} from Gondolin runtime record '${record.sessionId}' no longer looks like a Gondolin runner; skipping kill`);
206
+ return "reused";
207
+ }
208
+ log.logInfo(`Stopping orphaned Gondolin runner ${pid} (session '${record.sessionId}', instance '${record.instanceId}')`);
209
+ this.signal(pid, "SIGTERM");
210
+ if (await this.waitForExit(pid))
211
+ return "killed";
212
+ this.signal(pid, "SIGKILL");
213
+ return (await this.waitForExit(pid)) ? "killed" : "alive";
214
+ }
215
+ signal(pid, signal) {
216
+ try {
217
+ this.killImpl(pid, signal);
218
+ }
219
+ catch {
220
+ // process already gone
221
+ }
222
+ }
223
+ isPidAlive(pid) {
224
+ try {
225
+ this.killImpl(pid, 0);
226
+ return true;
227
+ }
228
+ catch {
229
+ return false;
230
+ }
231
+ }
232
+ async waitForExit(pid) {
233
+ const deadline = Date.now() + this.killWaitMs;
234
+ while (this.isPidAlive(pid)) {
235
+ if (Date.now() >= deadline)
236
+ return false;
237
+ await new Promise((resolve) => setTimeout(resolve, this.killPollIntervalMs));
238
+ }
239
+ return true;
240
+ }
241
+ write(record) {
242
+ if (!this.dir)
243
+ return;
244
+ try {
245
+ ensureDirExists(this.dir);
246
+ writeFileSync(this.recordPath(record.sessionId), JSON.stringify(record, null, 2) + "\n");
247
+ }
248
+ catch (err) {
249
+ log.logWarning(`Failed to persist Gondolin runtime record '${record.sessionId}'`, err instanceof Error ? err.message : String(err));
250
+ }
251
+ }
252
+ recordPath(sessionId) {
253
+ return join(this.dir, `${sessionId}.json`);
254
+ }
255
+ }
256
+ export const gondolinInventory = new GondolinRuntimeInventory();
257
+ //# sourceMappingURL=gondolin-inventory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gondolin-inventory.js","sourceRoot":"","sources":["../../src/sandbox/gondolin-inventory.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport * as log from \"../log.js\";\nimport { ensureDirExists, readJsonFileIfExists } from \"../utils/file-guards.js\";\nimport { execSimple } from \"./utils.js\";\n\n/**\n * Durable record of one Gondolin runtime. Written beside the state dir (never\n * inside the workspace) by whichever process owns the VM — the worker process\n * hosting it — so a crash that skips every exit hook still leaves enough\n * behind to adopt the runtime back or stop its orphaned VM runner.\n */\n// The record is a wire contract shape shared with the Go daemon's\n// rediscovery; its single home is gondolin-contract.ts.\nexport type { GondolinRuntimeRecord } from \"./gondolin-contract.js\";\nimport { isGondolinRuntimeRecord, type GondolinRuntimeRecord } from \"./gondolin-contract.js\";\n\ninterface GondolinInventoryOverrides {\n execFile?: (cmd: string, args: string[]) => Promise<string>;\n kill?: (pid: number, signal: NodeJS.Signals | 0) => void;\n gcSessions?: () => Promise<number>;\n killWaitMs?: number;\n killPollIntervalMs?: number;\n}\n\nasync function defaultGcSessions(): Promise<number> {\n const { gcSessions } = await import(\"@earendil-works/gondolin\");\n return gcSessions();\n}\n\nclass GondolinRuntimeInventory {\n private dir?: string;\n private execFileImpl = execSimple;\n private killImpl: (pid: number, signal: NodeJS.Signals | 0) => void = (pid, signal) =>\n process.kill(pid, signal);\n private gcSessionsImpl = defaultGcSessions;\n private killWaitMs = 2000;\n private killPollIntervalMs = 100;\n private readonly liveRecords = new Map<string, GondolinRuntimeRecord>();\n\n configure(dir?: string, overrides?: GondolinInventoryOverrides): void {\n this.dir = dir;\n this.execFileImpl = overrides?.execFile ?? execSimple;\n this.killImpl = overrides?.kill ?? ((pid, signal) => process.kill(pid, signal));\n this.gcSessionsImpl = overrides?.gcSessions ?? defaultGcSessions;\n this.killWaitMs = overrides?.killWaitMs ?? 2000;\n this.killPollIntervalMs = overrides?.killPollIntervalMs ?? 100;\n this.liveRecords.clear();\n }\n\n record(entry: {\n sessionId: string;\n instanceId: string;\n runnerPid: number | null;\n socketPath?: string;\n fingerprint?: string;\n }): void {\n if (!this.dir) return;\n const record: GondolinRuntimeRecord = {\n ...entry,\n ownerPid: process.pid,\n createdAt: new Date().toISOString(),\n };\n this.liveRecords.set(entry.sessionId, record);\n this.write(record);\n }\n\n /** Re-persist a runtime whose runner pid changed (late boot, runner restart). */\n refresh(sessionId: string, runnerPid: number | null): void {\n const record = this.liveRecords.get(sessionId);\n if (!record || record.runnerPid === runnerPid) return;\n record.runnerPid = runnerPid;\n this.write(record);\n }\n\n release(sessionId: string): void {\n this.liveRecords.delete(sessionId);\n if (!this.dir) return;\n try {\n rmSync(this.recordPath(sessionId), { force: true });\n } catch (err) {\n log.logWarning(\n `Failed to release Gondolin runtime record '${sessionId}'`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n /**\n * Newest worker-owned record for a session key whose owner is still alive —\n * a candidate for adoption instead of spawning a fresh worker. Records left\n * by dead owners are reaped on the spot.\n */\n async findAdoptable(instanceId: string): Promise<GondolinRuntimeRecord | undefined> {\n let newest: GondolinRuntimeRecord | undefined;\n for (const { record, path } of this.readRecords()) {\n if (record.instanceId !== instanceId || !record.socketPath) continue;\n if (!this.isPidAlive(record.ownerPid)) {\n await this.reapRecord(record, path);\n continue;\n }\n if (!newest || record.createdAt > newest.createdAt) newest = record;\n }\n return newest;\n }\n\n /** Reap one session's record if its owner is gone (e.g. after force-killing a worker). */\n async reapSession(sessionId: string): Promise<void> {\n for (const { record, path } of this.readRecords()) {\n if (record.sessionId !== sessionId) continue;\n if (!this.isPidAlive(record.ownerPid)) await this.reapRecord(record, path);\n return;\n }\n }\n\n /** Worker-owned records with live owners (runtimes surviving from a previous mikan). */\n listWorkerRecords(): GondolinRuntimeRecord[] {\n return this.readRecords()\n .map(({ record }) => record)\n .filter((record) => record.socketPath !== undefined && this.isPidAlive(record.ownerPid));\n }\n\n /**\n * mikan liveness signal for workers: a worker whose heartbeat goes stale\n * knows every mikan that could adopt it is gone and shuts itself down.\n */\n touchHeartbeat(): void {\n if (!this.dir) return;\n try {\n ensureDirExists(this.dir);\n writeFileSync(this.heartbeatPath() as string, new Date().toISOString() + \"\\n\");\n } catch (err) {\n log.logWarning(\n \"Failed to touch Gondolin heartbeat\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n heartbeatPath(): string | undefined {\n return this.dir ? join(this.dir, \"heartbeat\") : undefined;\n }\n\n directory(): string | undefined {\n return this.dir;\n }\n\n /**\n * Startup reconciliation: stop VM runners orphaned by a previous owner\n * process that died without cleanup, drop stale records, and let Gondolin\n * collect its own stale session registry entries. Runtimes whose owner is\n * still alive (workers surviving a mikan restart) are left for adoption.\n * Idempotent — a second run finds an empty inventory.\n */\n async reconcile(): Promise<void> {\n if (!this.dir) return;\n ensureDirExists(this.dir);\n let killed = 0;\n let stale = 0;\n let skipped = 0;\n for (const { record, path } of this.readRecords((badPath) => {\n rmSync(badPath, { force: true });\n stale += 1;\n })) {\n if (record.ownerPid === process.pid) continue;\n if (this.isPidAlive(record.ownerPid)) {\n skipped += 1;\n continue;\n }\n const outcome = await this.reapRecord(record, path);\n if (outcome === \"killed\") killed += 1;\n else if (outcome !== \"alive\") stale += 1;\n }\n try {\n await this.gcSessionsImpl();\n } catch (err) {\n log.logWarning(\n \"Failed to collect Gondolin session registry\",\n err instanceof Error ? err.message : String(err),\n );\n }\n log.logInfo(\n `Reconciled Gondolin runtimes (killed=${killed}, stale=${stale}, liveOwner=${skipped})`,\n );\n }\n\n /**\n * Reap one dead-owner record: stop its orphaned runner if it is still the\n * process we launched, then drop the record (kept only while the runner\n * refuses to die, so the next reconcile retries).\n */\n async reapRecord(\n record: GondolinRuntimeRecord,\n path = this.recordPath(record.sessionId),\n ): Promise<\"killed\" | \"gone\" | \"reused\" | \"alive\"> {\n const outcome = record.runnerPid === null ? \"gone\" : await this.stopOrphan(record);\n if (outcome === \"alive\") {\n log.logWarning(\n `Orphaned Gondolin runner ${record.runnerPid} survived SIGKILL; keeping record '${record.sessionId}'`,\n );\n return outcome;\n }\n rmSync(path, { force: true });\n return outcome;\n }\n\n private readRecords(\n onMalformed?: (path: string) => void,\n ): Array<{ record: GondolinRuntimeRecord; path: string }> {\n if (!this.dir) return [];\n ensureDirExists(this.dir);\n const entries: Array<{ record: GondolinRuntimeRecord; path: string }> = [];\n for (const file of readdirSync(this.dir)) {\n if (!file.endsWith(\".json\")) continue;\n const path = join(this.dir, file);\n let record: GondolinRuntimeRecord | undefined;\n try {\n record = readJsonFileIfExists(\n path,\n isGondolinRuntimeRecord,\n (detail) => `Malformed Gondolin runtime record at ${path}: ${detail}`,\n );\n } catch (err) {\n log.logWarning(\n \"Dropping malformed Gondolin runtime record\",\n err instanceof Error ? err.message : String(err),\n );\n }\n if (record) entries.push({ record, path });\n else onMalformed?.(path);\n }\n return entries;\n }\n\n private async stopOrphan(\n record: GondolinRuntimeRecord,\n ): Promise<\"killed\" | \"gone\" | \"reused\" | \"alive\"> {\n const pid = record.runnerPid as number;\n if (!this.isPidAlive(pid)) return \"gone\";\n // Guard against pid reuse: only kill processes that still look like a\n // Gondolin runner (its guest assets and overlay disk live under paths\n // containing \"gondolin\").\n let command: string;\n try {\n command = await this.execFileImpl(\"ps\", [\"-p\", String(pid), \"-o\", \"command=\"]);\n } catch {\n return \"gone\";\n }\n if (!command.toLowerCase().includes(\"gondolin\")) {\n log.logWarning(\n `Pid ${pid} from Gondolin runtime record '${record.sessionId}' no longer looks like a Gondolin runner; skipping kill`,\n );\n return \"reused\";\n }\n log.logInfo(\n `Stopping orphaned Gondolin runner ${pid} (session '${record.sessionId}', instance '${record.instanceId}')`,\n );\n this.signal(pid, \"SIGTERM\");\n if (await this.waitForExit(pid)) return \"killed\";\n this.signal(pid, \"SIGKILL\");\n return (await this.waitForExit(pid)) ? \"killed\" : \"alive\";\n }\n\n private signal(pid: number, signal: NodeJS.Signals): void {\n try {\n this.killImpl(pid, signal);\n } catch {\n // process already gone\n }\n }\n\n isPidAlive(pid: number): boolean {\n try {\n this.killImpl(pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n private async waitForExit(pid: number): Promise<boolean> {\n const deadline = Date.now() + this.killWaitMs;\n while (this.isPidAlive(pid)) {\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, this.killPollIntervalMs));\n }\n return true;\n }\n\n private write(record: GondolinRuntimeRecord): void {\n if (!this.dir) return;\n try {\n ensureDirExists(this.dir);\n writeFileSync(this.recordPath(record.sessionId), JSON.stringify(record, null, 2) + \"\\n\");\n } catch (err) {\n log.logWarning(\n `Failed to persist Gondolin runtime record '${record.sessionId}'`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n private recordPath(sessionId: string): string {\n return join(this.dir as string, `${sessionId}.json`);\n }\n}\n\nexport const gondolinInventory = new GondolinRuntimeInventory();\n"]}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Certificate provisioning and join-token authority for dial-home workers.
3
+ *
4
+ * Owns a private CA under the state dir (`gondolin-gateway/`), auto-issues the
5
+ * gateway's own server certificate from it, and exchanges one-time join
6
+ * tokens for CA-signed worker client certificates — the kubeadm-style
7
+ * bootstrap that replaces hand-provisioned mTLS material. All X.509 issuance
8
+ * shells out to openssl (Node can parse but not issue certificates).
9
+ */
10
+ declare class GondolinJoinService {
11
+ private dir?;
12
+ private execImpl;
13
+ private now;
14
+ private tokenTtlSeconds;
15
+ configure(dir?: string, overrides?: {
16
+ execFile?: (cmd: string, args: string[]) => Promise<unknown>;
17
+ now?: () => number;
18
+ tokenTtlSeconds?: number;
19
+ }): void;
20
+ isConfigured(): boolean;
21
+ paths(): {
22
+ caFile: string;
23
+ caKeyFile: string;
24
+ serverCertFile: string;
25
+ serverKeyFile: string;
26
+ };
27
+ /** Create the worker CA on first use; idempotent afterwards. */
28
+ ensureCa(): Promise<void>;
29
+ /**
30
+ * Issue the gateway's server certificate from the worker CA so joining
31
+ * workers can verify the host against the pinned CA. Reissued when the SAN
32
+ * list changes.
33
+ */
34
+ ensureServerCert(hostnames: string[]): Promise<{
35
+ certFile: string;
36
+ keyFile: string;
37
+ }>;
38
+ /** SHA-256 of the CA certificate DER — the join command's --ca-pin. */
39
+ caFingerprint(): string;
40
+ /** Mint a single-use join token; only its hash is stored. */
41
+ mintToken(): Promise<{
42
+ token: string;
43
+ fingerprint: string;
44
+ expiresAt: number;
45
+ }>;
46
+ /** Validate and burn a join token (single use, TTL-bounded). */
47
+ consumeToken(token: string): boolean;
48
+ /** Sign a joining worker's CSR with the worker CA. */
49
+ signCsr(csrPem: string): Promise<{
50
+ certPem: string;
51
+ caPem: string;
52
+ }>;
53
+ private subjectAltNames;
54
+ private openssl;
55
+ private tokensPath;
56
+ private readTokens;
57
+ private writeTokens;
58
+ private requireDir;
59
+ }
60
+ export declare const gondolinJoin: GondolinJoinService;
61
+ export {};
62
+ //# sourceMappingURL=gondolin-join.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gondolin-join.d.ts","sourceRoot":"","sources":["../../src/sandbox/gondolin-join.ts"],"names":[],"mappings":"AAyCA;;;;;;;;GAQG;AACH,cAAM,mBAAmB;IACvB,OAAO,CAAC,GAAG,CAAC,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAoE;IACpF,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,eAAe,CAAqB;IAE5C,SAAS,CACP,GAAG,CAAC,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE;QACV,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,GACA,IAAI,CAKN;IAED,YAAY,IAAI,OAAO,CAEtB;IAED,KAAK,IAAI;QACP,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,EAAE,MAAM,CAAC;KACvB,CAQA;IAED,gEAAgE;IAC1D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB9B;IAED;;;;OAIG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CA0D1F;IAED,uEAAuE;IACvE,aAAa,IAAI,MAAM,CAGtB;IAED,6DAA6D;IACvD,SAAS,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CASpF;IAED,gEAAgE;IAChE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAcnC;IAED,sDAAsD;IAChD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CA+BzE;IAED,OAAO,CAAC,eAAe;YAST,OAAO;IAWrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,UAAU;CAInB;AAED,eAAO,MAAM,YAAY,qBAA4B,CAAC"}
@@ -0,0 +1,261 @@
1
+ import { execFile } from "node:child_process";
2
+ import { X509Certificate, createHash, randomBytes, timingSafeEqual } from "node:crypto";
3
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import * as log from "../log.js";
8
+ import { isRecord, readJsonFileIfExists } from "../utils/file-guards.js";
9
+ const execFileAsync = promisify(execFile);
10
+ const TOKEN_TTL_SECONDS = 15 * 60;
11
+ const CA_DAYS = 3650;
12
+ const SERVER_CERT_DAYS = 825;
13
+ const WORKER_CERT_DAYS = 365;
14
+ function isTokenTable(value) {
15
+ return (isRecord(value) &&
16
+ Array.isArray(value.tokens) &&
17
+ value.tokens.every((entry) => isRecord(entry) &&
18
+ typeof entry.id === "string" &&
19
+ typeof entry.secretHash === "string" &&
20
+ typeof entry.expiresAt === "number"));
21
+ }
22
+ function sha256Hex(data) {
23
+ return createHash("sha256").update(data).digest("hex");
24
+ }
25
+ /**
26
+ * Certificate provisioning and join-token authority for dial-home workers.
27
+ *
28
+ * Owns a private CA under the state dir (`gondolin-gateway/`), auto-issues the
29
+ * gateway's own server certificate from it, and exchanges one-time join
30
+ * tokens for CA-signed worker client certificates — the kubeadm-style
31
+ * bootstrap that replaces hand-provisioned mTLS material. All X.509 issuance
32
+ * shells out to openssl (Node can parse but not issue certificates).
33
+ */
34
+ class GondolinJoinService {
35
+ constructor() {
36
+ this.execImpl = execFileAsync;
37
+ this.now = Date.now;
38
+ this.tokenTtlSeconds = TOKEN_TTL_SECONDS;
39
+ }
40
+ configure(dir, overrides) {
41
+ this.dir = dir;
42
+ this.execImpl = overrides?.execFile ?? execFileAsync;
43
+ this.now = overrides?.now ?? Date.now;
44
+ this.tokenTtlSeconds = overrides?.tokenTtlSeconds ?? TOKEN_TTL_SECONDS;
45
+ }
46
+ isConfigured() {
47
+ return this.dir !== undefined;
48
+ }
49
+ paths() {
50
+ const dir = this.requireDir();
51
+ return {
52
+ caFile: join(dir, "ca.pem"),
53
+ caKeyFile: join(dir, "ca-key.pem"),
54
+ serverCertFile: join(dir, "server.pem"),
55
+ serverKeyFile: join(dir, "server-key.pem"),
56
+ };
57
+ }
58
+ /** Create the worker CA on first use; idempotent afterwards. */
59
+ async ensureCa() {
60
+ const { caFile, caKeyFile } = this.paths();
61
+ if (existsSync(caFile) && existsSync(caKeyFile))
62
+ return;
63
+ mkdirSync(this.requireDir(), { recursive: true, mode: 0o700 });
64
+ await this.openssl([
65
+ "req",
66
+ "-x509",
67
+ "-newkey",
68
+ "ec",
69
+ "-pkeyopt",
70
+ "ec_paramgen_curve:P-256",
71
+ "-keyout",
72
+ caKeyFile,
73
+ "-out",
74
+ caFile,
75
+ "-days",
76
+ String(CA_DAYS),
77
+ "-nodes",
78
+ "-subj",
79
+ "/CN=mikan-worker-ca",
80
+ ]);
81
+ log.logInfo(`Gondolin worker CA created at ${caFile} (pin ${this.caFingerprint()})`);
82
+ }
83
+ /**
84
+ * Issue the gateway's server certificate from the worker CA so joining
85
+ * workers can verify the host against the pinned CA. Reissued when the SAN
86
+ * list changes.
87
+ */
88
+ async ensureServerCert(hostnames) {
89
+ await this.ensureCa();
90
+ const { caFile, caKeyFile, serverCertFile, serverKeyFile } = this.paths();
91
+ const altNames = this.subjectAltNames(hostnames);
92
+ if (existsSync(serverCertFile) && existsSync(serverKeyFile)) {
93
+ const existing = new X509Certificate(readFileSync(serverCertFile));
94
+ const current = (existing.subjectAltName ?? "")
95
+ .split(", ")
96
+ .filter(Boolean)
97
+ .toSorted()
98
+ .join(",");
99
+ const wanted = altNames
100
+ .split(",")
101
+ .map((entry) => entry.replace("DNS:", "DNS:").replace("IP:", "IP Address:"))
102
+ .toSorted()
103
+ .join(",");
104
+ if (current === wanted && this.now() < existing.validToDate.getTime() - 24 * 3600 * 1000) {
105
+ return { certFile: serverCertFile, keyFile: serverKeyFile };
106
+ }
107
+ }
108
+ const csrFile = join(this.requireDir(), "server.csr");
109
+ const extFile = join(this.requireDir(), "server.ext");
110
+ writeFileSync(extFile, `subjectAltName=${altNames}\n`);
111
+ await this.openssl([
112
+ "req",
113
+ "-newkey",
114
+ "ec",
115
+ "-pkeyopt",
116
+ "ec_paramgen_curve:P-256",
117
+ "-keyout",
118
+ serverKeyFile,
119
+ "-out",
120
+ csrFile,
121
+ "-nodes",
122
+ "-subj",
123
+ "/CN=mikan-gateway",
124
+ ]);
125
+ await this.openssl([
126
+ "x509",
127
+ "-req",
128
+ "-in",
129
+ csrFile,
130
+ "-CA",
131
+ caFile,
132
+ "-CAkey",
133
+ caKeyFile,
134
+ "-CAcreateserial",
135
+ "-days",
136
+ String(SERVER_CERT_DAYS),
137
+ "-extfile",
138
+ extFile,
139
+ "-out",
140
+ serverCertFile,
141
+ ]);
142
+ rmSync(csrFile, { force: true });
143
+ rmSync(extFile, { force: true });
144
+ log.logInfo(`Gondolin gateway certificate issued for ${altNames}`);
145
+ return { certFile: serverCertFile, keyFile: serverKeyFile };
146
+ }
147
+ /** SHA-256 of the CA certificate DER — the join command's --ca-pin. */
148
+ caFingerprint() {
149
+ const certificate = new X509Certificate(readFileSync(this.paths().caFile));
150
+ return `sha256:${certificate.fingerprint256.replaceAll(":", "").toLowerCase()}`;
151
+ }
152
+ /** Mint a single-use join token; only its hash is stored. */
153
+ async mintToken() {
154
+ await this.ensureCa();
155
+ const id = randomBytes(4).toString("hex");
156
+ const secret = randomBytes(24).toString("base64url");
157
+ const expiresAt = this.now() + this.tokenTtlSeconds * 1000;
158
+ const table = this.readTokens();
159
+ table.push({ id, secretHash: sha256Hex(secret), expiresAt });
160
+ this.writeTokens(table);
161
+ return { token: `${id}.${secret}`, fingerprint: this.caFingerprint(), expiresAt };
162
+ }
163
+ /** Validate and burn a join token (single use, TTL-bounded). */
164
+ consumeToken(token) {
165
+ const separator = token.indexOf(".");
166
+ if (separator <= 0)
167
+ return false;
168
+ const id = token.slice(0, separator);
169
+ const secret = token.slice(separator + 1);
170
+ const table = this.readTokens();
171
+ const record = table.find((entry) => entry.id === id);
172
+ if (!record || record.usedAt !== undefined || this.now() > record.expiresAt)
173
+ return false;
174
+ const expected = Buffer.from(record.secretHash, "hex");
175
+ const actual = createHash("sha256").update(secret).digest();
176
+ if (expected.length !== actual.length || !timingSafeEqual(expected, actual))
177
+ return false;
178
+ record.usedAt = this.now();
179
+ this.writeTokens(table);
180
+ return true;
181
+ }
182
+ /** Sign a joining worker's CSR with the worker CA. */
183
+ async signCsr(csrPem) {
184
+ await this.ensureCa();
185
+ const { caFile, caKeyFile } = this.paths();
186
+ const scratch = join(tmpdir(), `mikan-join-${randomBytes(6).toString("hex")}`);
187
+ mkdirSync(scratch, { recursive: true, mode: 0o700 });
188
+ const csrFile = join(scratch, "worker.csr");
189
+ const certFile = join(scratch, "worker.pem");
190
+ try {
191
+ writeFileSync(csrFile, csrPem);
192
+ await this.openssl([
193
+ "x509",
194
+ "-req",
195
+ "-in",
196
+ csrFile,
197
+ "-CA",
198
+ caFile,
199
+ "-CAkey",
200
+ caKeyFile,
201
+ "-CAcreateserial",
202
+ "-days",
203
+ String(WORKER_CERT_DAYS),
204
+ "-out",
205
+ certFile,
206
+ ]);
207
+ return {
208
+ certPem: readFileSync(certFile, "utf8"),
209
+ caPem: readFileSync(caFile, "utf8"),
210
+ };
211
+ }
212
+ finally {
213
+ rmSync(scratch, { recursive: true, force: true });
214
+ }
215
+ }
216
+ subjectAltNames(hostnames) {
217
+ const entries = new Set(["IP:127.0.0.1", "DNS:localhost"]);
218
+ for (const name of hostnames) {
219
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(name) || name.includes(":"))
220
+ entries.add(`IP:${name}`);
221
+ else
222
+ entries.add(`DNS:${name}`);
223
+ }
224
+ return Array.from(entries).join(",");
225
+ }
226
+ async openssl(args) {
227
+ try {
228
+ await this.execImpl("openssl", args);
229
+ }
230
+ catch (err) {
231
+ throw new Error(`openssl ${args[0]} failed (is openssl installed?): ${err instanceof Error ? err.message : String(err)}`, { cause: err });
232
+ }
233
+ }
234
+ tokensPath() {
235
+ return join(this.requireDir(), "tokens.json");
236
+ }
237
+ readTokens() {
238
+ let table = [];
239
+ try {
240
+ const parsed = readJsonFileIfExists(this.tokensPath(), isTokenTable, (detail) => `Malformed join token table: ${detail}`);
241
+ table = parsed?.tokens ?? [];
242
+ }
243
+ catch (err) {
244
+ log.logWarning("Resetting Gondolin join token table", err instanceof Error ? err.message : String(err));
245
+ }
246
+ return table.filter((entry) => this.now() <= entry.expiresAt);
247
+ }
248
+ writeTokens(tokens) {
249
+ mkdirSync(this.requireDir(), { recursive: true, mode: 0o700 });
250
+ const staged = `${this.tokensPath()}.tmp`;
251
+ writeFileSync(staged, JSON.stringify({ tokens }, null, 2) + "\n", { mode: 0o600 });
252
+ renameSync(staged, this.tokensPath());
253
+ }
254
+ requireDir() {
255
+ if (!this.dir)
256
+ throw new Error("gondolin join service is not configured");
257
+ return this.dir;
258
+ }
259
+ }
260
+ export const gondolinJoin = new GondolinJoinService();
261
+ //# sourceMappingURL=gondolin-join.js.map