agent-tempo 1.0.1

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 (484) hide show
  1. package/CLAUDE.md +213 -0
  2. package/LICENSE +21 -0
  3. package/README.md +289 -0
  4. package/assets/icon-32.png +0 -0
  5. package/assets/icon-512.png +0 -0
  6. package/assets/icon-64.png +0 -0
  7. package/assets/icon-dark-32.png +0 -0
  8. package/assets/icon-dark-64.png +0 -0
  9. package/assets/icon-dark.svg +9 -0
  10. package/assets/icon.svg +9 -0
  11. package/assets/logo-dark.svg +11 -0
  12. package/assets/logo-light.svg +11 -0
  13. package/dashboard/README.md +91 -0
  14. package/dashboard/dist/assets/index-CB78ToNE.css +2 -0
  15. package/dashboard/dist/assets/index-_5jV0Znu.js +62 -0
  16. package/dashboard/dist/assets/index-_5jV0Znu.js.map +1 -0
  17. package/dashboard/dist/index.html +21 -0
  18. package/dashboard/package.json +47 -0
  19. package/dist/activities/hard-terminate.d.ts +32 -0
  20. package/dist/activities/hard-terminate.js +460 -0
  21. package/dist/activities/maestro.d.ts +72 -0
  22. package/dist/activities/maestro.js +254 -0
  23. package/dist/activities/outbox.d.ts +188 -0
  24. package/dist/activities/outbox.js +849 -0
  25. package/dist/activities/resolve.d.ts +64 -0
  26. package/dist/activities/resolve.js +129 -0
  27. package/dist/activities/schedule-fire.d.ts +36 -0
  28. package/dist/activities/schedule-fire.js +147 -0
  29. package/dist/adapters/base.d.ts +426 -0
  30. package/dist/adapters/base.js +1270 -0
  31. package/dist/adapters/claude-api/adapter.d.ts +168 -0
  32. package/dist/adapters/claude-api/adapter.js +797 -0
  33. package/dist/adapters/claude-api/api-error.d.ts +96 -0
  34. package/dist/adapters/claude-api/api-error.js +191 -0
  35. package/dist/adapters/claude-api/index.d.ts +16 -0
  36. package/dist/adapters/claude-api/index.js +21 -0
  37. package/dist/adapters/claude-api/mcp-bridge.d.ts +50 -0
  38. package/dist/adapters/claude-api/mcp-bridge.js +157 -0
  39. package/dist/adapters/claude-code/adapter.d.ts +133 -0
  40. package/dist/adapters/claude-code/adapter.js +274 -0
  41. package/dist/adapters/claude-code/index.d.ts +15 -0
  42. package/dist/adapters/claude-code/index.js +20 -0
  43. package/dist/adapters/claude-code-headless/adapter.d.ts +131 -0
  44. package/dist/adapters/claude-code-headless/adapter.js +710 -0
  45. package/dist/adapters/claude-code-headless/error-mapper.d.ts +107 -0
  46. package/dist/adapters/claude-code-headless/error-mapper.js +281 -0
  47. package/dist/adapters/claude-code-headless/index.d.ts +17 -0
  48. package/dist/adapters/claude-code-headless/index.js +26 -0
  49. package/dist/adapters/claude-code-headless/pre-flight.d.ts +51 -0
  50. package/dist/adapters/claude-code-headless/pre-flight.js +207 -0
  51. package/dist/adapters/claude-code-headless/prompt.d.ts +93 -0
  52. package/dist/adapters/claude-code-headless/prompt.js +79 -0
  53. package/dist/adapters/claude-code-headless/stream-json.d.ts +242 -0
  54. package/dist/adapters/claude-code-headless/stream-json.js +208 -0
  55. package/dist/adapters/claude-code-headless/types.d.ts +28 -0
  56. package/dist/adapters/claude-code-headless/types.js +36 -0
  57. package/dist/adapters/copilot/adapter.d.ts +100 -0
  58. package/dist/adapters/copilot/adapter.js +730 -0
  59. package/dist/adapters/copilot/index.d.ts +15 -0
  60. package/dist/adapters/copilot/index.js +20 -0
  61. package/dist/adapters/index.d.ts +42 -0
  62. package/dist/adapters/index.js +115 -0
  63. package/dist/adapters/opencode/adapter.d.ts +82 -0
  64. package/dist/adapters/opencode/adapter.js +710 -0
  65. package/dist/adapters/opencode/config.d.ts +90 -0
  66. package/dist/adapters/opencode/config.js +137 -0
  67. package/dist/adapters/opencode/helpers.d.ts +40 -0
  68. package/dist/adapters/opencode/helpers.js +144 -0
  69. package/dist/adapters/opencode/index.d.ts +12 -0
  70. package/dist/adapters/opencode/index.js +17 -0
  71. package/dist/adapters/opencode/server-bridge.d.ts +124 -0
  72. package/dist/adapters/opencode/server-bridge.js +216 -0
  73. package/dist/adapters/sdk/base.d.ts +95 -0
  74. package/dist/adapters/sdk/base.js +134 -0
  75. package/dist/adapters/sdk/system-prompt.d.ts +64 -0
  76. package/dist/adapters/sdk/system-prompt.js +78 -0
  77. package/dist/adapters/terminal-error.d.ts +27 -0
  78. package/dist/adapters/terminal-error.js +39 -0
  79. package/dist/channel.d.ts +3 -0
  80. package/dist/channel.js +48 -0
  81. package/dist/cli/commands.d.ts +245 -0
  82. package/dist/cli/commands.js +2438 -0
  83. package/dist/cli/config-command.d.ts +8 -0
  84. package/dist/cli/config-command.js +254 -0
  85. package/dist/cli/daemon-command.d.ts +57 -0
  86. package/dist/cli/daemon-command.js +493 -0
  87. package/dist/cli/daemon.d.ts +217 -0
  88. package/dist/cli/daemon.js +632 -0
  89. package/dist/cli/dashboard-command.d.ts +20 -0
  90. package/dist/cli/dashboard-command.js +241 -0
  91. package/dist/cli/dev-banner.d.ts +107 -0
  92. package/dist/cli/dev-banner.js +190 -0
  93. package/dist/cli/dev-mode-bootstrap.d.ts +29 -0
  94. package/dist/cli/dev-mode-bootstrap.js +36 -0
  95. package/dist/cli/dev-verbs.d.ts +43 -0
  96. package/dist/cli/dev-verbs.js +254 -0
  97. package/dist/cli/help-text.d.ts +1 -0
  98. package/dist/cli/help-text.js +158 -0
  99. package/dist/cli/legacy-migration.d.ts +35 -0
  100. package/dist/cli/legacy-migration.js +335 -0
  101. package/dist/cli/mcp.d.ts +8 -0
  102. package/dist/cli/mcp.js +63 -0
  103. package/dist/cli/output.d.ts +12 -0
  104. package/dist/cli/output.js +37 -0
  105. package/dist/cli/preflight.d.ts +9 -0
  106. package/dist/cli/preflight.js +96 -0
  107. package/dist/cli/removed-verbs.d.ts +9 -0
  108. package/dist/cli/removed-verbs.js +78 -0
  109. package/dist/cli/sa-preflight.d.ts +99 -0
  110. package/dist/cli/sa-preflight.js +183 -0
  111. package/dist/cli/scenarios-command.d.ts +6 -0
  112. package/dist/cli/scenarios-command.js +167 -0
  113. package/dist/cli/startup.d.ts +112 -0
  114. package/dist/cli/startup.js +641 -0
  115. package/dist/cli/upgrade-command.d.ts +5 -0
  116. package/dist/cli/upgrade-command.js +240 -0
  117. package/dist/cli.d.ts +2 -0
  118. package/dist/cli.js +680 -0
  119. package/dist/client/core.d.ts +33 -0
  120. package/dist/client/core.js +1260 -0
  121. package/dist/client/ensure-conductor-spawned.d.ts +35 -0
  122. package/dist/client/ensure-conductor-spawned.js +48 -0
  123. package/dist/client/index.d.ts +32 -0
  124. package/dist/client/index.js +22 -0
  125. package/dist/client/interface.d.ts +461 -0
  126. package/dist/client/interface.js +2 -0
  127. package/dist/client/subscribe.d.ts +108 -0
  128. package/dist/client/subscribe.js +598 -0
  129. package/dist/client/with-spawn.d.ts +27 -0
  130. package/dist/client/with-spawn.js +87 -0
  131. package/dist/config.d.ts +323 -0
  132. package/dist/config.js +593 -0
  133. package/dist/connection.d.ts +7 -0
  134. package/dist/connection.js +46 -0
  135. package/dist/constants.d.ts +50 -0
  136. package/dist/constants.js +74 -0
  137. package/dist/copilot-bridge.d.ts +22 -0
  138. package/dist/copilot-bridge.js +565 -0
  139. package/dist/daemon-adapter-versions.d.ts +52 -0
  140. package/dist/daemon-adapter-versions.js +170 -0
  141. package/dist/daemon.d.ts +275 -0
  142. package/dist/daemon.js +989 -0
  143. package/dist/ensemble/agent-types.d.ts +23 -0
  144. package/dist/ensemble/agent-types.js +132 -0
  145. package/dist/ensemble/loader.d.ts +14 -0
  146. package/dist/ensemble/loader.js +140 -0
  147. package/dist/ensemble/saver.d.ts +49 -0
  148. package/dist/ensemble/saver.js +201 -0
  149. package/dist/ensemble/schema.d.ts +71 -0
  150. package/dist/ensemble/schema.js +3 -0
  151. package/dist/git-info.d.ts +4 -0
  152. package/dist/git-info.js +29 -0
  153. package/dist/http/aggregate.d.ts +319 -0
  154. package/dist/http/aggregate.js +684 -0
  155. package/dist/http/auth.d.ts +67 -0
  156. package/dist/http/auth.js +177 -0
  157. package/dist/http/body.d.ts +71 -0
  158. package/dist/http/body.js +121 -0
  159. package/dist/http/catalog.d.ts +67 -0
  160. package/dist/http/catalog.js +209 -0
  161. package/dist/http/cors.d.ts +42 -0
  162. package/dist/http/cors.js +111 -0
  163. package/dist/http/dashboard-pair.d.ts +94 -0
  164. package/dist/http/dashboard-pair.js +148 -0
  165. package/dist/http/dashboard.d.ts +20 -0
  166. package/dist/http/dashboard.js +160 -0
  167. package/dist/http/event-bus.d.ts +217 -0
  168. package/dist/http/event-bus.js +365 -0
  169. package/dist/http/event-id.d.ts +77 -0
  170. package/dist/http/event-id.js +117 -0
  171. package/dist/http/event-types.d.ts +348 -0
  172. package/dist/http/event-types.js +36 -0
  173. package/dist/http/fixtures/chat-stress.d.ts +8 -0
  174. package/dist/http/fixtures/chat-stress.js +63 -0
  175. package/dist/http/fixtures/conductor-leaving.d.ts +8 -0
  176. package/dist/http/fixtures/conductor-leaving.js +80 -0
  177. package/dist/http/fixtures/constants.d.ts +10 -0
  178. package/dist/http/fixtures/constants.js +13 -0
  179. package/dist/http/fixtures/eight-player-broadcast.d.ts +10 -0
  180. package/dist/http/fixtures/eight-player-broadcast.js +81 -0
  181. package/dist/http/fixtures/empty-ensemble.d.ts +6 -0
  182. package/dist/http/fixtures/empty-ensemble.js +26 -0
  183. package/dist/http/fixtures/index.d.ts +55 -0
  184. package/dist/http/fixtures/index.js +110 -0
  185. package/dist/http/fixtures/single-conductor.d.ts +7 -0
  186. package/dist/http/fixtures/single-conductor.js +46 -0
  187. package/dist/http/fixtures/sse-reconnect.d.ts +8 -0
  188. package/dist/http/fixtures/sse-reconnect.js +77 -0
  189. package/dist/http/index.d.ts +21 -0
  190. package/dist/http/index.js +61 -0
  191. package/dist/http/port-file.d.ts +22 -0
  192. package/dist/http/port-file.js +132 -0
  193. package/dist/http/responses.d.ts +27 -0
  194. package/dist/http/responses.js +40 -0
  195. package/dist/http/ring-buffer.d.ts +41 -0
  196. package/dist/http/ring-buffer.js +80 -0
  197. package/dist/http/server.d.ts +122 -0
  198. package/dist/http/server.js +459 -0
  199. package/dist/http/snapshot.d.ts +85 -0
  200. package/dist/http/snapshot.js +180 -0
  201. package/dist/http/sse-handler.d.ts +87 -0
  202. package/dist/http/sse-handler.js +294 -0
  203. package/dist/http/writes.d.ts +55 -0
  204. package/dist/http/writes.js +240 -0
  205. package/dist/palette/index.d.ts +138 -0
  206. package/dist/palette/index.js +221 -0
  207. package/dist/reconcile/orphans.d.ts +255 -0
  208. package/dist/reconcile/orphans.js +340 -0
  209. package/dist/scripts/258-spotcheck.js +303 -0
  210. package/dist/scripts/check-components-css-sync.js +199 -0
  211. package/dist/scripts/run-shard.js +121 -0
  212. package/dist/scripts/verify-daemon-isolation-guard.js +128 -0
  213. package/dist/server-tools.d.ts +87 -0
  214. package/dist/server-tools.js +146 -0
  215. package/dist/server.d.ts +2 -0
  216. package/dist/server.js +366 -0
  217. package/dist/spawn.d.ts +296 -0
  218. package/dist/spawn.js +747 -0
  219. package/dist/tools/agent-types.d.ts +2 -0
  220. package/dist/tools/agent-types.js +21 -0
  221. package/dist/tools/attachment-info.d.ts +4 -0
  222. package/dist/tools/attachment-info.js +48 -0
  223. package/dist/tools/broadcast.d.ts +4 -0
  224. package/dist/tools/broadcast.js +76 -0
  225. package/dist/tools/cancel-stage.d.ts +3 -0
  226. package/dist/tools/cancel-stage.js +20 -0
  227. package/dist/tools/clear-state.d.ts +3 -0
  228. package/dist/tools/clear-state.js +37 -0
  229. package/dist/tools/coat-check-evict.d.ts +4 -0
  230. package/dist/tools/coat-check-evict.js +43 -0
  231. package/dist/tools/coat-check-get.d.ts +4 -0
  232. package/dist/tools/coat-check-get.js +56 -0
  233. package/dist/tools/coat-check-list.d.ts +4 -0
  234. package/dist/tools/coat-check-list.js +60 -0
  235. package/dist/tools/coat-check-put.d.ts +4 -0
  236. package/dist/tools/coat-check-put.js +53 -0
  237. package/dist/tools/cue.d.ts +44 -0
  238. package/dist/tools/cue.js +201 -0
  239. package/dist/tools/destroy.d.ts +4 -0
  240. package/dist/tools/destroy.js +188 -0
  241. package/dist/tools/detach.d.ts +4 -0
  242. package/dist/tools/detach.js +45 -0
  243. package/dist/tools/encore.d.ts +4 -0
  244. package/dist/tools/encore.js +31 -0
  245. package/dist/tools/ensemble.d.ts +32 -0
  246. package/dist/tools/ensemble.js +198 -0
  247. package/dist/tools/evaluate-gate.d.ts +3 -0
  248. package/dist/tools/evaluate-gate.js +32 -0
  249. package/dist/tools/fetch-state.d.ts +13 -0
  250. package/dist/tools/fetch-state.js +78 -0
  251. package/dist/tools/gates.d.ts +3 -0
  252. package/dist/tools/gates.js +41 -0
  253. package/dist/tools/helpers.d.ts +21 -0
  254. package/dist/tools/helpers.js +25 -0
  255. package/dist/tools/hosts.d.ts +4 -0
  256. package/dist/tools/hosts.js +40 -0
  257. package/dist/tools/listen.d.ts +3 -0
  258. package/dist/tools/listen.js +22 -0
  259. package/dist/tools/load-lineup.d.ts +5 -0
  260. package/dist/tools/load-lineup.js +381 -0
  261. package/dist/tools/migrate.d.ts +4 -0
  262. package/dist/tools/migrate.js +60 -0
  263. package/dist/tools/pause-ensemble.d.ts +4 -0
  264. package/dist/tools/pause-ensemble.js +58 -0
  265. package/dist/tools/pause.d.ts +4 -0
  266. package/dist/tools/pause.js +36 -0
  267. package/dist/tools/play.d.ts +4 -0
  268. package/dist/tools/play.js +57 -0
  269. package/dist/tools/quality-gate.d.ts +3 -0
  270. package/dist/tools/quality-gate.js +26 -0
  271. package/dist/tools/recall.d.ts +3 -0
  272. package/dist/tools/recall.js +32 -0
  273. package/dist/tools/recruit.d.ts +38 -0
  274. package/dist/tools/recruit.js +447 -0
  275. package/dist/tools/release.d.ts +4 -0
  276. package/dist/tools/release.js +98 -0
  277. package/dist/tools/report.d.ts +3 -0
  278. package/dist/tools/report.js +29 -0
  279. package/dist/tools/resolve.d.ts +1 -0
  280. package/dist/tools/resolve.js +7 -0
  281. package/dist/tools/restart.d.ts +35 -0
  282. package/dist/tools/restart.js +131 -0
  283. package/dist/tools/restore.d.ts +4 -0
  284. package/dist/tools/restore.js +107 -0
  285. package/dist/tools/resume-ensemble.d.ts +4 -0
  286. package/dist/tools/resume-ensemble.js +79 -0
  287. package/dist/tools/save-lineup.d.ts +4 -0
  288. package/dist/tools/save-lineup.js +36 -0
  289. package/dist/tools/save-state.d.ts +3 -0
  290. package/dist/tools/save-state.js +57 -0
  291. package/dist/tools/schedule.d.ts +4 -0
  292. package/dist/tools/schedule.js +152 -0
  293. package/dist/tools/schedules.d.ts +4 -0
  294. package/dist/tools/schedules.js +54 -0
  295. package/dist/tools/set-ensemble-description.d.ts +4 -0
  296. package/dist/tools/set-ensemble-description.js +37 -0
  297. package/dist/tools/set-name.d.ts +4 -0
  298. package/dist/tools/set-name.js +45 -0
  299. package/dist/tools/set-part.d.ts +3 -0
  300. package/dist/tools/set-part.js +20 -0
  301. package/dist/tools/shutdown.d.ts +4 -0
  302. package/dist/tools/shutdown.js +54 -0
  303. package/dist/tools/stage.d.ts +3 -0
  304. package/dist/tools/stage.js +28 -0
  305. package/dist/tools/stages.d.ts +3 -0
  306. package/dist/tools/stages.js +35 -0
  307. package/dist/tools/stop.d.ts +4 -0
  308. package/dist/tools/stop.js +29 -0
  309. package/dist/tools/unschedule.d.ts +4 -0
  310. package/dist/tools/unschedule.js +35 -0
  311. package/dist/tools/who-am-i.d.ts +3 -0
  312. package/dist/tools/who-am-i.js +34 -0
  313. package/dist/tools/worktree.d.ts +4 -0
  314. package/dist/tools/worktree.js +181 -0
  315. package/dist/tui/App.d.ts +85 -0
  316. package/dist/tui/App.js +1791 -0
  317. package/dist/tui/bootstrap-types.d.ts +46 -0
  318. package/dist/tui/bootstrap-types.js +7 -0
  319. package/dist/tui/client.d.ts +6 -0
  320. package/dist/tui/client.js +9 -0
  321. package/dist/tui/commands.d.ts +71 -0
  322. package/dist/tui/commands.js +1375 -0
  323. package/dist/tui/components/ActivityLog.d.ts +16 -0
  324. package/dist/tui/components/ActivityLog.js +36 -0
  325. package/dist/tui/components/ChatView.d.ts +35 -0
  326. package/dist/tui/components/ChatView.js +54 -0
  327. package/dist/tui/components/CommandOverlay.d.ts +15 -0
  328. package/dist/tui/components/CommandOverlay.js +34 -0
  329. package/dist/tui/components/CommandPalette.d.ts +21 -0
  330. package/dist/tui/components/CommandPalette.js +67 -0
  331. package/dist/tui/components/ConductorChat.d.ts +16 -0
  332. package/dist/tui/components/ConductorChat.js +32 -0
  333. package/dist/tui/components/ConversationStream.d.ts +114 -0
  334. package/dist/tui/components/ConversationStream.js +307 -0
  335. package/dist/tui/components/CreateEnsembleWizard.d.ts +19 -0
  336. package/dist/tui/components/CreateEnsembleWizard.js +223 -0
  337. package/dist/tui/components/DestroyConfirmModal.d.ts +17 -0
  338. package/dist/tui/components/DestroyConfirmModal.js +62 -0
  339. package/dist/tui/components/EnsembleListView.d.ts +14 -0
  340. package/dist/tui/components/EnsembleListView.js +32 -0
  341. package/dist/tui/components/EnsemblePanel.d.ts +12 -0
  342. package/dist/tui/components/EnsemblePanel.js +40 -0
  343. package/dist/tui/components/ErrorView.d.ts +31 -0
  344. package/dist/tui/components/ErrorView.js +129 -0
  345. package/dist/tui/components/HomeView.d.ts +54 -0
  346. package/dist/tui/components/HomeView.js +306 -0
  347. package/dist/tui/components/InputBar.d.ts +13 -0
  348. package/dist/tui/components/InputBar.js +58 -0
  349. package/dist/tui/components/LoadLineupModal.d.ts +18 -0
  350. package/dist/tui/components/LoadLineupModal.js +79 -0
  351. package/dist/tui/components/MainView.d.ts +21 -0
  352. package/dist/tui/components/MainView.js +107 -0
  353. package/dist/tui/components/NewEnsembleModal.d.ts +9 -0
  354. package/dist/tui/components/NewEnsembleModal.js +73 -0
  355. package/dist/tui/components/Picker.d.ts +23 -0
  356. package/dist/tui/components/Picker.js +70 -0
  357. package/dist/tui/components/PlayerDetailView.d.ts +26 -0
  358. package/dist/tui/components/PlayerDetailView.js +118 -0
  359. package/dist/tui/components/PromptArea.d.ts +50 -0
  360. package/dist/tui/components/PromptArea.js +303 -0
  361. package/dist/tui/components/RecruitWizard.d.ts +17 -0
  362. package/dist/tui/components/RecruitWizard.js +221 -0
  363. package/dist/tui/components/RestoreConfirmModal.d.ts +18 -0
  364. package/dist/tui/components/RestoreConfirmModal.js +71 -0
  365. package/dist/tui/components/ScheduleOverlay.d.ts +13 -0
  366. package/dist/tui/components/ScheduleOverlay.js +113 -0
  367. package/dist/tui/components/ScheduleWizard.d.ts +19 -0
  368. package/dist/tui/components/ScheduleWizard.js +259 -0
  369. package/dist/tui/components/Splash.d.ts +23 -0
  370. package/dist/tui/components/Splash.js +221 -0
  371. package/dist/tui/components/StatusBar.d.ts +48 -0
  372. package/dist/tui/components/StatusBar.js +128 -0
  373. package/dist/tui/components/StatusOverlay.d.ts +15 -0
  374. package/dist/tui/components/StatusOverlay.js +76 -0
  375. package/dist/tui/components/TitleBar.d.ts +10 -0
  376. package/dist/tui/components/TitleBar.js +21 -0
  377. package/dist/tui/components/TopBar.d.ts +12 -0
  378. package/dist/tui/components/TopBar.js +15 -0
  379. package/dist/tui/core-api.d.ts +26 -0
  380. package/dist/tui/core-api.js +67 -0
  381. package/dist/tui/hooks/useEnsembleDiscovery.d.ts +3 -0
  382. package/dist/tui/hooks/useEnsembleDiscovery.js +30 -0
  383. package/dist/tui/hooks/useMaestroPoller.d.ts +3 -0
  384. package/dist/tui/hooks/useMaestroPoller.js +36 -0
  385. package/dist/tui/hooks/useSendCommand.d.ts +7 -0
  386. package/dist/tui/hooks/useSendCommand.js +29 -0
  387. package/dist/tui/index.d.ts +15 -0
  388. package/dist/tui/index.js +156 -0
  389. package/dist/tui/ink-context.d.ts +18 -0
  390. package/dist/tui/ink-context.js +59 -0
  391. package/dist/tui/ink-loader.d.ts +26 -0
  392. package/dist/tui/ink-loader.js +42 -0
  393. package/dist/tui/removed-commands.d.ts +9 -0
  394. package/dist/tui/removed-commands.js +22 -0
  395. package/dist/tui/sse-handler.d.ts +52 -0
  396. package/dist/tui/sse-handler.js +157 -0
  397. package/dist/tui/store.d.ts +598 -0
  398. package/dist/tui/store.js +753 -0
  399. package/dist/tui/utils/format.d.ts +56 -0
  400. package/dist/tui/utils/format.js +155 -0
  401. package/dist/tui/utils/fullscreen.d.ts +23 -0
  402. package/dist/tui/utils/fullscreen.js +71 -0
  403. package/dist/tui/utils/history.d.ts +10 -0
  404. package/dist/tui/utils/history.js +85 -0
  405. package/dist/tui/utils/platform.d.ts +45 -0
  406. package/dist/tui/utils/platform.js +258 -0
  407. package/dist/tui/utils/theme.d.ts +21 -0
  408. package/dist/tui/utils/theme.js +24 -0
  409. package/dist/types.d.ts +1020 -0
  410. package/dist/types.js +39 -0
  411. package/dist/utils/attachment-format.d.ts +22 -0
  412. package/dist/utils/attachment-format.js +32 -0
  413. package/dist/utils/default-part.d.ts +43 -0
  414. package/dist/utils/default-part.js +104 -0
  415. package/dist/utils/duration.d.ts +30 -0
  416. package/dist/utils/duration.js +69 -0
  417. package/dist/utils/ensemble-ops.d.ts +61 -0
  418. package/dist/utils/ensemble-ops.js +77 -0
  419. package/dist/utils/format-hosts.d.ts +21 -0
  420. package/dist/utils/format-hosts.js +73 -0
  421. package/dist/utils/hosts.d.ts +113 -0
  422. package/dist/utils/hosts.js +265 -0
  423. package/dist/utils/parent-death-watchdog.d.ts +1 -0
  424. package/dist/utils/parent-death-watchdog.js +47 -0
  425. package/dist/utils/query-timeout.d.ts +103 -0
  426. package/dist/utils/query-timeout.js +113 -0
  427. package/dist/utils/recall-format.d.ts +78 -0
  428. package/dist/utils/recall-format.js +105 -0
  429. package/dist/utils/restore-format.d.ts +49 -0
  430. package/dist/utils/restore-format.js +91 -0
  431. package/dist/utils/safe-path.d.ts +10 -0
  432. package/dist/utils/safe-path.js +43 -0
  433. package/dist/utils/sdk-probe.d.ts +9 -0
  434. package/dist/utils/sdk-probe.js +45 -0
  435. package/dist/utils/search-attributes.d.ts +76 -0
  436. package/dist/utils/search-attributes.js +86 -0
  437. package/dist/utils/validation.d.ts +113 -0
  438. package/dist/utils/validation.js +163 -0
  439. package/dist/utils/visibility-deadline.d.ts +186 -0
  440. package/dist/utils/visibility-deadline.js +158 -0
  441. package/dist/utils/worktree.d.ts +103 -0
  442. package/dist/utils/worktree.js +327 -0
  443. package/dist/worker.d.ts +14 -0
  444. package/dist/worker.js +146 -0
  445. package/dist/workflows/attachment-math.d.ts +56 -0
  446. package/dist/workflows/attachment-math.js +47 -0
  447. package/dist/workflows/index.d.ts +3 -0
  448. package/dist/workflows/index.js +11 -0
  449. package/dist/workflows/maestro-signals.d.ts +217 -0
  450. package/dist/workflows/maestro-signals.js +155 -0
  451. package/dist/workflows/maestro.d.ts +3 -0
  452. package/dist/workflows/maestro.js +812 -0
  453. package/dist/workflows/scheduler-signals.d.ts +10 -0
  454. package/dist/workflows/scheduler-signals.js +14 -0
  455. package/dist/workflows/scheduler.d.ts +17 -0
  456. package/dist/workflows/scheduler.js +143 -0
  457. package/dist/workflows/session.d.ts +2 -0
  458. package/dist/workflows/session.js +1638 -0
  459. package/dist/workflows/signals.d.ts +297 -0
  460. package/dist/workflows/signals.js +239 -0
  461. package/examples/agents/tempo-composer.md +56 -0
  462. package/examples/agents/tempo-conductor.md +117 -0
  463. package/examples/agents/tempo-critic.md +73 -0
  464. package/examples/agents/tempo-improv.md +74 -0
  465. package/examples/agents/tempo-liner.md +75 -0
  466. package/examples/agents/tempo-roadie.md +61 -0
  467. package/examples/agents/tempo-soloist.md +71 -0
  468. package/examples/agents/tempo-tuner.md +94 -0
  469. package/examples/ensembles/tempo-big-band.yaml +146 -0
  470. package/examples/ensembles/tempo-dev-team.yaml +58 -0
  471. package/examples/ensembles/tempo-headless-jam.yaml +77 -0
  472. package/examples/ensembles/tempo-jam-session.yaml +41 -0
  473. package/examples/ensembles/tempo-mock-jam.yaml +79 -0
  474. package/examples/ensembles/tempo-review-squad.yaml +32 -0
  475. package/package.json +172 -0
  476. package/packaging/launchd/com.agent.tempo.plist +46 -0
  477. package/packaging/systemd/agent-tempo.service +32 -0
  478. package/packaging/windows/install-task.ps1 +71 -0
  479. package/scenarios/conductor-recruit-mock.yaml +33 -0
  480. package/scenarios/echo-roundtrip.yaml +15 -0
  481. package/scenarios/multi-player-handoff.yaml +38 -0
  482. package/scenarios/recruit-cascade.yaml +38 -0
  483. package/scenarios/two-player-conversation.yaml +33 -0
  484. package/workflow-bundle.js +14146 -0
