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,812 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentMaestroWorkflow = agentMaestroWorkflow;
4
+ exports.agentGlobalMaestroWorkflow = agentGlobalMaestroWorkflow;
5
+ const workflow_1 = require("@temporalio/workflow");
6
+ /**
7
+ * Workflow-deterministic clock — mirrors the helper in `src/workflows/session.ts`.
8
+ *
9
+ * The Temporal TS SDK intercepts `new Date()` at the sandbox level so it
10
+ * returns replay-consistent time. `Date.now()`, however, is NOT intercepted
11
+ * by default — calling it from workflow code reads the host clock and breaks
12
+ * replay determinism. The architect's #318 review flagged the per-ensemble
13
+ * maestro's idle-timeout check (`Date.now() - lastActiveSessionTime > IDLE_TIMEOUT_MS`)
14
+ * as a pre-existing instance of this bug; this commit replaces every
15
+ * `Date.now()` site in `agentMaestroWorkflow` with `workflowNow().getTime()`
16
+ * so the determinism guarantee holds end-to-end. See CLAUDE.md
17
+ * ("no `Date.now()` in workflow code, use `workflow.now()` instead") and
18
+ * `src/workflows/session.ts:21-23` for the convention.
19
+ *
20
+ * Naming matches the session-workflow helper so a grep for `workflowNow`
21
+ * finds both implementations.
22
+ */
23
+ function workflowNow() {
24
+ return new Date();
25
+ }
26
+ const maestro_signals_1 = require("./maestro-signals");
27
+ const workflow_2 = require("@temporalio/workflow");
28
+ const validation_1 = require("../utils/validation");
29
+ // ── Activity Proxies ──
30
+ // Only proxy activities actually used in the workflow.
31
+ // fetchConductorHistory is available in the activities but reserved for Phase 2 (TUI).
32
+ const { refreshEnsembleState, relayCommandToConductor, fetchEnsembleChat } = (0, workflow_1.proxyActivities)({
33
+ startToCloseTimeout: '30 seconds',
34
+ retry: { maximumAttempts: 3 },
35
+ });
36
+ const DEFAULT_REFRESH_INTERVAL_MS = 5_000; // 5 seconds
37
+ const MAX_EVENTS = 200;
38
+ const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes with no running sessions
39
+ // #399 W1 (Q5.6 Flavor B) — tempo bucket sizing.
40
+ const TEMPO_BUCKET_MS = 30_000; // 30s windows
41
+ const TEMPO_HISTORY_MAX = 60; // 60 buckets × 30s = 30-minute sparkline
42
+ // BPM is "messages per minute" — derived from the most recent window
43
+ // so the dashboard reads a stable number even between bucket rollovers.
44
+ const TEMPO_BPM_WINDOW_MS = 60_000;
45
+ // ══════════════════════════════════════════════════════════════════════════════
46
+ // Per-Ensemble Maestro (existing — unchanged)
47
+ // ══════════════════════════════════════════════════════════════════════════════
48
+ async function agentMaestroWorkflow(input) {
49
+ (0, workflow_1.patched)('v0.17-initial');
50
+ const refreshIntervalMs = input.pollIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
51
+ let players = input.players ?? [];
52
+ const events = input.events ?? [];
53
+ const pendingCommands = input.pendingCommands ?? [];
54
+ let cachedChat = input.cachedChat ?? [];
55
+ let cachedChatMeta = input.cachedChatMeta ?? { hasConductor: false };
56
+ let chatHighWater = input.chatHighWater ?? maestro_signals_1.ZERO_CHAT_HIGH_WATER;
57
+ let shutdownRequested = false;
58
+ let commandQueued = false;
59
+ let lastActiveSessionTime = workflowNow().getTime();
60
+ let ensemblePaused = input.paused ?? false;
61
+ // #399 W1 (Q5.1) — ensemble description, restored across CAN.
62
+ let description = input.description ?? '';
63
+ // #399 W1 (Q5.3a) — first-ever start time, frozen across CAN.
64
+ // Use the input value if we're a continueAsNew successor; otherwise
65
+ // adopt the current execution's startTime as the canonical first start.
66
+ const startTimeIso = input.startTimeIso ?? (0, workflow_1.workflowInfo)().startTime.toISOString();
67
+ // #399 W1 (Q5.6 Flavor B) — activity-bucket state machine.
68
+ // `historyBuckets` is the sparkline (oldest first, max 60). The
69
+ // `currentBucket` accumulates until 30 s elapse, then rolls.
70
+ const tempoHistoryBuckets = input.tempoHistoryBuckets
71
+ ? input.tempoHistoryBuckets.slice(-TEMPO_HISTORY_MAX)
72
+ : [];
73
+ let tempoCurrentBucket = input.tempoCurrentBucket ?? { startMs: workflowNow().getTime(), count: 0 };
74
+ // ── Coat-check (#318, ADR 0008) ─────────────────────────────────────
75
+ //
76
+ // Per-ensemble ticket-keyed stash. Restored across CAN; mutated by the
77
+ // four `coatCheck*` handlers below. Inline-sweep eviction runs at the
78
+ // start of every handler entry so an idle ensemble doesn't need a
79
+ // dedicated timer to GC expired entries (the 5s refresh tick below
80
+ // also touches it opportunistically).
81
+ //
82
+ // `putByIndex` is a reverse index of `putBy → Set<ticket>` rebuilt from
83
+ // the stored entries on CAN restore. It's cheap to maintain at admission
84
+ // time and supports the future per-host quota (researcher's flagged v1
85
+ // mitigation, intentionally not enforced in v1).
86
+ const coatCheck = { ...(input.coatCheck ?? {}) };
87
+ const putByIndex = new Map();
88
+ for (const [ticket, entry] of Object.entries(coatCheck)) {
89
+ const bucket = putByIndex.get(entry.putBy) ?? new Set();
90
+ bucket.add(ticket);
91
+ putByIndex.set(entry.putBy, bucket);
92
+ }
93
+ /**
94
+ * Drop entries whose `expiresAt` is in the past. Pure, deterministic —
95
+ * uses `workflowNow()`. Called at the head of every coat-check handler
96
+ * and opportunistically inside the main 5s refresh tick so idle ensembles
97
+ * still trim. Mutates `coatCheck` + `putByIndex` in place.
98
+ */
99
+ function sweepExpiredCoatCheck() {
100
+ const nowMs = workflowNow().getTime();
101
+ for (const [ticket, entry] of Object.entries(coatCheck)) {
102
+ if (Date.parse(entry.expiresAt) <= nowMs) {
103
+ delete coatCheck[ticket];
104
+ const bucket = putByIndex.get(entry.putBy);
105
+ if (bucket) {
106
+ bucket.delete(ticket);
107
+ if (bucket.size === 0)
108
+ putByIndex.delete(entry.putBy);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ // ── Signal Handlers ──
114
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroShutdownSignal, () => {
115
+ shutdownRequested = true;
116
+ });
117
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroSetPausedSignal, (value) => {
118
+ ensemblePaused = value;
119
+ });
120
+ // #399 W1 (Q5.1) — store the description as-supplied. Length is
121
+ // enforced at the MCP tool boundary (`set_ensemble_description` Zod
122
+ // schema clamps to ENSEMBLE_DESCRIPTION_MAX from utils/validation).
123
+ (0, workflow_1.setHandler)(maestro_signals_1.setEnsembleDescriptionSignal, (next) => {
124
+ description = next;
125
+ });
126
+ // ── Query Handlers ──
127
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroPlayersQuery, () => players);
128
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroEventsQuery, () => events);
129
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroPendingCommandsQuery, () => pendingCommands);
130
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroPausedQuery, () => ensemblePaused);
131
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroEnsembleChatQuery, ({ offset = 0, limit = 50 } = {}) => {
132
+ const clampedLimit = Math.min(limit, 200);
133
+ const total = cachedChat.length;
134
+ const end = Math.max(0, total - offset);
135
+ const start = Math.max(0, end - clampedLimit);
136
+ return {
137
+ messages: cachedChat.slice(start, end),
138
+ total,
139
+ hasMore: start > 0,
140
+ hasConductor: cachedChatMeta.hasConductor,
141
+ };
142
+ });
143
+ // #399 W1 (Q5.1 / Q5.3a / Q5.6) — new W1 query handlers.
144
+ (0, workflow_1.setHandler)(maestro_signals_1.getEnsembleDescriptionQuery, () => description);
145
+ (0, workflow_1.setHandler)(maestro_signals_1.getEnsembleStartTimeQuery, () => startTimeIso);
146
+ (0, workflow_1.setHandler)(maestro_signals_1.getCurrentBpmQuery, () => computeCurrentBpm(tempoHistoryBuckets, tempoCurrentBucket));
147
+ (0, workflow_1.setHandler)(maestro_signals_1.getTempoSeriesQuery, () => [...tempoHistoryBuckets]);
148
+ // ── Update Handler ──
149
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroSendCommandUpdate, (cmd) => {
150
+ const entry = {
151
+ id: (0, workflow_1.uuid4)(),
152
+ text: cmd.text,
153
+ source: cmd.source,
154
+ replyTo: cmd.replyTo,
155
+ createdAt: new Date().toISOString(),
156
+ status: 'pending',
157
+ };
158
+ pendingCommands.push(entry);
159
+ commandQueued = true;
160
+ return entry.id;
161
+ }, {
162
+ validator: (cmd) => {
163
+ if (!cmd.text || cmd.text.trim().length === 0) {
164
+ throw new Error('Command text must not be empty');
165
+ }
166
+ },
167
+ });
168
+ // ── Coat-check Handlers (#318, ADR 0008) ─────────────────────────────
169
+ (0, workflow_1.setHandler)(maestro_signals_1.coatCheckPutUpdate, (input) => {
170
+ // Sweep first so a saturation rejection reflects fresh state — a
171
+ // caller who fills the cap but waits past TTL of older entries should
172
+ // succeed on retry.
173
+ sweepExpiredCoatCheck();
174
+ const ticket = (0, workflow_1.uuid4)();
175
+ const nowDate = workflowNow();
176
+ const ttlMs = input.ttlMs ?? validation_1.COAT_CHECK_TTL_DEFAULT_MS;
177
+ const expiresAtMs = nowDate.getTime() + ttlMs;
178
+ const size = new TextEncoder().encode(input.content).length;
179
+ const entry = {
180
+ summary: input.summary,
181
+ content: input.content,
182
+ ...(input.contentType !== undefined ? { contentType: input.contentType } : {}),
183
+ putBy: input.putBy,
184
+ putAt: nowDate.toISOString(),
185
+ expiresAt: new Date(expiresAtMs).toISOString(),
186
+ size,
187
+ fetchCount: 0,
188
+ };
189
+ coatCheck[ticket] = entry;
190
+ const bucket = putByIndex.get(input.putBy) ?? new Set();
191
+ bucket.add(ticket);
192
+ putByIndex.set(input.putBy, bucket);
193
+ const slotsUsed = Object.keys(coatCheck).length;
194
+ return {
195
+ ticket,
196
+ expiresAt: entry.expiresAt,
197
+ slotsUsed,
198
+ slotsTotal: validation_1.COAT_CHECK_SLOTS_MAX,
199
+ };
200
+ }, {
201
+ validator: (input) => {
202
+ if (typeof input.summary !== 'string' || input.summary.length === 0) {
203
+ throw workflow_2.ApplicationFailure.nonRetryable('coat-check summary must be a non-empty string', 'CoatCheckInvalidSummary');
204
+ }
205
+ if (input.summary.length > validation_1.COAT_CHECK_SUMMARY_MAX) {
206
+ throw workflow_2.ApplicationFailure.nonRetryable(`coat-check summary exceeds ${validation_1.COAT_CHECK_SUMMARY_MAX} chars`, 'CoatCheckSummaryTooLarge');
207
+ }
208
+ if (typeof input.content !== 'string') {
209
+ throw workflow_2.ApplicationFailure.nonRetryable('coat-check content must be a string', 'CoatCheckInvalidContent');
210
+ }
211
+ // `TextEncoder` is replay-safe (pure string→bytes); `Buffer` is Node-
212
+ // only and not available in the workflow sandbox. Same idiom as #334.
213
+ if (new TextEncoder().encode(input.content).length > validation_1.COAT_CHECK_CONTENT_MAX) {
214
+ throw workflow_2.ApplicationFailure.nonRetryable(`coat-check content exceeds ${validation_1.COAT_CHECK_CONTENT_MAX} bytes`, 'CoatCheckEntryTooLarge');
215
+ }
216
+ if (input.contentType !== undefined) {
217
+ if (typeof input.contentType !== 'string' || input.contentType.length > validation_1.COAT_CHECK_CONTENT_TYPE_MAX) {
218
+ throw workflow_2.ApplicationFailure.nonRetryable(`coat-check contentType must be a string ≤ ${validation_1.COAT_CHECK_CONTENT_TYPE_MAX} chars`, 'CoatCheckInvalidContentType');
219
+ }
220
+ }
221
+ if (input.ttlMs !== undefined) {
222
+ if (typeof input.ttlMs !== 'number' || !Number.isFinite(input.ttlMs)
223
+ || input.ttlMs < validation_1.COAT_CHECK_TTL_MIN_MS || input.ttlMs > validation_1.COAT_CHECK_TTL_MAX_MS) {
224
+ throw workflow_2.ApplicationFailure.nonRetryable(`coat-check ttlMs must be a number in [${validation_1.COAT_CHECK_TTL_MIN_MS}, ${validation_1.COAT_CHECK_TTL_MAX_MS}]`, 'CoatCheckInvalidTtl');
225
+ }
226
+ }
227
+ if (typeof input.putBy !== 'string' || input.putBy.length === 0) {
228
+ throw workflow_2.ApplicationFailure.nonRetryable('coat-check putBy must be a non-empty string', 'CoatCheckInvalidPutBy');
229
+ }
230
+ // Saturation check runs LAST + on a post-sweep view so a caller who
231
+ // hit saturation at T=0 but whose entries have since TTL-expired can
232
+ // succeed on retry. Sweep is idempotent inside the validator scope —
233
+ // it runs again in the handler body before admitting.
234
+ const probe = { ...coatCheck };
235
+ const nowMs = workflowNow().getTime();
236
+ for (const [t, e] of Object.entries(probe)) {
237
+ if (Date.parse(e.expiresAt) <= nowMs)
238
+ delete probe[t];
239
+ }
240
+ if (Object.keys(probe).length >= validation_1.COAT_CHECK_SLOTS_MAX) {
241
+ const oldest = Object.entries(probe)
242
+ .sort((a, b) => Date.parse(a[1].putAt) - Date.parse(b[1].putAt))
243
+ .slice(0, 3)
244
+ .map(([t, e]) => `${t} (putBy=${e.putBy}, putAt=${e.putAt})`)
245
+ .join('; ');
246
+ throw workflow_2.ApplicationFailure.nonRetryable(`coat-check slots full (${validation_1.COAT_CHECK_SLOTS_MAX}). Evict one via \`coat_check_evict\` (owner-or-conductor) or wait for TTL. Oldest 3: ${oldest}`, 'CoatCheckSlotsFull');
247
+ }
248
+ },
249
+ });
250
+ (0, workflow_1.setHandler)(maestro_signals_1.coatCheckGetUpdate, ({ ticket, fetchedBy }) => {
251
+ sweepExpiredCoatCheck();
252
+ const entry = coatCheck[ticket];
253
+ if (!entry)
254
+ return null;
255
+ // Successful fetch — bump audit. The bump only fires on the happy path
256
+ // so failed lookups don't pollute the counter.
257
+ entry.lastFetchedAt = workflowNow().toISOString();
258
+ entry.lastFetchedBy = fetchedBy;
259
+ entry.fetchCount += 1;
260
+ return entry;
261
+ });
262
+ (0, workflow_1.setHandler)(maestro_signals_1.coatCheckListQuery, (input) => {
263
+ // Queries cannot mutate state, but inline filtering against a post-
264
+ // sweep VIEW gives consistent results — expired entries shouldn't
265
+ // appear in the listing even if they haven't been physically swept.
266
+ const nowMs = workflowNow().getTime();
267
+ const headers = [];
268
+ for (const [ticket, entry] of Object.entries(coatCheck)) {
269
+ if (Date.parse(entry.expiresAt) <= nowMs)
270
+ continue;
271
+ if (input?.putBy && entry.putBy !== input.putBy)
272
+ continue;
273
+ if (input?.prefix && !entry.summary.startsWith(input.prefix))
274
+ continue;
275
+ if (input?.unfetchedOnly && entry.fetchCount > 0)
276
+ continue;
277
+ headers.push({
278
+ ticket,
279
+ summary: entry.summary,
280
+ ...(entry.contentType !== undefined ? { contentType: entry.contentType } : {}),
281
+ putBy: entry.putBy,
282
+ putAt: entry.putAt,
283
+ expiresAt: entry.expiresAt,
284
+ size: entry.size,
285
+ ...(entry.lastFetchedAt !== undefined ? { lastFetchedAt: entry.lastFetchedAt } : {}),
286
+ ...(entry.lastFetchedBy !== undefined ? { lastFetchedBy: entry.lastFetchedBy } : {}),
287
+ fetchCount: entry.fetchCount,
288
+ });
289
+ }
290
+ // Newest-first so the operator sees recent activity at the top.
291
+ headers.sort((a, b) => Date.parse(b.putAt) - Date.parse(a.putAt));
292
+ return headers;
293
+ });
294
+ (0, workflow_1.setHandler)(maestro_signals_1.coatCheckEvictUpdate, ({ ticket, evictedBy }) => {
295
+ sweepExpiredCoatCheck();
296
+ const entry = coatCheck[ticket];
297
+ if (!entry)
298
+ return { evicted: false };
299
+ // Owner-or-conductor permission gate. `players` is the closure-captured
300
+ // snapshot, refreshed every 5s by the main loop — good enough for an
301
+ // identity check (conductor identity is stable across the ensemble's
302
+ // lifetime). A non-owner / non-conductor evict throws structurally so
303
+ // the caller's MCP tool surfaces it as a permission error.
304
+ const isOwner = entry.putBy === evictedBy;
305
+ const isConductor = players.some((p) => p.isConductor && p.playerId === evictedBy);
306
+ if (!isOwner && !isConductor) {
307
+ throw workflow_2.ApplicationFailure.nonRetryable(`coat-check evict denied: "${evictedBy}" is neither the owner ("${entry.putBy}") nor the ensemble conductor`, 'CoatCheckEvictPermissionDenied');
308
+ }
309
+ delete coatCheck[ticket];
310
+ const bucket = putByIndex.get(entry.putBy);
311
+ if (bucket) {
312
+ bucket.delete(ticket);
313
+ if (bucket.size === 0)
314
+ putByIndex.delete(entry.putBy);
315
+ }
316
+ return { evicted: true };
317
+ }, {
318
+ validator: ({ ticket, evictedBy }) => {
319
+ if (typeof ticket !== 'string' || ticket.length === 0 || ticket.length > validation_1.COAT_CHECK_TICKET_MAX
320
+ || !validation_1.COAT_CHECK_TICKET_REGEX.test(ticket)) {
321
+ throw workflow_2.ApplicationFailure.nonRetryable(`coat-check ticket must match ${validation_1.COAT_CHECK_TICKET_REGEX} and be ≤ ${validation_1.COAT_CHECK_TICKET_MAX} chars`, 'CoatCheckInvalidTicket');
322
+ }
323
+ if (typeof evictedBy !== 'string' || evictedBy.length === 0) {
324
+ throw workflow_2.ApplicationFailure.nonRetryable('coat-check evictedBy must be a non-empty string', 'CoatCheckInvalidEvictedBy');
325
+ }
326
+ },
327
+ });
328
+ // ── Main Loop ──
329
+ while (!shutdownRequested) {
330
+ // Wait for either the refresh interval or a queued command
331
+ commandQueued = false;
332
+ await (0, workflow_1.condition)(() => shutdownRequested || commandQueued, `${refreshIntervalMs} milliseconds`);
333
+ if (shutdownRequested)
334
+ break;
335
+ // ── Refresh Ensemble State ──
336
+ try {
337
+ const newPlayers = await refreshEnsembleState(input.ensemble);
338
+ const nowDate = workflowNow();
339
+ const now = nowDate.toISOString();
340
+ const nowMs = nowDate.getTime();
341
+ // #399 W1 (Q5.6 Flavor B) — accumulate per-player activity deltas
342
+ // into the current bucket BEFORE replacing `players`. We diff the
343
+ // monotonic counter from each session's `getActivityState` query
344
+ // (forwarded via `MaestroPlayerInfo.activityCount`); a positive
345
+ // delta means the player emitted N messages since the last refresh.
346
+ const oldActivity = new Map(players
347
+ .filter((p) => p.activityCount !== undefined)
348
+ .map((p) => [p.playerId, p.activityCount]));
349
+ let bucketDelta = 0;
350
+ for (const np of newPlayers) {
351
+ if (np.activityCount === undefined)
352
+ continue;
353
+ const prev = oldActivity.get(np.playerId);
354
+ if (prev === undefined) {
355
+ // First sighting — adopt the counter as the baseline; don't
356
+ // back-fill the bucket with a "delta" against zero, otherwise
357
+ // a long-running session that just joined would spike the
358
+ // sparkline by its lifetime activity.
359
+ continue;
360
+ }
361
+ const d = np.activityCount - prev;
362
+ if (d > 0)
363
+ bucketDelta += d;
364
+ }
365
+ if (bucketDelta > 0 || nowMs - tempoCurrentBucket.startMs >= TEMPO_BUCKET_MS) {
366
+ tempoCurrentBucket = rollTempoBucket(tempoCurrentBucket, bucketDelta, tempoHistoryBuckets, nowMs);
367
+ }
368
+ // Diff snapshots to generate events
369
+ const oldMap = new Map(players.map((p) => [p.playerId, p]));
370
+ const newMap = new Map(newPlayers.map((p) => [p.playerId, p]));
371
+ // Player joined
372
+ for (const [id, player] of newMap) {
373
+ if (!oldMap.has(id)) {
374
+ events.push({ type: 'player_joined', playerId: id, timestamp: now });
375
+ }
376
+ else {
377
+ const old = oldMap.get(id);
378
+ // `status_changed` events fire on attachment-phase transitions; the
379
+ // event name is kept for dashboard stability (MaestroEvent wire shape).
380
+ if (old.phase !== player.phase) {
381
+ events.push({
382
+ type: 'status_changed',
383
+ playerId: id,
384
+ timestamp: now,
385
+ oldValue: old.phase,
386
+ newValue: player.phase,
387
+ });
388
+ }
389
+ // Part changed
390
+ if (old.part !== player.part) {
391
+ events.push({
392
+ type: 'part_changed',
393
+ playerId: id,
394
+ timestamp: now,
395
+ oldValue: old.part,
396
+ newValue: player.part,
397
+ });
398
+ }
399
+ }
400
+ }
401
+ // Player left
402
+ for (const [id] of oldMap) {
403
+ if (!newMap.has(id)) {
404
+ events.push({ type: 'player_left', playerId: id, timestamp: now });
405
+ }
406
+ }
407
+ const eventsExcess = events.length - MAX_EVENTS;
408
+ if (eventsExcess > 0)
409
+ events.splice(0, eventsExcess);
410
+ players = newPlayers;
411
+ // Track last time we saw running sessions — phases the ensemble can
412
+ // meaningfully coordinate with (attached/processing/awaiting/booting).
413
+ const COORDINATABLE_PHASES = ['attached', 'processing', 'awaiting', 'booting'];
414
+ const hasRunningSessions = players.some((p) => p.phase !== undefined && COORDINATABLE_PHASES.includes(p.phase));
415
+ if (hasRunningSessions) {
416
+ lastActiveSessionTime = workflowNow().getTime();
417
+ }
418
+ }
419
+ catch {
420
+ // Activity failure after retries — skip this cycle, try again next loop
421
+ }
422
+ // ── Refresh Ensemble Chat ──
423
+ if ((0, workflow_1.patched)('v0.19-ensemble-chat')) {
424
+ try {
425
+ const chatResult = await fetchEnsembleChat({
426
+ ensemble: input.ensemble,
427
+ knownCounts: chatHighWater,
428
+ });
429
+ if (chatResult.success) {
430
+ cachedChat.push(...chatResult.newMessages);
431
+ const MAX_CACHED_CHAT = 500;
432
+ const chatExcess = cachedChat.length - MAX_CACHED_CHAT;
433
+ if (chatExcess > 0)
434
+ cachedChat.splice(0, chatExcess);
435
+ chatHighWater = chatResult.currentCounts;
436
+ cachedChatMeta = { hasConductor: chatResult.hasConductor };
437
+ }
438
+ }
439
+ catch {
440
+ // Chat refresh failed — keep stale cache, retry next cycle
441
+ }
442
+ }
443
+ // ── Dispatch Pending Commands ──
444
+ const pending = pendingCommands.filter((c) => c.status === 'pending');
445
+ for (const cmd of pending) {
446
+ cmd.status = 'delivered'; // optimistic — revert on failure
447
+ try {
448
+ const result = await relayCommandToConductor({
449
+ ensemble: input.ensemble,
450
+ text: cmd.text,
451
+ source: cmd.source,
452
+ replyTo: cmd.replyTo,
453
+ });
454
+ if (!result.success) {
455
+ cmd.status = 'failed';
456
+ cmd.error = result.error;
457
+ }
458
+ }
459
+ catch (err) {
460
+ cmd.status = 'failed';
461
+ cmd.error = String(err);
462
+ }
463
+ }
464
+ // ── Coat-check opportunistic TTL sweep (#318) ──
465
+ // Idle ensembles still trim expired entries even when no caller is
466
+ // exercising the handlers. The sweep is also cheap (Object.entries scan)
467
+ // and idempotent, so it's safe to call on every refresh tick.
468
+ sweepExpiredCoatCheck();
469
+ // ── Auto-terminate if idle ──
470
+ // #318: was `Date.now() - lastActiveSessionTime` — non-deterministic on
471
+ // replay. `workflowNow()` reads the SDK-intercepted clock; idle decision
472
+ // is now replay-stable.
473
+ if (workflowNow().getTime() - lastActiveSessionTime > IDLE_TIMEOUT_MS) {
474
+ break;
475
+ }
476
+ // ── ContinueAsNew if suggested ──
477
+ const info = (0, workflow_1.workflowInfo)();
478
+ if (info.continueAsNewSuggested) {
479
+ await (0, workflow_1.condition)(workflow_1.allHandlersFinished);
480
+ // #318: only carry coat-check across CAN when non-empty. Matches the
481
+ // `playerState` carry idiom in `src/workflows/session.ts:1617-1638`
482
+ // — keeps the wire small for the common no-coat-check case.
483
+ const carryCoatCheck = Object.keys(coatCheck).length > 0;
484
+ await (0, workflow_1.continueAsNew)({
485
+ ensemble: input.ensemble,
486
+ players,
487
+ events,
488
+ // Only carry pending commands; delivered/failed are historical
489
+ pendingCommands: pendingCommands.filter((c) => c.status === 'pending'),
490
+ pollIntervalMs: input.pollIntervalMs,
491
+ cachedChat,
492
+ cachedChatMeta,
493
+ chatHighWater,
494
+ paused: ensemblePaused,
495
+ // #399 W1 — forward description, original start time, and tempo
496
+ // state across CAN so dashboard signals stay continuous.
497
+ description,
498
+ startTimeIso,
499
+ tempoHistoryBuckets,
500
+ tempoCurrentBucket,
501
+ // #318 — carry the coat-check map only when populated.
502
+ ...(carryCoatCheck ? { coatCheck } : {}),
503
+ });
504
+ }
505
+ }
506
+ // Graceful shutdown — wait for in-flight handlers
507
+ await (0, workflow_1.condition)(workflow_1.allHandlersFinished);
508
+ }
509
+ // ──────────────────────────────────────────────────────────────────────
510
+ // #399 W1 (Q5.6 Flavor B) — tempo bucket helpers.
511
+ //
512
+ // `rollTempoBucket` runs in workflow context and is therefore
513
+ // deterministic — `nowMs` is supplied by the caller via `Date.now()`
514
+ // during the (workflow-time) refresh tick, never sampled inside the
515
+ // helper. The current bucket finalises (gets pushed onto the history
516
+ // ring) once it covers a full `TEMPO_BUCKET_MS` window OR when the next
517
+ // refresh sees activity that should land in a fresh bucket — either way
518
+ // we never lose a window.
519
+ // ──────────────────────────────────────────────────────────────────────
520
+ function rollTempoBucket(current, pendingDelta, history, nowMs) {
521
+ const elapsed = nowMs - current.startMs;
522
+ if (elapsed >= TEMPO_BUCKET_MS) {
523
+ history.push(current.count);
524
+ while (history.length > TEMPO_HISTORY_MAX)
525
+ history.shift();
526
+ return { startMs: nowMs, count: pendingDelta };
527
+ }
528
+ return { startMs: current.startMs, count: current.count + pendingDelta };
529
+ }
530
+ function computeCurrentBpm(history, current) {
531
+ // Sum activity in the last `TEMPO_BPM_WINDOW_MS` (1 minute):
532
+ // - the in-progress bucket always counts
533
+ // - prior history buckets count proportionally to how recent they are
534
+ // We approximate by taking the last two finished buckets (60s of
535
+ // history at 30s/bucket) plus the current. That over-counts slightly
536
+ // when the current bucket is fresh — acceptable for a heads-up display.
537
+ const tail = history.slice(-2);
538
+ const sum = tail.reduce((a, b) => a + b, 0) + current.count;
539
+ return sum;
540
+ }
541
+ // ══════════════════════════════════════════════════════════════════════════════
542
+ // Global Maestro — single instance handling ALL ensembles
543
+ // ══════════════════════════════════════════════════════════════════════════════
544
+ const GLOBAL_MAX_MESSAGES = 500;
545
+ const GLOBAL_MAX_EVENTS = 500;
546
+ const globalActivities = (0, workflow_1.proxyActivities)({
547
+ startToCloseTimeout: '30 seconds',
548
+ retry: { maximumAttempts: 3 },
549
+ });
550
+ async function agentGlobalMaestroWorkflow(input) {
551
+ (0, workflow_1.patched)('v0.18-global-maestro');
552
+ const refreshIntervalMs = input.pollIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
553
+ let knownEnsembles = input.knownEnsembles ?? [];
554
+ let playersByEnsemble = input.playersByEnsemble ?? {};
555
+ const recentMessages = input.recentMessages ?? [];
556
+ const events = input.events ?? [];
557
+ const pendingCommands = input.pendingCommands ?? [];
558
+ // #274 — host capability ledger. Plain `Record<hostname, HostProfile>`
559
+ // so CAN serialization is the default-converter happy path (Maps require
560
+ // a codec tweak). Lazy GC of stale hosts happens at the `listHosts` join
561
+ // site; the workflow itself just stores forever (or until CAN).
562
+ const hostProfiles = { ...(input.hostProfiles ?? {}) };
563
+ let shutdownRequested = false;
564
+ let actionQueued = false;
565
+ // ── Signal Handlers ──
566
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroShutdownSignal, () => {
567
+ shutdownRequested = true;
568
+ });
569
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroNotifyMessageSignal, (msg) => {
570
+ recentMessages.push(msg);
571
+ const msgExcess = recentMessages.length - GLOBAL_MAX_MESSAGES;
572
+ if (msgExcess > 0)
573
+ recentMessages.splice(0, msgExcess);
574
+ });
575
+ /**
576
+ * #274 — daemon boot signal carrying the host's capability profile.
577
+ *
578
+ * Validation policy per architect delta AC3c (M9): ONLY `hostname` is
579
+ * validated here (required, ≤64 chars, alphanumeric + `_-`). All other
580
+ * fields are stored opaquely — the per-field Zod guard lives at the
581
+ * `listHosts` join site in `src/utils/hosts.ts`, never at this
582
+ * handler. This keeps the workflow additive-compatible across daemon
583
+ * versions: a newer daemon can signal new fields and older maestros
584
+ * will still accept the payload; an older daemon's payload is accepted
585
+ * by a newer maestro without special-casing.
586
+ *
587
+ * The hostname regex is inlined (rather than importing from
588
+ * `src/utils/validation.ts`) to keep the workflow bundle's import
589
+ * surface narrow — it's pure constants either way.
590
+ */
591
+ (0, workflow_1.setHandler)(maestro_signals_1.hostProfileSignal, (payload) => {
592
+ if (!payload || typeof payload !== 'object')
593
+ return;
594
+ const hostname = payload.hostname;
595
+ if (typeof hostname !== 'string')
596
+ return;
597
+ if (hostname.length === 0 || hostname.length > 64)
598
+ return;
599
+ if (!/^[a-zA-Z0-9_-]+$/.test(hostname))
600
+ return;
601
+ // Cast is deliberate: `HostProfile` has an open `[extraField]: unknown`
602
+ // index signature, so the spread is semantically safe. TypeScript can't
603
+ // prove narrower optional fields from the spread alone.
604
+ hostProfiles[hostname] = { ...payload, hostname };
605
+ });
606
+ // ── Query Handlers ──
607
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroEnsemblesQuery, () => knownEnsembles);
608
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroPlayersByEnsembleQuery, () => ({ ...playersByEnsemble }));
609
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroRecentMessagesQuery, () => recentMessages);
610
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroEventsQuery, () => events);
611
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroPendingCommandsQuery, () => pendingCommands);
612
+ // Return a defensive copy so callers can't mutate workflow state.
613
+ (0, workflow_1.setHandler)(maestro_signals_1.hostProfilesQuery, () => ({ ...hostProfiles }));
614
+ // #280 — combined query: reaching this handler proves the workflow is
615
+ // running, so `exists` is always `true` here. Callers infer "missing"
616
+ // by catching transport-level errors (workflow not found, etc.).
617
+ (0, workflow_1.setHandler)(maestro_signals_1.hostProfilesWithExistenceQuery, () => ({
618
+ exists: true,
619
+ profiles: { ...hostProfiles },
620
+ }));
621
+ // ── Update Handlers (can await activities) ──
622
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroSendMessageUpdate, async (req) => {
623
+ const msgId = (0, workflow_1.uuid4)();
624
+ const result = await globalActivities.deliverMaestroMessage({
625
+ ensemble: req.ensemble,
626
+ to: req.to,
627
+ text: req.text,
628
+ source: req.source,
629
+ });
630
+ if (!result.success) {
631
+ throw new Error(result.error ?? 'Failed to deliver message');
632
+ }
633
+ // Record in ring buffer
634
+ const relayMsg = {
635
+ id: msgId,
636
+ ensemble: req.ensemble,
637
+ from: req.source,
638
+ to: req.to,
639
+ text: req.text,
640
+ timestamp: new Date().toISOString(),
641
+ direction: 'outbound',
642
+ };
643
+ recentMessages.push(relayMsg);
644
+ const relayExcess = recentMessages.length - GLOBAL_MAX_MESSAGES;
645
+ if (relayExcess > 0)
646
+ recentMessages.splice(0, relayExcess);
647
+ return msgId;
648
+ }, {
649
+ validator: (req) => {
650
+ if (!req.ensemble || !req.ensemble.trim())
651
+ throw new Error('Ensemble must not be empty');
652
+ if (!req.to || !req.to.trim())
653
+ throw new Error('Target player must not be empty');
654
+ if (!req.text || !req.text.trim())
655
+ throw new Error('Message text must not be empty');
656
+ },
657
+ });
658
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroFetchPlayerMessagesUpdate, async (req) => {
659
+ const result = await globalActivities.fetchPlayerMessages({
660
+ ensemble: req.ensemble,
661
+ playerId: req.playerId,
662
+ });
663
+ if (!result.success) {
664
+ throw new Error(result.error ?? 'Failed to fetch player messages');
665
+ }
666
+ return result.messages;
667
+ }, {
668
+ validator: (req) => {
669
+ if (!req.ensemble || !req.ensemble.trim())
670
+ throw new Error('Ensemble must not be empty');
671
+ if (!req.playerId || !req.playerId.trim())
672
+ throw new Error('Player ID must not be empty');
673
+ },
674
+ });
675
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroFetchConductorHistoryUpdate, async (req) => {
676
+ const result = await globalActivities.fetchConductorHistory({
677
+ ensemble: req.ensemble,
678
+ });
679
+ return result;
680
+ }, {
681
+ validator: (req) => {
682
+ if (!req.ensemble || !req.ensemble.trim())
683
+ throw new Error('Ensemble must not be empty');
684
+ },
685
+ });
686
+ (0, workflow_1.setHandler)(maestro_signals_1.maestroGlobalSendCommandUpdate, (cmd) => {
687
+ const entry = {
688
+ id: (0, workflow_1.uuid4)(),
689
+ text: cmd.text,
690
+ source: cmd.source,
691
+ replyTo: cmd.replyTo,
692
+ createdAt: new Date().toISOString(),
693
+ status: 'pending',
694
+ };
695
+ entry.ensemble = cmd.ensemble;
696
+ pendingCommands.push(entry);
697
+ actionQueued = true;
698
+ return entry.id;
699
+ }, {
700
+ validator: (cmd) => {
701
+ if (!cmd.ensemble || !cmd.ensemble.trim())
702
+ throw new Error('Ensemble must not be empty');
703
+ if (!cmd.text || !cmd.text.trim())
704
+ throw new Error('Command text must not be empty');
705
+ },
706
+ });
707
+ // ── Main Loop ──
708
+ while (!shutdownRequested) {
709
+ actionQueued = false;
710
+ await (0, workflow_1.condition)(() => shutdownRequested || actionQueued, `${refreshIntervalMs} milliseconds`);
711
+ if (shutdownRequested)
712
+ break;
713
+ // ── Discover ensembles ──
714
+ try {
715
+ knownEnsembles = await globalActivities.discoverEnsembles();
716
+ }
717
+ catch {
718
+ // Discovery failed — use last known ensembles
719
+ }
720
+ // ── Refresh players per ensemble ──
721
+ for (const ensemble of knownEnsembles) {
722
+ try {
723
+ const newPlayers = await globalActivities.refreshEnsembleState(ensemble);
724
+ const oldPlayers = playersByEnsemble[ensemble] ?? [];
725
+ const now = new Date().toISOString();
726
+ // Diff snapshots to generate events
727
+ const oldMap = new Map(oldPlayers.map((p) => [p.playerId, p]));
728
+ const newMap = new Map(newPlayers.map((p) => [p.playerId, p]));
729
+ for (const [id, player] of newMap) {
730
+ if (!oldMap.has(id)) {
731
+ events.push({ type: 'player_joined', playerId: id, timestamp: now });
732
+ }
733
+ else {
734
+ const old = oldMap.get(id);
735
+ // Diffs attachment-phase values (see per-ensemble Maestro comment).
736
+ if (old.phase !== player.phase) {
737
+ events.push({ type: 'status_changed', playerId: id, timestamp: now, oldValue: old.phase, newValue: player.phase });
738
+ }
739
+ if (old.part !== player.part) {
740
+ events.push({ type: 'part_changed', playerId: id, timestamp: now, oldValue: old.part, newValue: player.part });
741
+ }
742
+ }
743
+ }
744
+ for (const [id] of oldMap) {
745
+ if (!newMap.has(id)) {
746
+ events.push({ type: 'player_left', playerId: id, timestamp: now });
747
+ }
748
+ }
749
+ playersByEnsemble[ensemble] = newPlayers;
750
+ }
751
+ catch {
752
+ // Activity failure — keep last known state for this ensemble
753
+ }
754
+ }
755
+ // Remove stale ensembles (no longer discovered)
756
+ for (const key of Object.keys(playersByEnsemble)) {
757
+ if (!knownEnsembles.includes(key)) {
758
+ delete playersByEnsemble[key];
759
+ }
760
+ }
761
+ const globalEventsExcess = events.length - GLOBAL_MAX_EVENTS;
762
+ if (globalEventsExcess > 0)
763
+ events.splice(0, globalEventsExcess);
764
+ // ── Dispatch Pending Commands ──
765
+ const pending = pendingCommands.filter((c) => c.status === 'pending');
766
+ for (const cmd of pending) {
767
+ const ensemble = cmd.ensemble;
768
+ if (!ensemble) {
769
+ cmd.status = 'failed';
770
+ cmd.error = 'Missing ensemble';
771
+ continue;
772
+ }
773
+ cmd.status = 'delivered';
774
+ try {
775
+ const result = await globalActivities.relayCommandToConductor({
776
+ ensemble,
777
+ text: cmd.text,
778
+ source: cmd.source,
779
+ replyTo: cmd.replyTo,
780
+ });
781
+ if (!result.success) {
782
+ cmd.status = 'failed';
783
+ cmd.error = result.error;
784
+ }
785
+ }
786
+ catch (err) {
787
+ cmd.status = 'failed';
788
+ cmd.error = String(err);
789
+ }
790
+ }
791
+ // ── ContinueAsNew if suggested ──
792
+ const info = (0, workflow_1.workflowInfo)();
793
+ if (info.continueAsNewSuggested) {
794
+ await (0, workflow_1.condition)(workflow_1.allHandlersFinished);
795
+ await (0, workflow_1.continueAsNew)({
796
+ knownEnsembles,
797
+ playersByEnsemble,
798
+ recentMessages,
799
+ events,
800
+ pendingCommands: pendingCommands.filter((c) => c.status === 'pending'),
801
+ pollIntervalMs: input.pollIntervalMs,
802
+ // #274 — carry the capability ledger across the CAN boundary so
803
+ // hosts that signaled their profile in a prior execution don't
804
+ // disappear on the next one (they won't re-signal until the next
805
+ // daemon boot).
806
+ hostProfiles,
807
+ });
808
+ }
809
+ }
810
+ // Graceful shutdown
811
+ await (0, workflow_1.condition)(workflow_1.allHandlersFinished);
812
+ }