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,797 @@
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.DirectApiAttachment = exports.claudeApiDescriptor = void 0;
37
+ exports.handleStreamEvent = handleStreamEvent;
38
+ exports.buildAnthropicMessages = buildAnthropicMessages;
39
+ /**
40
+ * Headless Claude API adapter — SDK class.
41
+ *
42
+ * Issue #131 Phase C. Uses the Anthropic Messages API directly via
43
+ * `@anthropic-ai/sdk` — no terminal, no Claude Code CLI, headless from
44
+ * spawn to detach. Mirrors the Copilot bridge structure: detached Node
45
+ * subprocess, dual-purpose entry point (class import vs `require.main`
46
+ * self-exec), `claimAttachment` + heartbeat lifecycle inherited from
47
+ * `SdkAttachment`.
48
+ *
49
+ * Class hierarchy: `DirectApiAttachment extends SdkAttachment extends BaseAttachment`.
50
+ * Concrete adapter overrides `invokeSdk` (the LLM turn loop, commit 4),
51
+ * `onSuperseded` (AbortController.abort, commit 4), and the descriptor;
52
+ * everything else (claim, heartbeat, phase watcher, `processingStart`/`End`
53
+ * pairing, `markDelivered`) is free.
54
+ *
55
+ * **Commit progression for #131 Phase C**:
56
+ * - Commit 1: wire scaffold (descriptor, optional-dep guard, class stub).
57
+ * - Commit 2: in-process MCP bridge module + tool-registration helpers.
58
+ * - **Commit 3 (this commit)**: full lifecycle wiring — workflow connect,
59
+ * workflow-register polling, runId pin, MCP bridge boot, V2 attachment
60
+ * claim, terminal-cleanup hook, signal handlers, idle poll loop. The
61
+ * `invokeSdk` method is still a stub that throws — commit 4 wires the
62
+ * tool-use loop against the Anthropic Messages API.
63
+ * - Commit 4: tool-use loop + verification-addendum landmines.
64
+ * - Commit 5: tests + docs.
65
+ *
66
+ * Design reference: `docs/design/131-claude-api-adapter.md` §0 (TL;DR), §2
67
+ * (adapter precedents), §3 (spawn integration), §6 (cancellation + lifecycle),
68
+ * §8 (engineer-facing skeleton). Verification addendum (2026-04-28) for
69
+ * landmines applied to commit 4.
70
+ */
71
+ const fs = __importStar(require("fs"));
72
+ const os = __importStar(require("os"));
73
+ const path = __importStar(require("path"));
74
+ const client_1 = require("@temporalio/client");
75
+ const base_1 = require("../sdk/base");
76
+ const config_1 = require("../../config");
77
+ const connection_1 = require("../../connection");
78
+ const signals_1 = require("../../workflows/signals");
79
+ const server_tools_1 = require("../../server-tools");
80
+ const mcp_bridge_1 = require("./mcp-bridge");
81
+ const api_error_1 = require("./api-error");
82
+ /**
83
+ * Descriptor for the claude-api adapter. Kept colocated with the class so
84
+ * `adapter.ts` has no import dependency on `index.ts` (breaks the circular
85
+ * module-graph cycle that QA flagged on copilot's PR-B). `index.ts` re-exports
86
+ * this constant alongside the class.
87
+ *
88
+ * Design reference: docs/design/131-claude-api-adapter.md §2 + ADR 0012.
89
+ */
90
+ exports.claudeApiDescriptor = {
91
+ adapterId: 'claude-api',
92
+ adapterClass: 'sdk',
93
+ // messages.create blocks on the LLM turn — processingStart/End pairing is
94
+ // mandatory and provided by SdkAttachment.deliver().
95
+ blocksOnLLMTurn: true,
96
+ // SDK class — 30s cadence per design doc + lifecycle-rebuild-v2 §4.3.
97
+ // Inherited from BaseAttachment's heartbeat loop via the descriptor.
98
+ heartbeatMs: 30_000,
99
+ };
100
+ // Optional dependency — must be installed separately: npm install @anthropic-ai/sdk
101
+ // Mirrors the Copilot pattern: when run as the adapter subprocess entry point,
102
+ // print an actionable error and exit. When imported by the registry during
103
+ // normal MCP server startup, stay silent — the SDK is optional and non-API
104
+ // users should see no noise.
105
+ let Anthropic;
106
+ try {
107
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
108
+ Anthropic = require('@anthropic-ai/sdk').default;
109
+ }
110
+ catch {
111
+ if (require.main === module) {
112
+ console.error('Error: @anthropic-ai/sdk is not installed.\n' +
113
+ 'Install it with: npm install @anthropic-ai/sdk\n' +
114
+ 'Or recruit with a different agent (claude / copilot).');
115
+ process.exit(1);
116
+ }
117
+ }
118
+ // Unbuffered logging — fs.writeSync(2, ...) bypasses Node.js stream buffering
119
+ // so log lines appear immediately even when stderr is redirected to a file.
120
+ // Same pattern the copilot bridge uses for the same reason.
121
+ const log = (...args) => {
122
+ const msg = `[agent-tempo:claude-api] ${args.map((a) => typeof a === 'string' ? a : JSON.stringify(a)).join(' ')}\n`;
123
+ fs.writeSync(2, msg);
124
+ };
125
+ /**
126
+ * Format an unknown thrown value for a single log line. Caps long bodies
127
+ * (Anthropic 4xx error JSON can be hundreds of chars) so individual log
128
+ * lines stay grep-friendly. Used by the #521 retry-classification path.
129
+ */
130
+ function truncateErr(err, max = 240) {
131
+ const raw = err?.message ?? (typeof err === 'string' ? err : JSON.stringify(err));
132
+ if (!raw)
133
+ return 'unknown';
134
+ return raw.length > max ? raw.slice(0, max) + '…' : raw;
135
+ }
136
+ /** Default model id — verification addendum §1 (no date suffix on direct API). */
137
+ const DEFAULT_MODEL = 'claude-opus-4-7';
138
+ /** Idle poll cadence (ms). Matches Copilot bridge — short enough for snappy cue delivery, loose enough not to hammer Temporal. */
139
+ const POLL_INTERVAL_MS = 2000;
140
+ /** Workflow-register poll loop bounds. */
141
+ const WORKFLOW_REGISTER_ATTEMPTS = 30;
142
+ const WORKFLOW_REGISTER_INTERVAL_MS = 1000;
143
+ /** Workflow status check cadence (every N polls). */
144
+ const WORKFLOW_STATUS_CHECK_INTERVAL = 15;
145
+ /** Per-turn timeout passed to invokeSdk. Anthropic streams can run minutes for long tool-use chains. */
146
+ const TURN_TIMEOUT_MS = 5 * 60 * 1000;
147
+ /** Max tokens per assistant turn. Sized for tool-use chains; bounded to prevent runaway billing. */
148
+ const MAX_TOKENS_PER_TURN = 8192;
149
+ /** Headless-identity addendum appended to the shared instructions per design §10. */
150
+ const HEADLESS_ADDENDUM = '\n\nYou are a **headless** claude-api player — you have access to the agent-tempo MCP tools ' +
151
+ '(cue, report, recall, ensemble, broadcast, recruit, set_part, …) but **NOT** the file-edit, shell, ' +
152
+ 'or web tools that a `claude-code` player would have (no Bash, Read, Write, Edit, Glob, Grep, ' +
153
+ 'WebSearch, WebFetch). For tasks requiring file edits or shell commands, ask the conductor to ' +
154
+ 'recruit a `claude-code` player and hand off via cue. (File-op tool support is planned for a Phase 2 ' +
155
+ 'enhancement.)';
156
+ /**
157
+ * SDK-class adapter for the Anthropic Messages API.
158
+ *
159
+ * Delivery model is pull-based (blocks on LLM turn): the adapter polls the
160
+ * workflow for pending messages, runs a tool-use loop against the Messages
161
+ * API (commit 4), and acks via `markDelivered`. `processingStart`/`End`
162
+ * are paired around each blocking turn so the workflow's stale detection
163
+ * doesn't misclassify a long tool-call sequence as a dead session.
164
+ */
165
+ class DirectApiAttachment extends base_1.SdkAttachment {
166
+ descriptor = exports.claudeApiDescriptor;
167
+ /** AbortController stashed during invokeSdk so onSuperseded can abort the in-flight stream. */
168
+ abortController = null;
169
+ /** In-process MCP bridge — server + client + cached tool list. Populated by run(). */
170
+ mcp = null;
171
+ /** Anthropic SDK client (typed `unknown` because the SDK is an optional dep). Populated by run(). */
172
+ apiClient = null;
173
+ /** Resolved model id (recruit-arg → env → DEFAULT_MODEL). */
174
+ model;
175
+ /** Cached system prompt — built once at session start, stays in the cached prefix every turn. */
176
+ systemPrompt = '';
177
+ /** Player id stashed for the per-turn telemetry log. */
178
+ playerName = '';
179
+ constructor(opts = {}) {
180
+ super();
181
+ this.model = opts.model ?? process.env[config_1.ENV.API_MODEL] ?? DEFAULT_MODEL;
182
+ }
183
+ /**
184
+ * Lease-revocation hook — fired by `SdkAttachment` when the base-class
185
+ * phase watcher detects another claimant stole the attachment. Aborts the
186
+ * in-flight `messages.create` via the AbortController wired in
187
+ * `invokeSdk`. Concrete abort wiring lands in commit 4.
188
+ */
189
+ onSuperseded() {
190
+ const c = this.abortController;
191
+ this.abortController = null;
192
+ if (!c)
193
+ return;
194
+ log('lease revoked — aborting in-flight messages.create');
195
+ try {
196
+ c.abort();
197
+ }
198
+ catch (err) {
199
+ log('abort threw:', err?.message ?? err);
200
+ }
201
+ }
202
+ /**
203
+ * Subprocess entry point. Boots the in-process MCP bridge, connects to
204
+ * Temporal, claims the attachment via V2 lifecycle, and runs the poll loop.
205
+ */
206
+ async run() {
207
+ if (!Anthropic) {
208
+ throw new Error('@anthropic-ai/sdk not installed — adapter cannot start (this should have been caught by the optional-dep guard above).');
209
+ }
210
+ const config = (0, config_1.getConfig)();
211
+ const isConductor = process.env[config_1.ENV.CONDUCTOR] === 'true';
212
+ const requestedName = process.env[config_1.ENV.PLAYER_NAME] || '';
213
+ const playerIdForWorkflow = isConductor
214
+ ? 'conductor'
215
+ : (requestedName && requestedName !== 'conductor' ? requestedName : '') || `claude-api-${Date.now()}`;
216
+ const expectedWorkflowId = `agent-session-${config.ensemble}-${playerIdForWorkflow}`;
217
+ const workDir = process.cwd();
218
+ log(`Starting claude-api adapter in ${workDir} (ensemble: ${config.ensemble}, player: ${playerIdForWorkflow}, model: ${this.model})`);
219
+ // Connect Temporal client — used for workflow polling + claim. The
220
+ // adapter's MCP server uses it too via the shared registerAllTempoTools
221
+ // path so cue/report/recruit/… all reach the right namespace.
222
+ const connection = await (0, connection_1.createTemporalConnection)(config);
223
+ const client = new client_1.Client({
224
+ connection,
225
+ namespace: config.temporalNamespace,
226
+ });
227
+ // Hand the client + host to BaseAttachment so startV2Lifecycle can
228
+ // issue claimAttachment + heartbeat against it.
229
+ this.configureV2(client, os.hostname());
230
+ // Wait for the session workflow to register in Temporal (the spawn
231
+ // activity is responsible for starting it; we know the ID deterministically).
232
+ log(`Waiting for workflow ${expectedWorkflowId} to register...`);
233
+ let handle = client.workflow.getHandle(expectedWorkflowId);
234
+ let pinnedRunId;
235
+ let workflowReady = false;
236
+ for (let attempt = 0; attempt < WORKFLOW_REGISTER_ATTEMPTS; attempt++) {
237
+ try {
238
+ const desc = await handle.describe();
239
+ if (desc.status.name === 'RUNNING') {
240
+ workflowReady = true;
241
+ pinnedRunId = desc.runId;
242
+ break;
243
+ }
244
+ }
245
+ catch { /* not yet started */ }
246
+ await new Promise((r) => setTimeout(r, WORKFLOW_REGISTER_INTERVAL_MS));
247
+ if (attempt % 5 === 4)
248
+ log(`Still waiting for workflow... attempt ${attempt + 1}/${WORKFLOW_REGISTER_ATTEMPTS}`);
249
+ }
250
+ if (!workflowReady) {
251
+ log(`ERROR: Workflow ${expectedWorkflowId} did not register within ${WORKFLOW_REGISTER_ATTEMPTS}s — exiting`);
252
+ process.exit(1);
253
+ }
254
+ handle = client.workflow.getHandle(expectedWorkflowId, pinnedRunId);
255
+ log(`Workflow ready: ${expectedWorkflowId} (pinned runId ${pinnedRunId})`);
256
+ // Boot the in-process MCP bridge. The adapter's `setPlayerId` is a
257
+ // no-op — headless players don't need rename support (the recruit flow
258
+ // assigns the name at spawn time and it's pinned in the workflow id).
259
+ log('Booting in-process MCP bridge...');
260
+ this.mcp = await (0, mcp_bridge_1.bootMcpBridge)({
261
+ client,
262
+ config,
263
+ getPlayerId: () => playerIdForWorkflow,
264
+ setPlayerId: () => { },
265
+ handle,
266
+ workflowId: expectedWorkflowId,
267
+ ownAgentType: 'claude-api',
268
+ isConductor,
269
+ });
270
+ log(`MCP bridge ready — ${this.mcp.tools.length} tools available`);
271
+ // Build the cached system prompt once at session start. Lives under the
272
+ // `cache_control` ephemeral block on every turn (verification addendum
273
+ // §2 + design §5.2) so we pay the cache-create cost on the first turn
274
+ // and ride the cache-read price on every subsequent one. Recruited
275
+ // players have hasRequestedName=true (the spawn activity always passes
276
+ // PLAYER_NAME) so the addendum's `set_name` directive is suppressed.
277
+ this.systemPrompt = (0, server_tools_1.buildServerInstructions)({
278
+ ensemble: config.ensemble,
279
+ playerId: playerIdForWorkflow,
280
+ isConductor,
281
+ hasRequestedName: true,
282
+ }) + HEADLESS_ADDENDUM;
283
+ this.playerName = playerIdForWorkflow;
284
+ // Construct the Anthropic SDK client. SDK retry is disabled inside the
285
+ // tool-use loop so a 5xx mid-turn doesn't double-execute the previous
286
+ // turn's side-effecting tool calls. Adapter handles failure by exiting
287
+ // cleanly — operator restart picks the message up.
288
+ const AnthropicCtor = Anthropic;
289
+ this.apiClient = new AnthropicCtor({
290
+ apiKey: process.env.ANTHROPIC_API_KEY,
291
+ maxRetries: 0,
292
+ });
293
+ // Wire terminal-cleanup hook BEFORE claiming so a race between claim +
294
+ // lease loss can't drop the event.
295
+ let shuttingDown = false;
296
+ const cleanup = async () => {
297
+ if (shuttingDown)
298
+ return;
299
+ shuttingDown = true;
300
+ log('Cleanup running...');
301
+ // Graceful detach — fires `adapterExited` so the workflow collapses
302
+ // draining → detached immediately. Same pattern Copilot uses.
303
+ try {
304
+ await this.detachGracefully('user-stop');
305
+ }
306
+ catch (err) {
307
+ log('detachGracefully error:', err?.message ?? err);
308
+ }
309
+ try {
310
+ await this.mcp?.close();
311
+ }
312
+ catch (err) {
313
+ log('mcp.close error:', err?.message ?? err);
314
+ }
315
+ };
316
+ this.onTerminal((reason) => {
317
+ log(`V2 terminal (${reason}) — triggering cleanup`);
318
+ cleanup().catch((err) => log('terminal cleanup error:', err?.message ?? err));
319
+ });
320
+ // V2 path: claim the attachment + start the base-class heartbeat &
321
+ // phase watcher loops. `startV2Lifecycle` returns its own pinned
322
+ // handle; we use that going forward so the heartbeat and delivery
323
+ // share a consistent handle.
324
+ try {
325
+ const expectedAttachmentId = process.env[config_1.ENV.ATTACHMENT_ID] || undefined;
326
+ handle = await this.startV2Lifecycle(expectedWorkflowId, expectedAttachmentId);
327
+ log(`V2 attachment claimed (attachmentId=${this.token?.attachmentId}${expectedAttachmentId ? ', renewed' : ''})`);
328
+ }
329
+ catch (err) {
330
+ log(`ERROR: V2 claimAttachment failed: ${err?.message ?? err}`);
331
+ await this.mcp?.close().catch(() => { });
332
+ process.exit(1);
333
+ }
334
+ // PID file so callers can find / kill orphaned adapter processes.
335
+ const pidDir = path.join(workDir, 'logs');
336
+ const pidFile = path.join(pidDir, `${playerIdForWorkflow}.pid`);
337
+ try {
338
+ fs.mkdirSync(pidDir, { recursive: true });
339
+ fs.writeFileSync(pidFile, String(process.pid));
340
+ }
341
+ catch (err) {
342
+ log(`Warning: PID file write failed: ${err?.message ?? err}`);
343
+ }
344
+ // Graceful shutdown handlers. The cleanup fn is idempotent so racing
345
+ // signals are safe.
346
+ const shutdown = async () => {
347
+ log('Shutting down (signal received)...');
348
+ await cleanup();
349
+ try {
350
+ fs.unlinkSync(pidFile);
351
+ }
352
+ catch { /* already gone */ }
353
+ process.exit(0);
354
+ };
355
+ process.on('SIGINT', shutdown);
356
+ process.on('SIGTERM', shutdown);
357
+ // Drive the poll loop until cleanup is requested.
358
+ await this.pollLoop(handle);
359
+ try {
360
+ fs.unlinkSync(pidFile);
361
+ }
362
+ catch { /* already gone */ }
363
+ }
364
+ /**
365
+ * Poll the workflow for pending messages, drive each one through
366
+ * `SdkAttachment.deliver()`. Mirrors the Copilot bridge's loop shape;
367
+ * the LLM call lives in `invokeSdk` (commit 4).
368
+ *
369
+ * Periodic workflow-status checks detect external termination /
370
+ * destroy and exit cleanly so the daemon doesn't accumulate zombie
371
+ * adapter processes after a `destroy` from another player.
372
+ */
373
+ async pollLoop(handle) {
374
+ let polling = true;
375
+ let processing = false;
376
+ let pollCount = 0;
377
+ // #521: consecutive retriable-failure counter. Reset on every clean
378
+ // deliver; incremented on every retriable verdict; escalated to fatal
379
+ // once it exceeds DEFAULT_RETRY_BUDGET so a sustained outage doesn't
380
+ // wedge the player indefinitely.
381
+ let consecutiveFailures = 0;
382
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
383
+ while (polling && !this.shouldStop()) {
384
+ pollCount++;
385
+ // Periodic workflow status check — detect external destroy / completion.
386
+ if (pollCount % WORKFLOW_STATUS_CHECK_INTERVAL === 0) {
387
+ try {
388
+ const desc = await handle.describe();
389
+ if (desc.status.name !== 'RUNNING') {
390
+ log(`Workflow status is ${desc.status.name} — exiting cleanly`);
391
+ polling = false;
392
+ break;
393
+ }
394
+ try {
395
+ const isDestroyed = await handle.query(signals_1.isDestroyedQuery);
396
+ if (isDestroyed) {
397
+ log('Workflow destroyed — exiting cleanly');
398
+ polling = false;
399
+ break;
400
+ }
401
+ }
402
+ catch { /* isDestroyed query unavailable on pre-upgrade workflows — safe to skip */ }
403
+ }
404
+ catch (err) {
405
+ log(`Workflow describe failed: ${err?.message ?? err} — treating as terminated`);
406
+ polling = false;
407
+ break;
408
+ }
409
+ }
410
+ if (processing) {
411
+ await sleep(POLL_INTERVAL_MS);
412
+ continue;
413
+ }
414
+ let messages = [];
415
+ try {
416
+ messages = await handle.query(signals_1.pendingMessagesQuery);
417
+ }
418
+ catch (err) {
419
+ log(`pendingMessages query failed: ${err?.message ?? err}`);
420
+ await sleep(POLL_INTERVAL_MS);
421
+ continue;
422
+ }
423
+ if (messages.length === 0) {
424
+ await sleep(POLL_INTERVAL_MS);
425
+ continue;
426
+ }
427
+ processing = true;
428
+ try {
429
+ // Representative message for processingStart/End pairing; ack the
430
+ // full batch via the deliver() ackIds parameter.
431
+ const ackIds = messages.map((m) => m.id);
432
+ await this.deliver(handle, messages[0],
433
+ /* prompt unused — invokeSdk reads workflow history directly */ '', TURN_TIMEOUT_MS, this.invokeSdk.bind(this), ackIds);
434
+ // Clean delivery — reset the retry budget so a future transient
435
+ // doesn't compound with stale failure counts.
436
+ consecutiveFailures = 0;
437
+ }
438
+ catch (err) {
439
+ // #521: classify before retrying. Pre-#521 every error was treated
440
+ // as transient — a non-retriable 4xx (e.g. 400 invalid_request_error
441
+ // for low credits, 401 auth, 403 permission, 404 model) would hot-
442
+ // loop the API forever, burning quota and wedging the player.
443
+ //
444
+ // Lease-revocation path: `onSuperseded` calls `AbortController.abort()`
445
+ // which surfaces as `APIUserAbortError` here. We classify those as
446
+ // fatal but skip the detach call below — the base-class terminal
447
+ // hook has already fired and `shouldStop()` returns true.
448
+ if (this.shouldStop()) {
449
+ log(`deliver error during shutdown — exiting loop: ${err?.message ?? err}`);
450
+ polling = false;
451
+ break;
452
+ }
453
+ const verdict = (0, api_error_1.classifyApiError)(err);
454
+ log(`deliver error [${verdict.classification}] reason="${verdict.reason}" raw="${truncateErr(err)}"`);
455
+ // Both terminal paths below detach with `agent-exited` then exit
456
+ // the loop. Single helper keeps them obviously parallel.
457
+ const detachAndExit = async (logTag) => {
458
+ polling = false;
459
+ await this.detachGracefully('agent-exited').catch((e) => log(`${logTag} detachGracefully error:`, e?.message ?? e));
460
+ };
461
+ if (verdict.classification === 'fatal') {
462
+ // Non-retriable. Surface the reason loudly so the operator sees it
463
+ // in the adapter log, then detach gracefully so `attachment_info`
464
+ // shows the player as detached (not stuck in `processing`).
465
+ log(`FATAL: ${verdict.reason} — detaching and exiting (no retry)`);
466
+ await detachAndExit('fatal-detach');
467
+ break;
468
+ }
469
+ consecutiveFailures += 1;
470
+ if (consecutiveFailures > api_error_1.DEFAULT_RETRY_BUDGET) {
471
+ // Retry budget exhausted. Escalate to fatal so we don't hot-loop
472
+ // on a sustained outage.
473
+ log(`retry budget exhausted (${consecutiveFailures - 1} consecutive retriable failures; ` +
474
+ `last reason: ${verdict.reason}) — escalating to fatal, detaching and exiting`);
475
+ await detachAndExit('budget-exhausted');
476
+ break;
477
+ }
478
+ const sleepMs = verdict.retryAfterMs ?? (0, api_error_1.computeBackoffMs)(consecutiveFailures);
479
+ log(`retriable failure ${consecutiveFailures}/${api_error_1.DEFAULT_RETRY_BUDGET} — ` +
480
+ `sleeping ${sleepMs}ms before retry${verdict.retryAfterMs !== undefined ? ' (honoring retry-after)' : ''}`);
481
+ await sleep(sleepMs);
482
+ }
483
+ finally {
484
+ processing = false;
485
+ }
486
+ }
487
+ log('Poll loop exiting');
488
+ }
489
+ /**
490
+ * Whether the adapter should stop polling — true once `onTerminal` has
491
+ * fired (which sets `shuttingDown` indirectly via cleanup). The flag
492
+ * lives in cleanup's closure scope, so we read terminal state via the
493
+ * base class instead. `BaseAttachment` exposes lifecycle state via
494
+ * `this.token` — null after lease loss or graceful detach.
495
+ */
496
+ shouldStop() {
497
+ return this.token === null;
498
+ }
499
+ /**
500
+ * Concrete LLM-turn invocation. Rebuilds conversation state from workflow
501
+ * queries, calls `messages.create({ stream: true })` in a tool-use loop,
502
+ * dispatches tool calls through the in-process MCP bridge, and exits when
503
+ * the model signals `end_turn` / `max_tokens` / context overflow.
504
+ *
505
+ * Verification-addendum landmines applied here:
506
+ * - **§2.1 thinking-block round-trip**: assistant turns push the FULL
507
+ * content array (thinking + tool_use blocks together) so the
508
+ * signature chain stays intact between sub-turns of the loop.
509
+ * - **§2.2 Opus 4.7 param discipline**: no `temperature`, `top_p`,
510
+ * `top_k`, or `thinking.budget_tokens` on the request body. We don't
511
+ * set `thinking` at all — Opus 4.7 defaults to adaptive-omitted.
512
+ * - **§2.3 cache breakpoints**: `cache_control: { type: 'ephemeral' }`
513
+ * on the LAST system block and the LAST tool definition (2 of 4
514
+ * breakpoints used; the marker tells the server where the cached
515
+ * prefix ENDS, not where it starts).
516
+ * - **§2.4 input_json_delta**: tool_use input accumulates as a string
517
+ * and is only `JSON.parse`'d at `content_block_stop`.
518
+ * - **§2.5 mid-stream errors**: the `for await` loop is wrapped in
519
+ * try/catch; thrown errors propagate up so `SdkAttachment.deliver`'s
520
+ * `finally` fires `processingEnd` and the message stays PENDING for
521
+ * the next poll's retry. No turn-level retry inside the adapter
522
+ * (avoids double-execution of side-effecting tool calls).
523
+ */
524
+ async invokeSdk(_prompt, _timeoutMs) {
525
+ if (!this.mcp || !this.apiClient || !this.pinnedHandle) {
526
+ throw new Error('DirectApiAttachment invokeSdk called before run() finished initialization');
527
+ }
528
+ const handle = this.pinnedHandle;
529
+ // Build the Anthropic message array from workflow history. Parallel
530
+ // queries keep the per-turn latency tight.
531
+ const [received, sent] = await Promise.all([
532
+ handle.query(signals_1.allMessagesQuery),
533
+ handle.query(signals_1.allSentMessagesQuery),
534
+ ]);
535
+ // The history rebuild yields plain string content; in-loop turns push
536
+ // structured content arrays (assistant: thinking + tool_use blocks per
537
+ // §2.1; user: tool_result blocks). Widen the local variable explicitly
538
+ // so the tool-use-loop pushes typecheck without relaxing the helper's
539
+ // contract.
540
+ const messages = buildAnthropicMessages(received, sent);
541
+ // System + tools both carry an ephemeral cache_control breakpoint at
542
+ // their tail (verification §2.3). 2 of 4 breakpoints used; commit-5
543
+ // could add a third if the conversation tail grows past the cache
544
+ // threshold, but v1 leaves it implicit.
545
+ const system = [{ type: 'text', text: this.systemPrompt, cache_control: { type: 'ephemeral' } }];
546
+ const tools = this.mcp.tools.map((t, i) => i === this.mcp.tools.length - 1
547
+ ? { ...t, cache_control: { type: 'ephemeral' } }
548
+ : t);
549
+ this.abortController = new AbortController();
550
+ let assistantText = '';
551
+ const t0 = Date.now();
552
+ let stopReason = null;
553
+ let lastUsage = null;
554
+ try {
555
+ // Tool-use loop. Each pass is one streaming `messages.create` call;
556
+ // when `stop_reason === 'tool_use'` we dispatch the tools and loop.
557
+ while (true) {
558
+ const apiClient = this.apiClient;
559
+ const stream = await apiClient.messages.create({
560
+ model: this.model,
561
+ max_tokens: MAX_TOKENS_PER_TURN,
562
+ system,
563
+ tools,
564
+ messages,
565
+ }, { signal: this.abortController.signal, stream: true });
566
+ // Streaming consumer state — accumulate by content_block index so
567
+ // interleaved tool_use + text + thinking deltas land in the right
568
+ // bucket. Anthropic's stream emits `content_block_start` with an
569
+ // `index`, then deltas tagged with the same index, then `_stop`.
570
+ const blocks = [];
571
+ let turnUsage = null;
572
+ let turnStopReason = null;
573
+ try {
574
+ for await (const event of stream) {
575
+ handleStreamEvent(event, blocks);
576
+ if (event.type === 'message_delta') {
577
+ if (event.delta?.stop_reason)
578
+ turnStopReason = event.delta.stop_reason;
579
+ if (event.usage)
580
+ turnUsage = { ...(turnUsage ?? {}), ...event.usage };
581
+ }
582
+ else if (event.type === 'message_start' && event.message?.usage) {
583
+ turnUsage = { ...(turnUsage ?? {}), ...event.message.usage };
584
+ }
585
+ }
586
+ }
587
+ catch (err) {
588
+ // Verification §2.5 — mid-stream APIError. processingEnd will
589
+ // fire in SdkAttachment.deliver()'s finally; markDelivered will
590
+ // NOT fire (we threw); message stays PENDING; next poll retries.
591
+ log(`stream error mid-turn: ${err?.message ?? err}`);
592
+ throw err;
593
+ }
594
+ stopReason = turnStopReason;
595
+ lastUsage = turnUsage ?? lastUsage;
596
+ // Accumulate the assistant's user-facing text from this sub-turn
597
+ // for the markDelivered return value (the workflow doesn't store
598
+ // it — it's just diagnostic — but operators see it in adapter logs).
599
+ for (const b of blocks) {
600
+ if (b.type === 'text')
601
+ assistantText += b.text;
602
+ }
603
+ // Per-turn telemetry — design §5.6 stderr-only shape. No wire signal in v1.
604
+ if (turnUsage) {
605
+ log(`turn-usage model=${this.model} input=${turnUsage.input_tokens ?? 0} output=${turnUsage.output_tokens ?? 0} cache_create=${turnUsage.cache_creation_input_tokens ?? 0} cache_read=${turnUsage.cache_read_input_tokens ?? 0} elapsed_ms=${Date.now() - t0} player=${this.playerName} stop_reason=${stopReason ?? 'none'}`);
606
+ }
607
+ // Stop conditions.
608
+ if (stopReason === 'end_turn' || stopReason === 'max_tokens')
609
+ break;
610
+ if (stopReason === 'model_context_window_exceeded') {
611
+ await this.deliverContextOverflowMessage(handle);
612
+ break;
613
+ }
614
+ if (stopReason !== 'tool_use') {
615
+ log(`unexpected stop_reason "${stopReason}" — exiting turn`);
616
+ break;
617
+ }
618
+ // tool_use loop — push the FULL assistant content array (thinking
619
+ // + tool_use blocks per verification §2.1) and dispatch each tool.
620
+ const toolUses = blocks.filter((b) => b.type === 'tool_use');
621
+ if (toolUses.length === 0) {
622
+ log('stop_reason=tool_use but no tool_use blocks parsed — exiting turn');
623
+ break;
624
+ }
625
+ // Push the assistant message with full content (thinking + tool_use).
626
+ messages.push({ role: 'assistant', content: blocks });
627
+ // Dispatch tools in parallel — they may run side effects (cue,
628
+ // recruit, …) but each is independent within a single turn.
629
+ const toolResults = await Promise.all(toolUses.map(async (tu) => {
630
+ let parsedInput = {};
631
+ try {
632
+ parsedInput = tu.input ? JSON.parse(tu.input) : {};
633
+ }
634
+ catch (err) {
635
+ log(`tool_use ${tu.id} (${tu.name}) input JSON.parse failed: ${err?.message ?? err}`);
636
+ return {
637
+ type: 'tool_result',
638
+ tool_use_id: tu.id,
639
+ content: `Error: tool input JSON was malformed (${err?.message ?? err})`,
640
+ is_error: true,
641
+ };
642
+ }
643
+ try {
644
+ const result = await this.mcp.callTool(tu.name, parsedInput);
645
+ return {
646
+ type: 'tool_result',
647
+ tool_use_id: tu.id,
648
+ content: result.content,
649
+ is_error: result.isError ?? false,
650
+ };
651
+ }
652
+ catch (err) {
653
+ log(`tool ${tu.name} dispatch threw: ${err?.message ?? err}`);
654
+ return {
655
+ type: 'tool_result',
656
+ tool_use_id: tu.id,
657
+ content: `Error: ${err?.message ?? err}`,
658
+ is_error: true,
659
+ };
660
+ }
661
+ }));
662
+ messages.push({ role: 'user', content: toolResults });
663
+ }
664
+ }
665
+ finally {
666
+ this.abortController = null;
667
+ }
668
+ return {
669
+ sdkResult: { assistantText, stopReason, usage: lastUsage },
670
+ elapsedMs: Date.now() - t0,
671
+ };
672
+ }
673
+ /**
674
+ * Emit a workflow-side message recommending `save_state` + `restart`
675
+ * when Anthropic returns `stop_reason: 'model_context_window_exceeded'`.
676
+ * Design §5.5 — auto-compact via #334 deferred to Phase 2.
677
+ */
678
+ async deliverContextOverflowMessage(handle) {
679
+ const overflowMessage = [
680
+ '⚠️ **Context window exhausted.**',
681
+ '',
682
+ 'The conversation has grown beyond this model\'s context. Recommended actions:',
683
+ '1. `save_state(content: "<curated summary of where you are>")`',
684
+ '2. `restart({ loadFromState: true })` — new session resumes from your saved state',
685
+ '',
686
+ 'Alternatively, ask the conductor to recruit a fresh player and hand off via cue.',
687
+ ].join('\n');
688
+ try {
689
+ await handle.signal(signals_1.receiveMessageSignal, {
690
+ from: 'system',
691
+ text: overflowMessage,
692
+ responseRequested: false,
693
+ });
694
+ }
695
+ catch (err) {
696
+ log(`context-overflow message signal failed: ${err?.message ?? err}`);
697
+ }
698
+ }
699
+ }
700
+ exports.DirectApiAttachment = DirectApiAttachment;
701
+ /**
702
+ * Reduce one `MessageStreamEvent` into the assistant content-block array.
703
+ * Mutates `blocks` in place; same `index` slots map to the same block so
704
+ * interleaved deltas land in the right bucket. Tool input accumulates as
705
+ * a string per verification §2.4 (parsed only by the caller, after
706
+ * `content_block_stop`).
707
+ */
708
+ function handleStreamEvent(event, blocks) {
709
+ switch (event.type) {
710
+ case 'content_block_start': {
711
+ const idx = event.index ?? blocks.length;
712
+ const cb = event.content_block;
713
+ if (!cb)
714
+ return;
715
+ if (cb.type === 'text') {
716
+ blocks[idx] = { type: 'text', text: cb.text ?? '' };
717
+ }
718
+ else if (cb.type === 'tool_use') {
719
+ blocks[idx] = { type: 'tool_use', id: cb.id ?? '', name: cb.name ?? '', input: '' };
720
+ }
721
+ else if (cb.type === 'thinking') {
722
+ blocks[idx] = { type: 'thinking', thinking: cb.thinking ?? '' };
723
+ }
724
+ // Other block types (e.g. server_tool_use) silently ignored — Phase 2.
725
+ return;
726
+ }
727
+ case 'content_block_delta': {
728
+ const idx = event.index ?? -1;
729
+ const block = blocks[idx];
730
+ const d = event.delta;
731
+ if (!block || !d)
732
+ return;
733
+ if (d.type === 'text_delta' && block.type === 'text' && d.text) {
734
+ block.text += d.text;
735
+ }
736
+ else if (d.type === 'input_json_delta' && block.type === 'tool_use' && d.partial_json) {
737
+ // Verification §2.4 — append raw chunks; do NOT JSON.parse here.
738
+ block.input += d.partial_json;
739
+ }
740
+ else if (d.type === 'thinking_delta' && block.type === 'thinking' && d.thinking) {
741
+ block.thinking += d.thinking;
742
+ }
743
+ else if (d.type === 'signature_delta' && block.type === 'thinking' && d.signature) {
744
+ // Verification §2.1 — preserve the signature so the next turn's
745
+ // assistant message can replay it for reasoning continuity.
746
+ block.signature = (block.signature ?? '') + d.signature;
747
+ }
748
+ return;
749
+ }
750
+ // content_block_stop / message_start / message_delta / message_stop — handled by caller.
751
+ }
752
+ }
753
+ /**
754
+ * Merge workflow `Message[]` (received from others) + `SentMessage[]`
755
+ * (sent by this player) into the Anthropic Messages API array.
756
+ *
757
+ * - Sort chronologically by `timestamp`
758
+ * - `received` → `role: 'user'`, `sent` → `role: 'assistant'`
759
+ * - Fold consecutive same-role rows into one message (Anthropic requires
760
+ * strict user/assistant alternation; multiple cues stacking into the
761
+ * player's queue must collapse to a single user turn)
762
+ * - Tag each row with the originator (`[from: <name>]`) on user side so
763
+ * the model can disambiguate when several players are talking
764
+ *
765
+ * Exported for unit testing (commit 5).
766
+ */
767
+ function buildAnthropicMessages(received, sent) {
768
+ const rows = [];
769
+ for (const m of received)
770
+ rows.push({ role: 'user', text: `[from ${m.from}]: ${m.text}`, ts: m.timestamp });
771
+ for (const m of sent)
772
+ rows.push({ role: 'assistant', text: m.text, ts: m.timestamp });
773
+ rows.sort((a, b) => a.ts.localeCompare(b.ts));
774
+ const out = [];
775
+ for (const r of rows) {
776
+ const last = out[out.length - 1];
777
+ if (last && last.role === r.role) {
778
+ last.content += '\n\n' + r.text;
779
+ }
780
+ else {
781
+ out.push({ role: r.role, content: r.text });
782
+ }
783
+ }
784
+ return out;
785
+ }
786
+ if (require.main === module) {
787
+ if (!Anthropic) {
788
+ // The optional-dep guard above already exited — this is unreachable in
789
+ // practice but kept for type-narrowing safety on the self-exec path.
790
+ process.exit(1);
791
+ }
792
+ const model = process.env[config_1.ENV.API_MODEL];
793
+ new DirectApiAttachment(model ? { model } : {}).run().catch((err) => {
794
+ log('Fatal error:', err);
795
+ process.exit(1);
796
+ });
797
+ }