@stoneforge/smithy 0.1.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 (497) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +114 -0
  3. package/dist/api/index.d.ts +7 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +7 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/orchestrator-api.d.ts +153 -0
  8. package/dist/api/orchestrator-api.d.ts.map +1 -0
  9. package/dist/api/orchestrator-api.js +374 -0
  10. package/dist/api/orchestrator-api.js.map +1 -0
  11. package/dist/bin/sf.d.ts +3 -0
  12. package/dist/bin/sf.d.ts.map +1 -0
  13. package/dist/bin/sf.js +10 -0
  14. package/dist/bin/sf.js.map +1 -0
  15. package/dist/cli/commands/agent.d.ts +20 -0
  16. package/dist/cli/commands/agent.d.ts.map +1 -0
  17. package/dist/cli/commands/agent.js +861 -0
  18. package/dist/cli/commands/agent.js.map +1 -0
  19. package/dist/cli/commands/daemon.d.ts +14 -0
  20. package/dist/cli/commands/daemon.d.ts.map +1 -0
  21. package/dist/cli/commands/daemon.js +272 -0
  22. package/dist/cli/commands/daemon.js.map +1 -0
  23. package/dist/cli/commands/dispatch.d.ts +9 -0
  24. package/dist/cli/commands/dispatch.d.ts.map +1 -0
  25. package/dist/cli/commands/dispatch.js +128 -0
  26. package/dist/cli/commands/dispatch.js.map +1 -0
  27. package/dist/cli/commands/merge.d.ts +11 -0
  28. package/dist/cli/commands/merge.d.ts.map +1 -0
  29. package/dist/cli/commands/merge.js +246 -0
  30. package/dist/cli/commands/merge.js.map +1 -0
  31. package/dist/cli/commands/pool.d.ts +21 -0
  32. package/dist/cli/commands/pool.d.ts.map +1 -0
  33. package/dist/cli/commands/pool.js +762 -0
  34. package/dist/cli/commands/pool.js.map +1 -0
  35. package/dist/cli/commands/serve.d.ts +54 -0
  36. package/dist/cli/commands/serve.d.ts.map +1 -0
  37. package/dist/cli/commands/serve.js +57 -0
  38. package/dist/cli/commands/serve.js.map +1 -0
  39. package/dist/cli/commands/task.d.ts +36 -0
  40. package/dist/cli/commands/task.d.ts.map +1 -0
  41. package/dist/cli/commands/task.js +889 -0
  42. package/dist/cli/commands/task.js.map +1 -0
  43. package/dist/cli/commands/test-orchestration.d.ts +32 -0
  44. package/dist/cli/commands/test-orchestration.d.ts.map +1 -0
  45. package/dist/cli/commands/test-orchestration.js +392 -0
  46. package/dist/cli/commands/test-orchestration.js.map +1 -0
  47. package/dist/cli/index.d.ts +13 -0
  48. package/dist/cli/index.d.ts.map +1 -0
  49. package/dist/cli/index.js +15 -0
  50. package/dist/cli/index.js.map +1 -0
  51. package/dist/cli/plugin.d.ts +23 -0
  52. package/dist/cli/plugin.d.ts.map +1 -0
  53. package/dist/cli/plugin.js +36 -0
  54. package/dist/cli/plugin.js.map +1 -0
  55. package/dist/git/index.d.ts +10 -0
  56. package/dist/git/index.d.ts.map +1 -0
  57. package/dist/git/index.js +12 -0
  58. package/dist/git/index.js.map +1 -0
  59. package/dist/git/merge.d.ts +79 -0
  60. package/dist/git/merge.d.ts.map +1 -0
  61. package/dist/git/merge.js +254 -0
  62. package/dist/git/merge.js.map +1 -0
  63. package/dist/git/worktree-manager.d.ts +299 -0
  64. package/dist/git/worktree-manager.d.ts.map +1 -0
  65. package/dist/git/worktree-manager.js +744 -0
  66. package/dist/git/worktree-manager.js.map +1 -0
  67. package/dist/index.d.ts +24 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +31 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/prompts/director.md +272 -0
  72. package/dist/prompts/index.d.ts +100 -0
  73. package/dist/prompts/index.d.ts.map +1 -0
  74. package/dist/prompts/index.js +294 -0
  75. package/dist/prompts/index.js.map +1 -0
  76. package/dist/prompts/message-triage.md +50 -0
  77. package/dist/prompts/persistent-worker.md +240 -0
  78. package/dist/prompts/steward-base.md +64 -0
  79. package/dist/prompts/steward-docs.md +118 -0
  80. package/dist/prompts/steward-health.md +39 -0
  81. package/dist/prompts/steward-merge.md +168 -0
  82. package/dist/prompts/steward-ops.md +28 -0
  83. package/dist/prompts/steward-reminder.md +26 -0
  84. package/dist/prompts/worker.md +282 -0
  85. package/dist/providers/claude/headless.d.ts +18 -0
  86. package/dist/providers/claude/headless.d.ts.map +1 -0
  87. package/dist/providers/claude/headless.js +307 -0
  88. package/dist/providers/claude/headless.js.map +1 -0
  89. package/dist/providers/claude/index.d.ts +24 -0
  90. package/dist/providers/claude/index.d.ts.map +1 -0
  91. package/dist/providers/claude/index.js +80 -0
  92. package/dist/providers/claude/index.js.map +1 -0
  93. package/dist/providers/claude/interactive.d.ts +21 -0
  94. package/dist/providers/claude/interactive.d.ts.map +1 -0
  95. package/dist/providers/claude/interactive.js +142 -0
  96. package/dist/providers/claude/interactive.js.map +1 -0
  97. package/dist/providers/codex/event-mapper.d.ts +91 -0
  98. package/dist/providers/codex/event-mapper.d.ts.map +1 -0
  99. package/dist/providers/codex/event-mapper.js +299 -0
  100. package/dist/providers/codex/event-mapper.js.map +1 -0
  101. package/dist/providers/codex/headless.d.ts +20 -0
  102. package/dist/providers/codex/headless.d.ts.map +1 -0
  103. package/dist/providers/codex/headless.js +174 -0
  104. package/dist/providers/codex/headless.js.map +1 -0
  105. package/dist/providers/codex/index.d.ts +30 -0
  106. package/dist/providers/codex/index.d.ts.map +1 -0
  107. package/dist/providers/codex/index.js +55 -0
  108. package/dist/providers/codex/index.js.map +1 -0
  109. package/dist/providers/codex/interactive.d.ts +21 -0
  110. package/dist/providers/codex/interactive.d.ts.map +1 -0
  111. package/dist/providers/codex/interactive.js +141 -0
  112. package/dist/providers/codex/interactive.js.map +1 -0
  113. package/dist/providers/codex/jsonrpc-client.d.ts +52 -0
  114. package/dist/providers/codex/jsonrpc-client.d.ts.map +1 -0
  115. package/dist/providers/codex/jsonrpc-client.js +141 -0
  116. package/dist/providers/codex/jsonrpc-client.js.map +1 -0
  117. package/dist/providers/codex/server-manager.d.ts +100 -0
  118. package/dist/providers/codex/server-manager.d.ts.map +1 -0
  119. package/dist/providers/codex/server-manager.js +153 -0
  120. package/dist/providers/codex/server-manager.js.map +1 -0
  121. package/dist/providers/index.d.ts +15 -0
  122. package/dist/providers/index.d.ts.map +1 -0
  123. package/dist/providers/index.js +19 -0
  124. package/dist/providers/index.js.map +1 -0
  125. package/dist/providers/opencode/async-queue.d.ts +21 -0
  126. package/dist/providers/opencode/async-queue.d.ts.map +1 -0
  127. package/dist/providers/opencode/async-queue.js +51 -0
  128. package/dist/providers/opencode/async-queue.js.map +1 -0
  129. package/dist/providers/opencode/event-mapper.d.ts +132 -0
  130. package/dist/providers/opencode/event-mapper.d.ts.map +1 -0
  131. package/dist/providers/opencode/event-mapper.js +204 -0
  132. package/dist/providers/opencode/event-mapper.js.map +1 -0
  133. package/dist/providers/opencode/headless.d.ts +25 -0
  134. package/dist/providers/opencode/headless.d.ts.map +1 -0
  135. package/dist/providers/opencode/headless.js +190 -0
  136. package/dist/providers/opencode/headless.js.map +1 -0
  137. package/dist/providers/opencode/index.d.ts +33 -0
  138. package/dist/providers/opencode/index.d.ts.map +1 -0
  139. package/dist/providers/opencode/index.js +42 -0
  140. package/dist/providers/opencode/index.js.map +1 -0
  141. package/dist/providers/opencode/interactive.d.ts +21 -0
  142. package/dist/providers/opencode/interactive.d.ts.map +1 -0
  143. package/dist/providers/opencode/interactive.js +135 -0
  144. package/dist/providers/opencode/interactive.js.map +1 -0
  145. package/dist/providers/opencode/server-manager.d.ts +145 -0
  146. package/dist/providers/opencode/server-manager.d.ts.map +1 -0
  147. package/dist/providers/opencode/server-manager.js +163 -0
  148. package/dist/providers/opencode/server-manager.js.map +1 -0
  149. package/dist/providers/registry.d.ts +38 -0
  150. package/dist/providers/registry.d.ts.map +1 -0
  151. package/dist/providers/registry.js +82 -0
  152. package/dist/providers/registry.js.map +1 -0
  153. package/dist/providers/types.d.ts +144 -0
  154. package/dist/providers/types.d.ts.map +1 -0
  155. package/dist/providers/types.js +25 -0
  156. package/dist/providers/types.js.map +1 -0
  157. package/dist/runtime/event-utils.d.ts +8 -0
  158. package/dist/runtime/event-utils.d.ts.map +1 -0
  159. package/dist/runtime/event-utils.js +23 -0
  160. package/dist/runtime/event-utils.js.map +1 -0
  161. package/dist/runtime/handoff.d.ts +195 -0
  162. package/dist/runtime/handoff.d.ts.map +1 -0
  163. package/dist/runtime/handoff.js +332 -0
  164. package/dist/runtime/handoff.js.map +1 -0
  165. package/dist/runtime/index.d.ts +17 -0
  166. package/dist/runtime/index.d.ts.map +1 -0
  167. package/dist/runtime/index.js +60 -0
  168. package/dist/runtime/index.js.map +1 -0
  169. package/dist/runtime/message-mapper.d.ts +99 -0
  170. package/dist/runtime/message-mapper.d.ts.map +1 -0
  171. package/dist/runtime/message-mapper.js +202 -0
  172. package/dist/runtime/message-mapper.js.map +1 -0
  173. package/dist/runtime/predecessor-query.d.ts +212 -0
  174. package/dist/runtime/predecessor-query.d.ts.map +1 -0
  175. package/dist/runtime/predecessor-query.js +283 -0
  176. package/dist/runtime/predecessor-query.js.map +1 -0
  177. package/dist/runtime/session-manager.d.ts +466 -0
  178. package/dist/runtime/session-manager.d.ts.map +1 -0
  179. package/dist/runtime/session-manager.js +986 -0
  180. package/dist/runtime/session-manager.js.map +1 -0
  181. package/dist/runtime/spawner.d.ts +407 -0
  182. package/dist/runtime/spawner.d.ts.map +1 -0
  183. package/dist/runtime/spawner.js +781 -0
  184. package/dist/runtime/spawner.js.map +1 -0
  185. package/dist/server/config.d.ts +22 -0
  186. package/dist/server/config.d.ts.map +1 -0
  187. package/dist/server/config.js +59 -0
  188. package/dist/server/config.js.map +1 -0
  189. package/dist/server/daemon-state.d.ts +50 -0
  190. package/dist/server/daemon-state.d.ts.map +1 -0
  191. package/dist/server/daemon-state.js +100 -0
  192. package/dist/server/daemon-state.js.map +1 -0
  193. package/dist/server/events-websocket.d.ts +32 -0
  194. package/dist/server/events-websocket.d.ts.map +1 -0
  195. package/dist/server/events-websocket.js +96 -0
  196. package/dist/server/events-websocket.js.map +1 -0
  197. package/dist/server/formatters.d.ts +94 -0
  198. package/dist/server/formatters.d.ts.map +1 -0
  199. package/dist/server/formatters.js +142 -0
  200. package/dist/server/formatters.js.map +1 -0
  201. package/dist/server/index.d.ts +17 -0
  202. package/dist/server/index.d.ts.map +1 -0
  203. package/dist/server/index.js +153 -0
  204. package/dist/server/index.js.map +1 -0
  205. package/dist/server/lsp-websocket.d.ts +33 -0
  206. package/dist/server/lsp-websocket.d.ts.map +1 -0
  207. package/dist/server/lsp-websocket.js +161 -0
  208. package/dist/server/lsp-websocket.js.map +1 -0
  209. package/dist/server/routes/agents.d.ts +9 -0
  210. package/dist/server/routes/agents.d.ts.map +1 -0
  211. package/dist/server/routes/agents.js +369 -0
  212. package/dist/server/routes/agents.js.map +1 -0
  213. package/dist/server/routes/daemon.d.ts +13 -0
  214. package/dist/server/routes/daemon.d.ts.map +1 -0
  215. package/dist/server/routes/daemon.js +187 -0
  216. package/dist/server/routes/daemon.js.map +1 -0
  217. package/dist/server/routes/events.d.ts +23 -0
  218. package/dist/server/routes/events.d.ts.map +1 -0
  219. package/dist/server/routes/events.js +282 -0
  220. package/dist/server/routes/events.js.map +1 -0
  221. package/dist/server/routes/extensions.d.ts +9 -0
  222. package/dist/server/routes/extensions.d.ts.map +1 -0
  223. package/dist/server/routes/extensions.js +202 -0
  224. package/dist/server/routes/extensions.js.map +1 -0
  225. package/dist/server/routes/health.d.ts +7 -0
  226. package/dist/server/routes/health.d.ts.map +1 -0
  227. package/dist/server/routes/health.js +33 -0
  228. package/dist/server/routes/health.js.map +1 -0
  229. package/dist/server/routes/index.d.ts +21 -0
  230. package/dist/server/routes/index.d.ts.map +1 -0
  231. package/dist/server/routes/index.js +21 -0
  232. package/dist/server/routes/index.js.map +1 -0
  233. package/dist/server/routes/lsp.d.ts +9 -0
  234. package/dist/server/routes/lsp.d.ts.map +1 -0
  235. package/dist/server/routes/lsp.js +50 -0
  236. package/dist/server/routes/lsp.js.map +1 -0
  237. package/dist/server/routes/plugins.d.ts +9 -0
  238. package/dist/server/routes/plugins.d.ts.map +1 -0
  239. package/dist/server/routes/plugins.js +109 -0
  240. package/dist/server/routes/plugins.js.map +1 -0
  241. package/dist/server/routes/pools.d.ts +9 -0
  242. package/dist/server/routes/pools.d.ts.map +1 -0
  243. package/dist/server/routes/pools.js +189 -0
  244. package/dist/server/routes/pools.js.map +1 -0
  245. package/dist/server/routes/scheduler.d.ts +9 -0
  246. package/dist/server/routes/scheduler.d.ts.map +1 -0
  247. package/dist/server/routes/scheduler.js +162 -0
  248. package/dist/server/routes/scheduler.js.map +1 -0
  249. package/dist/server/routes/sessions.d.ts +27 -0
  250. package/dist/server/routes/sessions.d.ts.map +1 -0
  251. package/dist/server/routes/sessions.js +773 -0
  252. package/dist/server/routes/sessions.js.map +1 -0
  253. package/dist/server/routes/tasks.d.ts +9 -0
  254. package/dist/server/routes/tasks.d.ts.map +1 -0
  255. package/dist/server/routes/tasks.js +954 -0
  256. package/dist/server/routes/tasks.js.map +1 -0
  257. package/dist/server/routes/upload.d.ts +8 -0
  258. package/dist/server/routes/upload.d.ts.map +1 -0
  259. package/dist/server/routes/upload.js +40 -0
  260. package/dist/server/routes/upload.js.map +1 -0
  261. package/dist/server/routes/workflows.d.ts +9 -0
  262. package/dist/server/routes/workflows.d.ts.map +1 -0
  263. package/dist/server/routes/workflows.js +532 -0
  264. package/dist/server/routes/workflows.js.map +1 -0
  265. package/dist/server/routes/workspace-files.d.ts +12 -0
  266. package/dist/server/routes/workspace-files.d.ts.map +1 -0
  267. package/dist/server/routes/workspace-files.js +520 -0
  268. package/dist/server/routes/workspace-files.js.map +1 -0
  269. package/dist/server/routes/worktrees.d.ts +9 -0
  270. package/dist/server/routes/worktrees.d.ts.map +1 -0
  271. package/dist/server/routes/worktrees.js +94 -0
  272. package/dist/server/routes/worktrees.js.map +1 -0
  273. package/dist/server/server.d.ts +14 -0
  274. package/dist/server/server.d.ts.map +1 -0
  275. package/dist/server/server.js +258 -0
  276. package/dist/server/server.js.map +1 -0
  277. package/dist/server/services/lsp-manager.d.ts +93 -0
  278. package/dist/server/services/lsp-manager.d.ts.map +1 -0
  279. package/dist/server/services/lsp-manager.js +291 -0
  280. package/dist/server/services/lsp-manager.js.map +1 -0
  281. package/dist/server/services/session-messages.d.ts +61 -0
  282. package/dist/server/services/session-messages.d.ts.map +1 -0
  283. package/dist/server/services/session-messages.js +101 -0
  284. package/dist/server/services/session-messages.js.map +1 -0
  285. package/dist/server/services.d.ts +35 -0
  286. package/dist/server/services.d.ts.map +1 -0
  287. package/dist/server/services.js +159 -0
  288. package/dist/server/services.js.map +1 -0
  289. package/dist/server/static.d.ts +18 -0
  290. package/dist/server/static.d.ts.map +1 -0
  291. package/dist/server/static.js +71 -0
  292. package/dist/server/static.js.map +1 -0
  293. package/dist/server/types.d.ts +20 -0
  294. package/dist/server/types.d.ts.map +1 -0
  295. package/dist/server/types.js +7 -0
  296. package/dist/server/types.js.map +1 -0
  297. package/dist/server/websocket.d.ts +16 -0
  298. package/dist/server/websocket.d.ts.map +1 -0
  299. package/dist/server/websocket.js +143 -0
  300. package/dist/server/websocket.js.map +1 -0
  301. package/dist/services/agent-pool-service.d.ts +181 -0
  302. package/dist/services/agent-pool-service.d.ts.map +1 -0
  303. package/dist/services/agent-pool-service.js +590 -0
  304. package/dist/services/agent-pool-service.js.map +1 -0
  305. package/dist/services/agent-registry.d.ts +185 -0
  306. package/dist/services/agent-registry.d.ts.map +1 -0
  307. package/dist/services/agent-registry.js +432 -0
  308. package/dist/services/agent-registry.js.map +1 -0
  309. package/dist/services/dispatch-daemon.d.ts +429 -0
  310. package/dist/services/dispatch-daemon.d.ts.map +1 -0
  311. package/dist/services/dispatch-daemon.js +1833 -0
  312. package/dist/services/dispatch-daemon.js.map +1 -0
  313. package/dist/services/dispatch-service.d.ts +148 -0
  314. package/dist/services/dispatch-service.d.ts.map +1 -0
  315. package/dist/services/dispatch-service.js +170 -0
  316. package/dist/services/dispatch-service.js.map +1 -0
  317. package/dist/services/docs-steward-service.d.ts +199 -0
  318. package/dist/services/docs-steward-service.d.ts.map +1 -0
  319. package/dist/services/docs-steward-service.js +599 -0
  320. package/dist/services/docs-steward-service.js.map +1 -0
  321. package/dist/services/health-steward-service.d.ts +446 -0
  322. package/dist/services/health-steward-service.d.ts.map +1 -0
  323. package/dist/services/health-steward-service.js +866 -0
  324. package/dist/services/health-steward-service.js.map +1 -0
  325. package/dist/services/index.d.ts +26 -0
  326. package/dist/services/index.d.ts.map +1 -0
  327. package/dist/services/index.js +111 -0
  328. package/dist/services/index.js.map +1 -0
  329. package/dist/services/merge-request-provider.d.ts +59 -0
  330. package/dist/services/merge-request-provider.d.ts.map +1 -0
  331. package/dist/services/merge-request-provider.js +89 -0
  332. package/dist/services/merge-request-provider.js.map +1 -0
  333. package/dist/services/merge-steward-service.d.ts +268 -0
  334. package/dist/services/merge-steward-service.d.ts.map +1 -0
  335. package/dist/services/merge-steward-service.js +568 -0
  336. package/dist/services/merge-steward-service.js.map +1 -0
  337. package/dist/services/plugin-executor.d.ts +247 -0
  338. package/dist/services/plugin-executor.d.ts.map +1 -0
  339. package/dist/services/plugin-executor.js +451 -0
  340. package/dist/services/plugin-executor.js.map +1 -0
  341. package/dist/services/role-definition-service.d.ts +117 -0
  342. package/dist/services/role-definition-service.d.ts.map +1 -0
  343. package/dist/services/role-definition-service.js +289 -0
  344. package/dist/services/role-definition-service.js.map +1 -0
  345. package/dist/services/steward-scheduler.d.ts +336 -0
  346. package/dist/services/steward-scheduler.d.ts.map +1 -0
  347. package/dist/services/steward-scheduler.js +732 -0
  348. package/dist/services/steward-scheduler.js.map +1 -0
  349. package/dist/services/task-assignment-service.d.ts +291 -0
  350. package/dist/services/task-assignment-service.d.ts.map +1 -0
  351. package/dist/services/task-assignment-service.js +454 -0
  352. package/dist/services/task-assignment-service.js.map +1 -0
  353. package/dist/services/worker-task-service.d.ts +202 -0
  354. package/dist/services/worker-task-service.d.ts.map +1 -0
  355. package/dist/services/worker-task-service.js +228 -0
  356. package/dist/services/worker-task-service.js.map +1 -0
  357. package/dist/testing/index.d.ts +13 -0
  358. package/dist/testing/index.d.ts.map +1 -0
  359. package/dist/testing/index.js +17 -0
  360. package/dist/testing/index.js.map +1 -0
  361. package/dist/testing/orchestration-tests.d.ts +62 -0
  362. package/dist/testing/orchestration-tests.d.ts.map +1 -0
  363. package/dist/testing/orchestration-tests.js +1115 -0
  364. package/dist/testing/orchestration-tests.js.map +1 -0
  365. package/dist/testing/test-context.d.ts +171 -0
  366. package/dist/testing/test-context.d.ts.map +1 -0
  367. package/dist/testing/test-context.js +665 -0
  368. package/dist/testing/test-context.js.map +1 -0
  369. package/dist/testing/test-prompts.d.ts +46 -0
  370. package/dist/testing/test-prompts.d.ts.map +1 -0
  371. package/dist/testing/test-prompts.js +140 -0
  372. package/dist/testing/test-prompts.js.map +1 -0
  373. package/dist/testing/test-utils.d.ts +200 -0
  374. package/dist/testing/test-utils.d.ts.map +1 -0
  375. package/dist/testing/test-utils.js +378 -0
  376. package/dist/testing/test-utils.js.map +1 -0
  377. package/dist/types/agent-pool.d.ts +215 -0
  378. package/dist/types/agent-pool.d.ts.map +1 -0
  379. package/dist/types/agent-pool.js +143 -0
  380. package/dist/types/agent-pool.js.map +1 -0
  381. package/dist/types/agent.d.ts +265 -0
  382. package/dist/types/agent.d.ts.map +1 -0
  383. package/dist/types/agent.js +127 -0
  384. package/dist/types/agent.js.map +1 -0
  385. package/dist/types/index.d.ts +11 -0
  386. package/dist/types/index.d.ts.map +1 -0
  387. package/dist/types/index.js +40 -0
  388. package/dist/types/index.js.map +1 -0
  389. package/dist/types/message-types.d.ts +294 -0
  390. package/dist/types/message-types.d.ts.map +1 -0
  391. package/dist/types/message-types.js +354 -0
  392. package/dist/types/message-types.js.map +1 -0
  393. package/dist/types/role-definition.d.ts +272 -0
  394. package/dist/types/role-definition.d.ts.map +1 -0
  395. package/dist/types/role-definition.js +144 -0
  396. package/dist/types/role-definition.js.map +1 -0
  397. package/dist/types/task-meta.d.ts +248 -0
  398. package/dist/types/task-meta.d.ts.map +1 -0
  399. package/dist/types/task-meta.js +213 -0
  400. package/dist/types/task-meta.js.map +1 -0
  401. package/package.json +120 -0
  402. package/web/assets/abap-BrgZPUOV.js +6 -0
  403. package/web/assets/apex-DyP6w7ZV.js +6 -0
  404. package/web/assets/azcli-BaLxmfj-.js +6 -0
  405. package/web/assets/bat-CFOPXBzS.js +6 -0
  406. package/web/assets/bicep-BfEKNvv3.js +7 -0
  407. package/web/assets/cameligo-BFG1Mk7z.js +6 -0
  408. package/web/assets/clojure-DTECt2xU.js +6 -0
  409. package/web/assets/codicon-DCmgc-ay.ttf +0 -0
  410. package/web/assets/coffee-CDGzqUPQ.js +6 -0
  411. package/web/assets/cpp-CLLBncYj.js +6 -0
  412. package/web/assets/csharp-dUCx_-0o.js +6 -0
  413. package/web/assets/csp-5Rap-vPy.js +6 -0
  414. package/web/assets/css-D3h14YRZ.js +8 -0
  415. package/web/assets/cssMode-DMo-5YLA.js +9 -0
  416. package/web/assets/cypher-DrQuvNYM.js +6 -0
  417. package/web/assets/dart-CFKIUWau.js +6 -0
  418. package/web/assets/dockerfile-Zznr-cwX.js +6 -0
  419. package/web/assets/ecl-Ce3n6wWz.js +6 -0
  420. package/web/assets/elixir-deUWdS0T.js +6 -0
  421. package/web/assets/flow9-i9-g7ZhI.js +6 -0
  422. package/web/assets/freemarker2-D4qgkQzN.js +8 -0
  423. package/web/assets/fsharp-CzKuDChf.js +6 -0
  424. package/web/assets/go-Cphgjts3.js +6 -0
  425. package/web/assets/graphql-Cg7bfA9N.js +6 -0
  426. package/web/assets/handlebars-CXFvNjQC.js +6 -0
  427. package/web/assets/hcl-0cvrggvQ.js +6 -0
  428. package/web/assets/html-oyuB_D-B.js +6 -0
  429. package/web/assets/htmlMode-iWuZ24-r.js +9 -0
  430. package/web/assets/index-DqP-_E4F.css +32 -0
  431. package/web/assets/index-R1cylSgw.js +1665 -0
  432. package/web/assets/ini-Drc7WvVn.js +6 -0
  433. package/web/assets/java-B_fMsGYe.js +6 -0
  434. package/web/assets/javascript-CRIkN2Pg.js +6 -0
  435. package/web/assets/jsonMode-DVDkDgex.js +15 -0
  436. package/web/assets/julia-Bqgm2twL.js +6 -0
  437. package/web/assets/kotlin-BSkB5QuD.js +6 -0
  438. package/web/assets/less-BsTHnhdd.js +7 -0
  439. package/web/assets/lexon-YWi4-JPR.js +6 -0
  440. package/web/assets/liquid-CSfldbB5.js +6 -0
  441. package/web/assets/lua-nf6ki56Z.js +6 -0
  442. package/web/assets/m3-Cpb6xl2v.js +6 -0
  443. package/web/assets/markdown-DSZPf7rp.js +6 -0
  444. package/web/assets/mdx-Dd58iymR.js +6 -0
  445. package/web/assets/mips-B_c3zf-v.js +6 -0
  446. package/web/assets/monaco-editor-B4lwqA13.js +751 -0
  447. package/web/assets/monaco-editor-CQpyCxOA.css +1 -0
  448. package/web/assets/msdax-rUNN04Wq.js +6 -0
  449. package/web/assets/mysql-DDwshQtU.js +6 -0
  450. package/web/assets/objective-c-B5zXfXm9.js +6 -0
  451. package/web/assets/pascal-CXOwvkN_.js +6 -0
  452. package/web/assets/pascaligo-Bc-ZgV77.js +6 -0
  453. package/web/assets/perl-CwNk8-XU.js +6 -0
  454. package/web/assets/pgsql-tGk8EFnU.js +6 -0
  455. package/web/assets/php-CpIb_Oan.js +6 -0
  456. package/web/assets/pla-B03wrqEc.js +6 -0
  457. package/web/assets/postiats-BKlk5iyT.js +6 -0
  458. package/web/assets/powerquery-Bhzvs7bI.js +6 -0
  459. package/web/assets/powershell-Dd3NCNK9.js +6 -0
  460. package/web/assets/protobuf-COyEY5Pt.js +7 -0
  461. package/web/assets/pug-BaJupSGV.js +6 -0
  462. package/web/assets/python-XWrMqdhO.js +6 -0
  463. package/web/assets/qsharp-DXyYeYxl.js +6 -0
  464. package/web/assets/r-CdQndTaG.js +6 -0
  465. package/web/assets/razor-DPlhCpIs.js +6 -0
  466. package/web/assets/redis-CVwtpugi.js +6 -0
  467. package/web/assets/redshift-25W9uPmb.js +6 -0
  468. package/web/assets/restructuredtext-DfzH4Xui.js +6 -0
  469. package/web/assets/router-vendor-DHlGizSU.js +41 -0
  470. package/web/assets/ruby-Cp1zYvxS.js +6 -0
  471. package/web/assets/rust-D5C2fndG.js +6 -0
  472. package/web/assets/sb-CDntyWJ8.js +6 -0
  473. package/web/assets/scala-BoFRg7Ot.js +6 -0
  474. package/web/assets/scheme-Bio4gycK.js +6 -0
  475. package/web/assets/scss-4Ik7cdeQ.js +8 -0
  476. package/web/assets/shell-CX-rkNHf.js +6 -0
  477. package/web/assets/solidity-Tw7wswEv.js +6 -0
  478. package/web/assets/sophia-C5WLch3f.js +6 -0
  479. package/web/assets/sparql-DHaeiCBh.js +6 -0
  480. package/web/assets/sql-CCSDG5nI.js +6 -0
  481. package/web/assets/st-pnP8ivHi.js +6 -0
  482. package/web/assets/swift-DwJ7jVG9.js +8 -0
  483. package/web/assets/systemverilog-B9Xyijhd.js +6 -0
  484. package/web/assets/tcl-DnHyzjbg.js +6 -0
  485. package/web/assets/tsMode-BbA1Jbf3.js +16 -0
  486. package/web/assets/twig-CPajHgWi.js +6 -0
  487. package/web/assets/typescript-DcLHYzvH.js +6 -0
  488. package/web/assets/typespec-D-MeaMDU.js +6 -0
  489. package/web/assets/ui-vendor-BSco96uv.js +51 -0
  490. package/web/assets/utils-vendor-DaJ2Dubl.js +911 -0
  491. package/web/assets/vb-DgyLZaXg.js +6 -0
  492. package/web/assets/wgsl-DYQUnd45.js +303 -0
  493. package/web/assets/xml-xKYS3dO6.js +6 -0
  494. package/web/assets/yaml-CNmlXqzH.js +6 -0
  495. package/web/favicon.ico +0 -0
  496. package/web/index.html +22 -0
  497. package/web/logo.png +0 -0
