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,849 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createOutboxActivities = createOutboxActivities;
37
+ const client_1 = require("@temporalio/client");
38
+ const activity_1 = require("@temporalio/activity");
39
+ const os = __importStar(require("os"));
40
+ const crypto = __importStar(require("crypto"));
41
+ const config_1 = require("../config");
42
+ const validation_1 = require("../utils/validation");
43
+ const constants_1 = require("../constants");
44
+ const git_info_1 = require("../git-info");
45
+ const spawn_1 = require("../spawn");
46
+ const config_2 = require("../config");
47
+ const resolve_1 = require("./resolve");
48
+ const agent_types_1 = require("../ensemble/agent-types");
49
+ const default_part_1 = require("../utils/default-part");
50
+ const adapters_1 = require("../adapters");
51
+ const hard_terminate_1 = require("./hard-terminate");
52
+ const signals_1 = require("../workflows/signals");
53
+ const validation_2 = require("../utils/validation");
54
+ const log = (...args) => console.error('[agent-tempo:outbox]', ...args);
55
+ /**
56
+ * Classify a Temporal client error raised by `handle.query` / `handle.signal`
57
+ * / `handle.executeUpdate` as retryable (transient) vs permanent (#140).
58
+ *
59
+ * ## Contract
60
+ * - Returns `true` → caller should **re-throw the underlying Error** so the
61
+ * activity's retry policy can back off and retry (per-worker config).
62
+ * - Returns `false` → caller should wrap in `ApplicationFailure.nonRetryable`
63
+ * so the outbox surfaces a permanent failure and stops retrying.
64
+ *
65
+ * ## Safety posture
66
+ * **Conservative default: unknown → non-retryable.** Over-classifying as
67
+ * retryable causes infinite retry loops on genuinely permanent errors. The
68
+ * activity will fail fast on unknown cases; a follow-up PR can whitelist more
69
+ * transient signatures if we see false-permanent rates in the wild.
70
+ *
71
+ * ## Why name/message sniffing, not `instanceof`
72
+ * Matches the established pattern in `src/adapters/terminal-error.ts`
73
+ * `isTerminalWorkflowError`: the Temporal Node SDK surfaces slightly different
74
+ * error shapes between `@temporalio/client`, the gRPC layer, and
75
+ * `WorkflowUpdateFailedError` wrappers. Sniffing on name + message is resilient
76
+ * across those shapes. Activity-side classification is kept separate here so
77
+ * `src/activities/` has no adapter-module dependency.
78
+ */
79
+ function isRetryableTemporalError(err) {
80
+ // ApplicationFailure instances have already been classified by the thrower
81
+ // (nonRetryable=true/false). The calling code paths in this module only ask
82
+ // about non-ApplicationFailure errors, but this guard makes the helper safe
83
+ // to call unconditionally.
84
+ if (err instanceof activity_1.ApplicationFailure)
85
+ return false;
86
+ const e = err;
87
+ const name = e?.name ?? '';
88
+ const msg = e?.message ?? '';
89
+ // ── Permanent: workflow is genuinely gone or validator rejected the op. ──
90
+ if (name.includes('WorkflowNotFound') ||
91
+ name.includes('WorkflowExecutionAlreadyCompleted') ||
92
+ // Update rejected by the workflow-side validator (e.g. `WorkflowGone`
93
+ // thrown from `claimAttachment`'s validator on a destroyed session).
94
+ // A retry won't make the validator change its mind.
95
+ name.includes('WorkflowUpdateFailed') ||
96
+ msg.includes('WorkflowGone') ||
97
+ msg.includes('workflow execution already completed'))
98
+ return false;
99
+ // ── Transient: RPC / network / temporary SDK unavailability. ──
100
+ if (name.includes('TransportError') ||
101
+ name.includes('TimeoutError') ||
102
+ msg.includes('DEADLINE_EXCEEDED') ||
103
+ msg.includes('UNAVAILABLE') ||
104
+ msg.includes('RESOURCE_EXHAUSTED') ||
105
+ msg.includes('CANCELLED') ||
106
+ /\bECONNRESET\b/.test(msg) ||
107
+ /\bECONNREFUSED\b/.test(msg) ||
108
+ /\bETIMEDOUT\b/.test(msg) ||
109
+ /\bENOTFOUND\b/.test(msg) ||
110
+ /\bEAI_AGAIN\b/.test(msg))
111
+ return true;
112
+ // Unknown shape — stay permanent (see "Safety posture" above).
113
+ return false;
114
+ }
115
+ /**
116
+ * Standard shape for the 3 §8.2 deliver activities' catch-all tail.
117
+ * Centralises the branch so each activity body stays concise.
118
+ *
119
+ * - If `err` is already an `ApplicationFailure` (typed permanent — e.g. the
120
+ * explicit "not found" / "destroyed" throws), re-throw as-is.
121
+ * - If `err` is retryable per {@link isRetryableTemporalError}, re-throw the
122
+ * original `Error` so the activity retry policy handles it.
123
+ * - Otherwise wrap in `ApplicationFailure.nonRetryable` with a caller-supplied
124
+ * context prefix (e.g. `Detach failed for "alice"`).
125
+ */
126
+ function classifyAndRethrow(err, contextPrefix) {
127
+ if (err instanceof activity_1.ApplicationFailure)
128
+ throw err;
129
+ if (isRetryableTemporalError(err)) {
130
+ // Re-throw the original so the activity retry policy backs off and retries.
131
+ // Normalise non-Error throwables (extremely rare) into Error form.
132
+ throw err instanceof Error ? err : new Error(String(err));
133
+ }
134
+ throw activity_1.ApplicationFailure.nonRetryable(`${contextPrefix}: ${err instanceof Error ? err.message : String(err)}`);
135
+ }
136
+ /**
137
+ * Create outbox delivery activities bound to a Temporal client and config.
138
+ * The returned object is registered with the worker as activities.
139
+ */
140
+ function createOutboxActivities(client, config) {
141
+ return {
142
+ async deliverCue(input) {
143
+ const { ensemble, fromPlayerId, targetPlayerId, message, broadcastId, attachmentTicket } = input;
144
+ try {
145
+ const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
146
+ if (!handle) {
147
+ throw activity_1.ApplicationFailure.nonRetryable(`No active session found for "${targetPlayerId}"`);
148
+ }
149
+ // #357 + #318: thread broadcastId / attachmentTicket onto the
150
+ // receiver's `receiveMessage` signal payload. Both fields are
151
+ // additive optionals — direct cues omit one or both.
152
+ await handle.signal('receiveMessage', {
153
+ from: fromPlayerId,
154
+ text: message,
155
+ ...(broadcastId !== undefined ? { broadcastId } : {}),
156
+ ...(attachmentTicket !== undefined ? { attachmentTicket } : {}),
157
+ });
158
+ return { success: true };
159
+ }
160
+ catch (err) {
161
+ // #236: transient RPC errors (e.g. DEADLINE_EXCEEDED on the signal call)
162
+ // retry per the activity policy; WorkflowNotFound / validator rejections
163
+ // stay permanent. Unknown errors default to non-retryable.
164
+ classifyAndRethrow(err, `Cue failed for "${targetPlayerId}"`);
165
+ }
166
+ },
167
+ async deliverReport(input) {
168
+ const { ensemble, fromPlayerId, text, reportType } = input;
169
+ try {
170
+ const conductorId = (0, config_1.conductorWorkflowId)(ensemble);
171
+ const handle = client.workflow.getHandle(conductorId);
172
+ await handle.describe(); // throws if conductor workflow is not running
173
+ await handle.signal('playerReport', { playerId: fromPlayerId, text, type: reportType });
174
+ return { success: true };
175
+ }
176
+ catch (err) {
177
+ // #236: describe() / signal() hitting a transient RPC error now retries;
178
+ // WorkflowNotFound (conductor gone) stays permanent as before.
179
+ classifyAndRethrow(err, 'Failed to deliver report to conductor');
180
+ }
181
+ },
182
+ async terminateSession(input) {
183
+ const { ensemble, targetPlayerId, terminatedBy } = input;
184
+ try {
185
+ const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
186
+ if (!handle) {
187
+ throw activity_1.ApplicationFailure.nonRetryable(`No active session found for "${targetPlayerId}"`);
188
+ }
189
+ // PR-C commit 4: use the V2 `destroy` update — explicit operator termination
190
+ // per §2.5 (abandon in-flight, phase=gone, COMPLETE). The former
191
+ // `updateMetadata({ status: 'terminated' })` signal path was retired.
192
+ await handle.executeUpdate('destroy', {
193
+ args: [{ reason: 'stop via tool', terminatedBy }],
194
+ });
195
+ // Notify conductor about the termination (best effort)
196
+ try {
197
+ const conductorId = (0, config_1.conductorWorkflowId)(ensemble);
198
+ const conductorHandle = client.workflow.getHandle(conductorId);
199
+ await conductorHandle.signal('receiveMessage', {
200
+ from: 'system',
201
+ text: `Session "${targetPlayerId}" was terminated by ${terminatedBy}.`,
202
+ responseRequested: false,
203
+ });
204
+ }
205
+ catch {
206
+ // Conductor may not exist — that's fine
207
+ }
208
+ return { success: true };
209
+ }
210
+ catch (err) {
211
+ // #236: transient RPC on the destroy update now retries; validator rejection
212
+ // (WorkflowGone, AttachmentMismatch) stays permanent.
213
+ classifyAndRethrow(err, `Terminate failed for "${targetPlayerId}"`);
214
+ }
215
+ },
216
+ async startRecruitedSession(input) {
217
+ const { ensemble, targetName, workDir, isConductor, initialMessage, fromPlayerId, agent, systemPrompt, taskQueue, agentDefinition, agentDefinitionDescription, held, model } = input;
218
+ try {
219
+ const workflowId = isConductor
220
+ ? (0, config_1.conductorWorkflowId)(ensemble)
221
+ : (0, config_1.sessionWorkflowId)(ensemble, targetName);
222
+ const { gitRoot, gitBranch } = (0, git_info_1.getGitInfo)(workDir);
223
+ // Generate a UUID for the session — used for deterministic --resume on encore
224
+ const sessionId = crypto.randomUUID();
225
+ // Warm hold: process will spawn and go active, but outbox is locked and
226
+ // the initial message is deferred. A standby message is sent instead.
227
+ const standbyMessage = held
228
+ ? 'You are on standby. Your ensemble is loading — other players are still connecting. Wait for your task assignment. Do not start work or send messages yet.'
229
+ : undefined;
230
+ const sessionInput = {
231
+ metadata: {
232
+ playerId: targetName,
233
+ ensemble,
234
+ hostname: os.hostname(),
235
+ workDir,
236
+ gitRoot,
237
+ gitBranch,
238
+ isConductor,
239
+ agentType: agent,
240
+ // PR-B (v0.25 step 2/7): populate adapterId on fresh recruits so the
241
+ // session workflow and dispatch path can resolve the adapter descriptor
242
+ // from the registry without falling back to the legacy agentType field.
243
+ adapterId: adapters_1.registry.resolveFromAgentType(agent),
244
+ sessionId,
245
+ // #131 / #449 Phase C — persist the claude-api / opencode model
246
+ // on durable metadata so restart can recover the original choice
247
+ // across CAN. Both adapters use the same `model` metadata field
248
+ // (different value shapes — bare vs `provider/model`) — the spawn
249
+ // path inspects `metadata.agentType` to know which env var to set.
250
+ ...((agent === 'claude-api' || agent === 'opencode') && model ? { model } : {}),
251
+ ...(agentDefinition ? { playerType: agentDefinition } : {}),
252
+ ...(agentDefinitionDescription ? { playerTypeDescription: agentDefinitionDescription } : {}),
253
+ recruitedBy: fromPlayerId,
254
+ },
255
+ // Issue #450 — derive default `part` from the recruited
256
+ // player type so a freshly recruited session reads as e.g.
257
+ // `'Engineer session'` instead of the role-agnostic
258
+ // `'Session in <basename>'` placeholder.
259
+ autoSummary: (0, default_part_1.defaultPart)({
260
+ playerType: agentDefinition,
261
+ isConductor,
262
+ workDir,
263
+ adapterType: agent,
264
+ }),
265
+ disableStaleDetection: true,
266
+ // When held: store the initial message for delivery on release, inject standby message instead
267
+ ...(held ? { outboxLocked: true, heldMessage: initialMessage } : {}),
268
+ messages: held
269
+ ? [{
270
+ id: crypto.randomUUID(),
271
+ from: 'system',
272
+ text: standbyMessage,
273
+ timestamp: new Date().toISOString(),
274
+ delivered: false,
275
+ }]
276
+ : (initialMessage ? [{
277
+ id: crypto.randomUUID(),
278
+ from: fromPlayerId,
279
+ text: initialMessage,
280
+ timestamp: new Date().toISOString(),
281
+ delivered: false,
282
+ }] : undefined),
283
+ };
284
+ await client.workflow.start('agentSessionWorkflow', {
285
+ workflowId,
286
+ taskQueue,
287
+ args: [sessionInput],
288
+ workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
289
+ searchAttributes: {
290
+ ...(gitRoot ? { AgentTempoGitRoot: [gitRoot] } : {}),
291
+ AgentTempoHostname: [os.hostname()],
292
+ AgentTempoEnsemble: [ensemble],
293
+ AgentTempoPlayerId: [targetName],
294
+ },
295
+ });
296
+ log(`Pre-created workflow ${workflowId} for recruit "${targetName}" (sessionId=${sessionId}, held=${!!held})`);
297
+ return { success: true, sessionId };
298
+ }
299
+ catch (err) {
300
+ // #236: transient RPC during workflow.start (e.g. temporal server flap)
301
+ // now retries; WorkflowNotFound / validation / auth failures stay permanent.
302
+ // Note: this activity's pre-#236 catch was missing the ApplicationFailure
303
+ // passthrough guard — `classifyAndRethrow` restores it for free.
304
+ classifyAndRethrow(err, `Failed to start recruited session "${targetName}"`);
305
+ }
306
+ },
307
+ async spawnProcess(input) {
308
+ const { targetName, workDir, isConductor, agent, systemPrompt, ensemble, temporalAddress, temporalNamespace, agentDefinition, agentDefinitionPath, nativeResolvable, resume, sessionId, allowedTools, claudeBin, attachmentId, attachmentRunId, adapterId, mockMode, mockScenario, model, permissionMode, dangerouslySkipPermissions } = input;
309
+ // Read secrets from the worker's config closure — never from workflow state
310
+ const { temporalApiKey, temporalTlsCertPath, temporalTlsKeyPath } = config;
311
+ try {
312
+ if (agent === 'mock') {
313
+ // ADR 0014 PR-2 — mock adapter spawns headless. No terminal,
314
+ // no Claude binary, no MCP server child. Talks to Temporal
315
+ // directly and posts every action through the standard outbox.
316
+ const { spawnMockAdapter } = await Promise.resolve().then(() => __importStar(require('../spawn')));
317
+ const { pid } = spawnMockAdapter({
318
+ name: targetName,
319
+ ensemble,
320
+ temporalAddress,
321
+ temporalNamespace,
322
+ temporalApiKey,
323
+ temporalTlsCertPath,
324
+ temporalTlsKeyPath,
325
+ isConductor,
326
+ workDir,
327
+ mockMode,
328
+ mockScenario,
329
+ attachmentId,
330
+ attachmentRunId,
331
+ adapterId,
332
+ });
333
+ log(`Spawned mock adapter (pid ${pid}) in ${workDir} as "${targetName}" (mode=${mockMode ?? 'echo'}${attachmentId ? `, attachmentId=${attachmentId}` : ''})`);
334
+ }
335
+ else if (agent === 'copilot') {
336
+ if (allowedTools && allowedTools.length > 0) {
337
+ log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for copilot agent "${targetName}" — copilot bridge does not support --allowedTools, skipping`);
338
+ }
339
+ const { pid } = (0, spawn_1.spawnCopilotBridge)({
340
+ name: targetName,
341
+ ensemble,
342
+ temporalAddress,
343
+ temporalNamespace,
344
+ temporalApiKey,
345
+ temporalTlsCertPath,
346
+ temporalTlsKeyPath,
347
+ isConductor,
348
+ workDir,
349
+ sessionId,
350
+ attachmentId,
351
+ attachmentRunId,
352
+ adapterId,
353
+ });
354
+ log(`Spawned copilot-bridge (pid ${pid}) in ${workDir} as "${targetName}"${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
355
+ }
356
+ else if (agent === 'claude-api') {
357
+ // #131 Phase C — headless Anthropic Messages API adapter. No
358
+ // terminal, no Claude binary, no MCP-server child process. The
359
+ // adapter boots an in-process MCP server + paired client and
360
+ // talks to Anthropic via @anthropic-ai/sdk. Tool surface is
361
+ // MCP-only in v1; file-edit/shell/web tools deferred to Phase 2.
362
+ if (allowedTools && allowedTools.length > 0) {
363
+ log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for claude-api agent "${targetName}" — claude-api adapter does not gate tools per recruit, skipping`);
364
+ }
365
+ const { pid } = (0, spawn_1.spawnClaudeApiAdapter)({
366
+ name: targetName,
367
+ ensemble,
368
+ temporalAddress,
369
+ temporalNamespace,
370
+ temporalApiKey,
371
+ temporalTlsCertPath,
372
+ temporalTlsKeyPath,
373
+ isConductor,
374
+ workDir,
375
+ model,
376
+ attachmentId,
377
+ attachmentRunId,
378
+ adapterId,
379
+ });
380
+ log(`Spawned claude-api adapter (pid ${pid}) in ${workDir} as "${targetName}"${model ? ` (model=${model})` : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
381
+ }
382
+ else if (agent === 'opencode') {
383
+ // #449 Phase C — headless multi-provider adapter via OpenCode.
384
+ // No terminal, no Claude binary. The adapter manages its own
385
+ // `opencode serve` subprocess (probed-free port, hardcoded
386
+ // loopback bind); tools dispatch via OpenCode's MCP-native config
387
+ // block (it spawns dist/server.js as its own MCP child). Per-tool
388
+ // allowlists don't apply — opencode players have full file/shell/
389
+ // web access via OpenCode's built-in tool registry.
390
+ if (allowedTools && allowedTools.length > 0) {
391
+ log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for opencode agent "${targetName}" — opencode adapter does not gate tools per recruit, skipping`);
392
+ }
393
+ const { pid } = (0, spawn_1.spawnOpenCodeAdapter)({
394
+ name: targetName,
395
+ ensemble,
396
+ temporalAddress,
397
+ temporalNamespace,
398
+ temporalApiKey,
399
+ temporalTlsCertPath,
400
+ temporalTlsKeyPath,
401
+ isConductor,
402
+ workDir,
403
+ model,
404
+ attachmentId,
405
+ attachmentRunId,
406
+ adapterId,
407
+ });
408
+ log(`Spawned opencode adapter (pid ${pid}) in ${workDir} as "${targetName}"${model ? ` (model=${model})` : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
409
+ }
410
+ else if (agent === 'claude-code-headless') {
411
+ // #520 — headless Claude Code adapter. Spawns the host's `claude` CLI
412
+ // as a per-turn subprocess; uses the operator's existing OAuth login
413
+ // so turns bill against subscription extra-usage credits. The adapter
414
+ // process itself is a Node subprocess (this branch); the per-turn
415
+ // `claude -p` invocations happen inside the adapter's invokeSdk loop
416
+ // (PR-3). Per-tool allowlists don't apply — claude-code-headless
417
+ // players inherit the full Claude Code tool surface.
418
+ if (allowedTools && allowedTools.length > 0) {
419
+ log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for claude-code-headless agent "${targetName}" — claude-code-headless adapter does not gate tools per recruit, skipping`);
420
+ }
421
+ const { pid } = (0, spawn_1.spawnClaudeCodeHeadlessAdapter)({
422
+ name: targetName,
423
+ ensemble,
424
+ temporalAddress,
425
+ temporalNamespace,
426
+ temporalApiKey,
427
+ temporalTlsCertPath,
428
+ temporalTlsKeyPath,
429
+ isConductor,
430
+ workDir,
431
+ permissionMode,
432
+ dangerouslySkipPermissions,
433
+ attachmentId,
434
+ attachmentRunId,
435
+ adapterId,
436
+ });
437
+ log(`Spawned claude-code-headless adapter (pid ${pid}) in ${workDir} as "${targetName}"${permissionMode ? ` (permissionMode=${permissionMode})` : ''}${dangerouslySkipPermissions ? ' (dangerouslySkipPermissions=true)' : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
438
+ }
439
+ else {
440
+ // Resolve agent flags: --agent (native) > --system-prompt (shipped/legacy)
441
+ let agentFlags = [];
442
+ if (agentDefinition && nativeResolvable) {
443
+ agentFlags = ['--agent', agentDefinition];
444
+ }
445
+ else if (agentDefinitionPath) {
446
+ agentFlags = ['--system-prompt', agentDefinitionPath];
447
+ }
448
+ else if (systemPrompt) {
449
+ agentFlags = ['--system-prompt', systemPrompt];
450
+ }
451
+ // Use --resume for encore (reconnect to existing session) or -n for new sessions.
452
+ // For encore: use UUID for deterministic --resume (no interactive picker).
453
+ // For new sessions: use --session-id to track the UUID for future encores.
454
+ const nameArgs = resume
455
+ ? ['--resume', sessionId || targetName]
456
+ : ['-n', targetName, ...(sessionId ? ['--session-id', sessionId] : [])];
457
+ // Build --allowedTools flag from agent definition frontmatter
458
+ const allowedToolsFlags = allowedTools && allowedTools.length > 0
459
+ ? ['--allowedTools', ...allowedTools]
460
+ : [];
461
+ // ENSEMBLE_SENTINEL_FLAG carries the ensemble name into the spawned
462
+ // claude.exe's CommandLine so hard-terminate can scope `destroy --all`
463
+ // kills by ensemble (issue #180). See src/constants.ts for details.
464
+ const spawnArgs = [
465
+ '--dangerously-skip-permissions',
466
+ '--dangerously-load-development-channels', 'server:agent-tempo',
467
+ constants_1.ENSEMBLE_SENTINEL_FLAG, ensemble,
468
+ ...nameArgs,
469
+ ...agentFlags,
470
+ ...allowedToolsFlags,
471
+ ];
472
+ const envVars = {
473
+ [config_2.ENV.ENSEMBLE]: ensemble,
474
+ [config_2.ENV.CONDUCTOR]: isConductor ? 'true' : '',
475
+ [config_2.ENV.PLAYER_NAME]: targetName,
476
+ [config_2.ENV.TEMPORAL_ADDRESS]: temporalAddress,
477
+ [config_2.ENV.TEMPORAL_NAMESPACE]: temporalNamespace,
478
+ };
479
+ if (agentDefinition)
480
+ envVars[config_2.ENV.PLAYER_TYPE] = agentDefinition;
481
+ if (temporalApiKey)
482
+ envVars[config_2.ENV.TEMPORAL_API_KEY] = temporalApiKey;
483
+ if (temporalTlsCertPath)
484
+ envVars[config_2.ENV.TEMPORAL_TLS_CERT_PATH] = temporalTlsCertPath;
485
+ if (temporalTlsKeyPath)
486
+ envVars[config_2.ENV.TEMPORAL_TLS_KEY_PATH] = temporalTlsKeyPath;
487
+ // PR-D: forward pre-claimed attachment so the adapter renews rather than fresh-claims.
488
+ if (attachmentId)
489
+ envVars[config_2.ENV.ATTACHMENT_ID] = attachmentId;
490
+ if (attachmentRunId)
491
+ envVars[config_2.ENV.ATTACHMENT_RUN_ID] = attachmentRunId;
492
+ if (adapterId)
493
+ envVars[config_2.ENV.ADAPTER_ID] = adapterId;
494
+ const { pid } = (0, spawn_1.spawnInTerminal)(spawnArgs, workDir, envVars, { claudeBin });
495
+ log(`Spawned claude process (pid ${pid}) in ${workDir} as "${targetName}" (resume=${!!resume}${attachmentId ? `, attachmentId=${attachmentId}` : ''})`);
496
+ }
497
+ return { success: true };
498
+ }
499
+ catch (err) {
500
+ // #236: spawnProcess throws predominantly OS-side errors (ENOENT/EACCES
501
+ // on the claude binary, EAGAIN on process-table overflow). The classifier
502
+ // is tuned for Temporal RPC; OS errors don't match its transient
503
+ // signatures, so they still flow through as non-retryable — byte-for-byte
504
+ // behavior preservation. The upside of going through the helper: if a
505
+ // future OS error surfaces a transient shape we add to the classifier,
506
+ // spawnProcess benefits automatically.
507
+ classifyAndRethrow(err, `Failed to spawn process for "${targetName}"`);
508
+ }
509
+ },
510
+ async releasePlayer(input) {
511
+ const { ensemble, targetPlayerId } = input;
512
+ try {
513
+ const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
514
+ if (!handle) {
515
+ throw activity_1.ApplicationFailure.nonRetryable(`No session found for "${targetPlayerId}"`);
516
+ }
517
+ // Check if the session is actually held (outbox locked)
518
+ const isLocked = await handle.query('outboxLocked');
519
+ if (!isLocked) {
520
+ throw activity_1.ApplicationFailure.nonRetryable(`Cannot release "${targetPlayerId}" — session is not held (outbox not locked).`);
521
+ }
522
+ // Signal the session to release — unlocks outbox and delivers held message
523
+ await handle.signal('releaseHeld');
524
+ log(`Released held session "${targetPlayerId}"`);
525
+ return { success: true };
526
+ }
527
+ catch (err) {
528
+ // #236: transient RPC on outboxLocked query / releaseHeld signal now
529
+ // retries; WorkflowNotFound / not-held validation stay permanent.
530
+ classifyAndRethrow(err, `Release failed for "${targetPlayerId}"`);
531
+ }
532
+ },
533
+ /**
534
+ * PR-D `deliverDetach` — resolve target session and signal `requestDetach`.
535
+ * Thin wrapper so the `detach` tool can enqueue through the outbox instead
536
+ * of firing a signal directly from tool code (QA B1).
537
+ */
538
+ async deliverDetach(input) {
539
+ const { ensemble, targetPlayerId, reason = 'user-stop', deadlineMs = 5_000 } = input;
540
+ try {
541
+ const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
542
+ if (!handle) {
543
+ throw activity_1.ApplicationFailure.nonRetryable(`No session found for "${targetPlayerId}"`);
544
+ }
545
+ const info = await handle.query(signals_1.attachmentInfoQuery);
546
+ if (info.phase === 'detached') {
547
+ log(`Detach skipped for "${targetPlayerId}" — already detached`);
548
+ return { success: true };
549
+ }
550
+ if (info.phase === 'gone') {
551
+ throw activity_1.ApplicationFailure.nonRetryable(`Cannot detach "${targetPlayerId}" — session is destroyed`);
552
+ }
553
+ await handle.signal(signals_1.requestDetachSignal, { reason, deadlineMs });
554
+ log(`Detach signaled for "${targetPlayerId}" (deadline=${deadlineMs}ms)`);
555
+ return { success: true };
556
+ }
557
+ catch (err) {
558
+ // #140: re-throw transient RPC/network errors so the activity retry
559
+ // policy handles them; permanent cases (validator rejection, workflow
560
+ // gone, unknown) become `ApplicationFailure.nonRetryable`.
561
+ classifyAndRethrow(err, `Detach failed for "${targetPlayerId}"`);
562
+ }
563
+ },
564
+ /**
565
+ * PR-D `deliverDestroy` — execute `destroyUpdate` on the target and
566
+ * optionally notify the ensemble conductor via `receiveMessageSignal`
567
+ * (typed constant, not a string literal per QA B2).
568
+ */
569
+ async deliverDestroy(input) {
570
+ const { ensemble, targetPlayerId, reason, terminatedBy, notifyConductor = true } = input;
571
+ try {
572
+ const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
573
+ if (!handle) {
574
+ throw activity_1.ApplicationFailure.nonRetryable(`No session found for "${targetPlayerId}"`);
575
+ }
576
+ await handle.executeUpdate(signals_1.destroyUpdate, {
577
+ args: [{
578
+ reason: reason ?? 'destroyed via destroy tool',
579
+ terminatedBy,
580
+ }],
581
+ });
582
+ log(`Destroyed "${targetPlayerId}"${reason ? ` (reason: ${reason})` : ''}`);
583
+ if (notifyConductor) {
584
+ try {
585
+ const condId = (0, config_1.conductorWorkflowId)(ensemble);
586
+ const condHandle = client.workflow.getHandle(condId);
587
+ await condHandle.signal(signals_1.receiveMessageSignal, {
588
+ from: 'system',
589
+ text: `Session "${targetPlayerId}" was destroyed by ${terminatedBy}${reason ? ` (reason: ${reason})` : ''}.`,
590
+ responseRequested: false,
591
+ });
592
+ }
593
+ catch {
594
+ // Conductor may not exist — non-fatal.
595
+ }
596
+ }
597
+ return { success: true };
598
+ }
599
+ catch (err) {
600
+ // #140: transient errors (network, RPC timeout) become retryable;
601
+ // permanent cases (WorkflowNotFound, validator rejection) stay
602
+ // non-retryable. Unknown errors default to non-retryable.
603
+ classifyAndRethrow(err, `Destroy failed for "${targetPlayerId}"`);
604
+ }
605
+ },
606
+ /**
607
+ * PR-D `deliverRestart` — owns the §8.2 restart algorithm on the target.
608
+ * Graceful `requestDetach` → re-query phase → `forceDetach` (if --force
609
+ * OR already was the TOCTOU case) → `claimAttachment` → optional context
610
+ * replay via `receiveMessage` → `enqueueSpawn` on the target's outbox.
611
+ *
612
+ * Mid-algorithm failures surface as ApplicationFailures and retry per the
613
+ * activity's policy. QA B3 — replaces the pre-PR-D tool-side
614
+ * `performRestart` helper so no multi-step cross-workflow mutation happens
615
+ * outside the outbox pattern.
616
+ */
617
+ async deliverRestart(input) {
618
+ const { ensemble, targetPlayerId, invokerPlayerId, force = false, host, fresh = false, contextMessages = 10, loadFromState, transcript } = input;
619
+ try {
620
+ const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
621
+ if (!handle) {
622
+ throw activity_1.ApplicationFailure.nonRetryable(`No workflow for "${targetPlayerId}". Use recruit to start a fresh session.`);
623
+ }
624
+ // Step 1 — inspect phase. `gone` means the workflow COMPLETEd.
625
+ const info = await handle.query(signals_1.attachmentInfoQuery);
626
+ if (info.phase === 'gone') {
627
+ throw activity_1.ApplicationFailure.nonRetryable(`"${targetPlayerId}" was destroyed. Use recruit to start a fresh session.`);
628
+ }
629
+ // Step 2 — reap current attachment.
630
+ if (info.phase !== 'detached' && info.phase !== 'booting') {
631
+ if (info.phase === 'attached' || info.phase === 'awaiting' || info.phase === 'processing') {
632
+ try {
633
+ await handle.signal(signals_1.requestDetachSignal, {
634
+ reason: 'restart',
635
+ deadlineMs: validation_1.DEFAULT_RESTART_DETACH_DEADLINE_MS,
636
+ });
637
+ }
638
+ catch {
639
+ // Best-effort; force path handles it below.
640
+ }
641
+ }
642
+ const info2 = await handle.query(signals_1.attachmentInfoQuery);
643
+ if (info2.phase !== 'detached' && info2.phase !== 'booting') {
644
+ if (!force) {
645
+ const holder = info2.currentAttachment?.hostname ?? 'unknown host';
646
+ throw activity_1.ApplicationFailure.nonRetryable(`"${targetPlayerId}" has a live attachment on ${holder} (phase: ${info2.phase}). ` +
647
+ `Use force=true to steal the lease.`);
648
+ }
649
+ // #159 Gap 2: OS-level kill is owned by the `forceDetachUpdate` handler itself
650
+ // — it invokes `hardTerminateAttachment` on the reaped host's per-host queue
651
+ // *before* flipping workflow state. That keeps the "kill first, then state"
652
+ // ordering inside the durable workflow layer where it belongs; deliverRestart
653
+ // just awaits the update and surfaces retryable errors to the caller.
654
+ await handle.executeUpdate(signals_1.forceDetachUpdate, {
655
+ args: [{
656
+ reason: 'restart',
657
+ ...(info2.currentAttachment ? { expectedAttachmentId: info2.currentAttachment.attachmentId } : {}),
658
+ gracePeriodMs: 0,
659
+ }],
660
+ });
661
+ }
662
+ }
663
+ // Step 3 — metadata + adapter routing.
664
+ const metadata = await handle.query(signals_1.getMetadataQuery);
665
+ // ADR 0014 PR-2 — mock adapter restart path. `mock` is dev-mode only;
666
+ // a metadata.agentType='mock' here implies the dev daemon previously
667
+ // spawned this player and is now restarting it (encore / migrate).
668
+ // Prod restart never lands here for `mock` because gate 3 in
669
+ // src/tools/recruit.ts rejected the original recruit.
670
+ const rawAgent = metadata.agentType;
671
+ // #131 / #449 Phase C — claude-api / opencode join copilot / mock
672
+ // as recognized sdk-class agents here so restart/encore/migrate of
673
+ // a headless player resolves to the right adapterId + class. Without
674
+ // this branch, restart falls through to 'claude' → 'interactive'
675
+ // and the spawn path picks the terminal Claude Code path.
676
+ const agentType = rawAgent === 'copilot'
677
+ ? 'copilot'
678
+ : rawAgent === 'mock'
679
+ ? 'mock'
680
+ : rawAgent === 'claude-api'
681
+ ? 'claude-api'
682
+ : rawAgent === 'opencode'
683
+ ? 'opencode'
684
+ : rawAgent === 'claude-code-headless'
685
+ ? 'claude-code-headless'
686
+ : 'claude';
687
+ const adapterId = metadata.adapterId
688
+ || (agentType === 'copilot'
689
+ ? 'copilot'
690
+ : agentType === 'mock'
691
+ ? 'mock'
692
+ : agentType === 'claude-api'
693
+ ? 'claude-api'
694
+ : agentType === 'opencode'
695
+ ? 'opencode'
696
+ : agentType === 'claude-code-headless'
697
+ ? 'claude-code-headless'
698
+ : 'claude-code');
699
+ const adapterClass = agentType === 'claude' ? 'interactive' : 'sdk';
700
+ const targetHost = host ?? info.preferredHost ?? metadata.hostname;
701
+ // Step 4 — claim fresh attachment.
702
+ const token = await handle.executeUpdate(signals_1.claimAttachmentUpdate, {
703
+ args: [{
704
+ host: targetHost,
705
+ adapterId,
706
+ adapterClass,
707
+ leaseMs: validation_1.DEFAULT_RESTART_LEASE_MS,
708
+ }],
709
+ });
710
+ // Step 5 — context seed (saved state and/or transcript replay).
711
+ //
712
+ // #334 PR-2: `loadFromState` lets the player choose to seed the new
713
+ // session from a curated state slot instead of (or alongside) the
714
+ // existing transcript replay. The semantics matrix (design §4.4):
715
+ //
716
+ // loadFromState set → seed from saved state, suppress replay by default
717
+ // loadFromState + 'replay' → stack: saved state first, then transcript
718
+ // loadFromState set, slot empty → graceful fallback to transcript replay
719
+ // loadFromState absent → existing behaviour (replay governed by `fresh`)
720
+ //
721
+ // Backward compat is structural (no `patched()` marker — see ADR 0011
722
+ // §Consequences and design §7.3): old outbox entries omit
723
+ // `loadFromState`, `wantsState` evaluates falsy, and execution falls
724
+ // through to the original transcript-replay block bit-for-bit.
725
+ const wantsState = loadFromState !== undefined;
726
+ const wantsTranscriptByFlag = transcript === 'replay';
727
+ let saved = null;
728
+ if (wantsState) {
729
+ const stateKey = typeof loadFromState === 'string' ? loadFromState : validation_2.PLAYER_STATE_DEFAULT_KEY;
730
+ saved = await handle.query(signals_1.playerStateQuery, { key: stateKey });
731
+ if (saved) {
732
+ await handle.signal(signals_1.receiveMessageSignal, {
733
+ from: 'self-restart',
734
+ text: `🎵 **Restored state — "${stateKey}"** (saved ${saved.savedAt} by ${saved.savedBy})\n\n${saved.content}`,
735
+ responseRequested: false,
736
+ });
737
+ }
738
+ else {
739
+ log(`Restart loadFromState requested for slot "${stateKey}" but slot is empty — falling back to transcript replay`);
740
+ // UX-friendly fallback per design §4.4: replay the transcript as
741
+ // if the caller had not passed `loadFromState`. The fall-through
742
+ // below picks this up via `wantsState && !saved`.
743
+ }
744
+ }
745
+ // Replay the transcript when (a) loadFromState was not requested, OR
746
+ // (b) caller opted into stacking via `transcript: 'replay'`, OR
747
+ // (c) loadFromState was requested but the slot was empty (fallback).
748
+ // The pre-existing `fresh` flag still wins — `fresh: true` skips
749
+ // replay regardless of state-seeding outcome.
750
+ const wantsTranscript = !wantsState || wantsTranscriptByFlag || (wantsState && !saved);
751
+ if (!fresh && wantsTranscript) {
752
+ const [part, allMessages] = await Promise.all([
753
+ handle.query(signals_1.getPartQuery),
754
+ handle.query(signals_1.allMessagesQuery),
755
+ ]);
756
+ const recent = allMessages.slice(-contextMessages);
757
+ const summary = recent.length > 0
758
+ ? recent.map((m) => `[${m.from}] ${m.text.slice(0, validation_1.PREVIEW_MAX_LENGTH)}`).join('\n')
759
+ : '(no recent messages)';
760
+ const contextMessage = [
761
+ `🎵 **Restart** — you've been revived by ${invokerPlayerId}.`,
762
+ part ? `Your last status: ${part}` : '',
763
+ `Recent messages (last ${recent.length}):`,
764
+ summary,
765
+ '',
766
+ 'Resume where you left off. Use `ensemble` to see who is active.',
767
+ ].filter(Boolean).join('\n');
768
+ await handle.signal(signals_1.receiveMessageSignal, {
769
+ from: invokerPlayerId,
770
+ text: contextMessage,
771
+ responseRequested: false,
772
+ });
773
+ }
774
+ // Step 6 — enqueue the spawn.
775
+ //
776
+ // Issue #183: Claude Code rejects `--session-id <uuid>` when a transcript
777
+ // already exists at `~/.claude/projects/<encoded-path>/<uuid>.jsonl`
778
+ // ("Session ID already in use"). A prior failed spawn can leave that
779
+ // file behind, wedging every subsequent `fresh` restart that reuses the
780
+ // stored sessionId.
781
+ //
782
+ // #306: `/restart` ALWAYS spawns a fresh Claude Code process — never
783
+ // `--resume <id>`. The transcript `.jsonl` for the prior `spawnSessionId`
784
+ // is NOT guaranteed to have been flushed to disk before the prior
785
+ // adapter was hard-terminated (Windows `taskkill /T /F` is synchronous
786
+ // and unconditional). Claude Code then errors out with "No conversation
787
+ // found with session ID" and the new terminal drops to shell.
788
+ //
789
+ // Context preservation is already handled by the Step 5 replay above —
790
+ // we re-send recent messages to the fresh session. That's authoritative;
791
+ // the session-id `--resume` path was only ever a bonus on top. So we
792
+ // mint a new UUID on every restart, persist it to metadata, and never
793
+ // pass `resume: true` to the spawn.
794
+ const spawnSessionId = crypto.randomUUID();
795
+ await handle.signal(signals_1.updateMetadataSignal, { sessionId: spawnSessionId });
796
+ // Issue #184: re-resolve on the invoker host against the session's
797
+ // workDir (NOT the daemon's process.cwd — daemon runs elsewhere than
798
+ // the session's project, so the project-tier lookup needs the session's
799
+ // own cwd). `nativeResolvable` means the target can `--agent <name>`
800
+ // from its own tier lookup; the path fallback is shipped-relative so
801
+ // it exists on any host with agent-tempo installed — safe cross-host.
802
+ const resolved = metadata.playerType
803
+ ? (0, agent_types_1.resolveAgentType)(metadata.playerType, metadata.workDir)
804
+ : null;
805
+ const { spawnEntryId } = await handle.executeUpdate(signals_1.enqueueSpawnUpdate, {
806
+ args: [{
807
+ host: targetHost,
808
+ attachmentId: token.attachmentId,
809
+ runId: token.runId,
810
+ // #306: `/restart` is always a fresh spawn (see comment above).
811
+ resume: false,
812
+ sessionId: spawnSessionId,
813
+ adapterId,
814
+ // #131 Phase C — claude-api adapter model carried across restart.
815
+ // Read from durable session metadata so the restarted player runs
816
+ // the same model the original recruit chose. Absent for non-claude-api
817
+ // sessions and for legacy claude-api sessions recruited before the
818
+ // metadata.model field landed (which fall back to the env / default
819
+ // chain inside the adapter).
820
+ ...(metadata.model !== undefined ? { model: metadata.model } : {}),
821
+ ...(resolved ? {
822
+ agentDefinition: resolved.name,
823
+ agentDefinitionPath: resolved.path,
824
+ nativeResolvable: resolved.nativeResolvable,
825
+ } : {}),
826
+ }],
827
+ });
828
+ log(`Restart prepared for "${targetPlayerId}" — attachmentId=${token.attachmentId}, spawnEntryId=${spawnEntryId}, host=${targetHost}, fresh sessionId=${spawnSessionId}${fresh ? ' (context replay skipped)' : ''}`);
829
+ return { success: true };
830
+ }
831
+ catch (err) {
832
+ // #140: the §8.2 restart algorithm fires many RPCs; any of them may
833
+ // hit a transient network/RPC error. Those get retried. Validator
834
+ // rejections (e.g. claim race), workflow-gone, and unknown errors
835
+ // stay permanent to avoid wedging the outbox on a dead target.
836
+ classifyAndRethrow(err, `Restart failed for "${targetPlayerId}"`);
837
+ }
838
+ },
839
+ /**
840
+ * #159 Gap 2 — OS-level process-tree kill. Registered on the per-host task queue so
841
+ * it runs on the machine that actually hosts the child process. Never throws: the
842
+ * returned `HardTerminateResult` tells the caller what happened (strategy, PIDs,
843
+ * notes), which the workflow can log without blocking the state flip.
844
+ */
845
+ async hardTerminateAttachment(input) {
846
+ return (0, hard_terminate_1.hardTerminateAttachment)(input);
847
+ },
848
+ };
849
+ }