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,753 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CREATE_ENSEMBLE_ANSWERS = exports.CREATE_ENSEMBLE_STEPS = exports.DEFAULT_SCHEDULE_ANSWERS = exports.SCHEDULE_STEPS = exports.RECRUIT_STEPS = exports.NOTIFICATION_CAP = exports.NOTIFICATION_TTL_MS = void 0;
4
+ exports.toConversationEntry = toConversationEntry;
5
+ exports.defaultRecruitAnswers = defaultRecruitAnswers;
6
+ exports.initialState = initialState;
7
+ exports.tuiReducer = tuiReducer;
8
+ /**
9
+ * Project an `EnsembleChatMessage` into a `ConversationEntry`. Shared by
10
+ * the reducer's incremental path (`APPEND_CHAT_MESSAGE`) and the SSE
11
+ * snapshot/recovery paths (`src/tui/sse-handler.ts`) so both produce the
12
+ * same shape.
13
+ */
14
+ function toConversationEntry(m) {
15
+ return {
16
+ id: m.id,
17
+ from: m.from,
18
+ to: m.to,
19
+ text: m.text,
20
+ timestamp: m.timestamp,
21
+ direction: m.role === 'maestro-out' ? 'out' : 'in',
22
+ role: m.role,
23
+ thirdParty: m.role === 'conductor-out' || m.role === 'conductor-in',
24
+ // #357: forward broadcastId so the renderer can fold fan-out groups.
25
+ ...(m.broadcastId !== undefined ? { broadcastId: m.broadcastId } : {}),
26
+ };
27
+ }
28
+ /** Default TTLs by kind — errors get extra time to read. */
29
+ exports.NOTIFICATION_TTL_MS = {
30
+ error: 8000,
31
+ warn: 5000,
32
+ info: 5000,
33
+ };
34
+ /** Maximum concurrent notifications; oldest is dropped when exceeded. */
35
+ exports.NOTIFICATION_CAP = 3;
36
+ exports.RECRUIT_STEPS = ['name', 'agent', 'type', 'workDir', 'message', 'host', 'confirm'];
37
+ exports.SCHEDULE_STEPS = ['target', 'message', 'schedType', 'timing', 'timezone', 'confirm'];
38
+ exports.DEFAULT_SCHEDULE_ANSWERS = {
39
+ target: '',
40
+ message: '',
41
+ schedType: 'every',
42
+ timing: '',
43
+ timezone: '',
44
+ name: '',
45
+ };
46
+ function defaultRecruitAnswers(defaultAgent = 'claude') {
47
+ return {
48
+ name: '',
49
+ agent: defaultAgent,
50
+ playerType: '',
51
+ workDir: process.cwd(),
52
+ initialMessage: '',
53
+ host: 'localhost',
54
+ };
55
+ }
56
+ exports.CREATE_ENSEMBLE_STEPS = ['name', 'workDir', 'lineup', 'confirm'];
57
+ exports.DEFAULT_CREATE_ENSEMBLE_ANSWERS = {
58
+ name: '',
59
+ workDir: process.cwd(),
60
+ lineup: '',
61
+ };
62
+ function initialState(ensemble) {
63
+ return {
64
+ phase: ensemble ? 'main' : 'splash',
65
+ view: ensemble ? 'ensemble' : 'home',
66
+ splashStatus: 'Starting up...',
67
+ splashChecks: [],
68
+ splashConnected: false,
69
+ splashSummary: undefined,
70
+ ensembles: null,
71
+ selectedEnsembleIndex: 0,
72
+ activeEnsemble: ensemble || null,
73
+ players: [],
74
+ playersLoaded: false,
75
+ messages: [],
76
+ conductorHistory: [],
77
+ schedules: [],
78
+ conversation: null,
79
+ ensembleChat: [],
80
+ hasConductor: false,
81
+ ensemblePaused: false,
82
+ ensembleHeld: false,
83
+ selectedPlayerIndex: 0,
84
+ activePlayer: null,
85
+ playerMetadata: null,
86
+ playerMessages: [],
87
+ playerScrollOffset: 0,
88
+ staticItems: [],
89
+ notifications: [],
90
+ notificationTick: 0,
91
+ chatTarget: undefined,
92
+ sentMessages: [],
93
+ statusOverlay: false,
94
+ overlay: null,
95
+ statusScrollOffset: 0,
96
+ paletteVisible: false,
97
+ paletteIndex: 0,
98
+ pickerVisible: false,
99
+ pickerType: null,
100
+ pickerIntent: null,
101
+ pickerIndex: 0,
102
+ pickerStatusFilter: null,
103
+ confirmingStop: undefined,
104
+ confirmingStopReason: undefined,
105
+ scheduleWizard: undefined,
106
+ };
107
+ }
108
+ // ── Reducer ──
109
+ function tuiReducer(state, action) {
110
+ switch (action.type) {
111
+ case 'SET_PHASE':
112
+ return { ...state, phase: action.phase, error: action.error };
113
+ case 'SET_SPLASH_STATUS':
114
+ return { ...state, splashStatus: action.status };
115
+ case 'SET_SPLASH_CHECKS':
116
+ return { ...state, splashChecks: action.checks };
117
+ case 'SET_SPLASH_CONNECTED':
118
+ return { ...state, splashConnected: true, splashSummary: action.summary };
119
+ // ── Navigation ──
120
+ case 'NAVIGATE_HOME':
121
+ // Auto-select was removed from the poller (#306 follow-up): HomeView
122
+ // is the explicit ensemble picker, so once the user lands on home they
123
+ // stay there until they pick an ensemble themselves. This action just
124
+ // resets ensemble/player state and the chat shell.
125
+ return {
126
+ ...state,
127
+ view: 'home',
128
+ phase: 'main',
129
+ activeEnsemble: null,
130
+ activePlayer: null,
131
+ chatTarget: undefined,
132
+ players: [],
133
+ playersLoaded: false,
134
+ messages: [],
135
+ sentMessages: [],
136
+ conductorHistory: [],
137
+ schedules: [],
138
+ conversation: null,
139
+ playerMetadata: null,
140
+ playerMessages: [],
141
+ ensemblePaused: false,
142
+ ensembleHeld: false,
143
+ selectedPlayerIndex: 0,
144
+ };
145
+ case 'NAVIGATE_ENSEMBLE':
146
+ return {
147
+ ...state,
148
+ view: 'ensemble',
149
+ phase: 'main',
150
+ activeEnsemble: action.ensemble,
151
+ activePlayer: null,
152
+ chatTarget: undefined,
153
+ players: [],
154
+ playersLoaded: false,
155
+ messages: [],
156
+ sentMessages: [],
157
+ conductorHistory: [],
158
+ schedules: [],
159
+ conversation: null,
160
+ playerMetadata: null,
161
+ playerMessages: [],
162
+ ensemblePaused: false,
163
+ ensembleHeld: false,
164
+ selectedPlayerIndex: 0,
165
+ };
166
+ case 'NAVIGATE_PLAYER':
167
+ return {
168
+ ...state,
169
+ view: 'player',
170
+ activePlayer: action.playerId,
171
+ playerMetadata: null,
172
+ playerMessages: [],
173
+ playerScrollOffset: 0,
174
+ };
175
+ // ── Data refresh ──
176
+ case 'REFRESH_ENSEMBLES':
177
+ return {
178
+ ...state,
179
+ ensembles: action.ensembles,
180
+ // Clamp selection index
181
+ selectedEnsembleIndex: Math.min(state.selectedEnsembleIndex, Math.max(0, action.ensembles.length - 1)),
182
+ };
183
+ case 'REFRESH_ENSEMBLE_DATA': {
184
+ const lastMsg = action.messages.length > 0 ? action.messages[action.messages.length - 1] : null;
185
+ return {
186
+ ...state,
187
+ players: action.players,
188
+ playersLoaded: true,
189
+ messages: action.messages,
190
+ conductorHistory: action.history,
191
+ schedules: action.schedules ?? state.schedules,
192
+ lastSeenMessageId: lastMsg?.id ?? state.lastSeenMessageId,
193
+ // Clamp selection index
194
+ selectedPlayerIndex: Math.min(state.selectedPlayerIndex, Math.max(0, action.players.length - 1)),
195
+ };
196
+ }
197
+ case 'SET_CONVERSATION':
198
+ return { ...state, conversation: action.conversation };
199
+ case 'SET_ENSEMBLE_CHAT':
200
+ return {
201
+ ...state,
202
+ ensembleChat: action.chat.messages,
203
+ hasConductor: action.chat.hasConductor,
204
+ };
205
+ // ── #94/#95 PR-4a — SSE event stream incremental updates ──
206
+ case 'APPEND_CHAT_MESSAGE': {
207
+ // `chat.appended` SSE events arrive one-per-message. We push onto
208
+ // `ensembleChat` and derive a matching conversation entry via
209
+ // `toConversationEntry` (also used by the snapshot path), so
210
+ // ChatView/ConversationStream see a continuous timeline. Cap the
211
+ // in-memory slice at 500 to bound memory on long-running sessions;
212
+ // PR-4b will adjust this as part of the scroll-view migration.
213
+ const m = action.message;
214
+ const newChat = [...state.ensembleChat, m];
215
+ const cappedChat = newChat.length > 500 ? newChat.slice(-500) : newChat;
216
+ const baseConv = state.conversation ?? [];
217
+ const newConv = [...baseConv, toConversationEntry(m)];
218
+ const cappedConv = newConv.length > 500 ? newConv.slice(-500) : newConv;
219
+ return { ...state, ensembleChat: cappedChat, conversation: cappedConv };
220
+ }
221
+ case 'UPSERT_PLAYER': {
222
+ // `player.added` and `player.phase_changed` both land here — added
223
+ // creates a new entry; phase_changed updates fields on the existing
224
+ // entry. Identity-preserving when the wire payload matches the
225
+ // current cached entry exactly so StatusBar/MainView don't churn
226
+ // on duplicate events (e.g. a quick disconnect/reconnect).
227
+ const incoming = action.player;
228
+ const idx = state.players.findIndex((p) => p.playerId === incoming.playerId);
229
+ if (idx === -1) {
230
+ return { ...state, players: [...state.players, incoming], playersLoaded: true };
231
+ }
232
+ const existing = state.players[idx];
233
+ const merged = { ...existing, ...incoming };
234
+ const fieldsEqual = existing.phase === merged.phase
235
+ && existing.part === merged.part
236
+ && existing.hostname === merged.hostname
237
+ && existing.workDir === merged.workDir
238
+ && existing.isConductor === merged.isConductor
239
+ && existing.gitBranch === merged.gitBranch
240
+ && existing.playerType === merged.playerType
241
+ && existing.agentType === merged.agentType;
242
+ if (fieldsEqual)
243
+ return state;
244
+ const next = state.players.slice();
245
+ next[idx] = merged;
246
+ return { ...state, players: next };
247
+ }
248
+ case 'PATCH_PLAYER_PHASE': {
249
+ // Rationale on the action type. Identity-preserving guard mirrors
250
+ // SET_ENSEMBLE_PAUSED — same StatusBar churn rationale.
251
+ const idx = state.players.findIndex((p) => p.playerId === action.playerId);
252
+ if (idx === -1)
253
+ return state;
254
+ if (state.players[idx].phase === action.phase)
255
+ return state;
256
+ const next = state.players.slice();
257
+ next[idx] = { ...state.players[idx], phase: action.phase };
258
+ return { ...state, players: next };
259
+ }
260
+ case 'REMOVE_PLAYER': {
261
+ const next = state.players.filter((p) => p.playerId !== action.playerId);
262
+ if (next.length === state.players.length)
263
+ return state;
264
+ return {
265
+ ...state,
266
+ players: next,
267
+ selectedPlayerIndex: Math.min(state.selectedPlayerIndex, Math.max(0, next.length - 1)),
268
+ };
269
+ }
270
+ case 'SET_SCHEDULES':
271
+ return { ...state, schedules: action.schedules };
272
+ case 'REFRESH_PLAYER_DATA':
273
+ return {
274
+ ...state,
275
+ playerMetadata: action.metadata,
276
+ playerMessages: action.messages,
277
+ };
278
+ case 'PLAYER_SCROLL_UP':
279
+ if (state.playerScrollOffset <= 0)
280
+ return state;
281
+ return { ...state, playerScrollOffset: state.playerScrollOffset - 1 };
282
+ case 'PLAYER_SCROLL_DOWN': {
283
+ const maxScroll = Math.max(0, state.playerMessages.length - 20);
284
+ if (state.playerScrollOffset >= maxScroll)
285
+ return state;
286
+ return { ...state, playerScrollOffset: state.playerScrollOffset + 1 };
287
+ }
288
+ // ── Selection & Focus ──
289
+ // ── Chat shell ──
290
+ case 'COMMIT_STATIC': {
291
+ const newItems = [...state.staticItems, action.item];
292
+ // Trim to last 500 entries for memory management
293
+ const trimmed = newItems.length > 500 ? newItems.slice(-500) : newItems;
294
+ return { ...state, staticItems: trimmed };
295
+ }
296
+ // ── Bottom-pinned notifications (#306) ──
297
+ case 'ADD_NOTIFICATION': {
298
+ const now = Date.now();
299
+ // Filter out expired entries before appending so the cap check isn't
300
+ // polluted by stale notifications that should already be gone.
301
+ const live = state.notifications.filter(n => n.expiresAt > now);
302
+ const appended = [...live, action.notification];
303
+ // Cap at NOTIFICATION_CAP — oldest drops off when exceeded.
304
+ const capped = appended.length > exports.NOTIFICATION_CAP
305
+ ? appended.slice(appended.length - exports.NOTIFICATION_CAP)
306
+ : appended;
307
+ return { ...state, notifications: capped };
308
+ }
309
+ case 'DISMISS_NOTIFICATION': {
310
+ const next = state.notifications.filter(n => n.id !== action.id);
311
+ if (next.length === state.notifications.length)
312
+ return state;
313
+ return { ...state, notifications: next };
314
+ }
315
+ case 'DISMISS_OLDEST_NOTIFICATION': {
316
+ // Filter-expired-first so Esc acts on what the user actually sees.
317
+ const now = Date.now();
318
+ const live = state.notifications.filter(n => n.expiresAt > now);
319
+ if (live.length === 0) {
320
+ // Still compact if any expired slipped through — keeps the array clean
321
+ if (live.length !== state.notifications.length) {
322
+ return { ...state, notifications: live };
323
+ }
324
+ return state;
325
+ }
326
+ return { ...state, notifications: live.slice(1) };
327
+ }
328
+ case 'CLEAR_NOTIFICATIONS': {
329
+ if (state.notifications.length === 0)
330
+ return state;
331
+ return { ...state, notifications: [] };
332
+ }
333
+ case 'NOTIFICATION_TICK': {
334
+ // Cheap re-render trigger so expired notifications disappear from the
335
+ // rendered stack without requiring a reducer action per expiration.
336
+ // The render pass filters by `expiresAt > Date.now()`. Only the counter
337
+ // changes — we leave the notifications array alone to avoid thrashing
338
+ // the React children identity.
339
+ return { ...state, notificationTick: state.notificationTick + 1 };
340
+ }
341
+ // ── Command palette ──
342
+ case 'SHOW_PALETTE':
343
+ if (state.paletteVisible && state.paletteIndex === 0)
344
+ return state;
345
+ return { ...state, paletteVisible: true, paletteIndex: 0 };
346
+ case 'HIDE_PALETTE':
347
+ if (!state.paletteVisible && state.paletteIndex === 0)
348
+ return state;
349
+ return { ...state, paletteVisible: false, paletteIndex: 0 };
350
+ case 'PALETTE_UP': {
351
+ // #108: identity-preserving — returning `{ ...state, paletteIndex: same }`
352
+ // forces a re-render of the full app tree even when the value didn't change
353
+ // (e.g. holding arrow-up at index 0). Mirrors the OVERLAY_SELECT pattern
354
+ // below. See CLAUDE.md "Reducer state identity matters".
355
+ const next = Math.max(0, state.paletteIndex - 1);
356
+ if (next === state.paletteIndex)
357
+ return state;
358
+ return { ...state, paletteIndex: next };
359
+ }
360
+ case 'PALETTE_DOWN': {
361
+ // #108: same identity-preserving guard — holding arrow-down at the clamped
362
+ // max must not trigger spurious re-renders.
363
+ const next = state.paletteIndex + 1;
364
+ const clamped = action.max != null ? Math.min(next, action.max) : next;
365
+ if (clamped === state.paletteIndex)
366
+ return state;
367
+ return { ...state, paletteIndex: clamped };
368
+ }
369
+ case 'PALETTE_SET_INDEX':
370
+ return { ...state, paletteIndex: action.index };
371
+ // ── Picker overlay ──
372
+ case 'SHOW_STATUS':
373
+ return { ...state, statusOverlay: true, statusScrollOffset: 0 };
374
+ case 'HIDE_STATUS':
375
+ return { ...state, statusOverlay: false, statusScrollOffset: 0 };
376
+ case 'SHOW_COMMAND_OVERLAY':
377
+ return { ...state, overlay: { type: 'command', title: action.title, items: [{ id: '_', label: action.content }], selectedIndex: 0, hint: 'esc \u2014 close' } };
378
+ case 'SHOW_OVERLAY':
379
+ return { ...state, overlay: { ...action.overlay, selectedIndex: 0 } };
380
+ case 'HIDE_OVERLAY':
381
+ if (!state.overlay)
382
+ return state;
383
+ return { ...state, overlay: null };
384
+ case 'OVERLAY_SELECT': {
385
+ if (!state.overlay || state.overlay.items.length === 0)
386
+ return state;
387
+ const len = state.overlay.items.length;
388
+ const idx = action.direction === 'up'
389
+ ? (state.overlay.selectedIndex - 1 + len) % len
390
+ : (state.overlay.selectedIndex + 1) % len;
391
+ if (idx === state.overlay.selectedIndex)
392
+ return state;
393
+ return { ...state, overlay: { ...state.overlay, selectedIndex: idx } };
394
+ }
395
+ case 'STATUS_SCROLL_UP': {
396
+ const next = Math.max(0, state.statusScrollOffset - 1);
397
+ if (next === state.statusScrollOffset)
398
+ return state;
399
+ return { ...state, statusScrollOffset: next };
400
+ }
401
+ case 'STATUS_SCROLL_DOWN':
402
+ // #250: intentional — no upper-bound clamp lives in the reducer. The max
403
+ // offset depends on `players.length` and `contentHeight`, neither of which
404
+ // the reducer can see. `StatusOverlay` performs the final clamp via
405
+ // `Math.min(scrollOffset, max)` at render (components/StatusOverlay.tsx).
406
+ // Leaving the reducer unbounded + render-time clamping is cheaper than
407
+ // threading the render-time max back through every dispatch.
408
+ return { ...state, statusScrollOffset: state.statusScrollOffset + 1 };
409
+ case 'SHOW_PICKER':
410
+ return { ...state, pickerVisible: true, pickerType: action.pickerType, pickerIntent: action.intent || null, pickerIndex: 0, pickerStatusFilter: action.statusFilter || null };
411
+ case 'HIDE_PICKER':
412
+ return { ...state, pickerVisible: false, pickerType: null, pickerIntent: null, pickerIndex: 0, pickerStatusFilter: null };
413
+ case 'PICKER_UP': {
414
+ const next = Math.max(0, state.pickerIndex - 1);
415
+ if (next === state.pickerIndex)
416
+ return state;
417
+ return { ...state, pickerIndex: next };
418
+ }
419
+ case 'PICKER_DOWN': {
420
+ const maxIdx = (state.pickerType === 'ensembles' ? (state.ensembles?.length ?? 0) : state.players.length - 1);
421
+ if (state.pickerIndex >= maxIdx)
422
+ return state;
423
+ return { ...state, pickerIndex: state.pickerIndex + 1 };
424
+ }
425
+ case 'SET_ENSEMBLE_PAUSED':
426
+ // Identity-preserving: skip the dispatch when value didn't change so
427
+ // the StatusBar tree doesn't re-render every poll tick.
428
+ if (state.ensemblePaused === action.paused)
429
+ return state;
430
+ return { ...state, ensemblePaused: action.paused };
431
+ case 'SET_ENSEMBLE_HELD':
432
+ // Identity-preserving — same rationale as SET_ENSEMBLE_PAUSED. The
433
+ // 2s held-poll would otherwise reconcile the StatusBar every tick
434
+ // even when nothing changed.
435
+ if (state.ensembleHeld === action.held)
436
+ return state;
437
+ return { ...state, ensembleHeld: action.held };
438
+ case 'APPEND_SENT_MESSAGE': {
439
+ const newSent = [...state.sentMessages, { to: action.to, text: action.text, timestamp: new Date().toISOString() }];
440
+ const trimmedSent = newSent.length > 200 ? newSent.slice(-200) : newSent;
441
+ return { ...state, sentMessages: trimmedSent };
442
+ }
443
+ case 'HYDRATE_SENT_MESSAGES': {
444
+ // Merge server-side sent messages, dedup by text+timestamp
445
+ const existing = new Set(state.sentMessages.map(m => `${m.text.slice(0, 60)}:${m.timestamp}`));
446
+ const newMsgs = action.messages.filter(m => !existing.has(`${m.text.slice(0, 60)}:${m.timestamp}`));
447
+ if (newMsgs.length === 0)
448
+ return state;
449
+ return { ...state, sentMessages: [...state.sentMessages, ...newMsgs] };
450
+ }
451
+ case 'ENTER_CHAT':
452
+ return { ...state, phase: 'chat', chatTarget: action.target };
453
+ case 'EXIT_CHAT':
454
+ return { ...state, phase: 'main', chatTarget: undefined };
455
+ // ── Destroy confirmation (PR-H: was `/stop`; renamed to `/destroy`) ──
456
+ case 'CONFIRM_STOP':
457
+ return {
458
+ ...state,
459
+ confirmingStop: action.player,
460
+ ...(action.reason !== undefined ? { confirmingStopReason: action.reason } : { confirmingStopReason: undefined }),
461
+ };
462
+ case 'CANCEL_STOP':
463
+ return { ...state, confirmingStop: undefined, confirmingStopReason: undefined };
464
+ // ── Disband confirmation ──
465
+ case 'CONFIRM_DISBAND':
466
+ return { ...state, confirmingDisband: action.ensemble };
467
+ case 'CANCEL_DISBAND':
468
+ return { ...state, confirmingDisband: undefined };
469
+ // ── Lineup confirmation ──
470
+ case 'CONFIRM_LINEUP':
471
+ return { ...state, confirmingLineup: { action: action.action, path: action.path, summary: action.summary } };
472
+ case 'CANCEL_LINEUP':
473
+ return { ...state, confirmingLineup: undefined };
474
+ // ── Recruit wizard ──
475
+ case 'ENTER_RECRUIT':
476
+ return {
477
+ ...state,
478
+ phase: 'recruit',
479
+ recruitState: {
480
+ step: 'name',
481
+ answers: { ...defaultRecruitAnswers(action.defaultAgent), ...action.answers },
482
+ preRecruitPhase: state.phase,
483
+ preRecruitChatTarget: state.chatTarget,
484
+ },
485
+ };
486
+ case 'RECRUIT_NEXT_STEP': {
487
+ if (!state.recruitState)
488
+ return state;
489
+ const answers = { ...state.recruitState.answers, ...action.answer };
490
+ const currentIdx = exports.RECRUIT_STEPS.indexOf(state.recruitState.step);
491
+ const nextStep = exports.RECRUIT_STEPS[currentIdx + 1] ?? state.recruitState.step;
492
+ return {
493
+ ...state,
494
+ recruitState: { ...state.recruitState, step: nextStep, answers },
495
+ };
496
+ }
497
+ case 'RECRUIT_PREV_STEP': {
498
+ if (!state.recruitState)
499
+ return state;
500
+ const currentIdx = exports.RECRUIT_STEPS.indexOf(state.recruitState.step);
501
+ if (currentIdx <= 0)
502
+ return state;
503
+ const prevStep = exports.RECRUIT_STEPS[currentIdx - 1];
504
+ return {
505
+ ...state,
506
+ recruitState: { ...state.recruitState, step: prevStep },
507
+ };
508
+ }
509
+ case 'RECRUIT_SUBMIT':
510
+ if (!state.recruitState)
511
+ return state;
512
+ return {
513
+ ...state,
514
+ recruitState: { ...state.recruitState, submitting: true },
515
+ };
516
+ case 'RECRUIT_DONE':
517
+ if (!state.recruitState)
518
+ return state;
519
+ return {
520
+ ...state,
521
+ recruitState: {
522
+ ...state.recruitState,
523
+ step: 'done',
524
+ submitting: false,
525
+ error: action.error,
526
+ },
527
+ };
528
+ case 'EXIT_RECRUIT': {
529
+ const restorePhase = state.recruitState?.preRecruitPhase || 'main';
530
+ const restoreChat = state.recruitState?.preRecruitChatTarget;
531
+ return {
532
+ ...state,
533
+ phase: restorePhase,
534
+ chatTarget: restoreChat,
535
+ recruitState: undefined,
536
+ };
537
+ }
538
+ // ── Schedule wizard ──
539
+ case 'ENTER_SCHEDULE_WIZARD':
540
+ return {
541
+ ...state,
542
+ phase: 'schedule-create',
543
+ scheduleWizard: {
544
+ step: 'target',
545
+ answers: { ...exports.DEFAULT_SCHEDULE_ANSWERS, ...action.answers },
546
+ prePhase: state.phase,
547
+ preChatTarget: state.chatTarget,
548
+ },
549
+ };
550
+ case 'SCHEDULE_NEXT_STEP': {
551
+ if (!state.scheduleWizard)
552
+ return state;
553
+ const answers = { ...state.scheduleWizard.answers, ...action.answer };
554
+ const currentIdx = exports.SCHEDULE_STEPS.indexOf(state.scheduleWizard.step);
555
+ // Skip timezone step for non-cron types
556
+ let nextIdx = currentIdx + 1;
557
+ if (exports.SCHEDULE_STEPS[nextIdx] === 'timezone' && answers.schedType !== 'cron') {
558
+ nextIdx++;
559
+ }
560
+ const nextStep = exports.SCHEDULE_STEPS[nextIdx] ?? state.scheduleWizard.step;
561
+ return {
562
+ ...state,
563
+ scheduleWizard: { ...state.scheduleWizard, step: nextStep, answers },
564
+ };
565
+ }
566
+ case 'SCHEDULE_PREV_STEP': {
567
+ if (!state.scheduleWizard)
568
+ return state;
569
+ const currentIdx = exports.SCHEDULE_STEPS.indexOf(state.scheduleWizard.step);
570
+ if (currentIdx <= 0)
571
+ return state;
572
+ let prevIdx = currentIdx - 1;
573
+ // Skip timezone going back for non-cron
574
+ if (exports.SCHEDULE_STEPS[prevIdx] === 'timezone' && state.scheduleWizard.answers.schedType !== 'cron') {
575
+ prevIdx--;
576
+ }
577
+ const prevStep = exports.SCHEDULE_STEPS[Math.max(0, prevIdx)];
578
+ return {
579
+ ...state,
580
+ scheduleWizard: { ...state.scheduleWizard, step: prevStep },
581
+ };
582
+ }
583
+ case 'SCHEDULE_SUBMIT':
584
+ if (!state.scheduleWizard)
585
+ return state;
586
+ return {
587
+ ...state,
588
+ scheduleWizard: { ...state.scheduleWizard, submitting: true },
589
+ };
590
+ case 'SCHEDULE_DONE':
591
+ if (!state.scheduleWizard)
592
+ return state;
593
+ return {
594
+ ...state,
595
+ scheduleWizard: {
596
+ ...state.scheduleWizard,
597
+ step: 'done',
598
+ submitting: false,
599
+ error: action.error,
600
+ },
601
+ };
602
+ case 'EXIT_SCHEDULE_WIZARD': {
603
+ const restoreP = state.scheduleWizard?.prePhase || 'main';
604
+ const restoreC = state.scheduleWizard?.preChatTarget;
605
+ return {
606
+ ...state,
607
+ phase: restoreP,
608
+ chatTarget: restoreC,
609
+ scheduleWizard: undefined,
610
+ };
611
+ }
612
+ // ── Create Ensemble wizard ──
613
+ case 'ENTER_CREATE_ENSEMBLE':
614
+ return {
615
+ ...state,
616
+ phase: 'create-ensemble',
617
+ createEnsembleState: {
618
+ step: 'name',
619
+ answers: { ...exports.DEFAULT_CREATE_ENSEMBLE_ANSWERS },
620
+ prePhase: state.phase,
621
+ },
622
+ };
623
+ case 'CREATE_ENSEMBLE_NEXT_STEP': {
624
+ if (!state.createEnsembleState)
625
+ return state;
626
+ const answers = { ...state.createEnsembleState.answers, ...action.answer };
627
+ const currentIdx = exports.CREATE_ENSEMBLE_STEPS.indexOf(state.createEnsembleState.step);
628
+ const nextStep = exports.CREATE_ENSEMBLE_STEPS[currentIdx + 1] ?? state.createEnsembleState.step;
629
+ return {
630
+ ...state,
631
+ createEnsembleState: { ...state.createEnsembleState, step: nextStep, answers },
632
+ };
633
+ }
634
+ case 'CREATE_ENSEMBLE_PREV_STEP': {
635
+ if (!state.createEnsembleState)
636
+ return state;
637
+ const currentIdx = exports.CREATE_ENSEMBLE_STEPS.indexOf(state.createEnsembleState.step);
638
+ if (currentIdx <= 0)
639
+ return state;
640
+ const prevStep = exports.CREATE_ENSEMBLE_STEPS[currentIdx - 1];
641
+ return {
642
+ ...state,
643
+ createEnsembleState: { ...state.createEnsembleState, step: prevStep },
644
+ };
645
+ }
646
+ case 'CREATE_ENSEMBLE_SUBMIT':
647
+ if (!state.createEnsembleState)
648
+ return state;
649
+ return {
650
+ ...state,
651
+ createEnsembleState: { ...state.createEnsembleState, submitting: true },
652
+ };
653
+ case 'CREATE_ENSEMBLE_DONE':
654
+ if (!state.createEnsembleState)
655
+ return state;
656
+ if (!action.error && action.ensemble) {
657
+ // Success — navigate to new ensemble
658
+ return {
659
+ ...state,
660
+ phase: 'main',
661
+ view: 'ensemble',
662
+ activeEnsemble: action.ensemble,
663
+ createEnsembleState: undefined,
664
+ players: [],
665
+ messages: [],
666
+ sentMessages: [],
667
+ conductorHistory: [],
668
+ schedules: [],
669
+ conversation: null,
670
+ selectedPlayerIndex: 0,
671
+ };
672
+ }
673
+ return {
674
+ ...state,
675
+ createEnsembleState: {
676
+ ...state.createEnsembleState,
677
+ step: 'done',
678
+ submitting: false,
679
+ error: action.error,
680
+ },
681
+ };
682
+ case 'EXIT_CREATE_ENSEMBLE': {
683
+ const restoreCE = state.createEnsembleState?.prePhase || 'main';
684
+ return {
685
+ ...state,
686
+ phase: restoreCE,
687
+ createEnsembleState: undefined,
688
+ };
689
+ }
690
+ case 'OPEN_HOME_MODAL':
691
+ return {
692
+ ...state,
693
+ homeModal: action.modal,
694
+ homeModalSubmitting: false,
695
+ homeModalError: undefined,
696
+ };
697
+ case 'CLOSE_HOME_MODAL':
698
+ return {
699
+ ...state,
700
+ homeModal: undefined,
701
+ homeModalSubmitting: false,
702
+ homeModalError: undefined,
703
+ };
704
+ case 'SET_HOME_MODAL_STATUS':
705
+ return {
706
+ ...state,
707
+ homeModalSubmitting: action.submitting ?? state.homeModalSubmitting,
708
+ homeModalError: action.error,
709
+ };
710
+ case 'CONFIRM_ENSEMBLE_DESTROY':
711
+ return {
712
+ ...state,
713
+ confirmingEnsembleDestroy: { ensemble: action.ensemble, input: '' },
714
+ };
715
+ case 'ENSEMBLE_DESTROY_INPUT': {
716
+ if (!state.confirmingEnsembleDestroy)
717
+ return state;
718
+ return {
719
+ ...state,
720
+ confirmingEnsembleDestroy: {
721
+ ...state.confirmingEnsembleDestroy,
722
+ input: action.input,
723
+ error: undefined,
724
+ },
725
+ };
726
+ }
727
+ case 'ENSEMBLE_DESTROY_SUBMIT_BUSY': {
728
+ if (!state.confirmingEnsembleDestroy)
729
+ return state;
730
+ return {
731
+ ...state,
732
+ confirmingEnsembleDestroy: { ...state.confirmingEnsembleDestroy, submitting: true, error: undefined },
733
+ };
734
+ }
735
+ case 'ENSEMBLE_DESTROY_MISMATCH': {
736
+ if (!state.confirmingEnsembleDestroy)
737
+ return state;
738
+ const { ensemble, input } = state.confirmingEnsembleDestroy;
739
+ return {
740
+ ...state,
741
+ confirmingEnsembleDestroy: {
742
+ ...state.confirmingEnsembleDestroy,
743
+ submitting: false,
744
+ error: `"${input}" \u2260 "${ensemble}" \u2014 type the ensemble name exactly to confirm.`,
745
+ },
746
+ };
747
+ }
748
+ case 'CANCEL_ENSEMBLE_DESTROY':
749
+ return { ...state, confirmingEnsembleDestroy: undefined };
750
+ default:
751
+ return state;
752
+ }
753
+ }