@@ -0,0 +1,986 @@
1
+ /**
2
+ * Session Manager Service
3
+ *
4
+ * This service manages agent sessions with provider session ID support for
5
+ * resumable sessions and cross-restart persistence.
6
+ *
7
+ * Key features:
8
+ * - Track active sessions with process metadata
9
+ * - Persist session state to database via Agent entity metadata
10
+ * - Start, resume, stop, and suspend sessions
11
+ * - Message running sessions via agent channels
12
+ * - Query session history per agent
13
+ *
14
+ * @module
15
+ */
16
+ import { EventEmitter } from 'node:events';
17
+ import { createTimestamp } from '@stoneforge/core';
18
+ import { getAgentMetadata } from '../api/orchestrator-api.js';
19
+ import { getProviderRegistry } from '../providers/registry.js';
20
+ import { trackListeners } from './event-utils.js';
21
+ // ============================================================================
22
+ // Session Manager Implementation
23
+ // ============================================================================
24
+ /**
25
+ * Implementation of the Session Manager.
26
+ */
27
+ export class SessionManagerImpl {
28
+ spawner;
29
+ api;
30
+ registry;
31
+ sessions = new Map();
32
+ agentSessions = new Map(); // agentId -> active sessionId
33
+ sessionHistory = new Map();
34
+ sessionCleanupFns = new Map(); // sessionId -> cleanup function
35
+ constructor(spawner, api, // Used for message operations in messageSession
36
+ registry) {
37
+ this.spawner = spawner;
38
+ this.api = api;
39
+ this.registry = registry;
40
+ // Subscribe to spawner events to track session state
41
+ this.setupSpawnerEventHandlers();
42
+ }
43
+ /**
44
+ * Gets the API instance for direct operations
45
+ */
46
+ getApi() {
47
+ return this.api;
48
+ }
49
+ // ----------------------------------------
50
+ // Session Lifecycle
51
+ // ----------------------------------------
52
+ async startSession(agentId, options) {
53
+ // Get agent to determine role and mode
54
+ const agent = await this.registry.getAgent(agentId);
55
+ if (!agent) {
56
+ throw new Error(`Agent not found: ${agentId}`);
57
+ }
58
+ const meta = getAgentMetadata(agent);
59
+ if (!meta) {
60
+ throw new Error(`Entity is not a valid agent: ${agentId}`);
61
+ }
62
+ // Check if agent already has an active session
63
+ const existingSessionId = this.agentSessions.get(agentId);
64
+ if (existingSessionId) {
65
+ const existingSession = this.sessions.get(existingSessionId);
66
+ if (existingSession && existingSession.status === 'running') {
67
+ throw new Error(`Agent ${agentId} already has an active session: ${existingSessionId}`);
68
+ }
69
+ }
70
+ // Determine interactive mode based on agent role if not explicitly specified
71
+ // Directors and persistent workers use interactive mode (PTY)
72
+ // Ephemeral workers and stewards use headless mode (stream-json)
73
+ const isInteractiveByRole = meta.agentRole === 'director' ||
74
+ (meta.agentRole === 'worker' && meta.workerMode === 'persistent');
75
+ const useInteractive = options?.interactive ?? isInteractiveByRole;
76
+ // Resolve provider from agent metadata
77
+ const providerName = meta.provider;
78
+ let providerOverride;
79
+ if (providerName && providerName !== 'claude') {
80
+ const registry = getProviderRegistry();
81
+ providerOverride = await registry.getOrThrow(providerName);
82
+ }
83
+ // Resolve model: use options override, or fall back to agent metadata
84
+ const modelOverride = options?.model ?? meta.model;
85
+ // Build spawn options
86
+ const spawnOptions = {
87
+ workingDirectory: options?.workingDirectory,
88
+ initialPrompt: options?.initialPrompt,
89
+ environmentVariables: options?.environmentVariables,
90
+ mode: useInteractive ? 'interactive' : 'headless',
91
+ cols: options?.cols,
92
+ rows: options?.rows,
93
+ provider: providerOverride,
94
+ model: modelOverride,
95
+ };
96
+ console.log('[session-manager] Starting session for agent', agentId, 'mode:', spawnOptions.mode, 'provider:', providerName ?? 'claude', 'model:', modelOverride ?? 'default', 'prompt length:', options?.initialPrompt?.length ?? 0);
97
+ // Spawn the session
98
+ const result = await this.spawner.spawn(agentId, meta.agentRole, spawnOptions);
99
+ // Create internal session state
100
+ const sessionState = this.createSessionState(result, agentId, meta, options);
101
+ // Track the session
102
+ this.sessions.set(sessionState.id, sessionState);
103
+ this.agentSessions.set(agentId, sessionState.id);
104
+ // Forward events from spawner BEFORE any awaits to avoid missing
105
+ // early exit events (e.g. when provider uses `exec` and the process
106
+ // terminates before the awaits below complete).
107
+ this.setupSessionEventForwarding(sessionState, result.events);
108
+ // Update agent's session status in database
109
+ await this.registry.updateAgentSession(agentId, result.session.providerSessionId, 'running');
110
+ // Persist session state
111
+ await this.persistSession(sessionState.id);
112
+ return {
113
+ session: this.toPublicSession(sessionState),
114
+ events: sessionState.events,
115
+ };
116
+ }
117
+ async resumeSession(agentId, options) {
118
+ // Get agent to determine role and mode
119
+ const agent = await this.registry.getAgent(agentId);
120
+ if (!agent) {
121
+ throw new Error(`Agent not found: ${agentId}`);
122
+ }
123
+ const meta = getAgentMetadata(agent);
124
+ if (!meta) {
125
+ throw new Error(`Entity is not a valid agent: ${agentId}`);
126
+ }
127
+ // Check if agent already has an active session
128
+ const existingSessionId = this.agentSessions.get(agentId);
129
+ if (existingSessionId) {
130
+ const existingSession = this.sessions.get(existingSessionId);
131
+ if (existingSession && existingSession.status === 'running') {
132
+ throw new Error(`Agent ${agentId} already has an active session: ${existingSessionId}`);
133
+ }
134
+ }
135
+ // Perform UWP check before resuming (if enabled)
136
+ // This implements the Universal Work Principle: check queue before continuing previous context
137
+ let uwpCheck;
138
+ const shouldCheckQueue = options.checkReadyQueue !== false; // Default to true
139
+ if (shouldCheckQueue && options.getReadyTasks) {
140
+ const readyTasks = await options.getReadyTasks(agentId, 1);
141
+ if (readyTasks.length > 0) {
142
+ const task = readyTasks[0];
143
+ uwpCheck = {
144
+ hasReadyTask: true,
145
+ taskId: task.id,
146
+ taskTitle: task.title,
147
+ taskPriority: task.priority,
148
+ shouldProcessFirst: true,
149
+ };
150
+ }
151
+ else {
152
+ uwpCheck = {
153
+ hasReadyTask: false,
154
+ shouldProcessFirst: false,
155
+ };
156
+ }
157
+ }
158
+ // Build the resume prompt, prepending task instructions if UWP found a task
159
+ let effectivePrompt = options.resumePrompt;
160
+ if (uwpCheck?.hasReadyTask && uwpCheck.shouldProcessFirst) {
161
+ const taskInstructions = this.buildUWPTaskPrompt(uwpCheck);
162
+ effectivePrompt = effectivePrompt
163
+ ? `${taskInstructions}\n\n${effectivePrompt}`
164
+ : taskInstructions;
165
+ }
166
+ // Look up the original session's working directory from history if not provided
167
+ let workingDirectory = options.workingDirectory;
168
+ if (!workingDirectory) {
169
+ const history = await this.getSessionHistory(agentId, 20);
170
+ const previousSession = history.find(h => h.providerSessionId === options.providerSessionId);
171
+ if (previousSession?.workingDirectory) {
172
+ workingDirectory = previousSession.workingDirectory;
173
+ }
174
+ }
175
+ // Resolve provider from agent metadata
176
+ const providerName = meta.provider;
177
+ let providerOverride;
178
+ if (providerName && providerName !== 'claude') {
179
+ const registry = getProviderRegistry();
180
+ providerOverride = await registry.getOrThrow(providerName);
181
+ }
182
+ // Resolve model from agent metadata (resume doesn't allow model override)
183
+ const modelFromMeta = meta.model;
184
+ // Build spawn options with resume
185
+ const spawnOptions = {
186
+ workingDirectory,
187
+ resumeSessionId: options.providerSessionId,
188
+ initialPrompt: effectivePrompt,
189
+ provider: providerOverride,
190
+ model: modelFromMeta,
191
+ };
192
+ // Spawn the session with resume
193
+ const result = await this.spawner.spawn(agentId, meta.agentRole, spawnOptions);
194
+ // Create internal session state
195
+ const sessionState = this.createSessionState(result, agentId, meta, {
196
+ workingDirectory: options.workingDirectory,
197
+ worktree: options.worktree,
198
+ });
199
+ // Track the session
200
+ this.sessions.set(sessionState.id, sessionState);
201
+ this.agentSessions.set(agentId, sessionState.id);
202
+ // Forward events from spawner BEFORE any awaits to avoid missing
203
+ // early exit events (e.g. when provider uses `exec` and the process
204
+ // terminates before the awaits below complete).
205
+ this.setupSessionEventForwarding(sessionState, result.events);
206
+ // Update agent's session status in database
207
+ await this.registry.updateAgentSession(agentId, options.providerSessionId, 'running');
208
+ // Persist session state
209
+ await this.persistSession(sessionState.id);
210
+ return {
211
+ session: this.toPublicSession(sessionState),
212
+ events: sessionState.events,
213
+ uwpCheck,
214
+ };
215
+ }
216
+ async stopSession(sessionId, options) {
217
+ const session = this.sessions.get(sessionId);
218
+ if (!session) {
219
+ throw new Error(`Session not found: ${sessionId}`);
220
+ }
221
+ // Clean up event listeners to prevent leaks
222
+ this.cleanupSessionEventListeners(sessionId);
223
+ // Update session state BEFORE terminating to prevent race with exit event handler
224
+ const updatedSession = {
225
+ ...session,
226
+ status: 'terminated',
227
+ endedAt: createTimestamp(),
228
+ terminationReason: options?.reason,
229
+ persisted: false,
230
+ };
231
+ this.sessions.set(sessionId, updatedSession);
232
+ // Clear active session for agent
233
+ if (this.agentSessions.get(session.agentId) === sessionId) {
234
+ this.agentSessions.delete(session.agentId);
235
+ }
236
+ // Add to history (do this before terminate to avoid race with exit handler)
237
+ this.addToHistory(session.agentId, updatedSession);
238
+ // Now terminate via spawner (may trigger exit event, but status already terminated)
239
+ await this.spawner.terminate(sessionId, options?.graceful ?? true);
240
+ // Update agent's session status in database
241
+ await this.registry.updateAgentSession(session.agentId, undefined, 'idle');
242
+ // Persist session state
243
+ await this.persistSession(sessionId);
244
+ // Schedule cleanup of terminated session from memory (M-1)
245
+ this.scheduleTerminatedSessionCleanup(sessionId);
246
+ // Emit status change
247
+ session.events.emit('status', 'terminated');
248
+ }
249
+ async interruptSession(sessionId) {
250
+ const session = this.sessions.get(sessionId);
251
+ if (!session) {
252
+ throw new Error(`Session not found: ${sessionId}`);
253
+ }
254
+ if (session.status !== 'running') {
255
+ throw new Error(`Cannot interrupt session in status: ${session.status}`);
256
+ }
257
+ // Interrupt via spawner
258
+ await this.spawner.interrupt(sessionId);
259
+ // Emit interrupt event
260
+ session.events.emit('interrupt');
261
+ }
262
+ async suspendSession(sessionId, reason) {
263
+ const session = this.sessions.get(sessionId);
264
+ if (!session) {
265
+ throw new Error(`Session not found: ${sessionId}`);
266
+ }
267
+ // Clean up event listeners to prevent leaks
268
+ this.cleanupSessionEventListeners(sessionId);
269
+ // Update session state BEFORE suspending to prevent race with exit event handler
270
+ const updatedSession = {
271
+ ...session,
272
+ status: 'suspended',
273
+ endedAt: createTimestamp(),
274
+ terminationReason: reason,
275
+ persisted: false,
276
+ };
277
+ this.sessions.set(sessionId, updatedSession);
278
+ // Clear active session for agent (but keep in sessions map for resume)
279
+ if (this.agentSessions.get(session.agentId) === sessionId) {
280
+ this.agentSessions.delete(session.agentId);
281
+ }
282
+ // Now suspend via spawner (may trigger exit event, but status already 'suspended')
283
+ try {
284
+ await this.spawner.suspend(sessionId);
285
+ }
286
+ catch (error) {
287
+ // Revert status on failure
288
+ this.sessions.set(sessionId, session);
289
+ if (this.agentSessions.get(session.agentId) !== sessionId) {
290
+ this.agentSessions.set(session.agentId, sessionId);
291
+ }
292
+ throw error;
293
+ }
294
+ // Add to history
295
+ this.addToHistory(session.agentId, updatedSession);
296
+ // Update agent's session status in database
297
+ await this.registry.updateAgentSession(session.agentId, session.providerSessionId, 'suspended');
298
+ // Persist session state
299
+ await this.persistSession(sessionId);
300
+ // Emit status change
301
+ updatedSession.events.emit('status', 'suspended');
302
+ }
303
+ // ----------------------------------------
304
+ // Session Queries
305
+ // ----------------------------------------
306
+ getSession(sessionId) {
307
+ const session = this.sessions.get(sessionId);
308
+ return session ? this.toPublicSession(session) : undefined;
309
+ }
310
+ getActiveSession(agentId) {
311
+ const sessionId = this.agentSessions.get(agentId);
312
+ if (!sessionId) {
313
+ return undefined;
314
+ }
315
+ const session = this.sessions.get(sessionId);
316
+ // Allow both 'starting' and 'running' status - 'starting' is needed for SSE
317
+ // connections that happen before the Claude CLI emits its init event
318
+ if (!session || (session.status !== 'running' && session.status !== 'starting')) {
319
+ return undefined;
320
+ }
321
+ // Validate that the process/session is actually alive
322
+ if (session.pid) {
323
+ // Interactive sessions: check OS process liveness
324
+ if (!this.isProcessAlive(session.pid)) {
325
+ this.cleanupDeadSession(session);
326
+ return undefined;
327
+ }
328
+ }
329
+ else {
330
+ // Headless sessions (no PID): cross-reference with spawner
331
+ const spawnerSession = this.spawner.getSession(sessionId);
332
+ if (!spawnerSession || spawnerSession.status === 'terminated') {
333
+ this.cleanupDeadSession(session);
334
+ return undefined;
335
+ }
336
+ }
337
+ return this.toPublicSession(session);
338
+ }
339
+ listSessions(filter) {
340
+ let sessions = Array.from(this.sessions.values());
341
+ // Validate liveness for sessions in active states.
342
+ // This catches sessions whose processes exited without the exit handler firing.
343
+ const activeStatuses = ['starting', 'running', 'terminating'];
344
+ let didCleanup = false;
345
+ for (const session of sessions) {
346
+ if (!activeStatuses.includes(session.status))
347
+ continue;
348
+ if (session.pid) {
349
+ // Interactive sessions: check if the OS process is still alive
350
+ if (!this.isProcessAlive(session.pid)) {
351
+ this.cleanupDeadSession(session);
352
+ didCleanup = true;
353
+ }
354
+ }
355
+ else {
356
+ // Headless sessions (no PID): cross-reference with the spawner.
357
+ // If the spawner no longer tracks this session, the process has exited
358
+ // and the exit event was lost or already processed by the spawner.
359
+ const spawnerSession = this.spawner.getSession(session.id);
360
+ if (!spawnerSession || spawnerSession.status === 'terminated') {
361
+ this.cleanupDeadSession(session);
362
+ didCleanup = true;
363
+ }
364
+ }
365
+ }
366
+ // Re-read after potential cleanups
367
+ if (didCleanup) {
368
+ sessions = Array.from(this.sessions.values());
369
+ }
370
+ if (filter) {
371
+ if (filter.agentId !== undefined) {
372
+ sessions = sessions.filter((s) => s.agentId === filter.agentId);
373
+ }
374
+ if (filter.role !== undefined) {
375
+ sessions = sessions.filter((s) => s.agentRole === filter.role);
376
+ }
377
+ if (filter.status !== undefined) {
378
+ const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
379
+ sessions = sessions.filter((s) => statuses.includes(s.status));
380
+ }
381
+ if (filter.startedAfter !== undefined) {
382
+ const afterTime = this.getTimestampMs(filter.startedAfter);
383
+ sessions = sessions.filter((s) => {
384
+ if (!s.startedAt)
385
+ return false;
386
+ return this.getTimestampMs(s.startedAt) >= afterTime;
387
+ });
388
+ }
389
+ if (filter.startedBefore !== undefined) {
390
+ const beforeTime = this.getTimestampMs(filter.startedBefore);
391
+ sessions = sessions.filter((s) => {
392
+ if (!s.startedAt)
393
+ return false;
394
+ return this.getTimestampMs(s.startedAt) <= beforeTime;
395
+ });
396
+ }
397
+ if (filter.resumable !== undefined) {
398
+ sessions = sessions.filter((s) => filter.resumable ? s.providerSessionId !== undefined : s.providerSessionId === undefined);
399
+ }
400
+ }
401
+ return sessions.map((s) => this.toPublicSession(s));
402
+ }
403
+ getMostRecentResumableSession(agentId) {
404
+ const agentSessions = Array.from(this.sessions.values())
405
+ .filter((s) => s.agentId === agentId && s.providerSessionId !== undefined)
406
+ .sort((a, b) => {
407
+ const aTime = this.getTimestampMs(a.createdAt);
408
+ const bTime = this.getTimestampMs(b.createdAt);
409
+ return bTime - aTime;
410
+ });
411
+ return agentSessions.length > 0 ? this.toPublicSession(agentSessions[0]) : undefined;
412
+ }
413
+ async getSessionHistory(agentId, limit = 10) {
414
+ // First, check in-memory history
415
+ const inMemoryHistory = this.sessionHistory.get(agentId) ?? [];
416
+ // Load from agent metadata if needed
417
+ const agent = await this.registry.getAgent(agentId);
418
+ if (!agent) {
419
+ return inMemoryHistory.slice(0, limit);
420
+ }
421
+ const meta = getAgentMetadata(agent);
422
+ if (!meta) {
423
+ return inMemoryHistory.slice(0, limit);
424
+ }
425
+ // Get persisted history from agent metadata
426
+ const persistedHistory = this.getPersistedHistory(agent);
427
+ // Merge and dedupe
428
+ const allHistory = this.mergeHistory(inMemoryHistory, persistedHistory);
429
+ return allHistory.slice(0, limit);
430
+ }
431
+ async getSessionHistoryByRole(role, limit = 10) {
432
+ // Get all agents with the specified role
433
+ const agents = await this.registry.getAgentsByRole(role);
434
+ // Collect all session history entries from all agents with this role
435
+ const allRoleHistory = [];
436
+ for (const agent of agents) {
437
+ // Agent.id is ElementId but we need EntityId for session history
438
+ const agentId = agent.id;
439
+ const agentHistory = await this.getSessionHistory(agentId, limit);
440
+ const agentMeta = getAgentMetadata(agent);
441
+ // Convert to role-based entries
442
+ for (const entry of agentHistory) {
443
+ allRoleHistory.push({
444
+ ...entry,
445
+ role: agentMeta?.agentRole ?? role,
446
+ agentId: agentId,
447
+ agentName: agent.name,
448
+ });
449
+ }
450
+ }
451
+ // Sort by ended or started time, most recent first
452
+ allRoleHistory.sort((a, b) => {
453
+ const aTime = a.endedAt
454
+ ? this.getTimestampMs(a.endedAt)
455
+ : a.startedAt
456
+ ? this.getTimestampMs(a.startedAt)
457
+ : 0;
458
+ const bTime = b.endedAt
459
+ ? this.getTimestampMs(b.endedAt)
460
+ : b.startedAt
461
+ ? this.getTimestampMs(b.startedAt)
462
+ : 0;
463
+ return bTime - aTime;
464
+ });
465
+ return allRoleHistory.slice(0, limit);
466
+ }
467
+ async getPreviousSession(role) {
468
+ // Get session history for the role
469
+ const roleHistory = await this.getSessionHistoryByRole(role, 100);
470
+ // Find the most recent session that has ended (suspended or terminated)
471
+ const previousSession = roleHistory.find((entry) => entry.status === 'suspended' || entry.status === 'terminated');
472
+ return previousSession;
473
+ }
474
+ // ----------------------------------------
475
+ // Session Communication
476
+ // ----------------------------------------
477
+ async messageSession(sessionId, options) {
478
+ const session = this.sessions.get(sessionId);
479
+ if (!session) {
480
+ return { success: false, error: `Session not found: ${sessionId}` };
481
+ }
482
+ // Validate options
483
+ if (!options.contentRef && !options.content) {
484
+ return { success: false, error: 'Either contentRef or content must be provided' };
485
+ }
486
+ // Get agent's channel
487
+ const agent = await this.registry.getAgent(session.agentId);
488
+ if (!agent) {
489
+ return { success: false, error: `Agent not found: ${session.agentId}` };
490
+ }
491
+ const meta = getAgentMetadata(agent);
492
+ const channelId = meta?.channelId;
493
+ if (!channelId) {
494
+ return { success: false, error: `Agent has no channel: ${session.agentId}` };
495
+ }
496
+ try {
497
+ // Resolve message content
498
+ let messageContent = options.content;
499
+ if (!messageContent && options.contentRef) {
500
+ const doc = await this.api.get(options.contentRef);
501
+ if (doc && 'content' in doc) {
502
+ messageContent = doc.content;
503
+ }
504
+ }
505
+ if (!messageContent) {
506
+ return { success: false, error: 'Could not resolve message content' };
507
+ }
508
+ // Format the message with sender context
509
+ const senderId = options.senderId ?? 'system';
510
+ const formattedMessage = `[Message from ${senderId}]: ${messageContent}`;
511
+ // Forward to the running process via spawner
512
+ if (session.mode === 'interactive') {
513
+ // Write the message content first
514
+ await this.spawner.writeToPty(sessionId, formattedMessage);
515
+ // Wait for the terminal to process the pasted content before sending Enter.
516
+ // The carriage return must be sent as a separate message to ensure proper submission.
517
+ // A longer delay (1500ms) is needed to ensure large messages are fully received
518
+ // by the PTY before the Enter key is sent.
519
+ await new Promise((resolve) => setTimeout(resolve, 1500));
520
+ await this.spawner.writeToPty(sessionId, '\r');
521
+ }
522
+ else {
523
+ await this.spawner.sendInput(sessionId, formattedMessage);
524
+ }
525
+ // Update session activity
526
+ const updatedSession = {
527
+ ...session,
528
+ lastActivityAt: createTimestamp(),
529
+ persisted: false,
530
+ };
531
+ this.sessions.set(sessionId, updatedSession);
532
+ return { success: true };
533
+ }
534
+ catch (error) {
535
+ const errorMessage = error instanceof Error ? error.message : String(error);
536
+ return { success: false, error: errorMessage };
537
+ }
538
+ }
539
+ getEventEmitter(sessionId) {
540
+ const session = this.sessions.get(sessionId);
541
+ return session?.events;
542
+ }
543
+ // ----------------------------------------
544
+ // User Idle Tracking
545
+ // ----------------------------------------
546
+ recordUserInput(sessionId) {
547
+ const session = this.sessions.get(sessionId);
548
+ if (session) {
549
+ session.lastUserInputAt = Date.now();
550
+ }
551
+ }
552
+ getSessionUserIdleMs(agentId) {
553
+ const sessionId = this.agentSessions.get(agentId);
554
+ if (!sessionId)
555
+ return undefined;
556
+ const session = this.sessions.get(sessionId);
557
+ if (!session || !session.lastUserInputAt)
558
+ return undefined;
559
+ return Date.now() - session.lastUserInputAt;
560
+ }
561
+ // ----------------------------------------
562
+ // Persistence
563
+ // ----------------------------------------
564
+ async persistSession(sessionId) {
565
+ const session = this.sessions.get(sessionId);
566
+ if (!session) {
567
+ return;
568
+ }
569
+ // Get current agent state
570
+ const agent = await this.registry.getAgent(session.agentId);
571
+ if (!agent) {
572
+ return;
573
+ }
574
+ // Get the current in-memory session history for this agent
575
+ const inMemoryHistory = this.sessionHistory.get(session.agentId) ?? [];
576
+ // Update agent metadata with session info and history
577
+ // This persists session history to database for cross-restart recovery
578
+ await this.registry.updateAgentMetadata(session.agentId, {
579
+ sessionId: session.providerSessionId,
580
+ sessionStatus: session.status === 'running' ? 'running' : session.status === 'suspended' ? 'suspended' : 'idle',
581
+ lastActivityAt: session.lastActivityAt,
582
+ // Persist session history (limited to 20 entries to avoid bloat)
583
+ sessionHistory: inMemoryHistory.slice(0, 20),
584
+ });
585
+ // Mark as persisted
586
+ const updatedSession = {
587
+ ...session,
588
+ persisted: true,
589
+ };
590
+ this.sessions.set(sessionId, updatedSession);
591
+ }
592
+ async loadSessionState(agentId) {
593
+ const agent = await this.registry.getAgent(agentId);
594
+ if (!agent) {
595
+ return;
596
+ }
597
+ const meta = getAgentMetadata(agent);
598
+ if (!meta) {
599
+ return;
600
+ }
601
+ // Load history from agent metadata
602
+ const persistedHistory = this.getPersistedHistory(agent);
603
+ if (persistedHistory.length > 0) {
604
+ this.sessionHistory.set(agentId, persistedHistory);
605
+ }
606
+ // Check if there's a suspended session that can be resumed
607
+ if (meta.sessionId && meta.sessionStatus === 'suspended') {
608
+ // Create a placeholder session record for the suspended session
609
+ const suspendedSession = persistedHistory.find((h) => h.providerSessionId === meta.sessionId && h.status === 'suspended');
610
+ if (suspendedSession) {
611
+ // Determine mode based on agent role - directors and persistent workers use interactive
612
+ const isInteractive = meta.agentRole === 'director' ||
613
+ (meta.agentRole === 'worker' && meta.workerMode === 'persistent');
614
+ const sessionState = {
615
+ id: suspendedSession.id,
616
+ providerSessionId: suspendedSession.providerSessionId,
617
+ agentId,
618
+ agentRole: meta.agentRole,
619
+ workerMode: meta.agentRole === 'worker' ? meta.workerMode : undefined,
620
+ mode: isInteractive ? 'interactive' : 'headless',
621
+ status: 'suspended',
622
+ workingDirectory: suspendedSession.workingDirectory,
623
+ worktree: suspendedSession.worktree,
624
+ createdAt: suspendedSession.startedAt ?? createTimestamp(),
625
+ startedAt: suspendedSession.startedAt,
626
+ lastActivityAt: suspendedSession.endedAt ?? createTimestamp(),
627
+ endedAt: suspendedSession.endedAt,
628
+ terminationReason: suspendedSession.terminationReason,
629
+ events: new EventEmitter(),
630
+ persisted: true,
631
+ };
632
+ this.sessions.set(sessionState.id, sessionState);
633
+ }
634
+ }
635
+ }
636
+ async reconcileOnStartup() {
637
+ let reconciled = 0;
638
+ const errors = [];
639
+ try {
640
+ // Find all agents that are marked as 'running' in the database
641
+ const agents = await this.registry.listAgents({ sessionStatus: 'running' });
642
+ for (const agent of agents) {
643
+ const agentId = agent.id;
644
+ const meta = getAgentMetadata(agent);
645
+ if (!meta)
646
+ continue;
647
+ // Check if there's a live in-memory session for this agent
648
+ const activeSessionId = this.agentSessions.get(agentId);
649
+ if (activeSessionId) {
650
+ const activeSession = this.sessions.get(activeSessionId);
651
+ if (activeSession && activeSession.status === 'running') {
652
+ // Session exists in memory and is running — check PID
653
+ if (activeSession.pid && this.isProcessAlive(activeSession.pid)) {
654
+ continue; // Process is alive, nothing to reconcile
655
+ }
656
+ }
657
+ }
658
+ // No live session — this agent is stale, reset to idle
659
+ try {
660
+ await this.registry.updateAgentSession(agentId, undefined, 'idle');
661
+ reconciled++;
662
+ }
663
+ catch (error) {
664
+ const msg = `Failed to reset agent ${agent.name} (${agentId}): ${error instanceof Error ? error.message : String(error)}`;
665
+ errors.push(msg);
666
+ }
667
+ }
668
+ }
669
+ catch (error) {
670
+ errors.push(`Failed to list agents: ${error instanceof Error ? error.message : String(error)}`);
671
+ }
672
+ return { reconciled, errors };
673
+ }
674
+ // ----------------------------------------
675
+ // Private Helpers
676
+ // ----------------------------------------
677
+ scheduleTerminatedSessionCleanup(sessionId) {
678
+ setTimeout(() => {
679
+ const session = this.sessions.get(sessionId);
680
+ if (session && session.status === 'terminated') {
681
+ this.sessions.delete(sessionId);
682
+ }
683
+ }, 5000);
684
+ }
685
+ /**
686
+ * Checks whether a process with the given PID is still alive.
687
+ * Uses signal 0 which doesn't kill — just checks existence.
688
+ */
689
+ isProcessAlive(pid) {
690
+ try {
691
+ process.kill(pid, 0);
692
+ return true;
693
+ }
694
+ catch {
695
+ return false;
696
+ }
697
+ }
698
+ /**
699
+ * Cleans up a session whose process is no longer alive.
700
+ * Transitions it to 'terminated' and schedules memory cleanup.
701
+ */
702
+ cleanupDeadSession(session) {
703
+ const updated = {
704
+ ...session,
705
+ status: 'terminated',
706
+ endedAt: createTimestamp(),
707
+ terminationReason: 'Process no longer alive (PID check)',
708
+ persisted: false,
709
+ };
710
+ this.sessions.set(session.id, updated);
711
+ if (this.agentSessions.get(session.agentId) === session.id) {
712
+ this.agentSessions.delete(session.agentId);
713
+ }
714
+ this.addToHistory(session.agentId, updated);
715
+ this.registry.updateAgentSession(session.agentId, undefined, 'idle').catch(() => { });
716
+ this.persistSession(session.id).then(() => {
717
+ this.scheduleTerminatedSessionCleanup(session.id);
718
+ }).catch(() => { });
719
+ }
720
+ setupSpawnerEventHandlers() {
721
+ // Note: The spawner emits events per-session via the session's event emitter
722
+ // We don't need global event handlers here
723
+ }
724
+ setupSessionEventForwarding(session, spawnerEvents) {
725
+ // Create named handler functions so we can remove them later
726
+ const onEvent = (event) => {
727
+ const current = this.sessions.get(session.id);
728
+ if (!current)
729
+ return;
730
+ current.events.emit('event', event);
731
+ // Update last activity
732
+ this.sessions.set(session.id, {
733
+ ...current,
734
+ lastActivityAt: createTimestamp(),
735
+ persisted: false,
736
+ });
737
+ };
738
+ const onPtyData = (data) => {
739
+ const current = this.sessions.get(session.id);
740
+ if (!current)
741
+ return;
742
+ current.events.emit('pty-data', data);
743
+ // Update last activity
744
+ this.sessions.set(session.id, {
745
+ ...current,
746
+ lastActivityAt: createTimestamp(),
747
+ persisted: false,
748
+ });
749
+ };
750
+ const onError = (error) => {
751
+ const current = this.sessions.get(session.id);
752
+ if (!current)
753
+ return;
754
+ current.events.emit('error', error);
755
+ };
756
+ const onStderr = (data) => {
757
+ const current = this.sessions.get(session.id);
758
+ if (!current)
759
+ return;
760
+ current.events.emit('stderr', data);
761
+ };
762
+ const onRaw = (data) => {
763
+ const current = this.sessions.get(session.id);
764
+ if (!current)
765
+ return;
766
+ current.events.emit('raw', data);
767
+ };
768
+ const onExit = async (code, signal) => {
769
+ console.log(`[session-manager] Exit event received for session ${session.id}, agent ${session.agentId}, code=${code}, signal=${signal}`);
770
+ // Clean up all spawner event listeners immediately to prevent leaks
771
+ this.cleanupSessionEventListeners(session.id, spawnerEvents);
772
+ const exitingSession = this.sessions.get(session.id);
773
+ if (exitingSession) {
774
+ exitingSession.events.emit('exit', code, signal);
775
+ }
776
+ // Update session status if not already updated
777
+ const currentSession = this.sessions.get(session.id);
778
+ if (currentSession && currentSession.status !== 'terminated' && currentSession.status !== 'suspended') {
779
+ console.log(`[session-manager] Cleaning up ${currentSession.status} session ${session.id} for agent ${session.agentId}`);
780
+ const updatedSession = {
781
+ ...currentSession,
782
+ status: 'terminated',
783
+ endedAt: createTimestamp(),
784
+ persisted: false,
785
+ };
786
+ this.sessions.set(session.id, updatedSession);
787
+ // Clear active session mapping
788
+ if (this.agentSessions.get(session.agentId) === session.id) {
789
+ this.agentSessions.delete(session.agentId);
790
+ console.log(`[session-manager] Cleared active session mapping for agent ${session.agentId}`);
791
+ }
792
+ // Add to history
793
+ this.addToHistory(session.agentId, updatedSession);
794
+ // Update agent status in registry to 'idle'
795
+ try {
796
+ await this.registry.updateAgentSession(session.agentId, undefined, 'idle');
797
+ console.log(`[session-manager] Updated agent ${session.agentId} status to idle in registry`);
798
+ }
799
+ catch (error) {
800
+ console.error(`[session-manager] Failed to update agent ${session.agentId} status:`, error);
801
+ }
802
+ // Persist session state
803
+ try {
804
+ await this.persistSession(session.id);
805
+ }
806
+ catch (error) {
807
+ console.error(`[session-manager] Failed to persist session ${session.id}:`, error);
808
+ }
809
+ // Schedule cleanup of terminated session from memory (M-1)
810
+ this.scheduleTerminatedSessionCleanup(session.id);
811
+ updatedSession.events.emit('status', 'terminated');
812
+ }
813
+ else {
814
+ console.log(`[session-manager] Session ${session.id} already in status: ${currentSession?.status ?? 'not found'}`);
815
+ }
816
+ };
817
+ // Update providerSessionId when discovered by the spawner
818
+ const onProviderSessionId = (providerSessionId) => {
819
+ const current = this.sessions.get(session.id);
820
+ if (!current || current.providerSessionId)
821
+ return;
822
+ this.sessions.set(session.id, {
823
+ ...current,
824
+ providerSessionId,
825
+ persisted: false,
826
+ });
827
+ // Persist session so providerSessionId survives a server restart
828
+ this.registry.updateAgentSession(session.agentId, providerSessionId, 'running').catch((err) => {
829
+ console.error(`[session-manager] Failed to persist providerSessionId for ${session.id}:`, err);
830
+ });
831
+ this.persistSession(session.id).catch((err) => {
832
+ console.error(`[session-manager] Failed to persist session after providerSessionId for ${session.id}:`, err);
833
+ });
834
+ };
835
+ // Attach all event listeners with tracked maxListeners
836
+ const cleanup = trackListeners(spawnerEvents, {
837
+ 'event': onEvent,
838
+ 'pty-data': onPtyData,
839
+ 'error': onError,
840
+ 'stderr': onStderr,
841
+ 'raw': onRaw,
842
+ 'exit': onExit,
843
+ 'provider-session-id': onProviderSessionId,
844
+ });
845
+ this.sessionCleanupFns.set(session.id, cleanup);
846
+ }
847
+ /**
848
+ * Cleans up event listeners for a session.
849
+ * Called when a session exits to prevent listener leaks.
850
+ */
851
+ cleanupSessionEventListeners(sessionId, _spawnerEvents) {
852
+ const cleanup = this.sessionCleanupFns.get(sessionId);
853
+ if (cleanup) {
854
+ cleanup();
855
+ this.sessionCleanupFns.delete(sessionId);
856
+ console.log(`[session-manager] Cleaned up event listeners for session ${sessionId}`);
857
+ }
858
+ }
859
+ createSessionState(result, agentId, meta, options) {
860
+ return {
861
+ id: result.session.id,
862
+ providerSessionId: result.session.providerSessionId,
863
+ agentId,
864
+ agentRole: meta.agentRole,
865
+ workerMode: meta.agentRole === 'worker' ? meta.workerMode : undefined,
866
+ mode: result.session.mode,
867
+ pid: result.session.pid,
868
+ status: result.session.status,
869
+ workingDirectory: result.session.workingDirectory,
870
+ worktree: options?.worktree,
871
+ createdAt: result.session.createdAt,
872
+ startedAt: result.session.startedAt,
873
+ lastActivityAt: result.session.lastActivityAt,
874
+ events: new EventEmitter(),
875
+ persisted: false,
876
+ };
877
+ }
878
+ toPublicSession(session) {
879
+ return {
880
+ id: session.id,
881
+ providerSessionId: session.providerSessionId,
882
+ agentId: session.agentId,
883
+ agentRole: session.agentRole,
884
+ workerMode: session.workerMode,
885
+ mode: session.mode,
886
+ pid: session.pid,
887
+ status: session.status,
888
+ workingDirectory: session.workingDirectory,
889
+ worktree: session.worktree,
890
+ createdAt: session.createdAt,
891
+ startedAt: session.startedAt,
892
+ lastActivityAt: session.lastActivityAt,
893
+ endedAt: session.endedAt,
894
+ terminationReason: session.terminationReason,
895
+ };
896
+ }
897
+ addToHistory(agentId, session) {
898
+ const historyEntry = {
899
+ id: session.id,
900
+ providerSessionId: session.providerSessionId,
901
+ status: session.status,
902
+ workingDirectory: session.workingDirectory,
903
+ worktree: session.worktree,
904
+ startedAt: session.startedAt,
905
+ endedAt: session.endedAt,
906
+ terminationReason: session.terminationReason,
907
+ };
908
+ const history = this.sessionHistory.get(agentId) ?? [];
909
+ this.sessionHistory.set(agentId, [historyEntry, ...history.filter((h) => h.id !== session.id)].slice(0, 20));
910
+ }
911
+ getPersistedHistory(agent) {
912
+ // Session history is stored under metadata.agent.sessionHistory
913
+ const agentMeta = agent.metadata?.agent;
914
+ const sessionHistory = agentMeta?.sessionHistory;
915
+ if (!Array.isArray(sessionHistory)) {
916
+ return [];
917
+ }
918
+ // Validate and type-cast
919
+ return sessionHistory.filter((entry) => {
920
+ if (typeof entry !== 'object' || entry === null)
921
+ return false;
922
+ const e = entry;
923
+ return (typeof e.id === 'string' &&
924
+ typeof e.status === 'string' &&
925
+ typeof e.workingDirectory === 'string');
926
+ });
927
+ }
928
+ mergeHistory(inMemory, persisted) {
929
+ const seen = new Set();
930
+ const merged = [];
931
+ // In-memory history takes precedence (more recent)
932
+ for (const entry of inMemory) {
933
+ if (!seen.has(entry.id)) {
934
+ seen.add(entry.id);
935
+ merged.push(entry);
936
+ }
937
+ }
938
+ // Add persisted entries not in memory
939
+ for (const entry of persisted) {
940
+ if (!seen.has(entry.id)) {
941
+ seen.add(entry.id);
942
+ merged.push(entry);
943
+ }
944
+ }
945
+ // Sort by start time descending
946
+ return merged.sort((a, b) => {
947
+ const aTime = a.startedAt ? this.getTimestampMs(a.startedAt) : 0;
948
+ const bTime = b.startedAt ? this.getTimestampMs(b.startedAt) : 0;
949
+ return bTime - aTime;
950
+ });
951
+ }
952
+ getTimestampMs(timestamp) {
953
+ return typeof timestamp === 'number' ? timestamp : new Date(timestamp).getTime();
954
+ }
955
+ /**
956
+ * Builds the UWP task prompt to instruct the agent to process the assigned task first.
957
+ * This ensures compliance with the Universal Work Principle: "If there is work on your anchor, YOU MUST RUN IT"
958
+ */
959
+ buildUWPTaskPrompt(uwpCheck) {
960
+ const parts = [
961
+ '**IMPORTANT: Task Assigned During Suspension**',
962
+ '',
963
+ 'Before continuing with any previous context, you must first process the following assigned task:',
964
+ '',
965
+ `- Task ID: ${uwpCheck.taskId}`,
966
+ ];
967
+ if (uwpCheck.taskTitle) {
968
+ parts.push(`- Title: ${uwpCheck.taskTitle}`);
969
+ }
970
+ if (uwpCheck.taskPriority !== undefined) {
971
+ parts.push(`- Priority: ${uwpCheck.taskPriority}`);
972
+ }
973
+ parts.push('', 'Please check the task details and begin working on it immediately.', 'Use `sf task get ' + uwpCheck.taskId + '` to retrieve full task details.');
974
+ return parts.join('\n');
975
+ }
976
+ }
977
+ // ============================================================================
978
+ // Factory Function
979
+ // ============================================================================
980
+ /**
981
+ * Creates a SessionManager instance
982
+ */
983
+ export function createSessionManager(spawner, api, registry) {
984
+ return new SessionManagerImpl(spawner, api, registry);
985
+ }
986
+ //# sourceMappingURL=session-manager.js.map