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,21 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark" data-density="6" data-accent="terracotta">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="dark light" />
7
+ <meta name="description" content="agent-tempo Maestro Dashboard" />
8
+ <title>agent-tempo · Maestro</title>
9
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
+ <link
12
+ rel="stylesheet"
13
+ href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
14
+ />
15
+ <script type="module" crossorigin src="/dashboard/assets/index-_5jV0Znu.js"></script>
16
+ <link rel="stylesheet" crossorigin href="/dashboard/assets/index-CB78ToNE.css">
17
+ </head>
18
+ <body>
19
+ <div id="root"></div>
20
+ </body>
21
+ </html>
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "agent-tempo-dashboard",
3
+ "private": true,
4
+ "version": "1.0.1",
5
+ "type": "module",
6
+ "description": "Web dashboard for agent-tempo. Bundled into the npm package; served by the daemon at /dashboard/*.",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "tsc -b && vite build",
10
+ "build:overflow": "tsc -b && vite build --mode overflow",
11
+ "preview": "vite preview",
12
+ "lint": "eslint src/ tests/",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "test:e2e": "playwright test",
16
+ "test:e2e:install": "playwright install chromium",
17
+ "test:overflow": "playwright test --config tests-overflow/playwright.config.ts"
18
+ },
19
+ "dependencies": {
20
+ "@radix-ui/react-dialog": "~1.1.15",
21
+ "@tanstack/react-query": "5.100.5",
22
+ "react": "19.2.5",
23
+ "react-dom": "19.2.5",
24
+ "react-router-dom": "7.14.2",
25
+ "zustand": "^5.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "@eslint/js": "^9.0.0",
29
+ "@playwright/test": "^1.50.0",
30
+ "@tailwindcss/vite": "4.2.4",
31
+ "@testing-library/dom": "^10.0.0",
32
+ "@testing-library/jest-dom": "^6.6.0",
33
+ "@testing-library/react": "^16.1.0",
34
+ "@types/node": "^22.0.0",
35
+ "@types/react": "^19.2.0",
36
+ "@types/react-dom": "^19.2.0",
37
+ "@typescript-eslint/parser": "^8.0.0",
38
+ "@vitejs/plugin-react": "^5.0.0",
39
+ "eslint": "^9.0.0",
40
+ "eslint-plugin-react-hooks": "^5.1.0",
41
+ "jsdom": "^25.0.0",
42
+ "tailwindcss": "4.2.4",
43
+ "typescript": "^5.7.0",
44
+ "vite": "8.0.10",
45
+ "vitest": "^2.1.9"
46
+ }
47
+ }
@@ -0,0 +1,32 @@
1
+ import type { AgentType } from '../types';
2
+ export interface HardTerminateInput {
3
+ /**
4
+ * Ensemble name. Load-bearing: matched against the `ENSEMBLE_SENTINEL_FLAG
5
+ * <ensemble>` pair in each candidate's CommandLine so two ensembles sharing a
6
+ * lineup template (identical player names) don't kill each other's processes
7
+ * on `destroy --all` (issue #180). The sentinel is injected by
8
+ * `src/activities/outbox.ts` on every Claude Code spawn. See src/constants.ts.
9
+ */
10
+ ensemble: string;
11
+ /** Player name the session was spawned as. Matched against `claude.exe -n <name>` in the search path. */
12
+ playerName: string;
13
+ /** Adapter type — controls PID-file lookup and expected process-name verification. */
14
+ agent: AgentType;
15
+ /** Session's workDir — used to locate the copilot bridge PID file (`<workDir>/logs/<playerName>.pid`). */
16
+ workDir: string;
17
+ /** Optional explicit logDir override; falls back to `<workDir>/logs`. */
18
+ logDir?: string;
19
+ }
20
+ export interface HardTerminateResult {
21
+ /** PIDs that were signaled/taskkilled. Empty on the "nothing to do" path. */
22
+ killedPids: number[];
23
+ /** Which code path produced the kill: PID-file lookup, command-line search, or no-op. */
24
+ strategy: 'pidfile' | 'search' | 'none';
25
+ /** Short human-readable notes recorded by the activity — surfaced in workflow logs. */
26
+ notes: string[];
27
+ }
28
+ /**
29
+ * Best-effort OS-level process-tree termination. Never throws — returns a result describing
30
+ * what happened so callers can record it in workflow history without blocking state flips.
31
+ */
32
+ export declare function hardTerminateAttachment(input: HardTerminateInput): Promise<HardTerminateResult>;
@@ -0,0 +1,460 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hardTerminateAttachment = hardTerminateAttachment;
4
+ /**
5
+ * OS-level process-tree termination for a detaching session.
6
+ *
7
+ * Fix for issue #159 Gap 2: workflow-side `forceDetach` / drainingDeadline only flip the
8
+ * phase — they do *not* kill the child process that adapter was driving. On Windows that
9
+ * leaves an orphaned `claude.exe` holding the session ID, and the next `-n <name>` spawn
10
+ * collides with its own past self ("Error: Session ID <uuid> is already in use").
11
+ *
12
+ * This activity runs on the target's per-host task queue (`agent-tempo-{hostname}`) so
13
+ * the kill happens on the machine where the process actually lives. Callers:
14
+ * - workflow main-loop §9.5.c drainingDeadline (best-effort, workflow keeps flipping state
15
+ * on failure so it doesn't get wedged in `draining` forever)
16
+ * - `deliverRestart` activity on the force path — strict "kill first, then flip state"
17
+ * order per the conductor's steering on #159.
18
+ *
19
+ * Strategy per adapter:
20
+ * - **Copilot bridge**: PID file at `<logDir>/<playerName>.pid` is authoritative.
21
+ * Verifies the PID still resolves to `node`/`node.exe` before firing (guard against
22
+ * PID reuse after the bridge already exited).
23
+ * - **Claude Code (interactive)**: no useful PID is captured at spawn time — the spawn
24
+ * returns the transient `cmd.exe` / `osascript` / `bash` launcher, not the eventual
25
+ * `claude.exe`. Search running processes for `claude` (or `.exe` on Windows) whose
26
+ * command line contains `-n <playerName>`. This is the operator workaround from #159
27
+ * ("Get-CimInstance ... Where CommandLine -match '<session-name>'") turned into
28
+ * automation.
29
+ */
30
+ const child_process_1 = require("child_process");
31
+ const fs_1 = require("fs");
32
+ const path_1 = require("path");
33
+ const constants_1 = require("../constants");
34
+ const log = (...args) => console.error('[agent-tempo:hard-terminate]', ...args);
35
+ /**
36
+ * Best-effort OS-level process-tree termination. Never throws — returns a result describing
37
+ * what happened so callers can record it in workflow history without blocking state flips.
38
+ */
39
+ async function hardTerminateAttachment(input) {
40
+ const { ensemble, playerName, agent, workDir, logDir } = input;
41
+ const notes = [];
42
+ const killedPids = [];
43
+ log(`hardTerminate start — ensemble=${ensemble} player=${playerName} agent=${agent}`);
44
+ // ── Copilot bridge: PID file is authoritative ──
45
+ if (agent === 'copilot') {
46
+ const pidDir = logDir || (0, path_1.join)(workDir, 'logs');
47
+ const pidPath = (0, path_1.join)(pidDir, `${playerName}.pid`);
48
+ if ((0, fs_1.existsSync)(pidPath)) {
49
+ try {
50
+ const pidStr = (0, fs_1.readFileSync)(pidPath, 'utf8').trim();
51
+ const pid = parseInt(pidStr, 10);
52
+ if (Number.isFinite(pid) && pid > 0) {
53
+ const expected = process.platform === 'win32' ? 'node.exe' : 'node';
54
+ if (processMatchesExpected(pid, expected)) {
55
+ const killed = await killProcessTree(pid);
56
+ if (killed) {
57
+ killedPids.push(pid);
58
+ notes.push(`Killed copilot bridge PID ${pid}`);
59
+ }
60
+ else {
61
+ notes.push(`kill of copilot PID ${pid} reported non-fatal error; process may have self-exited mid-call`);
62
+ }
63
+ }
64
+ else {
65
+ notes.push(`Skipped copilot PID ${pid} — process no longer matches "${expected}" (likely already exited; PID-reuse guard).`);
66
+ }
67
+ }
68
+ else {
69
+ notes.push(`Copilot PID file contained invalid value "${pidStr}"`);
70
+ }
71
+ try {
72
+ (0, fs_1.unlinkSync)(pidPath);
73
+ }
74
+ catch { /* best-effort cleanup */ }
75
+ }
76
+ catch (err) {
77
+ notes.push(`Copilot PID-file handling failed: ${errMsg(err)}`);
78
+ }
79
+ log(`hardTerminate done (pidfile) — killedPids=[${killedPids.join(',')}]`);
80
+ return { killedPids, strategy: 'pidfile', notes };
81
+ }
82
+ notes.push(`No copilot PID file at ${pidPath}; falling through to command-line search.`);
83
+ }
84
+ // ── Command-line search path (interactive claude.exe, or copilot fallback) ──
85
+ const binaryName = agent === 'copilot'
86
+ ? (process.platform === 'win32' ? 'node.exe' : 'node')
87
+ : (process.platform === 'win32' ? 'claude.exe' : 'claude');
88
+ const pids = findProcessesByCommandLine(binaryName, playerName, ensemble);
89
+ if (pids.length === 0) {
90
+ notes.push(`No ${binaryName} processes found matching playerName="${playerName}" ensemble="${ensemble}" — nothing to kill.`);
91
+ log(`hardTerminate done (none) — nothing to kill`);
92
+ return { killedPids, strategy: 'none', notes };
93
+ }
94
+ for (const pid of pids) {
95
+ try {
96
+ if (await killProcessTree(pid))
97
+ killedPids.push(pid);
98
+ }
99
+ catch (err) {
100
+ notes.push(`kill(${pid}) failed: ${errMsg(err)}`);
101
+ }
102
+ }
103
+ notes.push(`Killed ${killedPids.length} ${binaryName} process(es) matching "${playerName}" in ensemble "${ensemble}": [${killedPids.join(', ')}]`);
104
+ log(`hardTerminate done (search) — killedPids=[${killedPids.join(',')}]`);
105
+ return { killedPids, strategy: 'search', notes };
106
+ }
107
+ // ────────────────────────────────────────────────────────────────────────────────────────────
108
+ // Platform helpers
109
+ // ────────────────────────────────────────────────────────────────────────────────────────────
110
+ /**
111
+ * Verify that `pid` resolves to a process whose executable image name matches `expected`.
112
+ * Guards against Windows PID reuse after the target bridge/claude process has already exited
113
+ * — without this check, we might taskkill an unrelated process that happened to inherit the PID.
114
+ */
115
+ function processMatchesExpected(pid, expected) {
116
+ try {
117
+ if (process.platform === 'win32') {
118
+ // `tasklist /FI "PID eq <pid>" /FO CSV /NH` prints a single CSV line naming the image,
119
+ // or "INFO: No tasks running" when the PID is gone. No PowerShell dependency required.
120
+ const out = (0, child_process_1.execFileSync)('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], {
121
+ encoding: 'utf8',
122
+ stdio: ['ignore', 'pipe', 'ignore'],
123
+ windowsHide: true,
124
+ });
125
+ // First CSV field is the image name, quoted.
126
+ const m = out.match(/^"([^"]+)"/);
127
+ if (!m)
128
+ return false;
129
+ return m[1].toLowerCase() === expected.toLowerCase();
130
+ }
131
+ // Unix: liveness probe first, then /proc/<pid>/comm if available.
132
+ process.kill(pid, 0); // throws if the pid doesn't exist
133
+ try {
134
+ const comm = (0, fs_1.readFileSync)(`/proc/${pid}/comm`, 'utf8').trim();
135
+ if (comm)
136
+ return comm === expected;
137
+ }
138
+ catch {
139
+ // /proc not available (macOS, BSD) — fall back to `ps` lookup.
140
+ }
141
+ const psOut = (0, child_process_1.execFileSync)('ps', ['-p', String(pid), '-o', 'comm='], {
142
+ encoding: 'utf8',
143
+ stdio: ['ignore', 'pipe', 'ignore'],
144
+ }).trim();
145
+ return psOut === expected || psOut.endsWith(`/${expected}`);
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ }
151
+ /**
152
+ * Kill the process whose PID is `pid` AND all of its descendants. Returns `true` when the
153
+ * process is dead or was never running by the time this returns; `false` when the kill
154
+ * command ran but the process stubbornly refused to exit within the grace window.
155
+ *
156
+ * On Windows `taskkill /T /F` is synchronous and walks PPID → children; callers can rely on
157
+ * the return value. On Unix we SIGTERM the process group first (since spawns use
158
+ * `detached: true`), poll for exit for 500ms, then SIGKILL, poll again — so by the time
159
+ * this function returns the process really is gone, not "scheduled to die soon". That
160
+ * matters for the `forceDetachUpdate` strict-ordering path: a fresh `recruit` immediately
161
+ * after must see the session ID unlocked.
162
+ */
163
+ async function killProcessTree(pid) {
164
+ if (process.platform === 'win32') {
165
+ // /T walks PPID → children; /F forces immediate termination. 128 = not found,
166
+ // 255 = already gone. Treat those as success for idempotence.
167
+ const result = (0, child_process_1.spawnSync)('taskkill', ['/T', '/F', '/PID', String(pid)], {
168
+ stdio: ['ignore', 'ignore', 'pipe'],
169
+ encoding: 'utf8',
170
+ windowsHide: true,
171
+ });
172
+ if (result.status === 0)
173
+ return true;
174
+ const stderr = (result.stderr || '').toString();
175
+ if (/not found|not running|no tasks/i.test(stderr))
176
+ return false;
177
+ log(`taskkill /T /F /PID ${pid} → status ${result.status}, stderr: ${stderr.trim()}`);
178
+ // status is known to be non-zero here (line 184 handled the 0 case) and not a recognized
179
+ // "already gone" signature — report kill failure to the caller.
180
+ return false;
181
+ }
182
+ // Unix: SIGTERM → brief poll → SIGKILL → brief poll. Process-group signal first
183
+ // (negative pid) because spawnInTerminal uses `detached: true`; fall back to the bare pid.
184
+ // Catch ESRCH (already gone) and EPERM (permission) silently — both mean "nothing more to do".
185
+ const killPair = (sig) => {
186
+ try {
187
+ process.kill(-pid, sig);
188
+ }
189
+ catch { /* process-group not applicable */ }
190
+ try {
191
+ process.kill(pid, sig);
192
+ }
193
+ catch { /* already gone */ }
194
+ };
195
+ const pollUntilDead = async (maxMs) => {
196
+ const deadline = Date.now() + maxMs;
197
+ while (Date.now() < deadline) {
198
+ try {
199
+ process.kill(pid, 0);
200
+ }
201
+ catch {
202
+ return true;
203
+ }
204
+ await new Promise((r) => setTimeout(r, 50));
205
+ }
206
+ try {
207
+ process.kill(pid, 0);
208
+ return false;
209
+ }
210
+ catch {
211
+ return true;
212
+ }
213
+ };
214
+ killPair('SIGTERM');
215
+ if (await pollUntilDead(500))
216
+ return true;
217
+ killPair('SIGKILL');
218
+ return pollUntilDead(500);
219
+ }
220
+ /**
221
+ * Search the live process table for entries whose image matches `binaryName` and whose command
222
+ * line contains BOTH `-n <playerName>` AND `--remote-control-session-name-prefix <ensemble>`.
223
+ * Returns the set of matching PIDs, or `[]` when nothing matches (including when the native
224
+ * lookup tool is missing).
225
+ *
226
+ * The command-line match mirrors the operator workaround documented in #159 — every spawn
227
+ * we care about passes `-n <playerName>` (see `src/activities/outbox.ts` spawnArgs). The
228
+ * ensemble-prefix sentinel was added in #180 so two ensembles sharing a lineup template
229
+ * (identical player names) don't clobber each other on `destroy --all`.
230
+ */
231
+ function findProcessesByCommandLine(binaryName, playerName, ensemble) {
232
+ // Defensive: bail out on absurd inputs so we never inject into the PowerShell/pgrep expression.
233
+ if (!playerName || !/^[A-Za-z0-9._\-]+$/.test(playerName)) {
234
+ log(`findProcessesByCommandLine: refusing lookup for playerName="${playerName}" (failed regex guard)`);
235
+ return [];
236
+ }
237
+ if (!ensemble || !/^[A-Za-z0-9._\-]+$/.test(ensemble)) {
238
+ log(`findProcessesByCommandLine: refusing lookup for ensemble="${ensemble}" (failed regex guard)`);
239
+ return [];
240
+ }
241
+ if (process.platform === 'win32') {
242
+ // Prefer PowerShell's CIM provider — reliable CommandLine access without admin.
243
+ // Fall back to wmic if PowerShell is missing (older Windows without it).
244
+ //
245
+ // The regex matches `-n` followed by the playerName, tolerant of the Windows quoted
246
+ // arg form. In production the spawned `claude.exe` receives argv re-serialized with
247
+ // CRT-style quoting, so its CommandLine as visible to Win32_Process looks like:
248
+ // ... "server:agent-tempo" "-n" "<playerName>" "--session-id" ...
249
+ // Between `-n` and `<playerName>` there is literally `" "` — close-quote, space,
250
+ // open-quote — which `\s+` alone does NOT match. The character class `[\s"']+`
251
+ // accepts any combination of whitespace and quote characters, covering both the
252
+ // bare `-n <name>` form (used by tests and some launchers) and the quoted
253
+ // `"-n" "<name>"` form (real production). Same treatment on the trailing boundary
254
+ // so `"<name>"` terminates cleanly. This was the root cause of the smoke-run
255
+ // failure discovered behind #164+#165: the activity compiled and ran, but its
256
+ // regex never matched any real `claude.exe`, making the whole #159 kill path a
257
+ // silent no-op in production.
258
+ //
259
+ // The regex guard at the top of this function has already established that
260
+ // `playerName` is `[A-Za-z0-9._-]+`, so direct interpolation into the PowerShell
261
+ // string is safe. `.` and `-` are escaped as regex metachars before embedding
262
+ // into the `-match` pattern.
263
+ //
264
+ // Parent-walk (issue #165): for each matched process, look up its parent exactly one
265
+ // level via `ParentProcessId`. If that parent is `cmd.exe` AND its own CommandLine
266
+ // contains the same `-n <playerName>` sentinel, include the parent PID in the kill
267
+ // list. This clears the Windows Terminal tab when sessions are spawned via
268
+ // `cmd.exe /c start "" wt.exe ... cmd /k <innerCmd>` (see spawn.ts WT branch) — the
269
+ // inner `cmd /k` shell is the claude.exe parent; without killing it, WT leaves an
270
+ // unresponsive tab with cmd.exe alive on the prompt. Scope is strictly one level:
271
+ // grandparents are WT.exe / conhost.exe and must not be touched. The sentinel check
272
+ // reuses the same regex pattern used for the primary match — only cmd.exe shells
273
+ // that we spawned via the #159 pipeline can match.
274
+ const escapedName = (0, constants_1.escapeNameForRegex)(playerName);
275
+ const escapedEnsemble = (0, constants_1.escapeNameForRegex)(ensemble);
276
+ try {
277
+ // Emit PARENT PIDs before child PIDs. taskkill /T /F cascades to descendants,
278
+ // so killing cmd.exe first also kills its claude.exe child in the same call —
279
+ // fewer WMI round-trips, and we correctly credit the parent kill instead of
280
+ // losing it to a race where claude.exe dies first and cmd.exe exits on its
281
+ // own (e.g. when its console has no more input). Ordering here flows directly
282
+ // into the kill loop via `parsePids`, which preserves insertion order.
283
+ //
284
+ // PS single-quoted string literal escapes `'` by doubling it: `''`. The bracket
285
+ // class `[\s"'']` therefore denotes the set { whitespace, `"`, `'` } in the
286
+ // compiled regex. Double-quote needs no escaping inside a PS single-quoted
287
+ // literal.
288
+ //
289
+ // Two patterns are required to match (both AND-ed in the Where clause) so we
290
+ // never kill a process from a sibling ensemble that happens to share the same
291
+ // player name (#180). The ensemble sentinel is
292
+ // `--remote-control-session-name-prefix <ensemble>`, injected by outbox.ts.
293
+ // The parent-walk check applies the same pair so parent cmd.exe wrappers that
294
+ // don't carry the ensemble sentinel are left alone.
295
+ const psScript = [
296
+ `$procs = Get-CimInstance Win32_Process -Filter "Name='${binaryName}'";`,
297
+ `$pattern = '-n[\\s"'']+${escapedName}([\\s"'']|$)';`,
298
+ `$ensemblePattern = '${constants_1.ENSEMBLE_SENTINEL_FLAG}[\\s"'']+${escapedEnsemble}([\\s"'']|$)';`,
299
+ `$matched = $procs | Where-Object { $_.CommandLine -match $pattern -and $_.CommandLine -match $ensemblePattern };`,
300
+ `$result = @();`,
301
+ `foreach ($p in @($matched)) {`,
302
+ ` $ppid = $p.ParentProcessId;`,
303
+ ` if ($ppid) {`,
304
+ ` $parent = Get-CimInstance Win32_Process -Filter "ProcessId=$ppid" -ErrorAction SilentlyContinue;`,
305
+ ` if ($parent -and $parent.Name -ieq 'cmd.exe' -and $parent.CommandLine -match $pattern -and $parent.CommandLine -match $ensemblePattern) {`,
306
+ ` $result += [int]$parent.ProcessId;`,
307
+ ` }`,
308
+ ` }`,
309
+ ` $result += [int]$p.ProcessId;`,
310
+ `}`,
311
+ `$result | Select-Object -Unique`,
312
+ ].join(' ');
313
+ const out = (0, child_process_1.execFileSync)('powershell', ['-NoProfile', '-Command', psScript], {
314
+ encoding: 'utf8',
315
+ stdio: ['ignore', 'pipe', 'ignore'],
316
+ windowsHide: true,
317
+ });
318
+ return parsePids(out);
319
+ }
320
+ catch (err) {
321
+ log(`powershell lookup failed (${errMsg(err)}); falling back to wmic`);
322
+ }
323
+ // wmic fallback — older Windows without PowerShell. Parent-walk is best-effort here:
324
+ // we do a second wmic call to resolve parents for any matched child PIDs. If wmic
325
+ // itself is missing (Windows 11 24H2+ removed it), we return the child PIDs only —
326
+ // the WT orphan-tab fix won't engage, but the primary #159 kill still works.
327
+ //
328
+ // LIKE uses a single `%` between `-n` and `<playerName>` so the filter accepts any
329
+ // intervening characters (space, `" "`, `' '`). This intentionally overmatches —
330
+ // we then pull CommandLine back and post-filter with the same tolerate-quotes
331
+ // regex used in the PowerShell path so non-ours matches are rejected. Without
332
+ // this widening, the quoted production form (`"-n" "<name>"`) slips past the
333
+ // stricter `%-n <name>%` LIKE pattern entirely, which is the root cause this
334
+ // fix addresses.
335
+ //
336
+ // Both patterns (player name AND ensemble sentinel) must match for the block to
337
+ // survive the filter — mirrors the PowerShell path's AND guard for #180.
338
+ const tolerantPatterns = [
339
+ new RegExp(`-n[\\s"']+${escapedName}(?:[\\s"']|$)`),
340
+ new RegExp(`${constants_1.ENSEMBLE_SENTINEL_FLAG}[\\s"']+${escapedEnsemble}(?:[\\s"']|$)`),
341
+ ];
342
+ try {
343
+ const out = (0, child_process_1.execFileSync)('wmic', [
344
+ 'process',
345
+ 'where',
346
+ `Name='${binaryName}' and CommandLine like '%-n%${playerName}%' and CommandLine like '%${constants_1.ENSEMBLE_SENTINEL_FLAG}%${ensemble}%'`,
347
+ 'get',
348
+ 'CommandLine,ProcessId,ParentProcessId',
349
+ '/format:value',
350
+ ], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
351
+ const { pids: childPids, ppids } = parseWmicPidPpidFiltered(out, tolerantPatterns);
352
+ const parentPids = [];
353
+ for (const ppid of ppids) {
354
+ try {
355
+ const parentOut = (0, child_process_1.execFileSync)('wmic', [
356
+ 'process',
357
+ 'where',
358
+ `ProcessId=${ppid} and Name='cmd.exe' and CommandLine like '%-n%${playerName}%' and CommandLine like '%${constants_1.ENSEMBLE_SENTINEL_FLAG}%${ensemble}%'`,
359
+ 'get',
360
+ 'CommandLine,ProcessId,ParentProcessId',
361
+ '/format:value',
362
+ ], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
363
+ const { pids: matched } = parseWmicPidPpidFiltered(parentOut, tolerantPatterns);
364
+ parentPids.push(...matched);
365
+ }
366
+ catch {
367
+ /* no matching parent — leave it */
368
+ }
369
+ }
370
+ // Parents first so `taskkill /T /F` cascades to children in one call.
371
+ return [...new Set([...parentPids, ...childPids])];
372
+ }
373
+ catch (err) {
374
+ log(`wmic lookup failed (${errMsg(err)}); giving up on Windows search`);
375
+ return [];
376
+ }
377
+ }
378
+ // Unix: pgrep -f returns PIDs whose full cmdline matches the pattern.
379
+ // pgrep uses POSIX ERE — `\s` is NOT a metacharacter (it matches literal 's').
380
+ // Use `[[:space:]"']` for the whitespace/quote class instead.
381
+ //
382
+ // The combined pattern requires BOTH `-n <playerName>` AND
383
+ // `--remote-control-session-name-prefix <ensemble>` to be present, matching the
384
+ // Windows AND guard for #180. argv order is deterministic because spawnArgs in
385
+ // src/activities/outbox.ts places the ensemble sentinel before the name args.
386
+ try {
387
+ const escapedNameU = (0, constants_1.escapeNameForRegex)(playerName);
388
+ const escapedEnsembleU = (0, constants_1.escapeNameForRegex)(ensemble);
389
+ const pattern = `${binaryName}.*${constants_1.ENSEMBLE_SENTINEL_FLAG}[[:space:]"']+${escapedEnsembleU}([[:space:]"']|$)` +
390
+ `.*-n[[:space:]"']+${escapedNameU}([[:space:]"']|$)`;
391
+ const out = (0, child_process_1.execFileSync)('pgrep', ['-f', pattern], {
392
+ encoding: 'utf8',
393
+ stdio: ['ignore', 'pipe', 'ignore'],
394
+ });
395
+ return parsePids(out);
396
+ }
397
+ catch {
398
+ // pgrep exits non-zero when no matches.
399
+ return [];
400
+ }
401
+ }
402
+ function parsePids(raw) {
403
+ const pids = new Set();
404
+ for (const line of raw.split(/\r?\n/)) {
405
+ const m = line.match(/\b(\d{2,})\b/);
406
+ if (!m)
407
+ continue;
408
+ const pid = parseInt(m[1], 10);
409
+ if (Number.isFinite(pid) && pid > 0 && pid !== process.pid)
410
+ pids.add(pid);
411
+ }
412
+ return [...pids];
413
+ }
414
+ /**
415
+ * Parse wmic `/format:value` output that requested `CommandLine`, `ProcessId`, and
416
+ * `ParentProcessId`, and retain only blocks whose CommandLine matches EVERY pattern
417
+ * in `cmdPatterns`.
418
+ *
419
+ * The caller widens the LIKE filter to `%-n%<name>%` so the quoted production form
420
+ * `"-n" "<name>"` passes the SQL-style match. Because that filter overmatches
421
+ * (it would also accept e.g. `-name foo<name>bar`), we post-filter per-block here
422
+ * using the same tolerate-quotes regex used in the PowerShell path. Blocks whose
423
+ * CommandLine fails any pattern are discarded — their PID/PPID never enter the kill
424
+ * list. Output blocks look like:
425
+ * CommandLine=<cmdline>\r\nParentProcessId=<n>\r\nProcessId=<m>\r\n\r\n
426
+ * `wmic` key ordering is alphabetical so CommandLine always precedes the IDs.
427
+ *
428
+ * Multi-pattern matching supports the #180 AND guard: callers pass [`-n <name>`,
429
+ * `--remote-control-session-name-prefix <ensemble>`] and both must match before
430
+ * the block's PID is eligible to kill.
431
+ */
432
+ function parseWmicPidPpidFiltered(raw, cmdPatterns) {
433
+ const pids = new Set();
434
+ const ppids = new Set();
435
+ // Split on blank line separator — wmic uses \r\n\r\n between records.
436
+ for (const block of raw.split(/\r?\n\r?\n/)) {
437
+ if (!block.trim())
438
+ continue;
439
+ const cmdLineMatch = block.match(/^CommandLine=(.*)$/m);
440
+ const pidMatch = block.match(/^ProcessId=(\d+)$/m);
441
+ const ppidMatch = block.match(/^ParentProcessId=(\d+)$/m);
442
+ if (!cmdLineMatch || !pidMatch)
443
+ continue;
444
+ const cmdLine = cmdLineMatch[1];
445
+ if (!cmdPatterns.every((p) => p.test(cmdLine)))
446
+ continue;
447
+ const pid = parseInt(pidMatch[1], 10);
448
+ if (Number.isFinite(pid) && pid > 0 && pid !== process.pid)
449
+ pids.add(pid);
450
+ if (ppidMatch) {
451
+ const ppid = parseInt(ppidMatch[1], 10);
452
+ if (Number.isFinite(ppid) && ppid > 0 && ppid !== process.pid)
453
+ ppids.add(ppid);
454
+ }
455
+ }
456
+ return { pids: [...pids], ppids: [...ppids] };
457
+ }
458
+ function errMsg(err) {
459
+ return err instanceof Error ? err.message : String(err);
460
+ }
@@ -0,0 +1,72 @@
1
+ import { Client } from '@temporalio/client';
2
+ import { HistoryEntry, MaestroPlayerInfo, Message, SentMessage, EnsembleChatMessage, ChatHighWater } from '../types';
3
+ export interface RelayCommandInput {
4
+ ensemble: string;
5
+ text: string;
6
+ source: string;
7
+ replyTo?: string;
8
+ }
9
+ export interface RelayCommandResult {
10
+ success: boolean;
11
+ error?: string;
12
+ }
13
+ export interface FetchConductorHistoryInput {
14
+ ensemble: string;
15
+ }
16
+ export interface FetchConductorHistoryResult {
17
+ success: boolean;
18
+ history: HistoryEntry[];
19
+ error?: string;
20
+ }
21
+ export interface DeliverMaestroMessageInput {
22
+ ensemble: string;
23
+ to: string;
24
+ text: string;
25
+ source: string;
26
+ }
27
+ export interface DeliverMaestroMessageResult {
28
+ success: boolean;
29
+ error?: string;
30
+ }
31
+ export interface FetchPlayerMessagesInput {
32
+ ensemble: string;
33
+ playerId: string;
34
+ }
35
+ export interface FetchPlayerMessagesResult {
36
+ success: boolean;
37
+ messages: Array<(Message & {
38
+ direction?: 'received';
39
+ }) | (SentMessage & {
40
+ direction: 'sent';
41
+ })>;
42
+ error?: string;
43
+ }
44
+ export interface FetchEnsembleChatInput {
45
+ ensemble: string;
46
+ /** Known message counts to enable delta returns. */
47
+ knownCounts?: ChatHighWater;
48
+ }
49
+ export interface FetchEnsembleChatResult {
50
+ success: boolean;
51
+ /** Only NEW messages since the known counts. */
52
+ newMessages: EnsembleChatMessage[];
53
+ /** Updated counts for next call. */
54
+ currentCounts: ChatHighWater;
55
+ hasConductor: boolean;
56
+ error?: string;
57
+ }
58
+ /** Activity interface — used by proxyActivities in the Maestro workflow. */
59
+ export interface MaestroActivities {
60
+ refreshEnsembleState(ensemble: string): Promise<MaestroPlayerInfo[]>;
61
+ fetchConductorHistory(input: FetchConductorHistoryInput): Promise<FetchConductorHistoryResult>;
62
+ relayCommandToConductor(input: RelayCommandInput): Promise<RelayCommandResult>;
63
+ discoverEnsembles(): Promise<string[]>;
64
+ deliverMaestroMessage(input: DeliverMaestroMessageInput): Promise<DeliverMaestroMessageResult>;
65
+ fetchPlayerMessages(input: FetchPlayerMessagesInput): Promise<FetchPlayerMessagesResult>;
66
+ fetchEnsembleChat(input: FetchEnsembleChatInput): Promise<FetchEnsembleChatResult>;
67
+ }
68
+ /**
69
+ * Create the Maestro activity implementations bound to a Temporal client.
70
+ * Registered with the shared worker.
71
+ */
72
+ export declare function createMaestroActivities(client: Client): MaestroActivities;