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,255 @@
1
+ /**
2
+ * Orphan-session query — shared by `reconcileOnBoot()` in `src/daemon.ts` and
3
+ * the `agent-tempo restore` CLI command (`src/cli/commands.ts`).
4
+ *
5
+ * Design §10.1: a session is an **orphan** when the workflow is `Running` but
6
+ * no adapter process is alive to own its attachment. Two candidate shapes
7
+ * matter:
8
+ *
9
+ * 1. **Active-host sessions** — `AgentTempoAttachedHost = local` AND phase
10
+ * is `attached` / `processing` / `awaiting` / `draining`. The attachment
11
+ * exists but the adapter process may have died.
12
+ * 2. **Detached-home sessions** — `AgentTempoAttachmentState = detached` AND
13
+ * `AgentTempoHostname = local`. No adapter at all; the home host is us.
14
+ *
15
+ * For each candidate we query `attachmentInfo` + `orphanSummary`. If the
16
+ * adapter process is alive (`isAdapterProcessAlive` returns true) we skip —
17
+ * that's the daemon-restarted-under-a-live-adapter case, not an orphan.
18
+ *
19
+ * `isAdapterProcessAlive` is stubbed as `() => false` for v0.25.0-beta.1 per
20
+ * PR-E engineer brief §8 answer 1. No adapter PID file convention exists yet
21
+ * (only the daemon process has `daemon.pid`; Copilot bridges write their own
22
+ * per-session file but Claude Code CLI does not). A conservative always-dead
23
+ * stub is safe: false negatives cost an extra `claimAttachment` attempt,
24
+ * which the caller catches as `AttachmentConflict` and backs off silently
25
+ * (design §10.6). False positives — skipping a session that needs restore —
26
+ * are the worse failure mode.
27
+ */
28
+ import type { Client } from '@temporalio/client';
29
+ import type { AttachmentInfo, AttachmentPhase, OrphanSummary } from '../types';
30
+ /**
31
+ * A session workflow observed to be an orphan: the workflow is alive but no
32
+ * adapter is owning its attachment on this host.
33
+ */
34
+ export interface OrphanCandidate {
35
+ workflowId: string;
36
+ info: AttachmentInfo;
37
+ summary: OrphanSummary;
38
+ }
39
+ /** Filter options for {@link queryOrphanedSessions}. */
40
+ export interface OrphanQueryFilter {
41
+ /** Local hostname to match against `AgentTempoAttachedHost` / `AgentTempoHostname`. */
42
+ hostname: string;
43
+ /** Optional ensemble narrowing pushed into the visibility query. */
44
+ ensemble?: string;
45
+ /**
46
+ * Restrict the visibility query to specific attachment phases. Defaults to
47
+ * the broad live-phase set (`attached | processing | awaiting | draining`)
48
+ * for daemon reconcile-on-boot, which must treat every live phase as
49
+ * presumed-orphan (no PID memory post-crash). User-invoked `/restore`
50
+ * narrows this to `['detached']` so a healthy attached session is never
51
+ * flagged as an orphan candidate.
52
+ *
53
+ * The `detached` home-host clause is always included when `phases`
54
+ * contains `'detached'`; the active-host clause is included when `phases`
55
+ * contains any of the live phases.
56
+ */
57
+ phases?: AttachmentPhase[];
58
+ /**
59
+ * #151 cluster-view: when `true`, drop the `AgentTempoAttachedHost` /
60
+ * `AgentTempoHostname` predicates from the visibility query so the result
61
+ * spans **every** host's orphans, not just the local one. Used by
62
+ * `agent-tempo restore --all-hosts` to surface dormant remote orphans
63
+ * the operator would otherwise need per-host SSH to see. `hostname` is
64
+ * still required (it's the join key for the cross-host record) but no
65
+ * longer filters the query.
66
+ */
67
+ allHosts?: boolean;
68
+ /**
69
+ * When set, override `isAdapterProcessAlive`. Default stub returns `false`
70
+ * (always assume dead, conservative always-restore). Tests pass a custom
71
+ * predicate; production callers omit.
72
+ */
73
+ isAdapterProcessAlive?: (hostname: string, workflowId: string) => boolean;
74
+ }
75
+ /** Options shape for {@link buildOrphanQuery}. */
76
+ export interface BuildOrphanQueryOpts {
77
+ hostname: string;
78
+ ensemble?: string;
79
+ phases?: AttachmentPhase[];
80
+ /**
81
+ * #151: drop hostname predicates from the emitted query. The shape becomes
82
+ * `WorkflowType=... AND ExecutionStatus="Running" AND AgentTempoAttachmentState IN (...)`
83
+ * (no host clause), returning every orphan in the namespace. The `hostname`
84
+ * field is ignored when this is `true`.
85
+ */
86
+ allHosts?: boolean;
87
+ }
88
+ /**
89
+ * Stub per §8 answer 1 — always reports dead. Exported for test wiring even
90
+ * though production callers use the default via {@link queryOrphanedSessions}.
91
+ */
92
+ export declare function isAdapterProcessAliveStub(): boolean;
93
+ /**
94
+ * Build the visibility-query string matching the §10.1 candidate set for the
95
+ * given hostname. Exposed (rather than inlined) so tests can introspect the
96
+ * query shape without a live Temporal connection.
97
+ *
98
+ * The opts-object form carries a `phases` filter — used by user-invoked
99
+ * `/restore` to narrow the visibility query to `detached` only so live
100
+ * sessions are never flagged as orphan candidates.
101
+ */
102
+ export declare function buildOrphanQuery(opts: BuildOrphanQueryOpts): string;
103
+ /**
104
+ * Query Temporal for orphan candidates matching the filter. Runs the
105
+ * visibility query, then fetches `attachmentInfo` + `orphanSummary` per
106
+ * candidate. Skips candidates whose adapter process the liveness predicate
107
+ * reports as alive.
108
+ *
109
+ * Defensive: any per-candidate failure (workflow completed between list +
110
+ * query, query handler throws) is logged and the candidate is skipped — the
111
+ * result array always reflects only the candidates that could be fully
112
+ * resolved at query time.
113
+ *
114
+ * **Deadline (#336/#529):** the visibility iterator is bounded by
115
+ * `deadlineMs`. Default is `VISIBILITY_DEADLINES_MS.orphanQueryBoot`
116
+ * (60s) — suitable for the daemon's one-shot boot reconcile, which can
117
+ * afford a generous budget to enumerate thousands of workflows. The
118
+ * periodic 6h cleanup loop should pass
119
+ * `VISIBILITY_DEADLINES_MS.orphanQueryCleanup` (30s) since it runs
120
+ * frequently enough to amortize partial scans. On timeout, returns the
121
+ * partial result and emits a warn log: the function's existing comment
122
+ * states partial enumeration is acceptable ("next tick will retry"),
123
+ * so the boot path can call `restoreOrphansOnce` again on a subsequent
124
+ * cycle to pick up missed candidates.
125
+ */
126
+ export declare function queryOrphanedSessions(client: Client, filter: OrphanQueryFilter, log?: (...args: unknown[]) => void, deadlineMs?: number): Promise<OrphanCandidate[]>;
127
+ /**
128
+ * Options for {@link restoreOrphansOnce}. `policy: 'never'` is NOT included
129
+ * here — callers that want a silent no-op skip the helper entirely. The
130
+ * daemon's `reconcileOnBoot` short-circuits on `restorePolicy === 'never'`
131
+ * before reaching this helper.
132
+ */
133
+ export interface RestoreOrphansOpts {
134
+ /** Local hostname — matched against the orphan query filter and used as
135
+ * the default target when a candidate's `preferredHost` is unset. */
136
+ hostname: string;
137
+ /** Passed through to `TempoClient.restart(...)` as the operator identity
138
+ * (e.g. `'daemon'`, `'cli'`, or a specific player name). */
139
+ invokerPlayerId: string;
140
+ /** `'auto'` — call `restart` on each eligible candidate.
141
+ * `'prompt'` — log each candidate; do not call `restart`. */
142
+ policy: 'auto' | 'prompt';
143
+ /**
144
+ * #151 listing mode. `'local'` (default) keeps the pre-existing behavior:
145
+ * orphan query is filtered to this host, and `policy: 'auto'` restarts
146
+ * locally-preferred candidates. `'all-hosts-readonly'` widens the query
147
+ * to every host (cluster-view) AND short-circuits `policy` so `restart`
148
+ * is NEVER called — every visible orphan is emitted as
149
+ * `{ kind: 'skipped', reason: 'crossHost', detail: <preferredHost> }`
150
+ * for the CLI's `--all-hosts` discovery formatter. Recovery still
151
+ * happens through the existing safety valve (the remote daemon's own
152
+ * `reconcileOnBoot`, or a deliberate `restart --host <local> --force
153
+ * --confirm-steal-from-host <remote>`).
154
+ */
155
+ mode?: 'local' | 'all-hosts-readonly';
156
+ /** Optional narrowing filter — only consider orphans in this ensemble. */
157
+ ensemble?: string;
158
+ /**
159
+ * Optional phase narrowing — forwarded to {@link queryOrphanedSessions}.
160
+ * Defaults to the broad live-phase set for daemon reconcile-on-boot +
161
+ * CLI `up --resume`. User-invoked `/restore` narrows this to
162
+ * `['detached']` so a healthy attached session is never flagged.
163
+ */
164
+ phases?: AttachmentPhase[];
165
+ /** Orphans whose `detachedSince` exceeds this window are skipped.
166
+ * Default: 24 hours. Ignored when `policy === 'prompt'`. */
167
+ autoRestoreMaxAgeHours?: number;
168
+ /** Glob allowlist for ensemble names (`isEnsembleAllowed`). Empty /
169
+ * undefined → allow all. Ignored when `policy === 'prompt'`. */
170
+ autoRestoreEnsembles?: string[];
171
+ /** Injectable clock for tests. Default: `Date.now`. */
172
+ now?: () => number;
173
+ /** Override `createTempoClient` for tests. Default: production factory. */
174
+ tempoClientFactory?: (client: Client) => {
175
+ restart: (ensemble: string, playerId: string, opts: {
176
+ host: string;
177
+ invokerPlayerId: string;
178
+ }) => Promise<{
179
+ entryId: string;
180
+ playerId: string;
181
+ }>;
182
+ };
183
+ }
184
+ /**
185
+ * Structured outcome for a single orphan — discriminated union so callers
186
+ * can dispatch on `outcome.kind` without parsing strings. Use
187
+ * {@link formatRestoreOutcome} to render a human-readable form.
188
+ */
189
+ export type RestoreOutcome = {
190
+ kind: 'queued';
191
+ entryId: string;
192
+ } | {
193
+ kind: 'skipped';
194
+ reason: 'preferredHost'
195
+ /**
196
+ * #151: cluster-view listing entry. Emitted by `restoreOrphansOnce`
197
+ * when `mode === 'all-hosts-readonly'` — semantically distinct from
198
+ * `preferredHost`, which fires on an *active* restore attempt that
199
+ * stepped back because the remote daemon owns recovery. `crossHost`
200
+ * is a passive observation for `agent-tempo restore --all-hosts`:
201
+ * "this orphan exists, here's its preferred host, we did nothing."
202
+ * Same data shape (`detail` carries the preferred host); the
203
+ * different label lets CLI / dashboard surfaces distinguish
204
+ * "skipped-during-auto-restore" from "shown-during-readonly-listing".
205
+ */
206
+ | 'crossHost' | 'ageWindow' | 'ensembleAllowlist' | 'attachmentConflict' | 'prompt';
207
+ /** Extra context, e.g. the remote `preferredHost` value. */
208
+ detail?: string;
209
+ } | {
210
+ kind: 'failed';
211
+ error: string;
212
+ };
213
+ /** Per-orphan outcome produced by {@link restoreOrphansOnce}. */
214
+ export interface RestoreOrphanDetail {
215
+ playerId: string;
216
+ ensemble: string;
217
+ outcome: RestoreOutcome;
218
+ }
219
+ /** Render a {@link RestoreOutcome} for human-readable logs / CLI output. */
220
+ export declare function formatRestoreOutcome(o: RestoreOutcome): string;
221
+ /** Summary returned by {@link restoreOrphansOnce}. */
222
+ export interface RestoreOrphansSummary {
223
+ /** Orphans that successfully enqueued a `restart` outbox entry. */
224
+ reattached: number;
225
+ /** Orphans skipped (cross-host, age window, allowlist, AttachmentConflict,
226
+ * or `policy: 'prompt'`). */
227
+ skipped: number;
228
+ /** Orphans whose `restart` call threw a non-conflict error. */
229
+ failed: number;
230
+ /** Per-orphan details in visit order. Useful for UI rendering. */
231
+ details: RestoreOrphanDetail[];
232
+ }
233
+ /**
234
+ * Run one pass of orphan recovery. Shared by:
235
+ * - daemon's `reconcileOnBoot` (passes `invokerPlayerId: 'daemon'`)
236
+ * - CLI `up` option 2 and `conduct --resume` (passes `invokerPlayerId: 'cli'`)
237
+ *
238
+ * Flow (matches the former body of `reconcileOnBoot`):
239
+ * 1. Query orphan candidates on this host.
240
+ * 2. Filter out candidates whose `preferredHost` points elsewhere
241
+ * (PR-F §16 — remote host's daemon is the authoritative restorer).
242
+ * 3. If `policy === 'prompt'`: log each surviving candidate and return.
243
+ * 4. If `policy === 'auto'`: for each surviving candidate, apply the
244
+ * age window + ensemble allowlist filters, then call
245
+ * `TempoClient.restart` to enqueue the restart outbox entry.
246
+ *
247
+ * Never throws — all per-candidate failures are captured in
248
+ * {@link RestoreOrphansSummary.details}. The caller picks whether to log,
249
+ * surface to UI, or aggregate across multiple hosts.
250
+ *
251
+ * `AttachmentConflict` is counted as `skipped` (not `failed`) because the
252
+ * only cause is another operator or daemon having restored concurrently —
253
+ * the user's intent was satisfied, just not by us.
254
+ */
255
+ export declare function restoreOrphansOnce(client: Client, opts: RestoreOrphansOpts, log?: (...args: unknown[]) => void): Promise<RestoreOrphansSummary>;
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAdapterProcessAliveStub = isAdapterProcessAliveStub;
4
+ exports.buildOrphanQuery = buildOrphanQuery;
5
+ exports.queryOrphanedSessions = queryOrphanedSessions;
6
+ exports.formatRestoreOutcome = formatRestoreOutcome;
7
+ exports.restoreOrphansOnce = restoreOrphansOnce;
8
+ const signals_1 = require("../workflows/signals");
9
+ const config_1 = require("../config");
10
+ const client_1 = require("../client");
11
+ const query_timeout_1 = require("../utils/query-timeout");
12
+ const visibility_deadline_1 = require("../utils/visibility-deadline");
13
+ /**
14
+ * Default broad phase set used by daemon reconcile-on-boot and CLI
15
+ * `up --resume`. Both paths have no PID memory after a crash and must
16
+ * treat every live phase as a presumed orphan — the post-query
17
+ * `isAdapterProcessAlive` predicate (and, in practice, the restart outbox
18
+ * raising `AttachmentConflict` against a live adapter) is what protects
19
+ * genuinely-attached sessions from being torn down.
20
+ *
21
+ * Includes `'detached'` so the broad query emits both §10.1 clauses:
22
+ * - active-host live-phase (`AgentTempoAttachedHost = host AND state IN (...)`)
23
+ * - detached home-host (`state = "detached" AND AgentTempoHostname = host`)
24
+ *
25
+ * User-invoked `/restore` narrows this to `['detached']` via the `phases`
26
+ * filter so a healthy live session is never flagged as an orphan candidate.
27
+ */
28
+ const DEFAULT_ORPHAN_PHASES = [
29
+ 'attached',
30
+ 'processing',
31
+ 'awaiting',
32
+ 'draining',
33
+ 'detached',
34
+ ];
35
+ /**
36
+ * Stub per §8 answer 1 — always reports dead. Exported for test wiring even
37
+ * though production callers use the default via {@link queryOrphanedSessions}.
38
+ */
39
+ function isAdapterProcessAliveStub() {
40
+ return false;
41
+ }
42
+ /**
43
+ * Escape a value for interpolation into a Temporal visibility query string.
44
+ * Mirrors the helper in `src/client/index.ts`.
45
+ */
46
+ function sanitizeQueryValue(value) {
47
+ return value.replace(/["\\\n\r]/g, '');
48
+ }
49
+ /**
50
+ * Build the visibility-query string matching the §10.1 candidate set for the
51
+ * given hostname. Exposed (rather than inlined) so tests can introspect the
52
+ * query shape without a live Temporal connection.
53
+ *
54
+ * The opts-object form carries a `phases` filter — used by user-invoked
55
+ * `/restore` to narrow the visibility query to `detached` only so live
56
+ * sessions are never flagged as orphan candidates.
57
+ */
58
+ function buildOrphanQuery(opts) {
59
+ const h = sanitizeQueryValue(opts.hostname);
60
+ const ensembleClause = opts.ensemble
61
+ ? ` AND AgentTempoEnsemble = "${sanitizeQueryValue(opts.ensemble)}"`
62
+ : '';
63
+ const phases = opts.phases && opts.phases.length > 0
64
+ ? opts.phases
65
+ : DEFAULT_ORPHAN_PHASES;
66
+ const livePhases = phases.filter((p) => p !== 'detached');
67
+ const includeDetached = phases.includes('detached');
68
+ const clauses = [];
69
+ if (opts.allHosts) {
70
+ // #151 cluster-view — drop the per-host predicates entirely. Phase filter
71
+ // is still respected so we don't sweep up genuinely-live sessions when
72
+ // the caller narrowed to `['detached']`.
73
+ if (livePhases.length > 0) {
74
+ const liveList = livePhases.map((p) => `"${p}"`).join(',');
75
+ clauses.push(`AgentTempoAttachmentState IN (${liveList})`);
76
+ }
77
+ if (includeDetached) {
78
+ clauses.push(`AgentTempoAttachmentState = "detached"`);
79
+ }
80
+ }
81
+ else {
82
+ if (livePhases.length > 0) {
83
+ const liveList = livePhases.map((p) => `"${p}"`).join(',');
84
+ clauses.push(`(AgentTempoAttachedHost = "${h}" AND AgentTempoAttachmentState IN (${liveList}))`);
85
+ }
86
+ if (includeDetached) {
87
+ clauses.push(`(AgentTempoAttachmentState = "detached" AND AgentTempoHostname = "${h}")`);
88
+ }
89
+ }
90
+ // Safety net — both arms empty means caller passed `phases: []`. Fall back
91
+ // to the default broad set rather than emit an invalid `AND ()` clause.
92
+ if (clauses.length === 0) {
93
+ return buildOrphanQuery({ ...opts, phases: DEFAULT_ORPHAN_PHASES });
94
+ }
95
+ return (`WorkflowType = "agentSessionWorkflow" ` +
96
+ `AND ExecutionStatus = "Running" ` +
97
+ `AND (${clauses.join(' OR ')})${ensembleClause}`);
98
+ }
99
+ /**
100
+ * Query Temporal for orphan candidates matching the filter. Runs the
101
+ * visibility query, then fetches `attachmentInfo` + `orphanSummary` per
102
+ * candidate. Skips candidates whose adapter process the liveness predicate
103
+ * reports as alive.
104
+ *
105
+ * Defensive: any per-candidate failure (workflow completed between list +
106
+ * query, query handler throws) is logged and the candidate is skipped — the
107
+ * result array always reflects only the candidates that could be fully
108
+ * resolved at query time.
109
+ *
110
+ * **Deadline (#336/#529):** the visibility iterator is bounded by
111
+ * `deadlineMs`. Default is `VISIBILITY_DEADLINES_MS.orphanQueryBoot`
112
+ * (60s) — suitable for the daemon's one-shot boot reconcile, which can
113
+ * afford a generous budget to enumerate thousands of workflows. The
114
+ * periodic 6h cleanup loop should pass
115
+ * `VISIBILITY_DEADLINES_MS.orphanQueryCleanup` (30s) since it runs
116
+ * frequently enough to amortize partial scans. On timeout, returns the
117
+ * partial result and emits a warn log: the function's existing comment
118
+ * states partial enumeration is acceptable ("next tick will retry"),
119
+ * so the boot path can call `restoreOrphansOnce` again on a subsequent
120
+ * cycle to pick up missed candidates.
121
+ */
122
+ async function queryOrphanedSessions(client, filter, log = () => { }, deadlineMs = visibility_deadline_1.VISIBILITY_DEADLINES_MS.orphanQueryBoot) {
123
+ const isAlive = filter.isAdapterProcessAlive ?? isAdapterProcessAliveStub;
124
+ const query = buildOrphanQuery({
125
+ hostname: filter.hostname,
126
+ ...(filter.ensemble !== undefined ? { ensemble: filter.ensemble } : {}),
127
+ ...(filter.phases !== undefined ? { phases: filter.phases } : {}),
128
+ ...(filter.allHosts ? { allHosts: true } : {}),
129
+ });
130
+ // Defense-in-depth: even though the visibility query filters by phase,
131
+ // re-check `info.phase` after fetching the fresh `attachmentInfo`. A
132
+ // session may have transitioned between list + query, and we never want a
133
+ // live `attached`/`processing`/`awaiting` session to be returned to a
134
+ // caller that narrowed to `['detached']`.
135
+ const phaseAllowlist = filter.phases && filter.phases.length > 0
136
+ ? new Set(filter.phases)
137
+ : null;
138
+ const orphans = [];
139
+ try {
140
+ for await (const wf of (0, visibility_deadline_1.iterateWithDeadline)(client.workflow.list({ query }), deadlineMs, 'queryOrphanedSessions')) {
141
+ const handle = client.workflow.getHandle(wf.workflowId);
142
+ try {
143
+ // Issue #433 — bound each per-workflow query so a hung session
144
+ // (workflow `Running` but worker dead) can't block the orphan scan
145
+ // forever. The `try/catch` already treats query failure as "skip
146
+ // this candidate"; `QueryTimeoutError` falls into the same path.
147
+ const info = await (0, query_timeout_1.queryHandleWithTimeout)(handle, signals_1.attachmentInfoQuery);
148
+ // Phase allowlist re-check (see above).
149
+ if (phaseAllowlist && info.phase && !phaseAllowlist.has(info.phase)) {
150
+ continue;
151
+ }
152
+ // Live adapter — not an orphan.
153
+ if (info.currentAttachment && isAlive(info.currentAttachment.hostname, wf.workflowId)) {
154
+ continue;
155
+ }
156
+ const summary = await (0, query_timeout_1.queryHandleWithTimeout)(handle, signals_1.orphanSummaryQuery);
157
+ orphans.push({ workflowId: wf.workflowId, info, summary });
158
+ }
159
+ catch (err) {
160
+ // Workflow may have completed between list + query, a query handler
161
+ // threw, or the per-query timeout fired (#433 — wedged worker).
162
+ // Skip — not every candidate will be reachable, and partial results
163
+ // are acceptable for reconcile (next tick will retry).
164
+ log(`orphan-query skip ${wf.workflowId}:`, err instanceof Error ? err.message : String(err));
165
+ }
166
+ }
167
+ }
168
+ catch (err) {
169
+ if ((0, visibility_deadline_1.isVisibilityTimeout)(err)) {
170
+ // #336/#529 — partial-tolerant: warn-log and return what we have.
171
+ // The caller (reconcile-on-boot / cleanup loop) treats partial
172
+ // results as best-effort; the next sweep picks up any missed
173
+ // candidates.
174
+ log(`queryOrphanedSessions: ${err.message} — returning partial (${orphans.length} orphans)`);
175
+ }
176
+ else {
177
+ throw err;
178
+ }
179
+ }
180
+ return orphans;
181
+ }
182
+ /** Render a {@link RestoreOutcome} for human-readable logs / CLI output. */
183
+ function formatRestoreOutcome(o) {
184
+ switch (o.kind) {
185
+ case 'queued': return `queued (outbox ${o.entryId})`;
186
+ case 'failed': return `failed: ${o.error}`;
187
+ case 'skipped':
188
+ switch (o.reason) {
189
+ case 'preferredHost': return `skipped: preferredHost=${o.detail ?? '(unknown)'}`;
190
+ case 'crossHost': return `cross-host orphan: preferredHost=${o.detail ?? '(unknown)'}`;
191
+ case 'ageWindow': return 'skipped: age window';
192
+ case 'ensembleAllowlist': return 'skipped: ensemble allowlist';
193
+ case 'attachmentConflict': return 'skipped: AttachmentConflict';
194
+ case 'prompt': return 'prompt';
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Run one pass of orphan recovery. Shared by:
200
+ * - daemon's `reconcileOnBoot` (passes `invokerPlayerId: 'daemon'`)
201
+ * - CLI `up` option 2 and `conduct --resume` (passes `invokerPlayerId: 'cli'`)
202
+ *
203
+ * Flow (matches the former body of `reconcileOnBoot`):
204
+ * 1. Query orphan candidates on this host.
205
+ * 2. Filter out candidates whose `preferredHost` points elsewhere
206
+ * (PR-F §16 — remote host's daemon is the authoritative restorer).
207
+ * 3. If `policy === 'prompt'`: log each surviving candidate and return.
208
+ * 4. If `policy === 'auto'`: for each surviving candidate, apply the
209
+ * age window + ensemble allowlist filters, then call
210
+ * `TempoClient.restart` to enqueue the restart outbox entry.
211
+ *
212
+ * Never throws — all per-candidate failures are captured in
213
+ * {@link RestoreOrphansSummary.details}. The caller picks whether to log,
214
+ * surface to UI, or aggregate across multiple hosts.
215
+ *
216
+ * `AttachmentConflict` is counted as `skipped` (not `failed`) because the
217
+ * only cause is another operator or daemon having restored concurrently —
218
+ * the user's intent was satisfied, just not by us.
219
+ */
220
+ async function restoreOrphansOnce(client, opts, log = () => { }) {
221
+ const summary = {
222
+ reattached: 0,
223
+ skipped: 0,
224
+ failed: 0,
225
+ details: [],
226
+ };
227
+ const now = opts.now ?? Date.now;
228
+ const mode = opts.mode ?? 'local';
229
+ const allHostsReadonly = mode === 'all-hosts-readonly';
230
+ let orphans;
231
+ try {
232
+ orphans = await queryOrphanedSessions(client, {
233
+ hostname: opts.hostname,
234
+ ...(opts.ensemble ? { ensemble: opts.ensemble } : {}),
235
+ ...(opts.phases ? { phases: opts.phases } : {}),
236
+ ...(allHostsReadonly ? { allHosts: true } : {}),
237
+ }, log);
238
+ }
239
+ catch (err) {
240
+ log('restoreOrphansOnce: orphan query failed (non-fatal):', err instanceof Error ? err.message : String(err));
241
+ return summary;
242
+ }
243
+ if (orphans.length === 0) {
244
+ return summary;
245
+ }
246
+ // Shared emitter so every outcome is recorded + logged identically.
247
+ const record = (o, outcome) => {
248
+ if (outcome.kind === 'queued')
249
+ summary.reattached++;
250
+ else if (outcome.kind === 'failed')
251
+ summary.failed++;
252
+ else
253
+ summary.skipped++;
254
+ summary.details.push({
255
+ playerId: o.summary.playerId,
256
+ ensemble: o.summary.ensemble,
257
+ outcome,
258
+ });
259
+ log(`restoreOrphansOnce: ${o.workflowId} — ${formatRestoreOutcome(outcome)}`);
260
+ };
261
+ // #151: cluster-view readonly mode. Never calls `restart` — emits every
262
+ // visible orphan as a structured `crossHost` skip so the CLI's
263
+ // `--all-hosts` formatter can group, annotate, and surface the action
264
+ // edge. Recovery happens out-of-band: the remote daemon's reconcile-on-
265
+ // boot when it returns, or a deliberate TUI `/migrate <player> <host>
266
+ // --force`. The post-query phase allowlist (live `attached` not flagged
267
+ // as orphan candidate) and ensemble narrowing are both applied here too.
268
+ if (allHostsReadonly) {
269
+ for (const o of orphans) {
270
+ if (opts.ensemble && o.summary.ensemble !== opts.ensemble)
271
+ continue;
272
+ // `detail` carries the preferred host when set, falling back to the
273
+ // candidate's home hostname (from `OrphanSummary.lastAdapter.hostname`)
274
+ // and finally `(unknown)`. The CLI groups by this value.
275
+ const detail = o.summary.preferredHost ?? o.summary.lastAdapter?.hostname ?? '(unknown)';
276
+ record(o, { kind: 'skipped', reason: 'crossHost', detail });
277
+ }
278
+ return summary;
279
+ }
280
+ // Filter: (1) ensemble narrowing from the caller, (2) PR-F cross-host
281
+ // filter (design §16 — remote orphans are the remote daemon's problem).
282
+ const candidates = [];
283
+ for (const o of orphans) {
284
+ if (opts.ensemble && o.summary.ensemble !== opts.ensemble)
285
+ continue;
286
+ if (o.summary.preferredHost && o.summary.preferredHost !== opts.hostname) {
287
+ record(o, { kind: 'skipped', reason: 'preferredHost', detail: o.summary.preferredHost });
288
+ continue;
289
+ }
290
+ candidates.push(o);
291
+ }
292
+ if (candidates.length === 0) {
293
+ return summary;
294
+ }
295
+ if (opts.policy === 'prompt') {
296
+ for (const o of candidates)
297
+ record(o, { kind: 'skipped', reason: 'prompt' });
298
+ return summary;
299
+ }
300
+ // policy === 'auto'
301
+ const ageWindowMs = (opts.autoRestoreMaxAgeHours ?? 24) * 60 * 60 * 1000;
302
+ const allowlist = opts.autoRestoreEnsembles ?? [];
303
+ const nowMs = now();
304
+ const tempo = (opts.tempoClientFactory ?? client_1.createTempoClient)(client);
305
+ for (const o of candidates) {
306
+ // Age filter — skip orphans older than the restore window.
307
+ if (o.summary.detachedSince) {
308
+ const detachedAt = Date.parse(o.summary.detachedSince);
309
+ if (Number.isFinite(detachedAt) && nowMs - detachedAt > ageWindowMs) {
310
+ record(o, { kind: 'skipped', reason: 'ageWindow' });
311
+ continue;
312
+ }
313
+ }
314
+ // Ensemble allowlist (empty → allow all).
315
+ if (!(0, config_1.isEnsembleAllowed)(o.summary.ensemble, allowlist)) {
316
+ record(o, { kind: 'skipped', reason: 'ensembleAllowlist' });
317
+ continue;
318
+ }
319
+ const targetHost = o.summary.preferredHost ?? opts.hostname;
320
+ try {
321
+ const result = await tempo.restart(o.summary.ensemble, o.summary.playerId, {
322
+ host: targetHost,
323
+ invokerPlayerId: opts.invokerPlayerId,
324
+ });
325
+ record(o, { kind: 'queued', entryId: result.entryId });
326
+ }
327
+ catch (err) {
328
+ // §10.6: `AttachmentConflict` is a silent backoff — another restorer
329
+ // won the race concurrently.
330
+ const msg = err instanceof Error ? err.message : String(err);
331
+ if (msg.includes('AttachmentConflict')) {
332
+ record(o, { kind: 'skipped', reason: 'attachmentConflict' });
333
+ }
334
+ else {
335
+ record(o, { kind: 'failed', error: msg });
336
+ }
337
+ }
338
+ }
339
+ return summary;
340
+ }