@@ -0,0 +1,710 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OpenCodeAttachment = exports.opencodeDescriptor = void 0;
37
+ /**
38
+ * Headless OpenCode adapter — SDK class.
39
+ *
40
+ * Issue #449 Phase C. Drives [SST OpenCode](https://opencode.ai) as a
41
+ * headless local subprocess for multi-provider LLM access (Anthropic,
42
+ * OpenAI, Bedrock, Vertex, Ollama, ~70+ providers via OpenCode's
43
+ * `provider/model` selector). Mirrors the claude-api / Copilot bridge
44
+ * structure: detached Node subprocess, dual-purpose entry point,
45
+ * `claimAttachment` + heartbeat lifecycle inherited from `SdkAttachment`.
46
+ *
47
+ * Class hierarchy: `OpenCodeAttachment extends SdkAttachment extends BaseAttachment`.
48
+ * Concrete adapter overrides `invokeSdk` (HTTP/SSE round-trip via
49
+ * `OpenCodeServerBridge`) and `onSuperseded` (graceful `POST /session/:id/abort`
50
+ * with subprocess-kill fallback). Everything else (claim, heartbeat,
51
+ * phase watcher, `processingStart`/`End` pairing, `markDelivered`) is free.
52
+ *
53
+ * What's NEW vs claude-api / copilot:
54
+ * - Adapter manages a sibling `opencode serve` subprocess (probed-free
55
+ * port, hardcoded loopback bind, `mdns: false`).
56
+ * - Tool bridging is MCP-NATIVE — OpenCode spawns `dist/server.js` as
57
+ * its own MCP child via the `OPENCODE_CONFIG_CONTENT` env. No
58
+ * in-process MCP bridge / no schema translation layer.
59
+ * - Server-side history — adapter sends only the new turn's parts;
60
+ * OpenCode appends to its own session record.
61
+ * - PID file is two-line (adapter PID + opencode-serve PID).
62
+ *
63
+ * Design reference: `docs/design/449-opencode-adapter.md`. ADR locked at
64
+ * `docs/adr/0015-opencode-adapter.md`.
65
+ */
66
+ const fs = __importStar(require("fs"));
67
+ const os = __importStar(require("os"));
68
+ const path = __importStar(require("path"));
69
+ const child_process_1 = require("child_process");
70
+ const client_1 = require("@temporalio/client");
71
+ const base_1 = require("../sdk/base");
72
+ const config_1 = require("../../config");
73
+ const connection_1 = require("../../connection");
74
+ const signals_1 = require("../../workflows/signals");
75
+ const server_tools_1 = require("../../server-tools");
76
+ const config_2 = require("./config");
77
+ const server_bridge_1 = require("./server-bridge");
78
+ const helpers_1 = require("./helpers");
79
+ const sdk_probe_1 = require("../../utils/sdk-probe");
80
+ /**
81
+ * Descriptor for the opencode adapter. Colocated with the class so
82
+ * `adapter.ts` has no import dependency on `index.ts` (avoids the
83
+ * circular module-graph cycle QA flagged on copilot's PR-B).
84
+ */
85
+ exports.opencodeDescriptor = {
86
+ adapterId: 'opencode',
87
+ adapterClass: 'sdk',
88
+ blocksOnLLMTurn: true,
89
+ heartbeatMs: 30_000,
90
+ };
91
+ /**
92
+ * Optional-dep gate. The adapter uses raw `fetch` for the hot path (per
93
+ * design §8.3 #2) so the SDK isn't actually imported, but the install
94
+ * presence is the operator's signal that opencode integration is intended
95
+ * on this host. ADR 0015 §85.
96
+ *
97
+ * `probeSdkInstall` walks the filesystem rather than calling
98
+ * `require.resolve` because `@opencode-ai/sdk` ships an ESM-only
99
+ * `exports` map with no CJS-resolvable entry — see the helper's docblock.
100
+ */
101
+ const opencodeSdkAvailable = (0, sdk_probe_1.probeSdkInstall)('@opencode-ai/sdk');
102
+ if (!opencodeSdkAvailable && require.main === module) {
103
+ console.error('Error: @opencode-ai/sdk is not installed.\n' +
104
+ 'Install it with: npm install @opencode-ai/sdk\n' +
105
+ 'And install the opencode binary: npm install -g opencode-ai\n' +
106
+ 'Or recruit with a different agent (claude / copilot / claude-api).');
107
+ process.exit(1);
108
+ }
109
+ /**
110
+ * Unbuffered stderr logger. Errors are unpacked into `message + stack`
111
+ * because the default `JSON.stringify(err)` drops both fields (they're
112
+ * non-enumerable) and renders `Error` as `{}` — useless for debugging.
113
+ */
114
+ const log = (...args) => {
115
+ const msg = `[agent-tempo:opencode] ${args.map((a) => {
116
+ if (typeof a === 'string')
117
+ return a;
118
+ if (a instanceof Error)
119
+ return a.stack ? `${a.message}\n${a.stack}` : a.message;
120
+ try {
121
+ return JSON.stringify(a);
122
+ }
123
+ catch {
124
+ return String(a);
125
+ }
126
+ }).join(' ')}\n`;
127
+ fs.writeSync(2, msg);
128
+ };
129
+ /** Default model id — reviewable at next minor SDK bump per ADR 0015 §51. */
130
+ const DEFAULT_MODEL = 'anthropic/claude-opus-4-7';
131
+ /** Tested-pinned OpenCode SDK version — drift triggers a stderr WARNING. */
132
+ const TESTED_OPENCODE_VERSION = '~1.14.29';
133
+ /** Idle poll cadence — short enough for snappy cue delivery, loose on Temporal. */
134
+ const POLL_INTERVAL_MS = 2000;
135
+ /** Workflow-register poll bounds. */
136
+ const WORKFLOW_REGISTER_ATTEMPTS = 30;
137
+ const WORKFLOW_REGISTER_INTERVAL_MS = 1000;
138
+ /** Workflow status check cadence (every N polls). */
139
+ const WORKFLOW_STATUS_CHECK_INTERVAL = 15;
140
+ /** Per-turn timeout — opencode tool-use chains can run minutes. */
141
+ const TURN_TIMEOUT_MS = 5 * 60 * 1000;
142
+ /** opencode serve health-probe timeout. */
143
+ const HEALTH_PROBE_TIMEOUT_MS = 10_000;
144
+ /** Subprocess SIGTERM grace before SIGKILL escalation. */
145
+ const SIGTERM_TIMEOUT_MS = 5_000;
146
+ /** OpenCode-specific system-prompt addendum — design §10. */
147
+ const HEADLESS_OPENCODE_ADDENDUM = '\n\nYou are an **opencode** player — you have access to the agent-tempo MCP tools ' +
148
+ '(cue, report, recall, ensemble, broadcast, recruit, set_part, …) AND OpenCode\'s built-in ' +
149
+ 'tools (file edits, shell, web search). Use the agent-tempo tools for ensemble coordination ' +
150
+ 'and OpenCode\'s built-ins for local task work. Your model is delivered via OpenCode, so the ' +
151
+ 'underlying provider (Anthropic, OpenAI, Bedrock, Ollama, …) is opaque to you and to the rest ' +
152
+ 'of the ensemble.';
153
+ /**
154
+ * SDK-class adapter for OpenCode. Pull-based delivery: poll workflow for
155
+ * pending messages, post each batch to `prompt_async`, observe SSE until
156
+ * `finish`, ack via `markDelivered`. `processingStart`/`End` paired by
157
+ * `SdkAttachment.deliver()`.
158
+ */
159
+ class OpenCodeAttachment extends base_1.SdkAttachment {
160
+ descriptor = exports.opencodeDescriptor;
161
+ /** Resolved model id (recruit-arg → ENV.OPENCODE_MODEL → DEFAULT_MODEL). */
162
+ model;
163
+ /** Probed-free port the opencode serve subprocess binds. */
164
+ port = 0;
165
+ /** Subprocess handle — `null` before run() spawns, after cleanup kills. */
166
+ serveProcess = null;
167
+ /** HTTP/SSE bridge — `null` before run() boots the subprocess. */
168
+ bridge = null;
169
+ /** OpenCode session id — created on first turn, stashed on workflow metadata. */
170
+ openCodeSessionId = null;
171
+ /** AbortController for the in-flight SSE consumer / prompt_async fetch. */
172
+ inFlightAbortController = null;
173
+ /** Cached for the per-turn telemetry log. */
174
+ playerName = '';
175
+ /** Built once at session start; sent every turn (cheap; OpenCode caches server-side). */
176
+ systemPrompt = '';
177
+ constructor(opts = {}) {
178
+ super();
179
+ this.model = opts.model ?? process.env[config_1.ENV.OPENCODE_MODEL] ?? DEFAULT_MODEL;
180
+ }
181
+ /**
182
+ * Lease-revocation hook. Two-step graceful → SIGTERM → SIGKILL fallback
183
+ * per design §6.1: post `/session/:id/abort` first (cleanest, no
184
+ * subprocess kill), abort in-flight fetch via `inFlightAbortController`,
185
+ * fall back to subprocess kill if HTTP abort hangs.
186
+ */
187
+ onSuperseded() {
188
+ log('lease revoked — aborting in-flight + posting /session/abort');
189
+ const ctrl = this.inFlightAbortController;
190
+ this.inFlightAbortController = null;
191
+ if (ctrl) {
192
+ try {
193
+ ctrl.abort();
194
+ }
195
+ catch (err) {
196
+ log('abort threw:', err?.message ?? err);
197
+ }
198
+ }
199
+ if (this.openCodeSessionId && this.bridge) {
200
+ // Fire-and-forget — the SSE consumer above will tear down on its
201
+ // own; this just nudges OpenCode to release the session.
202
+ void this.bridge.abortSession(this.openCodeSessionId)
203
+ .catch((err) => log('graceful abort failed:', err?.message ?? err));
204
+ }
205
+ }
206
+ /**
207
+ * Subprocess entry point. Boots `opencode serve`, connects Temporal,
208
+ * claims the attachment, runs the poll loop. The optional-dep gate at
209
+ * module load (above) has already exited the process if the SDK isn't
210
+ * installed, so by the time `run()` executes we know it's available.
211
+ */
212
+ async run() {
213
+ const config = (0, config_1.getConfig)();
214
+ const isConductor = process.env[config_1.ENV.CONDUCTOR] === 'true';
215
+ const requestedName = process.env[config_1.ENV.PLAYER_NAME] || '';
216
+ const playerIdForWorkflow = isConductor
217
+ ? 'conductor'
218
+ : (requestedName && requestedName !== 'conductor' ? requestedName : '') || `opencode-${Date.now()}`;
219
+ const expectedWorkflowId = `agent-session-${config.ensemble}-${playerIdForWorkflow}`;
220
+ const workDir = process.cwd();
221
+ log(`Starting opencode adapter in ${workDir} (ensemble: ${config.ensemble}, player: ${playerIdForWorkflow}, model: ${this.model})`);
222
+ // (1) Probe a free port on loopback. Cheap insurance against port 4096
223
+ // collisions in CI / multi-ensemble setups.
224
+ this.port = await (0, helpers_1.probeFreePort)();
225
+ log(`Probed free port: ${this.port}`);
226
+ // (2) Synthesize OPENCODE_CONFIG_CONTENT. Provider env auto-detected
227
+ // from the model's `provider/...` prefix; secrets stay as `{env:VAR}`
228
+ // markers (OpenCode resolves at read time).
229
+ const mcpServerPath = path.resolve(__dirname, '../../server.js');
230
+ const configContent = (0, config_2.synthesizeOpenCodeConfig)({
231
+ model: this.model,
232
+ port: this.port,
233
+ mcpServerPath,
234
+ ensemble: config.ensemble,
235
+ playerName: playerIdForWorkflow,
236
+ temporalAddress: config.temporalAddress,
237
+ temporalNamespace: config.temporalNamespace,
238
+ });
239
+ log(`Synthesized OPENCODE_CONFIG_CONTENT: ${(0, helpers_1.redactSecrets)(configContent)}`);
240
+ // (3) Spawn opencode serve. Stdio redirected to a per-player log file
241
+ // so terminal noise from opencode doesn't clutter the adapter's log.
242
+ const logDir = path.join(workDir, 'logs');
243
+ fs.mkdirSync(logDir, { recursive: true });
244
+ const opencodeLogFile = path.join(logDir, `opencode-${playerIdForWorkflow}.log`);
245
+ const logFd = fs.openSync(opencodeLogFile, 'a');
246
+ // Windows: npm-installed binaries land as `.cmd` shims. Node's
247
+ // `CreateProcess` won't run `.cmd` files directly, and `shell: true`
248
+ // trips DEP0190 (the args-not-escaped warning). Use the same
249
+ // `cmd.exe /c <bin> <args>` pattern that `spawnInTerminal` uses for
250
+ // Windows Terminal — explicit, no shell: true, no deprecation.
251
+ const isWindows = process.platform === 'win32';
252
+ const spawnCmd = isWindows ? 'cmd.exe' : 'opencode';
253
+ const spawnArgs = isWindows
254
+ ? ['/c', 'opencode', 'serve', '--port', String(this.port), '--hostname', '127.0.0.1']
255
+ : ['serve', '--port', String(this.port), '--hostname', '127.0.0.1'];
256
+ try {
257
+ this.serveProcess = (0, child_process_1.spawn)(spawnCmd, spawnArgs, {
258
+ // Loopback hardcoded — see config.ts SECURITY note + ADR 0015 §53.
259
+ stdio: ['ignore', logFd, logFd],
260
+ env: { ...process.env, OPENCODE_CONFIG_CONTENT: configContent },
261
+ // `detached: false` — child dies when parent dies (Linux) or is
262
+ // killed via job object (Windows). We add explicit signal
263
+ // handling below to clean up on SIGTERM/SIGINT.
264
+ detached: false,
265
+ });
266
+ }
267
+ finally {
268
+ // Close our fd handle immediately — the spawn cloned it for the child.
269
+ try {
270
+ fs.closeSync(logFd);
271
+ }
272
+ catch { /* ignore */ }
273
+ }
274
+ if (!this.serveProcess.pid) {
275
+ throw new Error('Failed to spawn `opencode serve` — is the binary on PATH?');
276
+ }
277
+ log(`Spawned opencode serve (pid=${this.serveProcess.pid}, port=${this.port}, log=${opencodeLogFile})`);
278
+ // Surface early child-exit (e.g. ENOENT, missing config) loudly. The
279
+ // health-probe loop below would also catch this but with a less
280
+ // diagnostic error.
281
+ this.serveProcess.once('exit', (code, signal) => {
282
+ log(`opencode serve exited (code=${code ?? 'null'}, signal=${signal ?? 'null'})`);
283
+ });
284
+ // (4) Health-probe + version-drift gate.
285
+ this.bridge = new server_bridge_1.OpenCodeServerBridge({
286
+ baseUrl: `http://127.0.0.1:${this.port}`,
287
+ log,
288
+ });
289
+ await this.bridge.waitForHealth(HEALTH_PROBE_TIMEOUT_MS);
290
+ try {
291
+ const health = await this.bridge.getHealth();
292
+ if (!(0, helpers_1.isVersionMatch)(health.version, TESTED_OPENCODE_VERSION)) {
293
+ log(`WARNING: opencode version ${health.version} drift from tested ${TESTED_OPENCODE_VERSION}`);
294
+ }
295
+ else {
296
+ log(`opencode version ${health.version} (matches tested ${TESTED_OPENCODE_VERSION})`);
297
+ }
298
+ }
299
+ catch (err) {
300
+ // Health endpoint returned but version field missing / malformed —
301
+ // log + continue. Version-drift is diagnostic, not blocking.
302
+ log(`getHealth post-ready failed: ${err?.message ?? err}`);
303
+ }
304
+ // (5) Connect Temporal, wait for workflow, hand client to BaseAttachment.
305
+ const connection = await (0, connection_1.createTemporalConnection)(config);
306
+ const client = new client_1.Client({ connection, namespace: config.temporalNamespace });
307
+ this.configureV2(client, os.hostname());
308
+ log(`Waiting for workflow ${expectedWorkflowId} to register...`);
309
+ let handle = client.workflow.getHandle(expectedWorkflowId);
310
+ let pinnedRunId;
311
+ let workflowReady = false;
312
+ for (let attempt = 0; attempt < WORKFLOW_REGISTER_ATTEMPTS; attempt++) {
313
+ try {
314
+ const desc = await handle.describe();
315
+ if (desc.status.name === 'RUNNING') {
316
+ workflowReady = true;
317
+ pinnedRunId = desc.runId;
318
+ break;
319
+ }
320
+ }
321
+ catch { /* not yet started */ }
322
+ await sleep(WORKFLOW_REGISTER_INTERVAL_MS);
323
+ if (attempt % 5 === 4)
324
+ log(`Still waiting for workflow... attempt ${attempt + 1}/${WORKFLOW_REGISTER_ATTEMPTS}`);
325
+ }
326
+ if (!workflowReady) {
327
+ log(`ERROR: Workflow ${expectedWorkflowId} did not register within ${WORKFLOW_REGISTER_ATTEMPTS}s — exiting`);
328
+ await this.killSubprocessChain();
329
+ process.exit(1);
330
+ }
331
+ handle = client.workflow.getHandle(expectedWorkflowId, pinnedRunId);
332
+ log(`Workflow ready: ${expectedWorkflowId} (pinned runId ${pinnedRunId})`);
333
+ // (6) Build cached system prompt — agent-tempo MCP_INSTRUCTIONS plus
334
+ // the opencode-specific addendum. Sent every turn; OpenCode caches
335
+ // server-side per its provider transform layer.
336
+ this.systemPrompt = (0, server_tools_1.buildServerInstructions)({
337
+ ensemble: config.ensemble,
338
+ playerId: playerIdForWorkflow,
339
+ isConductor,
340
+ hasRequestedName: true,
341
+ }) + HEADLESS_OPENCODE_ADDENDUM;
342
+ this.playerName = playerIdForWorkflow;
343
+ // (7) Wire terminal-cleanup hook BEFORE claiming so a race between
344
+ // claim + lease loss can't drop the event.
345
+ let shuttingDown = false;
346
+ const cleanup = async () => {
347
+ if (shuttingDown)
348
+ return;
349
+ shuttingDown = true;
350
+ log('Cleanup running...');
351
+ // Best-effort opencode session teardown — avoids leaking server-side
352
+ // state if the subprocess outlives this call.
353
+ if (this.openCodeSessionId && this.bridge) {
354
+ await this.bridge.abortSession(this.openCodeSessionId).catch(() => { });
355
+ await this.bridge.deleteSession(this.openCodeSessionId).catch(() => { });
356
+ }
357
+ try {
358
+ await this.detachGracefully('user-stop');
359
+ }
360
+ catch (err) {
361
+ log('detachGracefully error:', err?.message ?? err);
362
+ }
363
+ await this.killSubprocessChain();
364
+ };
365
+ this.onTerminal((reason) => {
366
+ log(`V2 terminal (${reason}) — triggering cleanup`);
367
+ cleanup().catch((err) => log('terminal cleanup error:', err?.message ?? err));
368
+ });
369
+ // (8) Claim the attachment via V2 lifecycle.
370
+ try {
371
+ const expectedAttachmentId = process.env[config_1.ENV.ATTACHMENT_ID] || undefined;
372
+ handle = await this.startV2Lifecycle(expectedWorkflowId, expectedAttachmentId);
373
+ log(`V2 attachment claimed (attachmentId=${this.token?.attachmentId}${expectedAttachmentId ? ', renewed' : ''})`);
374
+ }
375
+ catch (err) {
376
+ log(`ERROR: V2 claimAttachment failed: ${err?.message ?? err}`);
377
+ await cleanup();
378
+ process.exit(1);
379
+ }
380
+ // (9) PID file — two lines (adapter PID + opencode-serve PID) per
381
+ // design §6.5. Operators can grep / kill either.
382
+ const pidFile = path.join(logDir, `${playerIdForWorkflow}.pid`);
383
+ try {
384
+ fs.writeFileSync(pidFile, `${process.pid}\n${this.serveProcess.pid ?? ''}\n`);
385
+ }
386
+ catch (err) {
387
+ log(`Warning: PID file write failed: ${err?.message ?? err}`);
388
+ }
389
+ // (10) Signal handlers. SIGTERM behaviour on Windows differs from POSIX
390
+ // (ADR 0015 §84) — handled below in killSubprocessChain.
391
+ const shutdown = async () => {
392
+ log('Shutting down (signal received)...');
393
+ await cleanup();
394
+ try {
395
+ fs.unlinkSync(pidFile);
396
+ }
397
+ catch { /* gone */ }
398
+ process.exit(0);
399
+ };
400
+ process.on('SIGINT', shutdown);
401
+ process.on('SIGTERM', shutdown);
402
+ // (11) Drive the poll loop.
403
+ await this.pollLoop(handle);
404
+ try {
405
+ fs.unlinkSync(pidFile);
406
+ }
407
+ catch { /* gone */ }
408
+ }
409
+ /**
410
+ * Poll the workflow for pending messages; call `deliver()` per batch.
411
+ * Mirrors claude-api / copilot — only the `invokeSdk` body differs.
412
+ */
413
+ async pollLoop(handle) {
414
+ let polling = true;
415
+ let processing = false;
416
+ let pollCount = 0;
417
+ while (polling && !this.shouldStop()) {
418
+ pollCount++;
419
+ // Periodic workflow-status check — detect external destroy / completion.
420
+ if (pollCount % WORKFLOW_STATUS_CHECK_INTERVAL === 0) {
421
+ try {
422
+ const desc = await handle.describe();
423
+ if (desc.status.name !== 'RUNNING') {
424
+ log(`Workflow status is ${desc.status.name} — exiting cleanly`);
425
+ polling = false;
426
+ break;
427
+ }
428
+ try {
429
+ const isDestroyed = await handle.query(signals_1.isDestroyedQuery);
430
+ if (isDestroyed) {
431
+ log('Workflow destroyed — exiting cleanly');
432
+ polling = false;
433
+ break;
434
+ }
435
+ }
436
+ catch { /* isDestroyed query unavailable on pre-upgrade workflows — safe to skip */ }
437
+ }
438
+ catch (err) {
439
+ log(`Workflow describe failed: ${err?.message ?? err} — treating as terminated`);
440
+ polling = false;
441
+ break;
442
+ }
443
+ }
444
+ if (processing) {
445
+ await sleep(POLL_INTERVAL_MS);
446
+ continue;
447
+ }
448
+ let messages = [];
449
+ try {
450
+ messages = await handle.query(signals_1.pendingMessagesQuery);
451
+ }
452
+ catch (err) {
453
+ log(`pendingMessages query failed: ${err?.message ?? err}`);
454
+ await sleep(POLL_INTERVAL_MS);
455
+ continue;
456
+ }
457
+ if (messages.length === 0) {
458
+ await sleep(POLL_INTERVAL_MS);
459
+ continue;
460
+ }
461
+ processing = true;
462
+ try {
463
+ const ackIds = messages.map((m) => m.id);
464
+ // The whole batch is delivered as one OpenCode prompt — the
465
+ // representative `messages[0]` only drives processingStart/End.
466
+ await this.deliver(handle, messages[0],
467
+ /* prompt unused — invokeSdk reads `messages` directly via closure */ '', TURN_TIMEOUT_MS, (timeoutPrompt, timeoutMs) => this.invokeSdkWithBatch(messages, timeoutPrompt, timeoutMs), ackIds);
468
+ }
469
+ catch (err) {
470
+ log(`deliver error: ${err?.message ?? err}`);
471
+ // Don't exit the loop — transient failures leave messages PENDING
472
+ // for the next poll. Only terminal events (lease loss, destroy)
473
+ // exit via onTerminal / status-check above.
474
+ }
475
+ finally {
476
+ processing = false;
477
+ }
478
+ }
479
+ log('Poll loop exiting');
480
+ }
481
+ /** True once `onTerminal` fired and cleanup tore down the V2 token. */
482
+ shouldStop() {
483
+ return this.token === null;
484
+ }
485
+ /**
486
+ * Per-turn LLM dispatch. Closes over the message batch so SdkAttachment's
487
+ * `invokeSdk` callback can stay (prompt, timeoutMs) shaped while the
488
+ * actual driving data is the `messages[]` from the poll.
489
+ *
490
+ * Steps per design §5.4:
491
+ * 1. Ensure OpenCode session exists (create on first turn, stash id
492
+ * on workflow metadata via the existing updateMetadataSignal
493
+ * sessionId field — same field copilot uses).
494
+ * 2. Subscribe to `/event` SSE.
495
+ * 3. POST `/session/:id/prompt_async` (returns 204, turn streams).
496
+ * 4. Consume SSE until `finish` reason — accumulate text, observe
497
+ * tool-use breadcrumbs.
498
+ * 5. Log per-turn `turn-usage` line (design §5.6).
499
+ */
500
+ async invokeSdkWithBatch(messages, _prompt, _timeoutMs) {
501
+ // `bridge` and `pinnedHandle` are guaranteed initialized by `run()`
502
+ // before the poll loop ever enters `deliver()`. Narrow once via
503
+ // non-null assertions so the rest of the method reads cleanly.
504
+ const bridge = this.bridge;
505
+ const handle = this.pinnedHandle;
506
+ // (1) Lazy-create the OpenCode session on first turn. Q6 carry-forward
507
+ // (design §5.2): Path A is implemented (re-attach to stashed id on
508
+ // restart). If the impl-time experiment shows OpenCode does NOT
509
+ // persist sessions across server restart, swap this for the
510
+ // workflow-history rebuild path documented in README.md.
511
+ if (!this.openCodeSessionId) {
512
+ const session = await bridge.createSession();
513
+ this.openCodeSessionId = session.id;
514
+ try {
515
+ await handle.signal(signals_1.updateMetadataSignal, { sessionId: session.id });
516
+ }
517
+ catch (err) {
518
+ log(`updateMetadata sessionId signal failed: ${err?.message ?? err}`);
519
+ }
520
+ log(`Created OpenCode session ${session.id}`);
521
+ }
522
+ const sessionId = this.openCodeSessionId;
523
+ // (2) Build the new turn's parts. Multi-cue batching mirrors
524
+ // claude-api's history-fold pattern: every queued message becomes
525
+ // one labelled text part; OpenCode appends to its server-side history.
526
+ const parts = messages.map((m) => ({
527
+ type: 'text',
528
+ text: `[from ${m.from}]: ${m.text}`,
529
+ }));
530
+ this.inFlightAbortController = new AbortController();
531
+ const t0 = Date.now();
532
+ let assistantText = '';
533
+ let stopReason = null;
534
+ let usage = null;
535
+ let providerSeen = null;
536
+ try {
537
+ // (3) Subscribe to SSE BEFORE posting prompt_async — a tight race
538
+ // window otherwise drops the early `Message` events that announce
539
+ // the turn's session. SSE consumer iterates concurrently with the
540
+ // prompt_async fetch.
541
+ const sse = bridge.subscribeEvents(this.inFlightAbortController.signal);
542
+ // (4) Post the turn. Returns 204 immediately; output streams over SSE.
543
+ await bridge.promptAsync(sessionId, {
544
+ model: this.model,
545
+ system: this.systemPrompt,
546
+ parts,
547
+ });
548
+ // (5) Consume SSE until finish.
549
+ for await (const event of sse) {
550
+ if (!isEventForSession(event, sessionId))
551
+ continue;
552
+ const summary = consumeEvent(event);
553
+ if (summary.text)
554
+ assistantText += summary.text;
555
+ if (summary.toolName)
556
+ log(`tool=${summary.toolName} session=${sessionId}`);
557
+ if (summary.providerSeen && !providerSeen)
558
+ providerSeen = summary.providerSeen;
559
+ if (summary.usage)
560
+ usage = { ...(usage ?? {}), ...summary.usage };
561
+ if (summary.stopReason) {
562
+ stopReason = summary.stopReason;
563
+ break;
564
+ }
565
+ if (summary.errorMessage) {
566
+ throw new Error(`OpenCode SSE error: ${summary.errorMessage}`);
567
+ }
568
+ }
569
+ }
570
+ finally {
571
+ this.inFlightAbortController = null;
572
+ }
573
+ // (6) Per-turn telemetry — design §5.6. Provider attribution from the
574
+ // event stream if available, otherwise inferred from the model prefix.
575
+ const provider = providerSeen ?? (this.model.split('/')[0] || 'unknown');
576
+ if (usage) {
577
+ log(`turn-usage provider=${provider} model=${this.model} input=${usage.input_tokens ?? 0} output=${usage.output_tokens ?? 0} cache_read=${usage.cache_read_input_tokens ?? 0} elapsed_ms=${Date.now() - t0} player=${this.playerName} stop_reason=${stopReason ?? 'none'}`);
578
+ }
579
+ return {
580
+ sdkResult: { assistantText, stopReason, usage, provider },
581
+ elapsedMs: Date.now() - t0,
582
+ };
583
+ }
584
+ /**
585
+ * SIGTERM → SIGKILL escalation on the `opencode serve` subprocess.
586
+ * Idempotent — racing signals during cleanup are safe.
587
+ *
588
+ * On Windows, Bun-runtime SIGTERM may not propagate cleanly (ADR 0015
589
+ * §84). The fallback to SIGKILL after `SIGTERM_TIMEOUT_MS` covers this
590
+ * case without per-platform branching.
591
+ */
592
+ async killSubprocessChain() {
593
+ const p = this.serveProcess;
594
+ if (!p || p.killed)
595
+ return;
596
+ try {
597
+ p.kill('SIGTERM');
598
+ }
599
+ catch (err) {
600
+ log(`SIGTERM threw: ${err?.message ?? err}`);
601
+ }
602
+ const exitCode = await (0, helpers_1.waitForExit)(p, SIGTERM_TIMEOUT_MS);
603
+ if (exitCode === null) {
604
+ log(`opencode serve did not exit within ${SIGTERM_TIMEOUT_MS}ms — escalating to SIGKILL`);
605
+ try {
606
+ p.kill('SIGKILL');
607
+ }
608
+ catch (err) {
609
+ log(`SIGKILL threw: ${err?.message ?? err}`);
610
+ }
611
+ }
612
+ }
613
+ }
614
+ exports.OpenCodeAttachment = OpenCodeAttachment;
615
+ /**
616
+ * Filter SSE events to a specific session id. OpenCode's `/event` stream
617
+ * is global per `opencode serve` instance — we only act on events whose
618
+ * payload carries our session's id. Permissive shape — events that
619
+ * don't carry a session id are passed through (they're typically
620
+ * server-level signals like `error` or `health.update`).
621
+ */
622
+ function isEventForSession(event, sessionId) {
623
+ // Common shapes seen in v1.14.x: `properties.sessionID`, `properties.session_id`,
624
+ // top-level `sessionID`. Be lenient — drop only events that explicitly carry
625
+ // a different session id.
626
+ const candidates = [
627
+ event.sessionID,
628
+ event.session_id,
629
+ event.properties?.sessionID,
630
+ event.properties?.session_id,
631
+ event.info?.sessionID,
632
+ ];
633
+ for (const c of candidates) {
634
+ if (typeof c === 'string') {
635
+ return c === sessionId;
636
+ }
637
+ }
638
+ // No session id on the event — not session-scoped, pass through.
639
+ return true;
640
+ }
641
+ /**
642
+ * Reduce one OpenCode SSE event into the bits the consumer needs. v1.14.x
643
+ * event shapes are still labeled experimental (ADR 0015 §82), so this is
644
+ * deliberately tolerant — unknown shapes are silently ignored.
645
+ */
646
+ const EVENT_TYPES = {
647
+ /** Text deltas; `properties.part.type === 'text'` carries the chunk. */
648
+ MESSAGE_PART_UPDATED: 'message.part.updated',
649
+ /** Assistant turn metadata — stop_reason + usage when role=assistant. */
650
+ MESSAGE_UPDATED: 'message.updated',
651
+ /** Tool-use breadcrumb — observability only; OpenCode owns dispatch. */
652
+ TOOL_CALLED: 'tool.called',
653
+ /** Server-level error — propagated as a thrown Error in the consumer. */
654
+ ERROR: 'error',
655
+ };
656
+ function consumeEvent(event) {
657
+ const summary = {};
658
+ const props = event.properties;
659
+ if (event.type === EVENT_TYPES.MESSAGE_PART_UPDATED && props) {
660
+ const part = props.part;
661
+ if (part && part.type === 'text' && typeof part.text === 'string') {
662
+ summary.text = part.text;
663
+ }
664
+ }
665
+ if (event.type === EVENT_TYPES.TOOL_CALLED && props) {
666
+ const name = props.name ?? props.tool;
667
+ if (typeof name === 'string')
668
+ summary.toolName = name;
669
+ }
670
+ if (event.type === EVENT_TYPES.MESSAGE_UPDATED && props) {
671
+ const message = props.message;
672
+ if (message) {
673
+ const role = message.role;
674
+ const stopReason = message.stop_reason ?? message.finishReason ?? message.finish_reason;
675
+ const usageBlock = message.usage;
676
+ const provider = (message.providerID ?? message.provider);
677
+ if (provider && typeof provider === 'string')
678
+ summary.providerSeen = provider;
679
+ if (role === 'assistant' && typeof stopReason === 'string') {
680
+ summary.stopReason = stopReason;
681
+ }
682
+ if (usageBlock) {
683
+ const numericUsage = {};
684
+ for (const [k, v] of Object.entries(usageBlock)) {
685
+ if (typeof v === 'number')
686
+ numericUsage[k] = v;
687
+ }
688
+ if (Object.keys(numericUsage).length > 0)
689
+ summary.usage = numericUsage;
690
+ }
691
+ }
692
+ }
693
+ if (event.type === EVENT_TYPES.ERROR && props) {
694
+ const message = props.message ?? props.error?.message;
695
+ summary.errorMessage = typeof message === 'string' ? message : 'unknown OpenCode error';
696
+ }
697
+ return summary;
698
+ }
699
+ function sleep(ms) {
700
+ return new Promise((r) => setTimeout(r, ms));
701
+ }
702
+ if (require.main === module) {
703
+ if (!opencodeSdkAvailable)
704
+ process.exit(1);
705
+ const model = process.env[config_1.ENV.OPENCODE_MODEL];
706
+ new OpenCodeAttachment(model ? { model } : {}).run().catch((err) => {
707
+ log('Fatal error:', err);
708
+ process.exit(1);
709
+ });
710
+ }