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,684 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AggregateRunner = exports.TickSkipped = exports.MAX_CONSECUTIVE_FAILURES = exports.AGGREGATE_LIST_DEADLINE_MS = exports.DEFAULT_POLL_INTERVAL_MS = void 0;
4
+ exports.canonicalize = canonicalize;
5
+ exports.hashOf = hashOf;
6
+ exports.diffEnsembleSnapshot = diffEnsembleSnapshot;
7
+ exports.diffHostProfiles = diffHostProfiles;
8
+ exports.diffEnsembleSet = diffEnsembleSet;
9
+ /**
10
+ * Aggregate poll loop (SSE-PROTOCOL.md §8, §11; ADR 0004).
11
+ *
12
+ * **Job**: every `pollIntervalMs` (default 750), snapshot the cluster
13
+ * state via `TempoClient`, diff against the prior snapshot, and emit
14
+ * events to the per-ensemble + global buses. Subscriber count is
15
+ * irrelevant — every TUI / web-dashboard reads from the same one
16
+ * snapshot per tick.
17
+ *
18
+ * **Backpressure** (§8): serial-with-skip. If a tick's queries don't
19
+ * complete before the next 750 ms boundary, the next tick is skipped
20
+ * and a `[agent-tempo:aggregate]` warn-log fires. The daemon never
21
+ * has more than one in-flight aggregate fetch.
22
+ *
23
+ * **Coalescing** (§6, §8):
24
+ *
25
+ * - `player.added/removed` — diff player set
26
+ * - `player.phase_changed` — emitted whenever the phase string changes
27
+ * between polls. The 250 ms / playerId latest-wins debounce in §6
28
+ * collapses naturally to "one emit per poll" because the poll
29
+ * cadence (750 ms) is wider than the debounce window — no extra
30
+ * timer needed.
31
+ * - `chat.appended` — per new message id since last poll. Bus-level
32
+ * §8 chat cap collapses excess to `chat.compressed`.
33
+ * - `flags.changed` — only when `(paused, held)` tuple changes.
34
+ * - `schedules.changed` — SHA-256 diff of sorted-by-name JSON.
35
+ * - `host_profile.changed` — per-host SHA-256 diff (scrubbed payload
36
+ * from `listHosts` already; aggregate just hashes the projection).
37
+ * - `ensemble.created/destroyed` — diff ensemble-name set.
38
+ */
39
+ const crypto_1 = require("crypto");
40
+ const event_bus_1 = require("./event-bus");
41
+ const event_id_1 = require("./event-id");
42
+ const snapshot_1 = require("./snapshot");
43
+ /** Default cadence per spec §8. */
44
+ exports.DEFAULT_POLL_INTERVAL_MS = 750;
45
+ /** How many chat-message ids to remember per ensemble. */
46
+ const CHAT_ID_MEMORY = 1024;
47
+ /** How many chat messages to fetch per poll — wider than the snapshot limit so bursts aren't silently lost. */
48
+ const POLL_CHAT_LIMIT = 200;
49
+ /**
50
+ * #336/#529 site 6 — correctness deadline for `listEnsemblesBounded()`
51
+ * inside `collect()`. 500ms < 750ms cadence leaves ≥250ms headroom for
52
+ * `applyDiff()` + per-ensemble fan-out + emission. Architect's call:
53
+ * the 15s `tickWatchdogMs` is the *liveness* guard; this 500ms is the
54
+ * *correctness* guard preventing partial-input ensemble diffs.
55
+ */
56
+ exports.AGGREGATE_LIST_DEADLINE_MS = 500;
57
+ /**
58
+ * #550 — consecutive-failure cap for the per-ensemble fan-out
59
+ * carry-forward. After this many `'failed'` outcomes in a row, the
60
+ * ensemble is promoted from `'failed'` (carry-forward in liveNames) to
61
+ * `'gone'` (excluded → emits `ensemble.destroyed`). Matches the
62
+ * 15s tick watchdog ceiling at 750ms cadence: 20 ticks × 750ms ≈ 15s.
63
+ * Operator-coherent: "can't observe → eventually treat as gone."
64
+ *
65
+ * Counted as the threshold to EXCEED (cf > K) so the spec table holds:
66
+ * 20 consecutive `'failed'` ticks stay 'failed'; the 21st promotes.
67
+ */
68
+ exports.MAX_CONSECUTIVE_FAILURES = 20;
69
+ const log = (...args) => console.error('[agent-tempo:aggregate]', ...args);
70
+ /**
71
+ * #336/#529 site 6 — sentinel thrown by `collect()` when the
72
+ * visibility-iterator deadline trips. `tick()`'s catch logs it calmly
73
+ * and SKIPS `applyDiff()`, preserving `knownEnsembles` so the next
74
+ * successful tick diffs against the truthful prior set rather than
75
+ * emitting phantom `ensemble.destroyed` events.
76
+ *
77
+ * Modeled as a distinct error class so the catch can branch
78
+ * informatively (`if (err instanceof TickSkipped)`) without parsing
79
+ * error messages — and so callers downstream don't accidentally
80
+ * propagate it as a fatal error.
81
+ */
82
+ class TickSkipped extends Error {
83
+ name = 'TickSkipped';
84
+ constructor(reason) { super(reason); }
85
+ }
86
+ exports.TickSkipped = TickSkipped;
87
+ /**
88
+ * Stable JSON canonicalization — keys sorted, no extraneous whitespace.
89
+ * Used for SHA-256 diff suppression so reordered key emits don't
90
+ * produce false-positive `*.changed` events.
91
+ */
92
+ function canonicalize(value) {
93
+ if (value === null || typeof value !== 'object')
94
+ return JSON.stringify(value);
95
+ if (Array.isArray(value)) {
96
+ return '[' + value.map(canonicalize).join(',') + ']';
97
+ }
98
+ const keys = Object.keys(value).sort();
99
+ return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalize(value[k])).join(',') + '}';
100
+ }
101
+ /** SHA-256 of `canonicalize(value)`. */
102
+ function hashOf(value) {
103
+ return (0, crypto_1.createHash)('sha256').update(canonicalize(value)).digest('hex');
104
+ }
105
+ function diffEnsembleSnapshot(prev, next, track, capturedAt) {
106
+ const events = [];
107
+ const ensemble = next.ensemble;
108
+ // Build maps for O(1) lookup.
109
+ const prevPlayers = new Map();
110
+ if (prev)
111
+ for (const p of prev.players)
112
+ prevPlayers.set(p.playerId, p);
113
+ const nextPlayers = new Map();
114
+ for (const p of next.players)
115
+ nextPlayers.set(p.playerId, p);
116
+ // player.added / player.phase_changed — iterate next.
117
+ for (const p of next.players) {
118
+ if (!prevPlayers.has(p.playerId)) {
119
+ events.push({ type: 'player.added', payload: p });
120
+ track.playerPhases.set(p.playerId, p.phase);
121
+ // #535 — record the adapter family so the prior reconstruction at
122
+ // the next tick (aggregate.ts ~L600) carries the real agentType
123
+ // instead of a hardcoded `'claude'` stand-in. Treated as immutable
124
+ // for the player's lifetime; cleared on `player.removed` below.
125
+ track.playerAgentTypes.set(p.playerId, p.agentType);
126
+ continue;
127
+ }
128
+ const lastPhase = track.playerPhases.get(p.playerId);
129
+ if (p.phase !== lastPhase) {
130
+ events.push({
131
+ type: 'player.phase_changed',
132
+ payload: {
133
+ playerId: p.playerId,
134
+ ensemble,
135
+ phase: p.phase,
136
+ ...(p.lastHeartbeatAt ? { lastHeartbeatAt: p.lastHeartbeatAt } : {}),
137
+ ...(p.processingSince ? { processingSince: p.processingSince } : {}),
138
+ at: capturedAt,
139
+ },
140
+ });
141
+ track.playerPhases.set(p.playerId, p.phase);
142
+ }
143
+ }
144
+ // player.removed — iterate prev.
145
+ if (prev) {
146
+ for (const p of prev.players) {
147
+ if (!nextPlayers.has(p.playerId)) {
148
+ events.push({
149
+ type: 'player.removed',
150
+ payload: {
151
+ playerId: p.playerId,
152
+ ensemble,
153
+ removedAt: capturedAt,
154
+ // We can't tell from a snapshot diff whether the player was
155
+ // destroyed or moved to `gone` — pick the safer default.
156
+ // PR-2 callers shouldn't depend on this distinction; PR-3+
157
+ // can refine when there's a stronger signal source.
158
+ reason: 'gone',
159
+ },
160
+ });
161
+ track.playerPhases.delete(p.playerId);
162
+ track.playerAgentTypes.delete(p.playerId);
163
+ }
164
+ }
165
+ }
166
+ // flags.changed — only on tuple change.
167
+ const prevFlags = track.flags;
168
+ if (!prevFlags ||
169
+ prevFlags.paused !== next.flags.paused ||
170
+ prevFlags.held !== next.flags.held) {
171
+ events.push({
172
+ type: 'flags.changed',
173
+ payload: {
174
+ ensemble,
175
+ paused: next.flags.paused,
176
+ held: next.flags.held,
177
+ at: capturedAt,
178
+ },
179
+ });
180
+ track.flags = { ...next.flags };
181
+ }
182
+ // schedules.changed — SHA-256 of sorted-by-name JSON.
183
+ const sortedSchedules = [...next.schedules].sort((a, b) => a.name.localeCompare(b.name));
184
+ const newScheduleHash = hashOf(sortedSchedules);
185
+ if (track.schedulesHash !== newScheduleHash) {
186
+ events.push({
187
+ type: 'schedules.changed',
188
+ payload: { ensemble, schedules: sortedSchedules, at: capturedAt },
189
+ });
190
+ track.schedulesHash = newScheduleHash;
191
+ }
192
+ // chat.appended — every message whose id we haven't seen yet.
193
+ for (const msg of next.chat) {
194
+ if (track.chatIds.has(msg.id))
195
+ continue;
196
+ events.push({ type: 'chat.appended', payload: msg });
197
+ track.chatIds.add(msg.id);
198
+ track.chatIdOrder.push(msg.id);
199
+ while (track.chatIdOrder.length > CHAT_ID_MEMORY) {
200
+ const evicted = track.chatIdOrder.shift();
201
+ if (evicted !== undefined)
202
+ track.chatIds.delete(evicted);
203
+ }
204
+ }
205
+ return events;
206
+ }
207
+ /**
208
+ * Diff host profiles map → per-host events. Pure function.
209
+ */
210
+ function diffHostProfiles(prevHashes, nextProfiles) {
211
+ const nextHashes = new Map();
212
+ const events = [];
213
+ for (const [hostname, profile] of Object.entries(nextProfiles)) {
214
+ const h = hashOf(profile);
215
+ nextHashes.set(hostname, h);
216
+ if (prevHashes.get(hostname) !== h) {
217
+ events.push({ type: 'host_profile.changed', payload: profile });
218
+ }
219
+ }
220
+ return { events, hashes: nextHashes };
221
+ }
222
+ /**
223
+ * Diff ensemble-name set → ensemble.created / ensemble.destroyed events.
224
+ *
225
+ * #550 — accepts the slim `{ensemble, hasConductor}` shape rather than
226
+ * a full `AggregateEnsembleSnapshot[]`. The full-snapshot type still
227
+ * satisfies it structurally, so existing tests passing
228
+ * `AggregateEnsembleSnapshot[]` continue to compile. The slimmer
229
+ * input lets `applyDiff` feed the `livePrelude` carry-forward set
230
+ * (which contains stub entries for `'failed'` outcomes that have no
231
+ * full snapshot this tick).
232
+ */
233
+ function diffEnsembleSet(prev, nextEnsembles, capturedAt) {
234
+ const events = [];
235
+ const names = new Set();
236
+ const seen = new Map();
237
+ for (const e of nextEnsembles) {
238
+ names.add(e.ensemble);
239
+ seen.set(e.ensemble, e);
240
+ }
241
+ for (const name of names) {
242
+ if (!prev.has(name)) {
243
+ const e = seen.get(name);
244
+ events.push({
245
+ type: 'ensemble.created',
246
+ payload: {
247
+ ensemble: name,
248
+ createdAt: capturedAt,
249
+ hasConductor: e.hasConductor,
250
+ },
251
+ });
252
+ }
253
+ }
254
+ for (const name of prev) {
255
+ if (!names.has(name)) {
256
+ events.push({
257
+ type: 'ensemble.destroyed',
258
+ payload: { ensemble: name, destroyedAt: capturedAt },
259
+ });
260
+ }
261
+ }
262
+ return { events, names };
263
+ }
264
+ /**
265
+ * Coordinates the poll loop and owns the per-ensemble + global buses.
266
+ * Daemon constructs one of these after `runDaemonBoot`. Hot path is
267
+ * `tick()` — production schedules it on a timer, tests call it
268
+ * directly for deterministic diffing.
269
+ */
270
+ class AggregateRunner {
271
+ client;
272
+ bootEpoch;
273
+ pollIntervalMs;
274
+ now;
275
+ busFactory;
276
+ /** Per-ensemble bus + tracking state. */
277
+ tracks = new Map();
278
+ /** Global bus — handles `ensemble.created/destroyed` + `host_profile.changed`. */
279
+ _globalBus;
280
+ globalSeqAllocator;
281
+ /** Last seen ensemble names + host profile hashes for diff. */
282
+ knownEnsembles = new Set();
283
+ hostHashes = new Map();
284
+ /** Loop state. */
285
+ timer = null;
286
+ inFlight = false;
287
+ skipCount = 0;
288
+ /** Last skip-warning emit time — rate-limited so a wedged Temporal doesn't drown the log. */
289
+ lastSkipWarn = 0;
290
+ stopped = false;
291
+ /**
292
+ * Issue #433 — tick generation counter. Incremented each time a tick
293
+ * starts. The watchdog stamps the current generation when it force-clears
294
+ * `inFlight`; a late-arriving completion checks `myGen === currentGen`
295
+ * before resetting state so it doesn't clobber a fresh tick that the
296
+ * watchdog already handed to the loop.
297
+ */
298
+ tickGen = 0;
299
+ /**
300
+ * Watchdog ceiling — if a tick exceeds this without finishing, the
301
+ * watchdog force-clears `inFlight` so the next scheduled tick can run.
302
+ * Default: 20 × poll interval (15s at the 750ms default). Should be
303
+ * comfortably larger than `pollIntervalMs × DEFAULT_QUERY_TIMEOUT_MS /
304
+ * pollInterval` so `queryHandleWithTimeout`-bounded ticks finish well
305
+ * inside the watchdog window in normal operation.
306
+ */
307
+ tickWatchdogMs;
308
+ constructor(opts) {
309
+ this.client = opts.client;
310
+ this.bootEpoch = opts.bootEpoch;
311
+ this.pollIntervalMs = opts.pollIntervalMs ?? exports.DEFAULT_POLL_INTERVAL_MS;
312
+ this.now = opts.now ?? Date.now;
313
+ this.tickWatchdogMs = opts.tickWatchdogMs ?? this.pollIntervalMs * 20;
314
+ this.busFactory = opts.busFactory
315
+ ?? ((ensemble, allocator) => new event_bus_1.EnsembleEventBus({
316
+ scope: `ensemble:${ensemble}`, allocator, now: this.now,
317
+ }));
318
+ this.globalSeqAllocator = new event_id_1.SeqAllocator(opts.bootEpoch);
319
+ this._globalBus = new event_bus_1.EnsembleEventBus({
320
+ scope: 'global', allocator: this.globalSeqAllocator, now: this.now,
321
+ });
322
+ }
323
+ /**
324
+ * Begin polling on the configured cadence.
325
+ *
326
+ * **One-shot by design**: a single `start()` schedules the first
327
+ * tick immediately and chains subsequent ticks via `setTimeout` —
328
+ * each tick reschedules the next as it completes. Repeated
329
+ * `start()` calls are no-ops once the chain is running (and after
330
+ * `stop()` flips `stopped = true`, future `start()` calls are
331
+ * also no-ops). Tests that want to drive ticks deterministically
332
+ * skip `start()` and call `tick()` directly. PR #324 review nit
333
+ * folded in.
334
+ */
335
+ start() {
336
+ if (this.timer || this.stopped)
337
+ return;
338
+ const tickAndSchedule = () => {
339
+ if (this.stopped)
340
+ return;
341
+ void this.tick();
342
+ this.timer = setTimeout(tickAndSchedule, this.pollIntervalMs);
343
+ this.timer.unref();
344
+ };
345
+ // Run once immediately so a fresh boot has events to serve.
346
+ tickAndSchedule();
347
+ }
348
+ /** Stop polling. Buses stay alive — daemon owns their close lifecycle. */
349
+ stop() {
350
+ this.stopped = true;
351
+ if (this.timer) {
352
+ clearTimeout(this.timer);
353
+ this.timer = null;
354
+ }
355
+ }
356
+ /** Drain every bus + clear tracking. */
357
+ close() {
358
+ this.stop();
359
+ for (const t of this.tracks.values())
360
+ t.bus.close();
361
+ this.tracks.clear();
362
+ this._globalBus.close();
363
+ }
364
+ /** The global bus — exposed for the SSE handler's `/v1/events` route. */
365
+ globalBus() { return this._globalBus; }
366
+ /**
367
+ * Look up (or lazily build) the per-ensemble bus. SSE handler uses
368
+ * this to subscribe a new client; aggregate diff uses it to emit
369
+ * `player.added`-and-friends.
370
+ */
371
+ getOrCreateEnsembleBus(ensemble) {
372
+ let track = this.tracks.get(ensemble);
373
+ if (!track) {
374
+ const allocator = new event_id_1.SeqAllocator(this.bootEpoch);
375
+ const bus = this.busFactory(ensemble, allocator);
376
+ track = {
377
+ bus,
378
+ consecutiveFailures: 0,
379
+ playerPhases: new Map(),
380
+ playerAgentTypes: new Map(),
381
+ flags: null,
382
+ schedulesHash: null,
383
+ chatIds: new Set(),
384
+ chatIdOrder: [],
385
+ };
386
+ this.tracks.set(ensemble, track);
387
+ }
388
+ return track.bus;
389
+ }
390
+ /** Returns the bus for an ensemble if one exists, else `null`. */
391
+ getEnsembleBus(ensemble) {
392
+ return this.tracks.get(ensemble)?.bus ?? null;
393
+ }
394
+ /** Total live subscriber count across all buses — `/v1/health.subscriberCount`. */
395
+ totalSubscriberCount() {
396
+ let n = this._globalBus.subscriberCount();
397
+ for (const t of this.tracks.values())
398
+ n += t.bus.subscriberCount();
399
+ return n;
400
+ }
401
+ /**
402
+ * Run one diff pass. Production schedules this on a timer; tests
403
+ * call it directly. Serial-with-skip per §8 — if a previous tick
404
+ * is still in flight, this one skips with a warn-log.
405
+ *
406
+ * **Watchdog (#433)**. Each tick takes a generation stamp. After
407
+ * `tickWatchdogMs` elapses without completion, the watchdog bumps
408
+ * the generation and clears `inFlight` so the next scheduled tick
409
+ * can run. The hung tick's eventual completion checks its own
410
+ * generation against the current one; if it's stale (watchdog
411
+ * already advanced past it), it returns without touching `inFlight`
412
+ * — the new tick owns that flag now.
413
+ */
414
+ async tick() {
415
+ if (this.inFlight) {
416
+ this.skipCount++;
417
+ const now = this.now();
418
+ // Throttle warn-logs to once per 5 s so a wedged Temporal doesn't flood.
419
+ if (now - this.lastSkipWarn >= 5_000) {
420
+ this.lastSkipWarn = now;
421
+ log(`tick skipped — prior tick still in flight (skipCount=${this.skipCount})`);
422
+ }
423
+ return;
424
+ }
425
+ this.inFlight = true;
426
+ const myGen = ++this.tickGen;
427
+ // Watchdog — if we don't complete within `tickWatchdogMs`, force-clear
428
+ // `inFlight` so the next tick can run. Bumping `tickGen` ensures our
429
+ // own (eventually-arriving) finally clause sees a stale generation
430
+ // and skips the second `inFlight = false`.
431
+ let watchdogFired = false;
432
+ const watchdog = setTimeout(() => {
433
+ if (this.tickGen === myGen) {
434
+ watchdogFired = true;
435
+ this.tickGen++;
436
+ this.inFlight = false;
437
+ log(`tick watchdog fired after ${this.tickWatchdogMs}ms — ` +
438
+ `clearing inFlight so the loop can advance ` +
439
+ `(prior tick may still be pending in memory; see #433)`);
440
+ }
441
+ }, this.tickWatchdogMs);
442
+ watchdog.unref?.();
443
+ try {
444
+ const snapshot = await this.collect();
445
+ this.applyDiff(snapshot);
446
+ }
447
+ catch (err) {
448
+ // #336/#529 site 6 — `TickSkipped` is the architect-approved
449
+ // signal that `collect()` deliberately bailed before applying
450
+ // any diff (e.g. visibility-iterator deadline tripped). It is
451
+ // expected and frequent under load; do NOT log noisily. Other
452
+ // throws are unexpected failures worth surfacing.
453
+ if (err instanceof TickSkipped) {
454
+ log(`tick=${myGen}: skipped — ${err.message}`);
455
+ }
456
+ else {
457
+ log('tick error (non-fatal):', err instanceof Error ? err.message : err);
458
+ }
459
+ }
460
+ finally {
461
+ clearTimeout(watchdog);
462
+ // Only release `inFlight` if the watchdog hasn't already done so —
463
+ // otherwise we'd clobber a fresh tick that the loop is in the
464
+ // middle of running on the next scheduled boundary.
465
+ if (!watchdogFired && this.tickGen === myGen) {
466
+ this.inFlight = false;
467
+ }
468
+ }
469
+ }
470
+ /** Fetch the current cluster state via `TempoClient`. */
471
+ async collect() {
472
+ const tickGen = this.tickGen;
473
+ const preludeStart = this.now();
474
+ log(`collect tick=${tickGen}: prelude started`);
475
+ const capturedAt = new Date(this.now()).toISOString();
476
+ // #336/#529 site 6 — bounded variant prevents the 750ms poll from
477
+ // emitting phantom `ensemble.destroyed` events when the visibility
478
+ // iterator partially enumerates under load. On timeout, throw
479
+ // `TickSkipped` so `tick()`'s catch preserves `knownEnsembles`
480
+ // unchanged (no `applyDiff` runs). 500ms budget leaves >=250ms
481
+ // headroom for the rest of `collect()` + `applyDiff` + emission
482
+ // within the 750ms cadence (architect's call).
483
+ const listRes = await this.client.listEnsemblesBounded(exports.AGGREGATE_LIST_DEADLINE_MS);
484
+ if (listRes.timedOut) {
485
+ log(`collect tick=${tickGen}: listEnsembles deadline (` +
486
+ `${exports.AGGREGATE_LIST_DEADLINE_MS}ms, ${listRes.scanned} scanned) — skip diff`);
487
+ throw new TickSkipped('listEnsembles deadline');
488
+ }
489
+ const ensembles = listRes.items;
490
+ const hostProfiles = {};
491
+ try {
492
+ const hosts = await this.client.listHosts();
493
+ for (const h of hosts) {
494
+ if (h.profile)
495
+ hostProfiles[h.hostname] = h.profile;
496
+ }
497
+ }
498
+ catch { /* fall through with empty hostProfiles */ }
499
+ const preludeMs = this.now() - preludeStart;
500
+ log(`collect tick=${tickGen}: prelude complete ` +
501
+ `(${preludeMs}ms, ensembles=${ensembles.length}, ` +
502
+ `hosts=${Object.keys(hostProfiles).length})`);
503
+ // Per-ensemble fan-out. #550 — returns `FanoutResult` discriminated
504
+ // union so the cluster diff downstream can distinguish "still alive
505
+ // (carry forward)" from "actually destroyed (emit destroy)."
506
+ const pollStart = this.now();
507
+ log(`collect tick=${tickGen}: poll started`);
508
+ const initialResults = await Promise.all(ensembles.map(async (e) => {
509
+ try {
510
+ // Reuse `buildEnsembleSnapshot` so the projection logic stays in
511
+ // one place. We only need a subset, but the full builder is cheap.
512
+ const snap = await (0, snapshot_1.buildEnsembleSnapshot)(this.client, e.name, {
513
+ now: () => new Date(this.now()),
514
+ });
515
+ // Replace chat with a wider window so the aggregate doesn't miss
516
+ // bursts between polls. The bus's §8 cap collapses excess.
517
+ let chat = snap.chat.messages;
518
+ try {
519
+ const wider = await this.client.getEnsembleChat(e.name, 0, POLL_CHAT_LIMIT);
520
+ chat = wider.messages;
521
+ }
522
+ catch { /* fall back to the snapshot's narrow slice */ }
523
+ return {
524
+ kind: 'ok',
525
+ snapshot: {
526
+ ensemble: e.name,
527
+ hasConductor: snap.hasConductor,
528
+ flags: snap.flags,
529
+ players: snap.players,
530
+ schedules: snap.schedules,
531
+ chat,
532
+ },
533
+ };
534
+ }
535
+ catch (err) {
536
+ // #550 — conservative classification: only `EnsembleNotFoundError`
537
+ // counts as a real removal. Everything else (query timeout,
538
+ // network blip, transient worker wedge) is `'failed'` →
539
+ // carry-forward. False-negative destroy events are the worst
540
+ // possible failure mode (SSE consumers may take destructive
541
+ // action), so be deliberate about what gets classified as
542
+ // genuinely gone.
543
+ if (err instanceof snapshot_1.EnsembleNotFoundError) {
544
+ return { kind: 'gone', name: e.name };
545
+ }
546
+ log(`collect: ensemble "${e.name}" failed (carry-forward):`, err instanceof Error ? err.message : err);
547
+ return { kind: 'failed', name: e.name };
548
+ }
549
+ }));
550
+ // #550 — Update per-ensemble `consecutiveFailures` counters and
551
+ // apply the K=20 promotion pass. Mutating tracks here is safe (we
552
+ // serialize after Promise.all completes; the tick is single-flight
553
+ // courtesy of `inFlight`). Promoting 'failed' → 'gone' triggers
554
+ // `applyDiff` to tear the track down.
555
+ let okCount = 0, goneCount = 0, failedCount = 0, promotedCount = 0;
556
+ const finalResults = initialResults.map((r) => {
557
+ if (r.kind === 'ok') {
558
+ const t = this.tracks.get(r.snapshot.ensemble);
559
+ if (t)
560
+ t.consecutiveFailures = 0;
561
+ okCount++;
562
+ return r;
563
+ }
564
+ if (r.kind === 'gone') {
565
+ goneCount++;
566
+ return r;
567
+ }
568
+ // r.kind === 'failed' — ensure a track exists so the counter has
569
+ // a home, then increment + maybe promote.
570
+ this.getOrCreateEnsembleBus(r.name);
571
+ const t = this.tracks.get(r.name);
572
+ t.consecutiveFailures++;
573
+ if (t.consecutiveFailures > exports.MAX_CONSECUTIVE_FAILURES) {
574
+ log(`collect tick=${tickGen}: ensemble "${r.name}" failed ` +
575
+ `${t.consecutiveFailures}× consecutively (cap=${exports.MAX_CONSECUTIVE_FAILURES}) — promote to 'gone'`);
576
+ promotedCount++;
577
+ // applyDiff's teardown loop will drop the track on the next tick.
578
+ return { kind: 'gone', name: r.name };
579
+ }
580
+ failedCount++;
581
+ return r;
582
+ });
583
+ // #550 — Build the cluster-diff input: ('ok' ∪ 'failed') with
584
+ // truthful `hasConductor` for each (from the fresh ok snapshot
585
+ // OR carried forward from the listEnsembles prelude for failed).
586
+ // 'gone' results (including K-promoted) are excluded so they
587
+ // trigger `ensemble.destroyed` downstream.
588
+ const preludeHasConductor = new Map(ensembles.map((e) => [e.name, e.hasConductor]));
589
+ const livePrelude = [];
590
+ const okSnapshots = [];
591
+ for (const r of finalResults) {
592
+ if (r.kind === 'ok') {
593
+ okSnapshots.push(r.snapshot);
594
+ livePrelude.push({ ensemble: r.snapshot.ensemble, hasConductor: r.snapshot.hasConductor });
595
+ }
596
+ else if (r.kind === 'failed') {
597
+ livePrelude.push({
598
+ ensemble: r.name,
599
+ hasConductor: preludeHasConductor.get(r.name) ?? false,
600
+ });
601
+ }
602
+ // 'gone' — explicitly excluded.
603
+ }
604
+ const pollMs = this.now() - pollStart;
605
+ log(`collect tick=${tickGen}: poll complete (${pollMs}ms, ` +
606
+ `ok=${okCount}, failed=${failedCount}, gone=${goneCount}, promoted=${promotedCount})`);
607
+ return {
608
+ capturedAt,
609
+ ensembles: okSnapshots,
610
+ livePrelude,
611
+ hostProfiles,
612
+ };
613
+ }
614
+ /** Apply diff and emit events — exposed for tests that want to inject fixtures. */
615
+ applyDiff(snapshot) {
616
+ // Global: ensemble created/destroyed. #550 — diff against
617
+ // `snapshot.livePrelude` (ok ∪ failed) so a transient per-ensemble
618
+ // fan-out failure does NOT trigger `ensemble.destroyed`. The
619
+ // discrimination between "failed (carry forward)" and "gone (real
620
+ // removal)" was made upstream in `collect()` per architect's spec;
621
+ // `applyDiff` is intentionally ignorant of the failure mode and
622
+ // just consumes the resolved live set.
623
+ const { events: ensembleEvents, names } = diffEnsembleSet(this.knownEnsembles, snapshot.livePrelude, snapshot.capturedAt);
624
+ for (const ev of ensembleEvents)
625
+ this._globalBus.emit(ev.type, ev.payload);
626
+ this.knownEnsembles = names;
627
+ // Global: host profile diffs.
628
+ const hostDiff = diffHostProfiles(this.hostHashes, snapshot.hostProfiles);
629
+ for (const ev of hostDiff.events)
630
+ this._globalBus.emit(ev.type, ev.payload);
631
+ this.hostHashes = hostDiff.hashes;
632
+ // Per-ensemble: player.added/removed/phase_changed, chat.appended, flags.changed, schedules.changed.
633
+ for (const eState of snapshot.ensembles) {
634
+ const track = this.ensureTrack(eState.ensemble);
635
+ // Build prior aggregate-snapshot view from track state — only used
636
+ // for player set diff; flags/schedules/chat use their own track fields.
637
+ const prior = {
638
+ ensemble: eState.ensemble,
639
+ hasConductor: false, // not used by diffEnsembleSnapshot
640
+ flags: track.flags ?? { paused: false, held: false }, // not used; uses track.flags directly
641
+ players: [...track.playerPhases.keys()].map((id) => ({
642
+ // Minimal stand-in — only `playerId` is consulted by `diffEnsembleSnapshot`
643
+ // for `player.removed` events (see L154-L174). The other fields are
644
+ // zero-cost placeholders that satisfy `PlayerSummaryV1` typing without
645
+ // affecting any emitted event payload. `agentType` reads from the
646
+ // parallel track Map so the prior carries the player's real adapter
647
+ // family (#535) — pre-#535 this was hardcoded `'claude' as const`,
648
+ // which became a type lie once the wire union expanded to mirror
649
+ // `AgentType`. Fallback to `'claude'` covers the brief migration
650
+ // window where a long-running daemon's tracks predate the new Map.
651
+ playerId: id, ensemble: eState.ensemble, hostname: '', isConductor: false,
652
+ agentType: track.playerAgentTypes.get(id) ?? 'claude', part: '', workDir: '',
653
+ phase: track.playerPhases.get(id),
654
+ })),
655
+ schedules: [],
656
+ chat: [],
657
+ };
658
+ const events = diffEnsembleSnapshot(prior, eState, track, snapshot.capturedAt);
659
+ for (const ev of events)
660
+ track.bus.emit(ev.type, ev.payload);
661
+ }
662
+ // Tear down buses for ensembles that disappeared.
663
+ for (const name of [...this.tracks.keys()]) {
664
+ if (!this.knownEnsembles.has(name)) {
665
+ const t = this.tracks.get(name);
666
+ if (t) {
667
+ t.bus.close();
668
+ this.tracks.delete(name);
669
+ }
670
+ }
671
+ }
672
+ }
673
+ ensureTrack(ensemble) {
674
+ let track = this.tracks.get(ensemble);
675
+ if (!track) {
676
+ this.getOrCreateEnsembleBus(ensemble);
677
+ track = this.tracks.get(ensemble);
678
+ }
679
+ return track;
680
+ }
681
+ /** Test-only — skip count for assertions. */
682
+ get _skipCount() { return this.skipCount; }
683
+ }
684
+ exports.AggregateRunner = AggregateRunner;