specrails-desktop 2.57.1 → 2.58.0

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 (335) hide show
  1. package/README.md +1 -1
  2. package/cli/dist/args.js +10 -4
  3. package/cli/dist/help.js +1 -1
  4. package/client/dist/assets/{ActivityFeedPage-DdMNmVBk.js → ActivityFeedPage-BQGppIR0.js} +1 -1
  5. package/client/dist/assets/{AgentBrowserCapture-CeerUf--.js → AgentBrowserCapture-PI-cxyxi.js} +1 -1
  6. package/client/dist/assets/{AgentModeAnalyticsPane-BjGb-8rh.js → AgentModeAnalyticsPane-mQsOqdXx.js} +2 -2
  7. package/client/dist/assets/{AgentModeCodePane-8XRkQEo_.js → AgentModeCodePane-BL-vpmlY.js} +2 -2
  8. package/client/dist/assets/{AgentModeJobsPane-BN3dgJOA.js → AgentModeJobsPane-DR_06oqH.js} +1 -1
  9. package/client/dist/assets/AgentsPage-BKIKjm_1.js +118 -0
  10. package/client/dist/assets/{AnalyticsPage-CyfhYiBn.js → AnalyticsPage-069npRVo.js} +1 -1
  11. package/client/dist/assets/{CodePage-C2Nf-Q5W.js → CodePage-Alitorrv.js} +2 -2
  12. package/client/dist/assets/DesktopAnalyticsPage-qtWAPM47.js +1 -0
  13. package/client/dist/assets/{DocsDialog-Br3bczCp.js → DocsDialog-DwL6rgcT.js} +1 -1
  14. package/client/dist/assets/{DocsPage-BcDr2cBm.js → DocsPage-BA1hiJZ-.js} +1 -1
  15. package/client/dist/assets/{ExportDropdown-BVGKEgkd.js → ExportDropdown-BcMpvV89.js} +1 -1
  16. package/client/dist/assets/InteractiveJobComposer-B3H1YHr6.js +19 -0
  17. package/client/dist/assets/JobDetailModal-XZQ9eAAy.js +1 -0
  18. package/client/dist/assets/{JobDetailPage-vBbNeRuz.js → JobDetailPage-B-SIaq_q.js} +1 -1
  19. package/client/dist/assets/{JobsPage-CJCtPhIz.js → JobsPage-iQXAv2mx.js} +1 -1
  20. package/client/dist/assets/LoopBuilderPage-B_gbFAHn.js +1 -0
  21. package/client/dist/assets/LoopPreviewModal-je5pSCf-.js +1 -0
  22. package/client/dist/assets/LoopsPage-BZwcY5cu.js +2 -0
  23. package/client/dist/assets/{MinimizedChatsContext-D3QzgV0B.js → MinimizedChatsContext-CNx_0bZu.js} +1 -1
  24. package/client/dist/assets/{PluginsPage-DOpTV75p.js → PluginsPage-DFocnoHX.js} +2 -2
  25. package/client/dist/assets/{ProjectSettingsDialog-Dkwrn0z1.js → ProjectSettingsDialog-Bh3S6EBB.js} +1 -1
  26. package/client/dist/assets/{RepositoryDeliveries-Du2UCf1a.js → RepositoryDeliveries-BiwJuS8r.js} +1 -1
  27. package/client/dist/assets/{RepositoryScopeSelector-hPGZNRBm.js → RepositoryScopeSelector-C2mvWBQ6.js} +1 -1
  28. package/client/dist/assets/{ReviewPacketPage-DcOq-UTA.js → ReviewPacketPage-CrAf-WDy.js} +2 -2
  29. package/client/dist/assets/TemplatePreviewModal-C7DordpN.js +1 -0
  30. package/client/dist/assets/{TicketDetailModalContext-DAK-NoK6.js → TicketDetailModalContext-B-DySpKL.js} +1 -1
  31. package/client/dist/assets/{Trans-DEbgkuAY.js → Trans-D3AfDylf.js} +1 -1
  32. package/client/dist/assets/agentRuntime-BeUb3g9g.js +1 -0
  33. package/client/dist/assets/agentRuntime-Bg9tbmqm.js +1 -0
  34. package/client/dist/assets/agentRuntime-D-GJVGkj.js +1 -0
  35. package/client/dist/assets/agentRuntime-D2__CVI_.js +1 -0
  36. package/client/dist/assets/agentRuntime-D35dGVOP.js +1 -0
  37. package/client/dist/assets/agentRuntime-DGz_IqKW.js +1 -0
  38. package/client/dist/assets/agentRuntime-O1liDt6G.js +1 -0
  39. package/client/dist/assets/agentRuntime-hInH9Fzj.js +1 -0
  40. package/client/dist/assets/agentstudio-B2CTtQgZ.js +1 -0
  41. package/client/dist/assets/agentstudio-BWAKeGgB.js +1 -0
  42. package/client/dist/assets/{agentstudio-DR7doNBV.js → agentstudio-Cja8Sjcp.js} +1 -1
  43. package/client/dist/assets/agentstudio-DtfPcr99.js +1 -0
  44. package/client/dist/assets/agentstudio-QdAJIDef.js +1 -0
  45. package/client/dist/assets/agentstudio-X70BKf6u.js +1 -0
  46. package/client/dist/assets/agentstudio-fLXpRGIi.js +1 -0
  47. package/client/dist/assets/agentstudio-qXKqVnWS.js +1 -0
  48. package/client/dist/assets/{analytics-jzCkOec3.js → analytics-B1DIQd4m.js} +1 -1
  49. package/client/dist/assets/{analytics-CoEzWPx4.js → analytics-BAIZ9vE_.js} +1 -1
  50. package/client/dist/assets/{analytics-DjLlE7jS.js → analytics-BTqgmgBd.js} +1 -1
  51. package/client/dist/assets/analytics-BWynXPEm.js +1 -0
  52. package/client/dist/assets/{analytics-nZIjjtCc.js → analytics-DGcUsQ44.js} +1 -1
  53. package/client/dist/assets/{analytics-BNgZ1Nj7.js → analytics-GnSnpJ4W.js} +1 -1
  54. package/client/dist/assets/{analytics-DFh5lSOl.js → analytics-TWgYaoO0.js} +1 -1
  55. package/client/dist/assets/{analytics-DZp-DS2U.js → analytics-kJy6Tuko.js} +1 -1
  56. package/client/dist/assets/builder-C0FyvM83.js +1 -0
  57. package/client/dist/assets/builder-C1neIYGR.js +1 -0
  58. package/client/dist/assets/builder-CvnFm0im.js +1 -0
  59. package/client/dist/assets/builder-CzW5ovyI.js +1 -0
  60. package/client/dist/assets/builder-D06L7iPy.js +1 -0
  61. package/client/dist/assets/builder-DGETbtZj.js +1 -0
  62. package/client/dist/assets/builder-DXEJUlC0.js +1 -0
  63. package/client/dist/assets/builder-DyQperXU.js +1 -0
  64. package/client/dist/assets/common-BCK7c9Pv.js +1 -0
  65. package/client/dist/assets/common-C0BOdeNi.js +1 -0
  66. package/client/dist/assets/common-C4Yvvhp6.js +1 -0
  67. package/client/dist/assets/{common-BrlwEnL7.js → common-DmsclwjP.js} +1 -1
  68. package/client/dist/assets/common-DpLWwgI1.js +1 -0
  69. package/client/dist/assets/common-OCvl3c8t.js +1 -0
  70. package/client/dist/assets/common-c0aujEZC.js +1 -0
  71. package/client/dist/assets/common-od8WThlf.js +1 -0
  72. package/client/dist/assets/dashboard-B1ndRHuC.js +1 -0
  73. package/client/dist/assets/dashboard-BX7KXiqA.js +1 -0
  74. package/client/dist/assets/dashboard-B_BSNxcN.js +1 -0
  75. package/client/dist/assets/dashboard-CCeFN7-V.js +1 -0
  76. package/client/dist/assets/dashboard-CRl0WtFJ.js +1 -0
  77. package/client/dist/assets/dashboard-XGdQ5mjw.js +1 -0
  78. package/client/dist/assets/dashboard-_oltA6Jd.js +1 -0
  79. package/client/dist/assets/dashboard-bAuf5KVk.js +1 -0
  80. package/client/dist/assets/{formatDistanceToNow-Cc2u5FwQ.js → formatDistanceToNow-aBss1Nai.js} +1 -1
  81. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BHm2U-w1.js → getTimezoneOffsetInMilliseconds-DOGj1Mfz.js} +1 -1
  82. package/client/dist/assets/index-BS7KCWkV.css +2 -0
  83. package/client/dist/assets/{index-Cxb288Rj.js → index-C4Mv6tV6.js} +32 -32
  84. package/client/dist/assets/{jira-api-DrGxiD9p.js → jira-api-BYbEPlkr.js} +1 -1
  85. package/client/dist/assets/{jobs-DSp9DoD_.js → jobs-6_-r75yC.js} +1 -1
  86. package/client/dist/assets/{jobs-C6qmvUAi.js → jobs-B1yqyId5.js} +1 -1
  87. package/client/dist/assets/{jobs-DoFaEl9g.js → jobs-BFuV7ttH.js} +1 -1
  88. package/client/dist/assets/{jobs-DTQO08nJ.js → jobs-BKa8LuwA.js} +1 -1
  89. package/client/dist/assets/{jobs-DrF8Do8s.js → jobs-BKiK-ZWh.js} +1 -1
  90. package/client/dist/assets/{jobs-DLHdDkHc.js → jobs-DmKV7b7d.js} +1 -1
  91. package/client/dist/assets/{jobs-C9j2PuwW.js → jobs-KG4Sn12X.js} +1 -1
  92. package/client/dist/assets/{jobs-Dpw6AEOP.js → jobs-pPv4KWjb.js} +1 -1
  93. package/client/dist/assets/loop-layout-BF8xMiNv.js +7 -0
  94. package/client/dist/assets/loops-B4_D-nbg.js +1 -0
  95. package/client/dist/assets/loops-BJDY9FTh.js +1 -0
  96. package/client/dist/assets/loops-BO3V8244.js +1 -0
  97. package/client/dist/assets/loops-C-hZK8pf.js +1 -0
  98. package/client/dist/assets/loops-CDOWZ0ci.js +1 -0
  99. package/client/dist/assets/loops-Dv849ckF.js +1 -0
  100. package/client/dist/assets/loops-Q9LIgGkq.js +1 -0
  101. package/client/dist/assets/loops-eUnCfLjC.js +1 -0
  102. package/client/dist/assets/{project-repositories-9Wzs0coQ.js → project-repositories-CSnfzUpg.js} +1 -1
  103. package/client/dist/assets/settings-3o4Ntzi5.js +1 -0
  104. package/client/dist/assets/settings-B97jlkwD.js +1 -0
  105. package/client/dist/assets/settings-BTaT_bsS.js +1 -0
  106. package/client/dist/assets/settings-CT8w7WiC.js +1 -0
  107. package/client/dist/assets/settings-Cvr8_tvh.js +1 -0
  108. package/client/dist/assets/settings-Dn1qgAC8.js +1 -0
  109. package/client/dist/assets/settings-EShDdg4J.js +1 -0
  110. package/client/dist/assets/settings-tfLhQZRN.js +1 -0
  111. package/client/dist/assets/{setup-Dwzt5nO1.js → setup-CBMu3tqH.js} +1 -1
  112. package/client/dist/assets/{setup-B5FHuxvB.js → setup-CPh-e6J2.js} +1 -1
  113. package/client/dist/assets/{setup-DpqzaEEA.js → setup-CRKLp_ke.js} +1 -1
  114. package/client/dist/assets/{setup-CROyqD5N.js → setup-Cy302v4J.js} +1 -1
  115. package/client/dist/assets/{setup-C9Fy9PKQ.js → setup-D681vTPf.js} +1 -1
  116. package/client/dist/assets/{setup-BihzXadx.js → setup-DYK0VBkE.js} +1 -1
  117. package/client/dist/assets/{setup-BtC6Hg4K.js → setup-KAuiIiVs.js} +1 -1
  118. package/client/dist/assets/{setup-zLEDnG7N.js → setup-OGX-oCfI.js} +1 -1
  119. package/client/dist/assets/{spending-T5GM7wpn.js → spending-HBSk-6jv.js} +1 -1
  120. package/client/dist/assets/{useDesktop-QogiN0zR.js → useDesktop-0unHDBJJ.js} +1 -1
  121. package/client/dist/assets/useRuntimeRuns-BN1rhyEm.js +1 -0
  122. package/client/dist/assets/{useSharedWebSocket-Dz-HI6De.js → useSharedWebSocket-BZMrX4jr.js} +2 -2
  123. package/client/dist/index.html +25 -25
  124. package/docs/agent-live-steering.md +16 -0
  125. package/docs/cli.md +7 -7
  126. package/docs/codex.md +2 -2
  127. package/docs/customizing.md +1 -1
  128. package/docs/gemini.md +1 -1
  129. package/docs/getting-started.md +1 -1
  130. package/docs/guide/de/integrations/7-local-engines.md +1 -1
  131. package/docs/guide/de/pipeline/1-rails-and-jobs.md +8 -6
  132. package/docs/guide/de/pipeline/2-the-job-detail-view.md +11 -2
  133. package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +2 -2
  134. package/docs/guide/de/pipeline/5-the-loop-builder.md +24 -5
  135. package/docs/guide/en/integrations/7-local-engines.md +1 -1
  136. package/docs/guide/en/pipeline/1-rails-and-jobs.md +8 -6
  137. package/docs/guide/en/pipeline/2-the-job-detail-view.md +11 -2
  138. package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +2 -2
  139. package/docs/guide/en/pipeline/5-the-loop-builder.md +34 -5
  140. package/docs/guide/en/settings/3-pipeline-telemetry-and-diagnostics.md +1 -1
  141. package/docs/guide/es/integrations/7-local-engines.md +1 -1
  142. package/docs/guide/es/pipeline/1-rails-and-jobs.md +8 -6
  143. package/docs/guide/es/pipeline/2-the-job-detail-view.md +11 -2
  144. package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +2 -2
  145. package/docs/guide/es/pipeline/5-the-loop-builder.md +30 -5
  146. package/docs/guide/es/settings/3-pipeline-telemetry-and-diagnostics.md +1 -2
  147. package/docs/guide/fr/integrations/7-local-engines.md +1 -1
  148. package/docs/guide/fr/pipeline/1-rails-and-jobs.md +8 -6
  149. package/docs/guide/fr/pipeline/2-the-job-detail-view.md +11 -2
  150. package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +2 -2
  151. package/docs/guide/fr/pipeline/5-the-loop-builder.md +24 -5
  152. package/docs/guide/it/integrations/7-local-engines.md +1 -1
  153. package/docs/guide/it/pipeline/1-rails-and-jobs.md +8 -6
  154. package/docs/guide/it/pipeline/2-the-job-detail-view.md +11 -2
  155. package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +2 -2
  156. package/docs/guide/it/pipeline/5-the-loop-builder.md +24 -5
  157. package/docs/guide/ja/integrations/7-local-engines.md +1 -1
  158. package/docs/guide/ja/pipeline/1-rails-and-jobs.md +8 -6
  159. package/docs/guide/ja/pipeline/2-the-job-detail-view.md +11 -2
  160. package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +2 -2
  161. package/docs/guide/ja/pipeline/5-the-loop-builder.md +24 -5
  162. package/docs/guide/pt/integrations/7-local-engines.md +1 -1
  163. package/docs/guide/pt/pipeline/1-rails-and-jobs.md +8 -6
  164. package/docs/guide/pt/pipeline/2-the-job-detail-view.md +11 -2
  165. package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +2 -2
  166. package/docs/guide/pt/pipeline/5-the-loop-builder.md +24 -5
  167. package/docs/guide/zh/integrations/7-local-engines.md +1 -1
  168. package/docs/guide/zh/pipeline/1-rails-and-jobs.md +8 -6
  169. package/docs/guide/zh/pipeline/2-the-job-detail-view.md +11 -2
  170. package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +2 -2
  171. package/docs/guide/zh/pipeline/5-the-loop-builder.md +24 -5
  172. package/docs/internals/api-reference.md +2 -2
  173. package/docs/internals/ci-performance.md +71 -0
  174. package/docs/internals/companion-rails-as-loops-contract.md +4 -3
  175. package/docs/internals/configuration.md +3 -3
  176. package/docs/internals/core-runtime-updates.md +3 -2
  177. package/docs/internals/interactive-jobs.md +2 -2
  178. package/docs/internals/loop-step-log-explorer.md +42 -0
  179. package/docs/internals/mission-rail-cards.md +1 -1
  180. package/docs/internals/operations-runbook.md +22 -0
  181. package/docs/internals/programmatic-agent-runtime.md +128 -7
  182. package/docs/internals/project-builder.md +35 -16
  183. package/docs/internals/source-map.md +48 -2
  184. package/docs/kimi.md +2 -2
  185. package/docs/local-providers.md +1 -1
  186. package/docs/running-pipelines.md +26 -18
  187. package/docs/tracking-cost.md +10 -1
  188. package/package.json +2 -1
  189. package/server/dist/core-compat.js +8 -3
  190. package/server/dist/db/migrations.js +101 -0
  191. package/server/dist/desktop-db.js +28 -0
  192. package/server/dist/desktop-router.js +2 -0
  193. package/server/dist/mcp/guide.js +6 -3
  194. package/server/dist/mcp/tools/jobs.js +14 -1
  195. package/server/dist/mcp/tools/loops.js +17 -8
  196. package/server/dist/mcp/tools/rails.js +3 -3
  197. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +295 -9
  198. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls-router.js +34 -1
  199. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +203 -21
  200. package/server/dist/modules/agent-runtime/runtime/agent-runtime-effective-config.js +46 -1
  201. package/server/dist/modules/agent-runtime/runtime/agent-runtime-engines.js +10 -0
  202. package/server/dist/modules/agent-runtime/runtime/agent-runtime-loader.js +68 -3
  203. package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +16 -8
  204. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-gc.js +81 -0
  205. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-lock.js +37 -0
  206. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package.js +30 -24
  207. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-host.js +87 -0
  208. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-quarantine.js +231 -0
  209. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-records.js +32 -0
  210. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention.js +102 -0
  211. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +12 -7
  212. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +57 -13
  213. package/server/dist/modules/agents/runtime/agent-catalog.js +32 -0
  214. package/server/dist/modules/agents/runtime/agent-refine-manager.js +12 -0
  215. package/server/dist/modules/agents/runtime/agent-role-descriptor.js +64 -0
  216. package/server/dist/modules/agents/runtime/profiles-router.js +26 -34
  217. package/server/dist/modules/builder/runtime/milestone-chain.js +121 -73
  218. package/server/dist/modules/delivery/runtime/definition-fork.js +158 -0
  219. package/server/dist/modules/delivery/runtime/delivery-evidence.js +56 -10
  220. package/server/dist/modules/delivery/runtime/isolated-settlement-reconstruction.js +99 -0
  221. package/server/dist/modules/delivery/runtime/isolated-settlement-store.js +53 -0
  222. package/server/dist/modules/delivery/runtime/multi-repo-execution-store.js +3 -1
  223. package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +388 -203
  224. package/server/dist/modules/delivery/runtime/rail-launch-parser.js +3 -2
  225. package/server/dist/modules/delivery/runtime/rail-pr-store.js +6 -1
  226. package/server/dist/modules/delivery/runtime/rails-router.js +66 -10
  227. package/server/dist/modules/execution/runtime/queue-manager.js +11 -2
  228. package/server/dist/modules/loops/runtime/builtin-loops.js +70 -0
  229. package/server/dist/modules/loops/runtime/definition-cancellation.js +31 -0
  230. package/server/dist/modules/loops/runtime/legacy-launch-telemetry.js +78 -0
  231. package/server/dist/modules/loops/runtime/loop-command-catalog.js +13 -12
  232. package/server/dist/modules/loops/runtime/loop-compat.js +219 -0
  233. package/server/dist/modules/loops/runtime/loop-core-factory.js +42 -0
  234. package/server/dist/modules/loops/runtime/loop-definition-controls.js +48 -0
  235. package/server/dist/modules/loops/runtime/loop-definition-events.js +145 -0
  236. package/server/dist/modules/loops/runtime/loop-definition-recovery.js +106 -0
  237. package/server/dist/modules/loops/runtime/loop-definition-run.js +36 -0
  238. package/server/dist/modules/loops/runtime/loop-definition.js +246 -0
  239. package/server/dist/modules/loops/runtime/loop-effect.js +10 -1
  240. package/server/dist/modules/loops/runtime/loop-executors.js +60 -0
  241. package/server/dist/modules/loops/runtime/loop-factory.js +35 -15
  242. package/server/dist/modules/loops/runtime/loop-graph.js +84 -8
  243. package/server/dist/modules/loops/runtime/loop-migration.js +36 -0
  244. package/server/dist/modules/loops/runtime/loop-preview.js +10 -0
  245. package/server/dist/modules/loops/runtime/loop-run-manager.js +206 -13
  246. package/server/dist/modules/loops/runtime/loop-runs-store.js +271 -7
  247. package/server/dist/modules/loops/runtime/loop-templates.js +205 -45
  248. package/server/dist/modules/loops/runtime/loops-router.js +246 -29
  249. package/server/dist/modules/loops/runtime/loops-store.js +140 -20
  250. package/server/dist/modules/missions/runtime/agent-operator-prompt.js +14 -11
  251. package/server/dist/modules/specs/runtime/spec-addenda.js +28 -0
  252. package/server/dist/project-registry.js +85 -51
  253. package/server/dist/project-router-jobs.js +17 -6
  254. package/server/dist/project-router-loop-runs.js +200 -0
  255. package/server/dist/project-router-spending.js +17 -0
  256. package/server/dist/project-router.js +8 -6
  257. package/server/dist/schemas/agent-runtime.schema.json +93 -1
  258. package/server/dist/schemas/workflow-definition.schema.json +312 -0
  259. package/server/dist/vitest-setup.js +22 -0
  260. package/server/dist/worktree-overlay.js +1 -1
  261. package/client/dist/assets/AgentsPage-DfHWgP_x.js +0 -87
  262. package/client/dist/assets/DesktopAnalyticsPage-tc0p4jJW.js +0 -1
  263. package/client/dist/assets/InteractiveJobComposer-DFPpdg2D.js +0 -19
  264. package/client/dist/assets/JobDetailModal-BdOv-l-M.js +0 -1
  265. package/client/dist/assets/LoopBuilderPage-BemvUOxy.js +0 -7
  266. package/client/dist/assets/LoopPreviewModal-C6hNAeYN.js +0 -1
  267. package/client/dist/assets/LoopsPage-BZr0quvh.js +0 -1
  268. package/client/dist/assets/TemplatePreviewModal-Cejcquah.js +0 -1
  269. package/client/dist/assets/agentRuntime-BUimiX__.js +0 -1
  270. package/client/dist/assets/agentRuntime-BcN97sAB.js +0 -1
  271. package/client/dist/assets/agentRuntime-Ckyg-ahT.js +0 -1
  272. package/client/dist/assets/agentRuntime-DJCwyX4t.js +0 -1
  273. package/client/dist/assets/agentRuntime-DVzPJOl0.js +0 -1
  274. package/client/dist/assets/agentRuntime-V9AZTG3c.js +0 -1
  275. package/client/dist/assets/agentRuntime-bAAjw1vw.js +0 -1
  276. package/client/dist/assets/agentRuntime-v4qKAYH-.js +0 -1
  277. package/client/dist/assets/agentstudio-BNUth0uj.js +0 -1
  278. package/client/dist/assets/agentstudio-BNdB5hkr.js +0 -1
  279. package/client/dist/assets/agentstudio-C7-bt8FO.js +0 -1
  280. package/client/dist/assets/agentstudio-C8uNchpV.js +0 -1
  281. package/client/dist/assets/agentstudio-CKabyMlg.js +0 -1
  282. package/client/dist/assets/agentstudio-CN2pokQu.js +0 -1
  283. package/client/dist/assets/agentstudio-y2dVDXeM.js +0 -1
  284. package/client/dist/assets/analytics-BlypLLR6.js +0 -1
  285. package/client/dist/assets/builder-765NeYTp.js +0 -1
  286. package/client/dist/assets/builder-8MG3V6ea.js +0 -1
  287. package/client/dist/assets/builder-B-1UBs5K.js +0 -1
  288. package/client/dist/assets/builder-BTiGnPT0.js +0 -1
  289. package/client/dist/assets/builder-Cx9Rz-7u.js +0 -1
  290. package/client/dist/assets/builder-DKFMELBl.js +0 -1
  291. package/client/dist/assets/builder-OcmKrC0h.js +0 -1
  292. package/client/dist/assets/builder-P9qVBXFZ.js +0 -1
  293. package/client/dist/assets/common-B5AZNYMH.js +0 -1
  294. package/client/dist/assets/common-BFZ0c_vN.js +0 -1
  295. package/client/dist/assets/common-BttQx3z4.js +0 -1
  296. package/client/dist/assets/common-CM65k_-e.js +0 -1
  297. package/client/dist/assets/common-D4mvFFlu.js +0 -1
  298. package/client/dist/assets/common-TZ9Ny96S.js +0 -1
  299. package/client/dist/assets/common-nFpSF4GC.js +0 -1
  300. package/client/dist/assets/dashboard-B9uWNGO2.js +0 -1
  301. package/client/dist/assets/dashboard-BIwmO9wc.js +0 -1
  302. package/client/dist/assets/dashboard-BV4vOZkR.js +0 -1
  303. package/client/dist/assets/dashboard-Bf6MPVsM.js +0 -1
  304. package/client/dist/assets/dashboard-C8klWVvZ.js +0 -1
  305. package/client/dist/assets/dashboard-D1ORhPF0.js +0 -1
  306. package/client/dist/assets/dashboard-DSubvs9x.js +0 -1
  307. package/client/dist/assets/dashboard-px_vlRx_.js +0 -1
  308. package/client/dist/assets/index-C51x61Bz.css +0 -2
  309. package/client/dist/assets/loops-B7YpGizO.js +0 -1
  310. package/client/dist/assets/loops-BomD2J-h.js +0 -1
  311. package/client/dist/assets/loops-BrQHSQw3.js +0 -1
  312. package/client/dist/assets/loops-BvBwvTPA.js +0 -1
  313. package/client/dist/assets/loops-CODk6Slz.js +0 -1
  314. package/client/dist/assets/loops-DC6k-Ym-.js +0 -1
  315. package/client/dist/assets/loops-DSEg8-uY.js +0 -1
  316. package/client/dist/assets/loops-DhCoXD_v.js +0 -1
  317. package/client/dist/assets/settings-BsehKpty.js +0 -1
  318. package/client/dist/assets/settings-CQWob3SB.js +0 -1
  319. package/client/dist/assets/settings-CgCl0hzy.js +0 -1
  320. package/client/dist/assets/settings-DLRLd22L.js +0 -1
  321. package/client/dist/assets/settings-Dn5prAFl.js +0 -1
  322. package/client/dist/assets/settings-HZSu-K4l.js +0 -1
  323. package/client/dist/assets/settings-p4OVIyRh.js +0 -1
  324. package/client/dist/assets/settings-rddaldUp.js +0 -1
  325. package/client/dist/assets/useRuntimeRuns-Dqmz9fS9.js +0 -1
  326. package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  327. package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +0 -126
  328. package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +0 -114
  329. package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  330. package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  331. package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  332. package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  333. package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  334. package/server/dist/modules/loops/runtime/loop-templates-ported.js +0 -675
  335. /package/client/dist/assets/{LoopBuilderPage-DtxX27Jz.css → loop-layout-DtxX27Jz.css} +0 -0
@@ -41,6 +41,7 @@ function jobsTools() {
41
41
  'runtime_runs (read the programmatic runtime state of jobId — or every run when omitted — status, nextStep, canResume, recoverableSteps, pendingApproval, pendingQuestion; THIS is how you learn why a run failed and what recovery it offers), ' +
42
42
  'runtime_diagnose (read-only recovery assessment for jobId: original scope, completed steps, repeated failures and verification invalidation reasons; use before recommending retry or relaunch), ' +
43
43
  'runtime_evidence (read the durable runtime evidence of jobId; page with evidenceId/section/sourceId/cursor/limit), ' +
44
+ 'runtime_steer (write — queue steeringText in an engine v2 run with a stable requestId; acceptance is not consumption; inspect runtime_runs steering receipts), ' +
44
45
  'runtime_resume (ai-spawn — continue a resumable run: optional approve/recover/invalidate step-id lists + answer for a pending question; act ONLY after the user confirmed on the card), ' +
45
46
  'runtime_recover (ai-spawn — shorthand for runtime_resume with recover:[stepIds] over the recoverable steps), ' +
46
47
  'runtime_approve (write — shorthand for runtime_resume with approve:[stepId] for a pending approval), ' +
@@ -53,7 +54,7 @@ function jobsTools() {
53
54
  return 'destructive';
54
55
  if (['spawn', 'interactive_turn', 'runtime_resume', 'runtime_recover'].includes(action))
55
56
  return 'ai-spawn';
56
- if (['pause', 'resume', 'reorder', 'priority', 'finalize', 'runtime_approve', 'runtime_settle', 'runtime_dismiss'].includes(action))
57
+ if (['pause', 'resume', 'reorder', 'priority', 'finalize', 'runtime_approve', 'runtime_settle', 'runtime_dismiss', 'runtime_steer'].includes(action))
57
58
  return 'write';
58
59
  return 'read';
59
60
  },
@@ -89,6 +90,7 @@ function jobsTools() {
89
90
  'runtime_runs',
90
91
  'runtime_diagnose',
91
92
  'runtime_evidence',
93
+ 'runtime_steer',
92
94
  'runtime_resume',
93
95
  'runtime_recover',
94
96
  'runtime_approve',
@@ -148,6 +150,8 @@ function jobsTools() {
148
150
  provider: zod_1.z.string().optional().describe('Provider to resolve the default spec model for (default_spec_model)'),
149
151
  // ── runtime_* (mission-rail-cards: the agent's eyes + hands on a failed run) ──
150
152
  railIndex: zod_1.z.number().int().nonnegative().optional().describe('runtime_runs: only the latest continuation of this rail (0-based)'),
153
+ steeringText: zod_1.z.string().min(1).max(20_000).optional().describe('runtime_steer: operator instruction consumed at the next eligible AI attempt'),
154
+ requestId: zod_1.z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/).optional().describe('runtime_steer: stable idempotency key; reuse only with identical text'),
151
155
  approve: zod_1.z.array(zod_1.z.string()).optional().describe('runtime_resume/runtime_approve: step ids to approve'),
152
156
  recover: zod_1.z.array(zod_1.z.string()).optional().describe('runtime_resume/runtime_recover: step ids to recover (default for runtime_recover: every recoverableStep)'),
153
157
  invalidate: zod_1.z.array(zod_1.z.string()).optional().describe('runtime_resume: step ids whose results must be discarded before continuing'),
@@ -374,6 +378,15 @@ function jobsTools() {
374
378
  query.set(key, String(args[key]));
375
379
  return (0, types_1.apiCall)(ctx, 'GET', `${base}/agent-runtime/runs/${encodeURIComponent(id)}/evidence${query.size ? '?' + query.toString() : ''}`);
376
380
  }
381
+ case 'runtime_steer': {
382
+ const id = args.jobId;
383
+ if (!id)
384
+ throw new Error('runtime_steer requires a "jobId".');
385
+ if (typeof args.steeringText !== 'string' || !args.steeringText.trim() || args.steeringText.length > 20_000 || typeof args.requestId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(args.requestId))
386
+ throw new Error('runtime_steer requires steeringText and a stable requestId.');
387
+ const result = await (0, types_1.apiCall)(ctx, 'POST', `${base}/agent-runtime/runs/${encodeURIComponent(id)}/steer`, { text: args.steeringText, requestId: args.requestId });
388
+ return { ...result, hint: 'Accepted into the durable inbox; consumption happens at the next eligible AI attempt. Inspect runtime_runs steering receipts.' };
389
+ }
377
390
  case 'runtime_resume':
378
391
  case 'runtime_recover':
379
392
  case 'runtime_approve': {
@@ -15,7 +15,7 @@ const types_1 = require("./types");
15
15
  * - read: list, get, templates, factory, commands, constants_list,
16
16
  * preview, run_get
17
17
  * - write: constant_create, constant_update, import, create, from_template,
18
- * fork, update, publish, unpublish, duplicate
18
+ * fork, update, publish, unpublish, duplicate, restore_builtin
19
19
  * - ai-spawn: run (spawns an AI CLI, incurs cost, async 202)
20
20
  * - destructive: constant_delete, delete
21
21
  */
@@ -32,7 +32,8 @@ function loopsTools() {
32
32
  'list (all saved loops), ' +
33
33
  'get (one loop by id), ' +
34
34
  'templates (built-in template catalog with graphs), ' +
35
- 'factory (built-in locked factory loops), ' +
35
+ 'factory (the built-in loops — factory:implement, factory:freestyle, factory:sdd-quick-openspec — with the graph a rail launch runs; ' +
36
+ 'built-ins are ordinary editable loops whose loopId IS the factory id: change them with update + publish and the change applies to every rail, chat launch and companion that uses that built-in), ' +
36
37
  'commands (magic-command catalog for the builder palette), ' +
37
38
  'constants_list (global cross-loop constants), ' +
38
39
  'constant_create (write), constant_update (write), constant_delete (destructive), ' +
@@ -40,11 +41,12 @@ function loopsTools() {
40
41
  'import (import loops from an export envelope), ' +
41
42
  'create (new Draft loop), ' +
42
43
  'from_template (instantiate from a template), ' +
43
- 'fork (fork a factory loop into an editable Draft), ' +
44
- 'update (name/description/graph — reverts to Draft, 409 while running), ' +
45
- 'publish (graph-validated), unpublish (409 while running), ' +
44
+ 'fork (same as duplicate for a built-in: copies it into a separate Draft; kept for back-compat), ' +
45
+ 'update (name/description/graph — reverts to Draft; 409 while running except for built-ins, whose rails keep using the last published version until you publish), ' +
46
+ 'publish (graph-validated), unpublish (409 while running; 409 builtin_loop for built-ins), ' +
46
47
  'duplicate (copy a loop), ' +
47
- 'delete (destructive — 409 while running), ' +
48
+ 'restore_builtin (reset a built-in to its original default, Published — 409 while running), ' +
49
+ 'delete (destructive — 409 while running; built-ins cannot be deleted: 409 builtin_loop, use restore_builtin), ' +
48
50
  'run (ai-spawn — standalone ticket-less run of a Published loop against a project: spawns an AI CLI, incurs token cost, returns 202 with loopRunId), ' +
49
51
  'run_get (read one loop run\'s live/terminal state — the post-timeout recovery read).',
50
52
  hintTier: 'read',
@@ -65,6 +67,7 @@ function loopsTools() {
65
67
  'publish',
66
68
  'unpublish',
67
69
  'duplicate',
70
+ 'restore_builtin',
68
71
  ].includes(action)) {
69
72
  return 'write';
70
73
  }
@@ -91,6 +94,7 @@ function loopsTools() {
91
94
  'publish',
92
95
  'unpublish',
93
96
  'duplicate',
97
+ 'restore_builtin',
94
98
  'delete',
95
99
  'run',
96
100
  'run_get',
@@ -100,12 +104,12 @@ function loopsTools() {
100
104
  .string()
101
105
  .optional()
102
106
  .describe('Project id (run / run_get only — defaults to the active project; ignored by every other action)'),
103
- loopId: zod_1.z.string().optional().describe('Loop id (for get / update / publish / unpublish / duplicate / delete / run — must be Published for run)'),
107
+ loopId: zod_1.z.string().optional().describe('Loop id (for get / update / publish / unpublish / duplicate / restore_builtin / delete / run — must be Published for run). Built-ins use their factory id, e.g. factory:implement'),
104
108
  repositoryIds: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional().describe('run only: repository memberships to include in one coordinated standalone execution; omission retains primary-only behavior.'),
105
109
  loopRunId: zod_1.z.string().optional().describe('Loop run id (for run_get — returned by run)'),
106
110
  constantId: zod_1.z.string().optional().describe('Constant id (for constant_update / constant_delete)'),
107
111
  templateId: zod_1.z.string().optional().describe('Template id (for from_template)'),
108
- factoryId: zod_1.z.string().optional().describe('Factory loop id (for fork)'),
112
+ factoryId: zod_1.z.string().optional().describe('Built-in (factory) loop id (for fork, e.g. factory:implement)'),
109
113
  name: zod_1.z
110
114
  .string()
111
115
  .optional()
@@ -246,6 +250,11 @@ function loopsTools() {
246
250
  ...(args.name !== undefined ? { name: args.name } : {}),
247
251
  });
248
252
  }
253
+ case 'restore_builtin': {
254
+ if (!loopId)
255
+ throw new Error('restore_builtin requires a "loopId" (a built-in id such as factory:implement).');
256
+ return (0, types_1.apiCall)(ctx, 'POST', `/loops/${encodeURIComponent(loopId)}/restore-builtin`);
257
+ }
249
258
  // ── Standalone runs (project-scoped) ────────────────────────────────
250
259
  case 'run': {
251
260
  if (!loopId)
@@ -47,7 +47,7 @@ function railsTools() {
47
47
  'stop (destructive — kills all active jobs and loop runs for the rail). ' +
48
48
  'For on_review tickets with an already-open GitHub PR (including a published pr_ready delivery), launch automatically tries to continue that PR head branch; Jira-linked in_progress tickets can do the same when the PR match is explicit; fresh tickets still start from the project integration branch. ' +
49
49
  'When launched from the in-app agent chat without an explicit aiEngine, the engine defaults to your conversation\'s provider. On that same provider, an omitted model inherits the conversation model; an explicit model wins (pass aiEngine to override; launch_all always uses each rail\'s stored engine). ' +
50
- 'For Implement and Batch Implement, the selected launch provider applies to ALL runtime roles (architect, developer, reviewer); an explicit launch model/effort also applies to each role. Resuming an existing execution keeps its original frozen role configuration; start a new launch to change providers. ' +
50
+ 'For Implement, the selected launch provider applies to ALL runtime roles (architect, developer, reviewer); an explicit launch model/effort also applies to each role. Resuming an existing execution keeps its original frozen role configuration; start a new launch to change providers. ' +
51
51
  'User-facing naming: call the free-form autonomous mode "Freestyle"; use "freestyle" as the canonical API enum value for that same capability. ' +
52
52
  'For small OpenSpec-governed work, recommend "SDD Quick (OpenSpec)" and launch with mode "loop" plus loopId "factory:sdd-quick-openspec"; keep Freestyle for ticket-local implementation-only work. ' +
53
53
  'NAMING: railIndex is the 0-BASED internal identity; the dashboard shows rails 1-based ("Rail N" = railIndex N-1). When talking to the user, ALWAYS say "Rail <railIndex + 1>" (or the rail\'s custom name) — results include railLabel with the correct user-facing label.',
@@ -101,9 +101,9 @@ function railsTools() {
101
101
  .describe('Rail display label (<=60 chars); null/empty clears to default (set_name). Also the optional initial name for create_rail.'),
102
102
  // launch
103
103
  mode: zod_1.z
104
- .enum(['implement', 'batch-implement', 'freestyle', 'loop'])
104
+ .enum(['implement', 'freestyle', 'loop'])
105
105
  .optional()
106
- .describe('Launch mode (launch; default "implement"). Use "freestyle" as the canonical API enum value for Freestyle. It invokes the selected provider\'s native free-form autonomous workflow (currently Claude and Kimi), one job per ticket. In prose, call it "Freestyle". Loop runs an app-driven loop per ticket.'),
106
+ .describe('Launch mode (launch; default "implement"). Implement runs all of the rail\'s tickets in one aggregate run; propose at most 3 specs per rail and split larger sets across rails. Use "freestyle" as the canonical API enum value for Freestyle. It invokes the selected provider\'s native free-form autonomous workflow (currently Claude and Kimi), one job per ticket. In prose, call it "Freestyle". Loop runs an app-driven loop per ticket.'),
107
107
  model: zod_1.z
108
108
  .string()
109
109
  .optional()
@@ -3,7 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RUNTIME_HOST_ENV_KEYS = void 0;
4
4
  exports.runtimeChangeName = runtimeChangeName;
5
5
  exports.scopedHostChecks = scopedHostChecks;
6
+ exports.readFrozenRuntimeHost = readFrozenRuntimeHost;
6
7
  exports.runAgentRuntimeInvocation = runAgentRuntimeInvocation;
8
+ exports.assertRetainedForkSupport = assertRetainedForkSupport;
9
+ exports.runAgentRuntimeControl = runAgentRuntimeControl;
7
10
  const agent_runtime_history_1 = require("./agent-runtime-history");
8
11
  const agent_runtime_repositories_1 = require("./agent-runtime-repositories");
9
12
  const node_util_1 = require("node:util");
@@ -11,6 +14,7 @@ const agent_runtime_effective_config_1 = require("./agent-runtime-effective-conf
11
14
  const node_child_process_1 = require("node:child_process");
12
15
  const node_crypto_1 = require("node:crypto");
13
16
  const node_fs_1 = require("node:fs");
17
+ const node_os_1 = require("node:os");
14
18
  const node_path_1 = require("node:path");
15
19
  const node_readline_1 = require("node:readline");
16
20
  const agent_runtime_loader_1 = require("./agent-runtime-loader");
@@ -58,9 +62,26 @@ function saveHostContext(options) {
58
62
  throw new Error('Desktop runtime scope changed; resume using its frozen host context');
59
63
  }
60
64
  }
65
+ /** Recover the original host scope; project settings may have changed since admission. */
66
+ function readFrozenRuntimeHost(contextPath, baseEnv, runId) {
67
+ const context = JSON.parse((0, node_fs_1.readFileSync)(contextPath, 'utf8'));
68
+ const roots = [context.backlogRoot, ...(context.repositories ?? []).map(repository => repository.path)];
69
+ if (context.runId !== runId || !context.repositories?.length || !context.repositories.some(repository => repository.path === context.artifactRoot) || roots.some(root => typeof root !== 'string' || !(0, node_path_1.isAbsolute)(root) || (0, node_fs_1.realpathSync)(root) !== root))
70
+ throw new Error('runtime_scope_changed: Original workflow scope is unavailable');
71
+ const host = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)((0, node_path_1.dirname)(contextPath), 'desktop-runtime-host.json'), 'utf8'));
72
+ if (host.schemaVersion !== 1 || typeof host.cwd !== 'string' || !roots.includes(host.cwd) || !host.env || typeof host.env !== 'object' || Array.isArray(host.env) || Object.entries(host.env).some(([key, value]) => !exports.RUNTIME_HOST_ENV_KEYS.includes(key) || typeof value !== 'string') || host.env.SPECRAILS_GIT_AUTO !== 'false')
73
+ throw new Error('runtime_host_invalid: Original workflow host settings are invalid');
74
+ const env = { ...baseEnv };
75
+ for (const key of exports.RUNTIME_HOST_ENV_KEYS)
76
+ delete env[key];
77
+ return { cwd: host.cwd, env: { ...env, ...host.env, SPECRAILS_EXECUTION_CONTEXT: contextPath } };
78
+ }
61
79
  /** Core owns the complete agent workflow in one managed process. The existing
62
80
  * rail's cancellation and worktree ownership remain in Desktop. */
63
81
  async function runAgentRuntimeInvocation(options) {
82
+ const definitionEngine = options.engineVersion === 2 || options.definitionPath !== undefined || options.prepareDefinition !== undefined;
83
+ if (options.resume && (options.definitionPath || options.prepareDefinition || options.workflowRoleBindings))
84
+ throw new Error('Resume must use the frozen workflow definition and role bindings');
64
85
  const selectedCli = options.resume ? (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(options.contextPath) : (0, agent_runtime_loader_1.findCoreAgentRuntimeCli)();
65
86
  let cli = selectedCli;
66
87
  if (!cli)
@@ -79,13 +100,27 @@ async function runAgentRuntimeInvocation(options) {
79
100
  repositoryIds: admittedContext.repositories.map(repo => repo.id), source, providerOverride: options.providerOverride,
80
101
  });
81
102
  config.verification = scopedHostChecks(config.verification, admittedContext.repositories);
82
- config.rolePrompts = { ...(await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).rolePromptDefaults(), ...(0, agent_runtime_settings_1.loadRuntimeRolePrompts)() };
103
+ config.rolePrompts = { ...(await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).rolePromptDefaults(), ...(0, agent_runtime_settings_1.loadRuntimeRolePrompts)(), ...config.rolePrompts };
83
104
  // Core rejects unknown connection keys: drop the desktop-only local-engine
84
105
  // fields (label/defaultModel/rates/supportsReasoningEffort) before Core sees it.
85
106
  const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
86
107
  config.providers = (0, agent_runtime_settings_1.stripDesktopConnectionFields)(config.providers, (0, agent_runtime_settings_1.coreConnectionFieldGates)(runtime.api?.capabilities));
87
108
  if (runtime.api?.capabilities?.configurableGuardrails !== 1)
88
109
  delete config.guardrails;
110
+ if (definitionEngine && (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1))
111
+ throw new Error('engine_unsupported: Update Core to run workflow definitions');
112
+ const compilationConfig = structuredClone(config);
113
+ if (options.workflowRoleBindings) {
114
+ compilationConfig.roles = { ...compilationConfig.roles, ...structuredClone(options.workflowRoleBindings) };
115
+ (0, agent_runtime_effective_config_1.fillDefaultRoleModels)(compilationConfig);
116
+ }
117
+ const draft = definitionEngine ? options.prepareDefinition
118
+ ? options.prepareDefinition(compilationConfig)
119
+ : options.definitionPath ? JSON.parse((0, node_fs_1.readFileSync)(options.definitionPath, 'utf8')) : undefined : undefined;
120
+ if (definitionEngine && !draft)
121
+ throw new Error('A new Core workflow requires a definition');
122
+ const selectedOrigins = (0, agent_runtime_effective_config_1.bindWorkflowRoleSelections)(config, draft, options.workflowRoleBindings);
123
+ const workflowOrigins = { ...(0, agent_runtime_effective_config_1.bindWorkflowRoleDefaults)(config, draft), ...selectedOrigins };
89
124
  const override = options.providerOverride;
90
125
  runtime.validateRuntimeConfig(JSON.parse(JSON.stringify(config)));
91
126
  (0, agent_runtime_loader_1.validateRequestedRoleEfforts)(runtime, config);
@@ -93,7 +128,7 @@ async function runAgentRuntimeInvocation(options) {
93
128
  args[0] = cli;
94
129
  saveHostContext(options);
95
130
  const selectionFile = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-runtime-selection.json');
96
- const selection = JSON.stringify({ schemaVersion: 1, runId: admittedContext.runId, providerOverride: override ?? null, origins }) + '\n';
131
+ const selection = JSON.stringify({ schemaVersion: 1, runId: admittedContext.runId, providerOverride: override ?? null, origins: { ...origins, ...workflowOrigins } }) + '\n';
97
132
  try {
98
133
  (0, node_fs_1.writeFileSync)(selectionFile, selection, { flag: 'wx', mode: 0o600 });
99
134
  }
@@ -112,8 +147,27 @@ async function runAgentRuntimeInvocation(options) {
112
147
  if ((0, node_fs_1.readFileSync)(scopedPath, 'utf8') !== serialized)
113
148
  throw new Error('Desktop runtime configuration changed; start a new run');
114
149
  }
150
+ if (definitionEngine) {
151
+ const validation = runtime.validateWorkflowDefinition(draft, { configPath: scopedPath, structural: false });
152
+ if (!validation.ok)
153
+ throw new Error('definition_invalid: ' + validation.errors.map(error => `${error.path ?? error.nodeId ?? 'graph'}: ${error.message}`).join('; '));
154
+ const definitionPath = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-workflow-definition.json');
155
+ const serializedDefinition = JSON.stringify(validation.definition, null, 2) + '\n';
156
+ try {
157
+ (0, node_fs_1.writeFileSync)(definitionPath, serializedDefinition, { flag: 'wx', mode: 0o600 });
158
+ }
159
+ catch (error) {
160
+ if (error.code !== 'EEXIST' || (0, node_fs_1.readFileSync)(definitionPath, 'utf8') !== serializedDefinition)
161
+ throw new Error('Frozen workflow definition changed; start a new run');
162
+ }
163
+ const pinPath = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-runtime-package.json');
164
+ options.onPrepared?.({ contextPath: options.contextPath, runtimeDirectory: (0, node_path_1.dirname)(options.contextPath), definitionPath, configPath: scopedPath, definitionHash: validation.version, definition: validation.definition, context: admittedContext, ...((0, node_fs_1.existsSync)(pinPath) ? { runtimeIdentity: JSON.parse((0, node_fs_1.readFileSync)(pinPath, 'utf8')) } : {}) });
165
+ args.push('--definition', definitionPath);
166
+ }
115
167
  args.push('--config', scopedPath, '--change', options.change);
116
168
  }
169
+ const definitionFile = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-workflow-definition.json');
170
+ const requiresVerified = definitionEngine && JSON.parse((0, node_fs_1.readFileSync)(definitionFile, 'utf8')).delivery?.requiresVerified === true;
117
171
  const frozenPath = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-runtime-config.json');
118
172
  if ((0, node_fs_1.existsSync)(frozenPath)) {
119
173
  const frozen = JSON.parse((0, node_fs_1.readFileSync)(frozenPath, 'utf8'));
@@ -133,10 +187,16 @@ async function runAgentRuntimeInvocation(options) {
133
187
  throw new Error('Answers apply to runtime resume');
134
188
  args.push('--answer', options.answer);
135
189
  }
190
+ if (options.interruptId) {
191
+ if (!options.resume)
192
+ throw new Error('Interrupt selection applies to resume');
193
+ args.push('--interrupt-id', options.interruptId);
194
+ }
136
195
  const started = Date.now();
137
196
  (0, agent_runtime_history_1.writeRuntimeHistory)(options.contextPath, { status: 'running' });
138
197
  return new Promise((resolve) => {
139
198
  let result;
199
+ let graph;
140
200
  let stderr = '', summary = '', invalidProtocol = false, timedOut = false;
141
201
  let timer;
142
202
  const child = (0, node_child_process_1.spawn)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), args, {
@@ -182,6 +242,23 @@ async function runAgentRuntimeInvocation(options) {
182
242
  if (repositories.length)
183
243
  event.repositories = repositories.map(repo => ({ id: repo.id, name: repo.name || repo.id }));
184
244
  }
245
+ if (definitionEngine && ((typeof event.runId === 'string' && event.runId !== admittedContext.runId) || (event.type === 'workflow-event' && event.event?.runId !== admittedContext.runId))) {
246
+ invalidProtocol = true;
247
+ if (child.pid)
248
+ (0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL');
249
+ return;
250
+ }
251
+ try {
252
+ options.onRuntimeEvent?.(event);
253
+ }
254
+ catch (error) {
255
+ observerError = error instanceof Error ? error.message : String(error);
256
+ if (child.pid)
257
+ (0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL');
258
+ return;
259
+ }
260
+ if (event.type === 'runtime-graph')
261
+ graph = event;
185
262
  observe(() => options.onRawLine?.(event.repositories ? JSON.stringify(event) : line));
186
263
  if (event.type === 'runtime-result') {
187
264
  if (result)
@@ -236,23 +313,232 @@ async function runAgentRuntimeInvocation(options) {
236
313
  const cost = known(usage?.costUsd), tokensIn = known(usage?.inputTokens), tokensOut = known(usage?.outputTokens);
237
314
  if (result?.status === 'succeeded' && result.runId !== admittedContext.runId)
238
315
  invalidProtocol = true;
239
- const failed = code !== 0 || result?.status !== 'succeeded' || invalidProtocol || timedOut || Boolean(observerError);
316
+ if (definitionEngine && result?.status === 'succeeded' && (!result.completion || typeof result.completion.ok !== 'boolean' || typeof result.completion.verified !== 'boolean' || (!Array.isArray(result.completion.reasons) || result.completion.reasons.some(reason => typeof reason !== 'string'))))
317
+ invalidProtocol = true;
318
+ if (definitionEngine && result?.status === 'paused' && result.runId !== admittedContext.runId)
319
+ invalidProtocol = true;
320
+ const acceptanceBlocked = definitionEngine && result?.status === 'succeeded' && result.completion && (!result.completion.ok || requiresVerified && !result.completion.verified);
321
+ const validPause = definitionEngine && code === 2 && result?.status === 'paused';
322
+ const failed = (!validPause && (code !== 0 || result?.status !== 'succeeded')) || invalidProtocol || timedOut || Boolean(observerError) || Boolean(acceptanceBlocked);
323
+ const runtimeError = typeof result?.error === 'string' ? result.error : result?.error?.message;
324
+ const runtimeStatus = invalidProtocol || timedOut || observerError || (result?.status === 'paused' && !validPause) || (result?.status === 'succeeded' && code !== 0) ? 'failed' : acceptanceBlocked ? 'blocked' : ['succeeded', 'paused', 'failed', 'blocked', 'cancelled'].includes(result?.status ?? '') ? result.status : 'failed';
240
325
  const errorText = observerError ?? (timedOut ? 'Programmatic workflow timed out; inspect its checkpoint before recovery'
241
326
  : invalidProtocol ? 'Core returned an invalid runtime event stream'
242
- : result?.error ?? (result?.status === 'paused' ? (typeof result.pendingQuestion?.question === 'string' && result.pendingQuestion.question.trim()
243
- ? `Workflow awaits an answer in Agent Runtime settings: ${result.pendingQuestion.question.trim().slice(0, 500)}`
244
- : 'Workflow awaits approval in Agent Runtime settings')
245
- : failed ? stderr || 'Core exited without a successful programmatic workflow result' : undefined));
327
+ : acceptanceBlocked ? result?.completion?.reasons.join('; ') || 'Core workflow acceptance or required verification is not satisfied'
328
+ : runtimeError ?? (result?.status === 'paused' ? (typeof result.pendingQuestion?.question === 'string' && result.pendingQuestion.question.trim()
329
+ ? `Workflow awaits an answer in Agent Runtime settings: ${result.pendingQuestion.question.trim().slice(0, 500)}`
330
+ : 'Workflow awaits approval in Agent Runtime settings')
331
+ : failed ? stderr || 'Core exited without a successful programmatic workflow result' : undefined));
246
332
  try {
247
333
  (0, agent_runtime_history_1.writeRuntimeHistory)(options.contextPath, invalidProtocol || timedOut || observerError || !result ? { status: 'failed', error: errorText } : { ...result });
248
334
  }
249
335
  catch { /* Projection failure cannot replay a completed execution. */ }
250
336
  resolve({
251
- text: summary || (failed ? errorText ?? '' : 'Programmatic implementation verified and archived.'),
337
+ text: summary || (failed ? errorText ?? '' : definitionEngine ? 'Workflow execution completed.' : 'Programmatic implementation verified and archived.'),
252
338
  provider: 'agent-runtime', model: 'per-role', failed, errorText,
339
+ ...(definitionEngine ? { runtimeStatus, completion: result?.completion, pendingInterrupts: result?.pendingInterrupts, pendingQuestion: result?.pendingQuestion, pendingApproval: result?.pendingApproval, graph } : {}),
253
340
  cost, tokensIn, tokensOut, tokens: tokensIn === undefined || tokensOut === undefined ? undefined : tokensIn + tokensOut,
254
- estimated: cost === undefined, durationMs: Date.now() - started,
341
+ estimated: cost === undefined, durationMs: definitionEngine ? known(result?.usage?.durationMs) : Date.now() - started,
255
342
  });
256
343
  });
257
344
  });
258
345
  }
346
+ const CONTROL_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
347
+ const CONTROL_TIMEOUT_MS = 60_000;
348
+ /** Check the retained package before recording a host operation that requires
349
+ * recoverable publication. Older forks cannot safely retry a lost receipt. */
350
+ async function assertRetainedForkSupport(options) {
351
+ const cli = (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(options.contextPath);
352
+ const events = await runControlProcess([cli, 'api'], options);
353
+ const api = events.find(event => event.type === 'runtime-api');
354
+ const capabilities = api?.capabilities;
355
+ if (capabilities?.forkIdempotency !== 1)
356
+ throw new Error('engine_unsupported: Retained Core does not support recoverable fork publication');
357
+ }
358
+ function controlError(event, fallback) {
359
+ const error = event?.error;
360
+ if (typeof error === 'string')
361
+ return new Error(error);
362
+ if (error && typeof error === 'object')
363
+ return new Error([error.code, error.message].filter(Boolean).join(': ') || fallback);
364
+ return new Error(fallback);
365
+ }
366
+ function runControlProcess(args, options, input) {
367
+ return new Promise((resolve, reject) => {
368
+ const events = [];
369
+ let stderr = '', invalid = false, timedOut = false;
370
+ const child = (0, node_child_process_1.spawn)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), args, { cwd: options.cwd, env: (0, win_spawn_1.windowsSpawnEnv)(options.env), shell: false, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] });
371
+ child.stdin.on('error', () => { });
372
+ child.stdin.end(input);
373
+ let bytes = 0;
374
+ child.stdout.on('data', (chunk) => {
375
+ bytes += chunk.length;
376
+ if (bytes > 4 * 1024 * 1024) {
377
+ invalid = true;
378
+ if (child.pid)
379
+ (0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL');
380
+ }
381
+ });
382
+ const lines = (0, node_readline_1.createInterface)({ input: child.stdout });
383
+ lines.on('line', line => {
384
+ if (invalid || !line.trim())
385
+ return;
386
+ try {
387
+ const event = JSON.parse(line);
388
+ if (!event || typeof event !== 'object' || Array.isArray(event)) {
389
+ invalid = true;
390
+ return;
391
+ }
392
+ events.push(event);
393
+ }
394
+ catch {
395
+ invalid = true;
396
+ }
397
+ });
398
+ child.stderr?.on('data', (chunk) => {
399
+ stderr = (stderr + chunk.toString('utf8')).slice(-32_000);
400
+ try {
401
+ options.onLine?.(chunk.toString('utf8'), 'stderr');
402
+ }
403
+ catch { /* advisory */ }
404
+ });
405
+ child.on('error', error => { stderr = error.message; });
406
+ const timer = setTimeout(() => { timedOut = true; if (child.pid)
407
+ (0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL'); }, options.timeoutMs && options.timeoutMs > 0 ? options.timeoutMs : CONTROL_TIMEOUT_MS);
408
+ timer.unref?.();
409
+ child.on('close', code => {
410
+ clearTimeout(timer);
411
+ lines.close();
412
+ const failure = events.find(event => event.type === 'runtime-result' && event.status === 'failed');
413
+ if (timedOut)
414
+ return reject(new Error('timeout: Core control command did not finish in time'));
415
+ if (failure)
416
+ return reject(controlError(failure, 'Core control command failed'));
417
+ if (invalid)
418
+ return reject(new Error('Core returned an invalid control event stream'));
419
+ if (code !== 0)
420
+ return reject(new Error(stderr.trim() || `Core control command exited with code ${code}`));
421
+ resolve(events);
422
+ });
423
+ });
424
+ }
425
+ function swapRunId(file, runId) {
426
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8'));
427
+ return JSON.stringify({ ...parsed, runId }, null, 2) + '\n';
428
+ }
429
+ /** Freeze the child's Desktop-side files from the SOURCE run's frozen files, never from live project settings. */
430
+ function materializeForkedRun(sourceContextPath, childRunId, childContextPath) {
431
+ const source = (0, node_path_1.dirname)(sourceContextPath), target = (0, node_path_1.dirname)(childContextPath);
432
+ (0, node_fs_1.mkdirSync)(target, { recursive: true, mode: 0o700 });
433
+ const write = (name, content) => {
434
+ const file = (0, node_path_1.join)(target, name);
435
+ if ((0, node_fs_1.existsSync)(file)) {
436
+ if ((0, node_fs_1.readFileSync)(file, 'utf8') !== content)
437
+ throw new Error(`Fork frozen input conflicts: ${name}`);
438
+ }
439
+ else
440
+ (0, node_fs_1.writeFileSync)(file, content, { flag: 'wx', mode: 0o600 });
441
+ return file;
442
+ };
443
+ const copy = (name, required = false) => {
444
+ const from = (0, node_path_1.join)(source, name);
445
+ if (!(0, node_fs_1.existsSync)(from)) {
446
+ if (required)
447
+ throw new Error(`Source run is missing ${name}`);
448
+ return undefined;
449
+ }
450
+ return write(name, (0, node_fs_1.readFileSync)(from, 'utf8'));
451
+ };
452
+ const context = swapRunId(sourceContextPath, childRunId);
453
+ write('desktop-context.json', context);
454
+ copy('desktop-runtime-package.json', true);
455
+ const hostFile = (0, node_path_1.join)(source, 'desktop-runtime-host.json');
456
+ if ((0, node_fs_1.existsSync)(hostFile)) {
457
+ const host = JSON.parse((0, node_fs_1.readFileSync)(hostFile, 'utf8'));
458
+ if (host.env && typeof host.env === 'object' && host.env.SPECRAILS_EXECUTION_CONTEXT !== undefined)
459
+ host.env = { ...host.env, SPECRAILS_EXECUTION_CONTEXT: childContextPath };
460
+ write('desktop-runtime-host.json', JSON.stringify(host, null, 2) + '\n');
461
+ }
462
+ const configPath = copy('desktop-runtime-config.json') ?? (0, node_path_1.join)(target, 'desktop-runtime-config.json');
463
+ const definitionPath = copy('desktop-workflow-definition.json') ?? (0, node_path_1.join)(target, 'desktop-workflow-definition.json');
464
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(source, 'desktop-runtime-selection.json')))
465
+ write('desktop-runtime-selection.json', swapRunId((0, node_path_1.join)(source, 'desktop-runtime-selection.json'), childRunId));
466
+ return { contextPath: childContextPath, runtimeDirectory: target, definitionPath, configPath, context: JSON.parse(context) };
467
+ }
468
+ async function runAgentRuntimeControl(options) {
469
+ const cli = (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(options.contextPath);
470
+ const admitted = JSON.parse((0, node_fs_1.readFileSync)(options.contextPath, 'utf8'));
471
+ if (typeof admitted.runId !== 'string' || admitted.runId !== options.runId)
472
+ throw new Error('runtime_scope_changed: Frozen context belongs to another run');
473
+ const args = [cli, options.kind, '--context', options.contextPath];
474
+ if (options.kind === 'signal') {
475
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(options.requestId) || typeof options.text !== 'string' || !options.text.trim() || options.text.length > 20_000)
476
+ throw new Error('invalid_arguments: Steering requires a safe request id and 1–20,000 characters');
477
+ args.push('--request-id', options.requestId, '--stdin');
478
+ const events = await runControlProcess(args, options, options.text);
479
+ const accepted = events.find(event => event.type === 'runtime-signal-accepted');
480
+ if (accepted?.id !== options.requestId || typeof accepted.acceptedAt !== 'string' || !Number.isFinite(Date.parse(accepted.acceptedAt)))
481
+ throw new Error('Core did not acknowledge the steering request');
482
+ return { kind: 'signal', id: accepted.id, acceptedAt: accepted.acceptedAt };
483
+ }
484
+ if (options.kind === 'cancel') {
485
+ if (typeof options.requestId !== 'string' || !options.requestId.trim() || options.requestId.length > 256 || options.requestId.includes('\0'))
486
+ throw new Error('invalid_arguments: Cancellation requires a request id of 1–256 characters');
487
+ args.push('--request-id', options.requestId);
488
+ const events = await runControlProcess(args, options);
489
+ const accepted = events.find(event => event.type === 'runtime-cancellation-accepted');
490
+ if (accepted?.requestId !== options.requestId || typeof accepted.createdAt !== 'string' || !Number.isFinite(Date.parse(accepted.createdAt)))
491
+ throw new Error('Core did not acknowledge the cancellation request');
492
+ return { kind: 'cancel', requestId: options.requestId, accepted };
493
+ }
494
+ if (!CONTROL_RUN_ID.test(options.childRunId) || options.childRunId === options.runId)
495
+ throw new Error('invalid_arguments: Fork requires a distinct child run id');
496
+ if (typeof options.fromNodePath !== 'string' || !options.fromNodePath.trim())
497
+ throw new Error('invalid_arguments: Fork requires a node path');
498
+ if (options.visit !== undefined && (!Number.isSafeInteger(options.visit) || options.visit < 1))
499
+ throw new Error('invalid_arguments: Fork visit must be a positive integer');
500
+ if (options.scopeId !== undefined && (typeof options.scopeId !== 'string' || !options.scopeId))
501
+ throw new Error('invalid_arguments: Fork scope must be a non-empty string');
502
+ const childContextPath = (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.dirname)(options.contextPath)), options.childRunId, 'desktop-context.json');
503
+ const childRuntimeDirectory = (0, node_path_1.dirname)(childContextPath);
504
+ if (options.requestId !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(options.requestId))
505
+ throw new Error('invalid_arguments: Fork requires a safe request id');
506
+ if (options.requestId)
507
+ await assertRetainedForkSupport(options);
508
+ if ((0, node_fs_1.existsSync)(childRuntimeDirectory) && !options.requestId)
509
+ throw new Error('run_exists: A run directory already exists for the fork child');
510
+ args.push('--from', options.fromNodePath, '--run-id', options.childRunId);
511
+ if (options.requestId)
512
+ args.push('--request-id', options.requestId);
513
+ if (options.scopeId !== undefined)
514
+ args.push('--scope-id', options.scopeId);
515
+ if (options.visit !== undefined)
516
+ args.push('--visit', String(options.visit));
517
+ let staged;
518
+ if (options.state !== undefined) {
519
+ if (!options.state || typeof options.state !== 'object' || Array.isArray(options.state))
520
+ throw new Error('invalid_arguments: Fork state must be an object');
521
+ staged = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'specrails-fork-'));
522
+ const stateFile = (0, node_path_1.join)(staged, 'state.json');
523
+ (0, node_fs_1.writeFileSync)(stateFile, JSON.stringify(options.state), { mode: 0o600 });
524
+ args.push('--state', stateFile);
525
+ }
526
+ try {
527
+ const events = await runControlProcess(args, options);
528
+ const forked = events.find(event => event.type === 'runtime-forked');
529
+ if (!forked || forked.runId !== options.childRunId || forked.forkOf !== options.runId || typeof forked.directory !== 'string')
530
+ throw new Error('Core returned an invalid fork result');
531
+ if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(forked.directory, 'run.sqlite')) || (0, node_fs_1.realpathSync)((0, node_path_1.dirname)(forked.directory)) !== (0, node_fs_1.realpathSync)(childRuntimeDirectory))
532
+ throw new Error('Core placed the fork outside the expected run directory');
533
+ const frozen = materializeForkedRun(options.contextPath, options.childRunId, childContextPath);
534
+ return { kind: 'fork', runId: options.childRunId, forkOf: options.runId, fromNodePath: options.fromNodePath,
535
+ scopeId: typeof forked.scopeId === 'string' ? forked.scopeId : null, visit: typeof forked.visit === 'number' ? forked.visit : null,
536
+ revision: typeof forked.revision === 'number' ? forked.revision : null, directory: forked.directory, ...frozen };
537
+ }
538
+ finally {
539
+ // Core owns publication/cleanup. A lost acknowledgement or host-file error
540
+ // must preserve a published child for the same idempotent request to retry.
541
+ if (staged)
542
+ (0, node_fs_1.rmSync)(staged, { recursive: true, force: true });
543
+ }
544
+ }
@@ -6,6 +6,7 @@ exports.cancelRuntimeContinuation = cancelRuntimeContinuation;
6
6
  exports.shutdownAgentRuntimeControls = shutdownAgentRuntimeControls;
7
7
  exports.runtimeRunSummary = runtimeRunSummary;
8
8
  exports.registerAgentRuntimeControlRoutes = registerAgentRuntimeControlRoutes;
9
+ const agent_runtime_retention_records_1 = require("./agent-runtime-retention-records");
9
10
  const agent_runtime_paths_1 = require("./agent-runtime-paths");
10
11
  const agent_runtime_controls_1 = require("./agent-runtime-controls");
11
12
  const controllers = new WeakMap();
@@ -45,6 +46,30 @@ function registerAgentRuntimeControlRoutes({ router, ctx }) {
45
46
  }
46
47
  return controller;
47
48
  }
49
+ router.get('/:projectId/agent-runtime/retention', (req, res) => {
50
+ try {
51
+ res.json({ policy: controls(req).retentionPolicy() });
52
+ }
53
+ catch {
54
+ res.status(503).json({ error: 'runtime_retention_unavailable' });
55
+ }
56
+ });
57
+ router.put('/:projectId/agent-runtime/retention', (req, res) => {
58
+ try {
59
+ res.json({ policy: controls(req).configureRetention(req.body) });
60
+ }
61
+ catch (error) {
62
+ res.status(error instanceof agent_runtime_controls_1.RuntimeControlError ? error.statusCode : 503).json({ error: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.code : 'runtime_retention_unavailable' });
63
+ }
64
+ });
65
+ router.post('/:projectId/agent-runtime/retention/collect', async (req, res) => {
66
+ try {
67
+ res.json(await controls(req).collectRetention(req.body ?? {}));
68
+ }
69
+ catch (error) {
70
+ res.status(error instanceof agent_runtime_controls_1.RuntimeControlError ? error.statusCode : 503).json({ error: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.code : 'runtime_retention_failed', message: error instanceof Error ? error.message : 'Retention could not inspect runtime storage' });
71
+ }
72
+ });
48
73
  router.get('/:projectId/agent-runtime/runs', async (req, res) => {
49
74
  try {
50
75
  if (req.query.railIndex !== undefined) {
@@ -69,7 +94,7 @@ function registerAgentRuntimeControlRoutes({ router, ctx }) {
69
94
  router.get('/:projectId/agent-runtime/runs/:runId', async (req, res) => {
70
95
  try {
71
96
  const context = ctx(req), runId = String(req.params.runId);
72
- res.json({ runs: (0, agent_runtime_paths_1.hasAgentRuntimeRequest)(context.project, runId) ? [await controls(req).summary(runId)] : [] });
97
+ res.json({ runs: (0, agent_runtime_paths_1.hasAgentRuntimeRequest)(context.project, runId) || (0, agent_runtime_retention_records_1.readRuntimeExpiration)(context.db, runId) ? [await controls(req).summary(runId)] : [] });
73
98
  }
74
99
  catch {
75
100
  res.status(500).json({ error: 'runtime_status_failed' });
@@ -100,6 +125,14 @@ function registerAgentRuntimeControlRoutes({ router, ctx }) {
100
125
  res.status(error instanceof agent_runtime_controls_1.RuntimeControlError ? error.statusCode : 500).json({ error: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.code : 'runtime_resume_failed', message: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.message : 'Could not resume runtime execution' });
101
126
  }
102
127
  });
128
+ router.post('/:projectId/agent-runtime/runs/:runId/steer', async (req, res) => {
129
+ try {
130
+ res.status(202).json(await controls(req).signal(String(req.params.runId), req.body));
131
+ }
132
+ catch (error) {
133
+ res.status(error instanceof agent_runtime_controls_1.RuntimeControlError ? error.statusCode : 500).json({ error: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.code : 'runtime_steering_failed', message: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.message : 'Could not send operator steering' });
134
+ }
135
+ });
103
136
  router.post('/:projectId/agent-runtime/runs/:runId/recovery', async (req, res) => {
104
137
  try {
105
138
  res.json(await controls(req).recovery(String(req.params.runId), req.body));