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
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LEGACY_DECIDER_ROLE = void 0;
4
+ exports.convertLegacyLoop = convertLegacyLoop;
5
+ const loop_graph_1 = require("./loop-graph");
6
+ const reserved = new Set(['START', 'END', '__start__', '__end__', 'next', 'constructor', 'prototype']);
7
+ const safeId = (id) => /^[A-Za-z0-9][A-Za-z0-9_-]{0,119}$/.test(id) && !reserved.has(id);
8
+ const PASS = 'compatPassFailed', ITERATION = 'compatIteration', STEPS = 'compatSteps';
9
+ exports.LEGACY_DECIDER_ROLE = 'legacy-loop-decider';
10
+ /** Pure projection only: no settings, database writes, provider calls or publication.
11
+ * The caller validates the result against its selected Core before saving it. */
12
+ function convertLegacyLoop(graph, bindings = {}) {
13
+ const validation = (0, loop_graph_1.validateLoopGraph)(graph);
14
+ if (!validation.valid)
15
+ return { ok: false, issues: validation.errors.map(error => ({ code: error.code, message: error.message, ...(error.nodeId ? { nodeId: error.nodeId } : {}) })) };
16
+ if ((0, loop_graph_1.isDefinitionGraph)(graph))
17
+ return { ok: false, issues: [{ code: 'already_core', message: 'This graph already uses Core pieces.' }] };
18
+ try {
19
+ (0, loop_graph_1.assertLoopFailureRecovery)(graph);
20
+ }
21
+ catch (error) {
22
+ return { ok: false, issues: [{ code: 'invalid_recovery', message: String(error) }] };
23
+ }
24
+ const issues = [];
25
+ for (const node of graph.nodes) {
26
+ if (node.type === 'shell' && !node.data?.repositoryId && !bindings.repositoryId)
27
+ issues.push({ code: 'repository_binding_required', nodeId: node.id, message: 'Select the original shell repository before converting this graph.' });
28
+ if (node.type === 'shell' && Array.isArray(node.data?.requireRunVars) && node.data.requireRunVars.some(name => typeof name !== 'string' || !/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(name)))
29
+ issues.push({ code: 'invalid_run_variable', nodeId: node.id, message: 'Required runtime variables must use Core variable names.' });
30
+ }
31
+ if (issues.length)
32
+ return { ok: false, issues };
33
+ const used = new Set(graph.nodes.filter(node => safeId(node.id)).map(node => node.id));
34
+ const fresh = (hint) => {
35
+ const base = ('compat-' + hint.replace(/[^A-Za-z0-9_-]/g, '-')).slice(0, 100);
36
+ let id = base, suffix = 0;
37
+ while (used.has(id))
38
+ id = `${base}-${++suffix}`;
39
+ used.add(id);
40
+ return id;
41
+ };
42
+ const nodeIds = Object.fromEntries(graph.nodes.map((node, index) => [node.id, safeId(node.id) ? node.id : fresh('node-' + index)]));
43
+ const start = graph.nodes.find(node => node.type === 'start');
44
+ const nodes = [{ ...structuredClone(start), id: nodeIds[start.id] }], edges = [];
45
+ const emit = (id, kind, params, ends, original) => {
46
+ nodes.push({ id, type: 'core', position: original?.position ? { ...original.position } : { x: 480, y: nodes.length * 120 }, data: { kind, params, retry: { maxAttempts: 1 }, ...(original?.data?.label ? { label: original.data.label } : {}) } });
47
+ for (const [label, target] of Object.entries(ends))
48
+ edges.push({ id: fresh('edge'), source: id, target, label });
49
+ };
50
+ const failure = fresh('failed'), exhausted = fresh('iteration-limit');
51
+ const end = (id, outcome, reason) => emit(id, 'end', { outcome, ...(reason ? { reason } : {}) }, {});
52
+ const assign = (id, params, target) => emit(id, 'assign', params, { next: target, failed: failure });
53
+ const condition = (id, expr, yes, no) => emit(id, 'condition', { expr }, { true: yes, false: no });
54
+ const entry = new Map();
55
+ const legacyCap = (graph.config.maxIterations + 1) * (graph.nodes.length + 2) + 16;
56
+ // Every original visit keeps its own legacy ceiling as well as Core's global
57
+ // control-transition ceiling. Free control nodes cannot buy another AI pass.
58
+ for (const node of graph.nodes.filter(node => node.type !== 'start')) {
59
+ const guard = fresh('visit'), count = fresh('visit-count');
60
+ entry.set(node.id, guard);
61
+ condition(guard, `$vars.${STEPS} < ${legacyCap}`, count, exhausted);
62
+ assign(count, { increment: { [STEPS]: 1 } }, nodeIds[node.id]);
63
+ }
64
+ if (graph.edges.some(edge => edge.target === start.id)) {
65
+ const guard = fresh('revisit-start'), count = fresh('revisit-start-count');
66
+ entry.set(start.id, guard);
67
+ condition(guard, `$vars.${STEPS} < ${legacyCap}`, count, exhausted);
68
+ assign(count, { increment: { [STEPS]: 1 } }, entry.get(graph.edges.find(edge => edge.source === start.id).target));
69
+ }
70
+ const successor = (node) => entry.get(graph.edges.find(edge => edge.source === node.id).target);
71
+ const recoveryVars = new Map(graph.nodes.filter(node => node.data?.failureRecovery).map((node, index) => [node.id, 'compatRecovery' + index]));
72
+ const writes = graph.nodes.some(node => node.type === 'ai-step' || node.type === 'shell');
73
+ let implementation = false;
74
+ const failedPass = (target) => {
75
+ const id = fresh('failed-pass');
76
+ assign(id, { set: { [PASS]: true } }, target);
77
+ return id;
78
+ };
79
+ const promptParams = (node, repair = false) => {
80
+ const text = repair
81
+ ? 'Repair ONLY the OpenSpec artifacts at openspec/changes/{{run.changeId}}/ using the validation diagnostics in history. Preserve requirements and acceptance criteria. Do not edit implementation code, run repository tests, archive, create a new change, or expand scope. {{const:GUARDRAILS}}'
82
+ : String(node.data?.prompt ?? '');
83
+ const verification = !repair && (node.data?.requireVerificationPass === true || /\{\{cmd:(?:verify|revision-verify|opsx:verify)\}\}/.test(text));
84
+ return { text: text || 'Continue the frozen workflow obligations.', access: 'write', sentinel: verification ? 'verification' : 'blocked',
85
+ sessionContinuity: node.data?.freshSession === true ? 'none' : 'run', appendHistory: true,
86
+ timeoutMs: Math.round((graph.config.aiStepTimeoutMinutes ?? 15) * 60_000), idleTimeoutMs: 30 * 60_000 };
87
+ };
88
+ const recover = (node) => {
89
+ const policy = node.data?.failureRecovery;
90
+ if (!policy)
91
+ return failure;
92
+ const guard = fresh('recovery-guard'), consume = fresh('recovery-count');
93
+ let target = entry.get(policy.target);
94
+ if (policy.target !== node.id || policy.artifactOnly) {
95
+ const repair = fresh('artifact-repair'), original = graph.nodes.find(item => item.id === policy.target);
96
+ target = fresh('repair-visit');
97
+ const count = fresh('repair-count');
98
+ condition(target, `$vars.${STEPS} < ${legacyCap}`, count, exhausted);
99
+ assign(count, { increment: { [STEPS]: 1 } }, repair);
100
+ emit(repair, 'prompt', promptParams(original, true), { next: entry.get(node.id), blocked: failure, failed: failure }, original);
101
+ }
102
+ condition(guard, `$vars.${recoveryVars.get(node.id)} < 1`, consume, failure);
103
+ assign(consume, { increment: { [recoveryVars.get(node.id)]: 1 } }, target);
104
+ return guard;
105
+ };
106
+ for (const node of graph.nodes) {
107
+ const id = nodeIds[node.id];
108
+ if (node.type === 'start')
109
+ continue;
110
+ if (node.type === 'end') {
111
+ if (node.data?.outcome === 'failure') {
112
+ end(id, 'failure');
113
+ continue;
114
+ }
115
+ const done = fresh('done');
116
+ condition(id, `$vars.${PASS} == false`, writes ? (() => { const verify = fresh('verify'); emit(verify, 'verify', { commands: 'configured' }, { pass: done, fail: failure, failed: failure }); return verify; })() : done, failure);
117
+ end(done, 'success');
118
+ continue;
119
+ }
120
+ if (node.type === 'condition') {
121
+ condition(id, 'true', successor(node), successor(node));
122
+ continue;
123
+ }
124
+ if (node.type === 'decider') {
125
+ const out = graph.edges.filter(edge => edge.source === node.id);
126
+ const branch = (name) => out.find(edge => edge.branch === name)?.target ?? out.find(edge => (graph.nodes.find(target => target.id === edge.target)?.type === 'end') === (name === 'stop')).target;
127
+ const cont = entry.get(branch('continue')), stop = entry.get(branch('stop'));
128
+ const evaluate = fresh('decision-count'), ordinary = fresh('continue-limit'), reset = fresh('new-pass'), route = fresh('decision-route');
129
+ // Redirect the visit counter into the decider's iteration admission.
130
+ edges.find(edge => edge.target === id).target = evaluate;
131
+ const ready = fresh('decision-ready');
132
+ condition(evaluate, `$vars.${ITERATION} < ${graph.config.maxIterations}`, ready, exhausted);
133
+ assign(ready, { increment: { [ITERATION]: 1 } }, id);
134
+ emit(id, 'decider', { roleId: exports.LEGACY_DECIDER_ROLE, goal: String(node.data?.goal ?? 'The loop goal is met.'), timeoutMs: 180_000, noProgress: 3, continueWhen: `$vars.${PASS} == true` }, { continue: route, stop, failed: failure }, node);
135
+ condition(route, `$outputs.${id}.blocked == true`, cont, ordinary);
136
+ condition(ordinary, `$vars.${ITERATION} < ${graph.config.maxIterations}`, reset, exhausted);
137
+ assign(reset, { set: { [PASS]: false } }, cont);
138
+ continue;
139
+ }
140
+ const next = successor(node), hardFailure = node.data?.stopOnFailure === true ? failure : failedPass(next);
141
+ if (node.type === 'ai-step') {
142
+ const raw = String(node.data?.prompt ?? '');
143
+ if (node.data?.operation === 'core-implementation' || /\{\{\s*cmd:(?:implement|batch)\s*\}\}/.test(raw) || /^\s*(?:\/specrails:|\/skill:specrails-|\$)(?:implement|batch-implement)(?:\s|$)/.test(raw)) {
144
+ implementation = true;
145
+ // The removed Batch command (`{{cmd:batch}}` / `batch-implement`) converts
146
+ // exactly like implement: one aggregate implementation node, no map/join.
147
+ emit(id, 'implementation', {}, { next, rejected: hardFailure, failed: hardFailure }, node);
148
+ }
149
+ else {
150
+ const params = promptParams(node);
151
+ emit(id, 'prompt', params, params.sentinel === 'verification'
152
+ ? { pass: next, fail: node.data?.stopOnFailure === true ? recover(node) : hardFailure, failed: hardFailure }
153
+ : { next, blocked: hardFailure, failed: hardFailure }, node);
154
+ }
155
+ }
156
+ else if (node.type === 'shell') {
157
+ const required = Array.isArray(node.data?.requireRunVars) ? node.data.requireRunVars : [];
158
+ let execute = id;
159
+ if (required.length) {
160
+ execute = fresh('shell');
161
+ condition(id, required.map(name => `exists($vars.${name}) && $vars.${name} != ""`).join(' && '), execute, failure);
162
+ }
163
+ let commandFailure = hardFailure;
164
+ const command = String(node.data?.command ?? '').trim();
165
+ const validate = /^openspec validate \{\{\s*run\.changeId\s*\}\} --type change --strict --no-interactive$/.test(command);
166
+ const archive = /^openspec archive \{\{\s*run\.changeId\s*\}\} -y$/.test(command);
167
+ if (validate || archive) {
168
+ const params = { change: '{{run.changeId}}', allowArchived: true, repositoryId: node.data?.repositoryId ?? bindings.repositoryId };
169
+ if (validate)
170
+ emit(execute, 'openspec-validate', params, { pass: next, fail: node.data?.stopOnFailure === true ? recover(node) : hardFailure, failed: hardFailure }, node);
171
+ else {
172
+ let failed = hardFailure;
173
+ if (node.data?.stopOnFailure === true && node.data.failureRecovery) {
174
+ failed = fresh('archive-recovery-exit');
175
+ condition(failed, `$outputs.${execute}.exitCode == 1`, recover(node), hardFailure);
176
+ }
177
+ emit(execute, 'openspec-archive', params, { next, failed }, node);
178
+ }
179
+ continue;
180
+ }
181
+ if (node.data?.stopOnFailure === true && node.data.failureRecovery) {
182
+ commandFailure = fresh('recoverable-exit');
183
+ condition(commandFailure, `$outputs.${execute}.exitCode == 1`, recover(node), failure);
184
+ }
185
+ emit(execute, 'shell', { commandLine: command, repositoryId: node.data?.repositoryId ?? bindings.repositoryId,
186
+ timeoutMs: 600_000, outputCapBytes: 262_144 }, { ok: next, fail: commandFailure, failed: hardFailure }, node);
187
+ }
188
+ }
189
+ end(failure, 'failure', 'legacy_required_work_failed');
190
+ end(exhausted, 'failure', 'legacy_iteration_limit');
191
+ const initial = { [PASS]: false, [ITERATION]: 0, [STEPS]: 1, ...Object.fromEntries([...recoveryVars.values()].map(name => [name, 0])) };
192
+ const chunks = Object.entries(initial);
193
+ let first = successor(start);
194
+ while (chunks.length) {
195
+ const id = fresh('initialize');
196
+ assign(id, { set: Object.fromEntries(chunks.splice(-64)) }, first);
197
+ first = id;
198
+ }
199
+ edges.unshift({ id: fresh('start-edge'), source: nodeIds[start.id], target: first });
200
+ // Avoid unreachable synthetic terminals in graphs that never branch/fail.
201
+ const reachable = new Set(), pending = [nodeIds[start.id]];
202
+ while (pending.length) {
203
+ const current = pending.pop();
204
+ if (reachable.has(current))
205
+ continue;
206
+ reachable.add(current);
207
+ pending.push(...edges.filter(edge => edge.source === current).map(edge => edge.target));
208
+ }
209
+ const maxTransitions = implementation ? 10_000 : legacyCap * 12 + nodes.length;
210
+ if (maxTransitions > 10_000)
211
+ return { ok: false, issues: [{ code: 'transition_limit', message: 'This conversion exceeds Core’s 10000-transition ceiling; reduce the requested iteration bound.' }] };
212
+ const converted = { nodes: nodes.filter(node => reachable.has(node.id)), edges: edges.filter(edge => reachable.has(edge.source)),
213
+ config: { ...graph.config, policies: { ...graph.config.policies, failFast: Math.min(graph.config.policies?.failFast ?? 2, 2) }, maxTransitions, journal: implementation ? 'implementation' : 'ledger-only', change: graph.config.change ?? (implementation ? undefined : 'none'),
214
+ ...(graph.nodes.some(node => node.type === 'decider') ? { legacyDeciderRole: exports.LEGACY_DECIDER_ROLE } : {}) } };
215
+ const result = (0, loop_graph_1.validateLoopGraph)(converted);
216
+ if (!result.valid)
217
+ return { ok: false, issues: result.errors.map(error => ({ code: error.code, message: error.message, ...(error.nodeId ? { nodeId: error.nodeId } : {}) })) };
218
+ return { ok: true, graph: converted, nodeIds, issues: [] };
219
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.coreFactoryGraph = coreFactoryGraph;
4
+ /** App-owned definition graphs. Core supplies schemas, execution and canonical versions. */
5
+ function coreFactoryGraph(mode) {
6
+ const nodes = [{ id: 'start', type: 'start', position: { x: 0, y: 0 } }];
7
+ const edges = [];
8
+ const config = { maxIterations: 12, maxTransitions: 120, timeoutMinutes: 0, aiStepTimeoutMinutes: 0,
9
+ journal: mode === 'implement' ? 'implementation' : 'ledger-only', change: mode === 'freestyle' ? 'none' : 'new' };
10
+ const node = (id, kind, params, outcomes) => {
11
+ nodes.push({ id, type: 'core', position: { x: 0, y: nodes.length * 120 }, data: { kind, params } });
12
+ for (const [label, target] of Object.entries(outcomes))
13
+ edges.push({ id: `e-${id}-${label}`, source: id, target, label });
14
+ };
15
+ const next = (id) => ({ next: id, failed: 'failed' });
16
+ const promptNext = (id) => ({ ...next(id), blocked: 'failed' });
17
+ const verify = (id, passed = 'done', failed = 'failed') => node(id, 'verify', { commands: 'configured' }, { pass: passed, fail: failed, failed: 'failed' });
18
+ let entry;
19
+ if (mode === 'implement') {
20
+ entry = 'implement';
21
+ node(entry, 'implementation', {}, { next: 'done', rejected: 'failed', failed: 'failed' });
22
+ }
23
+ else if (mode === 'quick-sdd') {
24
+ entry = 'prepare';
25
+ node('prepare', 'prompt', { nativeCommand: { id: 'opsx:ff', args: '{{run.changeId}}' }, access: 'write', sentinel: 'blocked' }, promptNext('validate'));
26
+ node('validate', 'openspec-validate', { change: '{{run.changeId}}' }, { pass: 'apply', fail: 'failed', failed: 'failed' });
27
+ node('apply', 'prompt', { nativeCommand: { id: 'opsx:apply', args: '{{run.changeId}}' }, access: 'write', sentinel: 'blocked' }, promptNext('check'));
28
+ verify('check', 'archive');
29
+ node('archive', 'openspec-archive', { change: '{{run.changeId}}' }, next('verify'));
30
+ verify('verify');
31
+ }
32
+ else {
33
+ entry = 'implement';
34
+ node('implement', 'prompt', { text: '{{cmd:freestyle}}', access: 'write', sentinel: 'blocked' }, promptNext('verify'));
35
+ verify('verify', 'decide', 'fix');
36
+ node('decide', 'decider', { roleId: 'loop-decider', goal: 'Stop only when actual behavioral evidence proves every frozen acceptance criterion is implemented across every selected ticket and repository. Passing baseline checks alone is insufficient.', noProgress: 3 }, { continue: 'fix', stop: 'done', failed: 'failed' });
37
+ node('fix', 'prompt', { text: '{{cmd:fix}}', access: 'write', sentinel: 'blocked' }, promptNext('verify'));
38
+ }
39
+ edges.unshift({ id: 'e-start', source: 'start', target: entry });
40
+ nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 120 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 320, y: nodes.length * 120 }, data: { outcome: 'failure' } });
41
+ return { nodes, edges, config };
42
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateDefinitionResumeControls = validateDefinitionResumeControls;
4
+ /** Resolve controls against the latest retained journal, never against UI labels or node paths. */
5
+ function validateDefinitionResumeControls(value, probe) {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value))
7
+ throw new Error('Resume controls must be an object');
8
+ const body = value;
9
+ if (Object.keys(body).some(key => !['answer', 'interruptId', 'approve', 'recover'].includes(key)))
10
+ throw new Error('Unknown resume control');
11
+ if (body.answer !== undefined && (typeof body.answer !== 'string' || !body.answer.trim() || body.answer.length > 20_000))
12
+ throw new Error('Answer must be nonempty and contain at most 20000 characters');
13
+ if (body.interruptId !== undefined && (typeof body.interruptId !== 'string' || !body.interruptId))
14
+ throw new Error('An exact interrupt ID is required');
15
+ const ids = (key) => {
16
+ const values = body[key];
17
+ if (values === undefined)
18
+ return undefined;
19
+ if (!Array.isArray(values) || values.length > 10_000 || values.some(id => typeof id !== 'string' || !id) || new Set(values).size !== values.length)
20
+ throw new Error(`${key} must contain unique IDs`);
21
+ return values;
22
+ };
23
+ const approve = ids('approve'), recover = ids('recover');
24
+ if (body.answer !== undefined && approve?.length)
25
+ throw new Error('Submit an answer or approvals separately');
26
+ const pending = probe.pendingInterrupts;
27
+ if (approve?.some(id => !pending.some(item => item.id === id && (item.kind === 'approval' || item.kind === 'gate'))))
28
+ throw new Error('Approval is no longer pending');
29
+ if (recover?.some(id => !probe.recoverableSteps.some(item => item.attemptId === id)))
30
+ throw new Error('Recovery requires an exact recoverable attempt ID');
31
+ let interruptId = body.interruptId;
32
+ if (body.answer !== undefined && !interruptId) {
33
+ const questions = pending.filter(item => item.kind === 'question');
34
+ if (questions.length !== 1)
35
+ throw new Error('Select an exact pending question');
36
+ interruptId = questions[0].id;
37
+ }
38
+ if (interruptId && !pending.some(item => item.id === interruptId))
39
+ throw new Error('Interrupt is no longer pending');
40
+ if (body.answer !== undefined && !pending.some(item => item.id === interruptId && item.kind === 'question'))
41
+ throw new Error('Answers require a pending question');
42
+ if (interruptId && body.answer === undefined && !approve?.includes(interruptId))
43
+ throw new Error('Select an answer or approval for the interrupt');
44
+ return {
45
+ ...(body.answer === undefined ? {} : { answer: body.answer }),
46
+ ...(interruptId ? { interruptId } : {}), ...(approve ? { approve } : {}), ...(recover ? { recover } : {}),
47
+ };
48
+ }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readDefinitionUsage = readDefinitionUsage;
4
+ exports.createDefinitionEventProjection = createDefinitionEventProjection;
5
+ const db_1 = require("../../../db");
6
+ const ai_invocations_1 = require("../../accounting/runtime/ai-invocations");
7
+ const distribute_int_1 = require("../../../util/distribute-int");
8
+ const loop_runs_store_1 = require("./loop-runs-store");
9
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
10
+ const text = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
11
+ const number = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
12
+ const timestamp = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value)) ? value : undefined;
13
+ const canonical = (value) => Array.isArray(value) ? value.map(canonical) : object(value)
14
+ ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, canonical(item)])) : value;
15
+ function readDefinitionUsage(db, runId) {
16
+ const row = db.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM(total_cost_usd),0) AS cost,
17
+ COALESCE(SUM(tokens_in),0) AS tokensIn, COALESCE(SUM(tokens_out),0) AS tokensOut,
18
+ COALESCE(SUM(tokens_cache_read),0) AS cacheRead, COALESCE(SUM(tokens_cache_create),0) AS cacheCreate,
19
+ COALESCE(SUM(duration_ms),0) AS duration, COALESCE(SUM(num_turns),0) AS turns,
20
+ MAX(total_cost_usd IS NULL) AS costUnknown, MAX(tokens_in IS NULL) AS tokensInUnknown, MAX(tokens_out IS NULL) AS tokensOutUnknown,
21
+ MAX(tokens_cache_read IS NULL) AS cacheReadUnknown, MAX(tokens_cache_create IS NULL) AS cacheCreateUnknown, MAX(num_turns IS NULL) AS turnsUnknown
22
+ FROM ai_invocations WHERE surface='loop' AND loop_run_id=?`).get(runId);
23
+ return { ...row, present: row.count > 0, costUnknown: Boolean(row.costUnknown), tokensInUnknown: Boolean(row.tokensInUnknown), tokensOutUnknown: Boolean(row.tokensOutUnknown), cacheReadUnknown: Boolean(row.cacheReadUnknown), cacheCreateUnknown: Boolean(row.cacheCreateUnknown), turnsUnknown: Boolean(row.turnsUnknown) };
24
+ }
25
+ /** Project committed Core events. The event marker and physical usage share one Desktop transaction. */
26
+ function createDefinitionEventProjection(input) {
27
+ const { db, runId } = input;
28
+ let count = Number(db.prepare("SELECT COALESCE(MAX(json_extract(payload,'$.index')),0) AS count FROM events WHERE job_id=? AND event_type='loop_step'").get(runId).count);
29
+ // Only a new physical invocation changes usage. Publish the cached aggregate
30
+ // after commit so a failed event transaction cannot poison later progress.
31
+ let totals = readDefinitionUsage(db, runId);
32
+ return (event) => {
33
+ const workflow = event.type === 'workflow-event' && object(event.event) ? event.event : undefined;
34
+ const durableId = workflow ? text(workflow.id) : text(event.eventId);
35
+ const owner = workflow?.runId ?? event.runId;
36
+ if (owner !== undefined && owner !== runId)
37
+ throw new Error('Core event belongs to another run');
38
+ const encoded = JSON.stringify(event);
39
+ const at = timestamp(workflow?.timestamp ?? event.timestamp) ?? new Date().toISOString();
40
+ const outgoing = [];
41
+ let nextCount = count;
42
+ let nextTotals = totals;
43
+ const persist = (eventType, payload) => {
44
+ const seq = input.nextSequence(), json = JSON.stringify(payload);
45
+ (0, db_1.appendEvent)(db, runId, seq, { event_type: eventType, source: 'stdout', payload: json });
46
+ outgoing.push({ eventType, payload: json, seq, timestamp: at });
47
+ };
48
+ db.transaction(() => {
49
+ if (durableId) {
50
+ const sequence = workflow?.sequence ?? event.sequence;
51
+ if (owner !== runId || !Number.isSafeInteger(sequence) || sequence < 1 || durableId !== `${runId}:${sequence}`)
52
+ throw new Error('Malformed durable Core event identity');
53
+ const prior = db.prepare("SELECT payload FROM events WHERE job_id=? AND event_type IN ('workflow-event','runtime-efficiency-event') AND COALESCE(json_extract(payload,'$.event.id'),json_extract(payload,'$.eventId'))=? LIMIT 1")
54
+ .get(runId, durableId);
55
+ if (prior) {
56
+ if (prior.payload !== encoded)
57
+ throw new Error('A replayed Core event changed its committed content');
58
+ return;
59
+ }
60
+ }
61
+ if (typeof event.type !== 'string')
62
+ throw new Error('Core event has no type');
63
+ if (event.type === 'runtime-efficiency-event' && event.kind === 'role-context' && object(event.payload) && object(event.payload.usage)) {
64
+ if (!durableId)
65
+ throw new Error('Physical usage requires a durable event identity');
66
+ const p = event.payload, usage = p.usage;
67
+ const invocationId = text(p.invocationId), attemptId = text(event.attemptId), provider = text(p.provider);
68
+ const startedAt = timestamp(p.startedAt), finishedAt = timestamp(p.finishedAt);
69
+ if (!invocationId || !attemptId || !provider || !startedAt || !finishedAt || !['succeeded', 'failed', 'interrupted'].includes(String(p.status)))
70
+ throw new Error('Incomplete physical invocation evidence');
71
+ const identity = `core:${runId}:${invocationId}`;
72
+ const priorEvidence = db.prepare("SELECT payload FROM events WHERE job_id=? AND event_type='runtime-efficiency-event' AND json_extract(payload,'$.payload.invocationId')=? LIMIT 1").get(runId, invocationId);
73
+ if (priorEvidence) {
74
+ const original = JSON.parse(priorEvidence.payload);
75
+ // Envelope sequence/time may differ on replay; physical identity,
76
+ // attribution and the provider's reported usage must not diverge.
77
+ const evidence = (value) => JSON.stringify(canonical([value.attemptId, value.nodePath, value.payload]));
78
+ if (evidence(original) !== evidence(event))
79
+ throw new Error('A physical Core invocation changed its committed evidence');
80
+ }
81
+ const targets = [...new Set(input.ticketIds)].sort((a, b) => a - b);
82
+ if (!targets.length)
83
+ targets.push(null);
84
+ const prior = db.prepare('SELECT id FROM ai_invocations WHERE id=?').get(identity + (targets.length > 1 ? `:t${targets[0]}` : ''));
85
+ if (prior && !priorEvidence)
86
+ throw new Error('Physical Core invocation is missing its committed evidence');
87
+ if (!prior) {
88
+ const payload = { version: 1, runId, stepKey: identity, invocationId: identity, projectId: input.projectId, provider,
89
+ model: text(p.model) ?? null, surfaceRefId: text(event.nodePath) ?? attemptId, ticketIds: input.ticketIds,
90
+ startedAt, baseline: (0, loop_runs_store_1.readLoopJobUsage)(db, runId), completedEventSeq: -1, providerCostBaseline: 0, providerTurnsBaseline: 0, loopDurationBaseline: 0, completedDurationMs: number(p.durationMs) ?? 0 };
91
+ (0, loop_runs_store_1.stageLoopStepRecovery)(db, payload);
92
+ (0, loop_runs_store_1.setLoopStepSettledResult)(db, runId, identity, { provider, cost: number(usage.costUsd), tokensIn: number(usage.inputTokens), tokensOut: number(usage.outputTokens), durationMs: number(p.durationMs), failed: p.status !== 'succeeded' });
93
+ (0, loop_runs_store_1.completeLoopStepRecovery)(db, runId, identity, () => {
94
+ const inTokens = (0, distribute_int_1.distributeIntEvenly)(number(usage.inputTokens), targets.length), outTokens = (0, distribute_int_1.distributeIntEvenly)(number(usage.outputTokens), targets.length);
95
+ const readTokens = (0, distribute_int_1.distributeIntEvenly)(number(usage.cacheReadInputTokens), targets.length), writeTokens = (0, distribute_int_1.distributeIntEvenly)(number(usage.cacheWriteInputTokens), targets.length);
96
+ targets.forEach((ticketId, index) => (0, ai_invocations_1.recordInvocation)(db, { id: identity + (targets.length > 1 ? `:t${ticketId}` : ''), project_id: input.projectId, provider,
97
+ model: text(p.model), surface: 'loop', surface_ref_id: `${attemptId}:${invocationId}`, loop_run_id: runId, ticket_id: ticketId,
98
+ status: p.status === 'succeeded' ? 'success' : p.status === 'interrupted' ? 'aborted' : 'failed', started_at: startedAt, finished_at: finishedAt,
99
+ total_cost_usd: number(usage.costUsd) === undefined ? undefined : Number(usage.costUsd) / targets.length, total_cost_usd_estimated: false,
100
+ tokens_in: inTokens[index], tokens_out: outTokens[index], tokens_cache_read: readTokens[index], tokens_cache_create: writeTokens[index],
101
+ duration_ms: number(p.durationMs) === undefined ? undefined : Number(p.durationMs) / targets.length,
102
+ }));
103
+ });
104
+ nextTotals = readDefinitionUsage(db, runId);
105
+ }
106
+ }
107
+ if (nextTotals !== totals) {
108
+ db.prepare(`UPDATE jobs SET total_cost_usd=?,tokens_in=?,tokens_out=?,tokens_cache_read=?,tokens_cache_create=?,num_turns=? WHERE id=? AND owner='loop'`).run(nextTotals.present && !nextTotals.costUnknown ? nextTotals.cost : null, nextTotals.present && !nextTotals.tokensInUnknown ? nextTotals.tokensIn : null, nextTotals.present && !nextTotals.tokensOutUnknown ? nextTotals.tokensOut : null, nextTotals.present && !nextTotals.cacheReadUnknown ? nextTotals.cacheRead : null, nextTotals.present && !nextTotals.cacheCreateUnknown ? nextTotals.cacheCreate : null, nextTotals.present && !nextTotals.turnsUnknown ? nextTotals.turns : null, runId);
109
+ db.prepare('UPDATE loop_runs SET total_cost_usd=?,total_tokens=? WHERE id=?').run(nextTotals.cost, nextTotals.tokensIn + nextTotals.tokensOut, runId);
110
+ }
111
+ persist(event.type, event);
112
+ if (durableId)
113
+ db.prepare('UPDATE loop_runs SET core_event_cursor=MAX(COALESCE(core_event_cursor,0),?) WHERE id=? AND engine_version=2').run(workflow?.sequence ?? event.sequence, runId);
114
+ if (event.type === 'runtime-result')
115
+ (0, loop_runs_store_1.recordDefinitionCheckpoint)(db, runId, {
116
+ ...(event.revision === undefined ? {} : { revision: event.revision }),
117
+ ...(event.eventCursor === undefined ? {} : { eventCursor: event.eventCursor }),
118
+ status: typeof event.status === 'string' ? event.status : undefined,
119
+ pendingInterrupts: Array.isArray(event.pendingInterrupts) ? event.pendingInterrupts : [],
120
+ ...(event.completion === undefined ? {} : { completion: event.completion }),
121
+ });
122
+ if (workflow && text(workflow.attemptId)) {
123
+ const attemptId = String(workflow.attemptId);
124
+ const prior = db.prepare("SELECT payload FROM events WHERE job_id=? AND event_type='loop_step' AND json_extract(payload,'$.attemptId')=? ORDER BY seq DESC LIMIT 1").get(runId, attemptId);
125
+ const opening = prior ? JSON.parse(prior.payload) : undefined;
126
+ const correlation = { nodeId: workflow.nodePath, nodePath: workflow.nodePath, scopeId: workflow.scopeId, branch: workflow.branch, visit: workflow.visit, attempt: workflow.attempt, attemptId, coreEventId: durableId, traceId: workflow.traceId, spanId: workflow.spanId };
127
+ if (workflow.type === 'step_started' && !opening) {
128
+ nextCount += 1;
129
+ persist('loop_step', { index: nextCount, kind: 'core', title: workflow.nodePath, iteration: workflow.visit, startedAtMs: Date.parse(at), ...correlation });
130
+ }
131
+ else if (opening && ['step_succeeded', 'step_failed', 'step_blocked', 'step_interrupted', 'step_retrying', 'step_paused'].includes(String(workflow.type))) {
132
+ persist('loop_step_end', { index: opening.index, status: workflow.type === 'step_succeeded' ? 'ok' : workflow.type === 'step_paused' ? 'paused' : workflow.type === 'step_interrupted' ? 'interrupted' : 'failed',
133
+ runtimeStatus: String(workflow.type).slice(5), outcome: workflow.outcome, exitCode: null,
134
+ durationMs: Math.max(0, Date.parse(at) - Number(opening.startedAtMs)), ...correlation });
135
+ }
136
+ }
137
+ })();
138
+ count = nextCount;
139
+ totals = nextTotals;
140
+ for (const projected of outgoing)
141
+ input.broadcast(projected);
142
+ if (outgoing.length)
143
+ input.onProgress?.(count, totals);
144
+ };
145
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.probeDefinitionRun = probeDefinitionRun;
7
+ exports.probeDefinitionRuns = probeDefinitionRuns;
8
+ exports.toDefinitionStates = toDefinitionStates;
9
+ const node_child_process_1 = require("node:child_process");
10
+ const node_fs_1 = require("node:fs");
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const node_util_1 = require("node:util");
13
+ const core_node_runtime_1 = require("../../../core-node-runtime");
14
+ const win_spawn_1 = require("../../../util/win-spawn");
15
+ const agent_runtime_bridge_1 = require("../../agent-runtime/runtime/agent-runtime-bridge");
16
+ const agent_runtime_package_1 = require("../../agent-runtime/runtime/agent-runtime-package");
17
+ const loop_runs_store_1 = require("./loop-runs-store");
18
+ const integer = (value) => Number.isSafeInteger(value) && value >= 0;
19
+ const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
20
+ /** Read only through the retained CLI. Failed inspection preserves ownership for a later explicit decision. */
21
+ async function probeDefinitionRun(ctx, runId, includeOutputs = false) {
22
+ const unavailable = (message) => ({ runId, engineVersion: 2, status: 'unavailable', resumable: false,
23
+ lease: null, recoverableSteps: [], pendingInterrupts: [], completion: null, coreRevision: null, eventCursor: null,
24
+ probedAt: new Date().toISOString(), error: { code: 'runtime_status_unavailable', message: message.slice(0, 2000) } });
25
+ try {
26
+ const frozen = (0, loop_runs_store_1.readDefinitionRun)(ctx.db, runId), contextPath = frozen?.metadata.contextPath;
27
+ if (!frozen || !contextPath || !node_path_1.default.isAbsolute(contextPath))
28
+ return unavailable('Frozen Core context is unavailable');
29
+ const context = JSON.parse((0, node_fs_1.readFileSync)(contextPath, 'utf8'));
30
+ if (context.runId !== runId || typeof context.backlogRoot !== 'string' || (0, node_fs_1.realpathSync)(contextPath) !== node_path_1.default.join((0, node_fs_1.realpathSync)(context.backlogRoot), '.specrails', 'pipeline', runId, 'desktop-context.json'))
31
+ return unavailable('Frozen Core context belongs to another run or path');
32
+ let host;
33
+ let scopeError;
34
+ let selector = ['--context', contextPath];
35
+ try {
36
+ host = (0, agent_runtime_bridge_1.readFrozenRuntimeHost)(contextPath, ctx.env, runId);
37
+ }
38
+ catch (error) {
39
+ // Released mounts cannot authorize execution, but the owned retained
40
+ // ledger can still answer a read-only status request. Never substitute
41
+ // today's project checkout for the frozen execution scope.
42
+ const runDirectory = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'agent-workflow');
43
+ if ((0, node_fs_1.realpathSync)(runDirectory) !== runDirectory || (0, node_fs_1.realpathSync)(node_path_1.default.join(runDirectory, 'run.sqlite')) !== node_path_1.default.join(runDirectory, 'run.sqlite'))
44
+ return unavailable('Retained run ledger is outside its original directory');
45
+ scopeError = error instanceof Error ? error.message : 'Original workflow scope is unavailable';
46
+ host = { cwd: node_path_1.default.dirname(contextPath), env: { ...ctx.env, SPECRAILS_GIT_AUTO: 'false' } };
47
+ selector = ['--run-dir', runDirectory];
48
+ }
49
+ const cli = (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(contextPath);
50
+ const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), [cli, 'status', ...selector, ...(includeOutputs ? [] : ['--compact'])], {
51
+ cwd: host.cwd, env: (0, win_spawn_1.windowsSpawnEnv)(host.env), windowsHide: true, timeout: 15_000, maxBuffer: 4 * 1024 * 1024, encoding: 'utf8',
52
+ });
53
+ const result = JSON.parse(stdout);
54
+ if (!record(result) || result.type !== 'runtime-status' || result.engineVersion !== 2 || !record(result.state))
55
+ return unavailable('Retained Core returned an incompatible status');
56
+ const state = result.state;
57
+ if (state.runId !== runId || !['paused', 'running', 'succeeded', 'failed', 'blocked', 'cancelled'].includes(String(state.status)) || !integer(result.revision) || !integer(result.eventCursor))
58
+ return unavailable('Retained Core returned an invalid run identity or cursor');
59
+ const lease = state.lease;
60
+ if (lease !== null && (!record(lease) || typeof lease.owner !== 'string' || !integer(lease.epoch) || !integer(lease.expiresAt) || typeof lease.active !== 'boolean'))
61
+ return unavailable('Retained Core returned an invalid lease');
62
+ if (!Array.isArray(state.recoverableSteps) || state.recoverableSteps.length > 10_000 || state.recoverableSteps.some(value => !record(value) || typeof value.attemptId !== 'string' || !value.attemptId || typeof value.nodePath !== 'string' || typeof value.scopeId !== 'string'))
63
+ return unavailable('Retained Core returned invalid recovery attempts');
64
+ if (!Array.isArray(state.pendingInterrupts) || state.pendingInterrupts.length > 10_000 || state.pendingInterrupts.some(value => !record(value) || typeof value.id !== 'string' || typeof value.nodePath !== 'string' || !['question', 'approval', 'gate'].includes(String(value.kind))))
65
+ return unavailable('Retained Core returned invalid interrupts');
66
+ if (includeOutputs && (!Array.isArray(state.scopes) || state.scopes.length > 10_000 || state.scopes.some(value => !record(value) || typeof value.nodePath !== 'string' || typeof value.scopeId !== 'string' || typeof value.kind !== 'string' || typeof value.status !== 'string' || (value.attemptId !== null && typeof value.attemptId !== 'string'))))
67
+ return unavailable('Retained Core returned invalid scoped evidence');
68
+ const completion = result.completion;
69
+ if (completion !== null && (!record(completion) || typeof completion.ok !== 'boolean' || typeof completion.verified !== 'boolean' || !Array.isArray(completion.reasons) || completion.reasons.some(reason => typeof reason !== 'string')))
70
+ return unavailable('Retained Core returned invalid completion evidence');
71
+ return { runId, engineVersion: 2, status: state.status, lease: lease,
72
+ resumable: !scopeError && !lease?.active && completion === null && !['succeeded', 'cancelled'].includes(String(state.status)),
73
+ recoverableSteps: state.recoverableSteps.map(value => ({ attemptId: value.attemptId, nodePath: value.nodePath, scopeId: value.scopeId })),
74
+ pendingInterrupts: state.pendingInterrupts, completion: completion,
75
+ ...(includeOutputs ? { scopes: state.scopes } : {}),
76
+ coreRevision: result.revision, eventCursor: result.eventCursor, probedAt: new Date().toISOString(),
77
+ ...(scopeError ? { error: { code: 'runtime_scope_unavailable', message: scopeError.slice(0, 2000) } } : {}) };
78
+ }
79
+ catch (error) {
80
+ return unavailable(error instanceof Error ? error.message : 'Retained runtime inspection failed');
81
+ }
82
+ }
83
+ /** Bound subprocess fan-out independently of the number of historical runs. */
84
+ async function probeDefinitionRuns(ctx, runIds, includeOutputs = false) {
85
+ const results = new Map(), ids = [...new Set(runIds)];
86
+ let cursor = 0;
87
+ await Promise.all(Array.from({ length: Math.min(4, ids.length) }, async () => {
88
+ for (;;) {
89
+ const id = ids[cursor++];
90
+ if (id === undefined)
91
+ return;
92
+ results.set(id, await probeDefinitionRun(ctx, id, includeOutputs));
93
+ }
94
+ }));
95
+ return results;
96
+ }
97
+ function toDefinitionStates(probes) {
98
+ return new Map([...probes].map(([id, probe]) => [id, {
99
+ // Preserve even unavailable/terminal Core rows until Desktop explicitly
100
+ // settles their original delivery. A failed probe is not failed work.
101
+ status: probe.status === 'paused' ? 'paused' : 'interrupted', coreStatus: probe.status,
102
+ ...(probe.coreRevision === null ? {} : { revision: probe.coreRevision }), ...(probe.eventCursor === null ? {} : { eventCursor: probe.eventCursor }),
103
+ lease: probe.lease, completion: probe.completion, pendingInterrupts: probe.pendingInterrupts, recoverableSteps: probe.recoverableSteps,
104
+ ...(probe.error ? { error: probe.error } : {}),
105
+ }]));
106
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDefinitionLoop = runDefinitionLoop;
4
+ /** One retained Core process at a time; all graph scheduling remains in Core. */
5
+ async function runDefinitionLoop(request, runId, ports) {
6
+ let continuation = ports.continuation ?? {};
7
+ for (;;) {
8
+ if (ports.isCancelled())
9
+ return { text: '', runtimeStatus: 'cancelled', failed: true };
10
+ const result = await ports.invoke({ request, runId, contextPath: ports.contextPath?.(), ...continuation,
11
+ onPrepared: ports.onPrepared, onLine: ports.onLine, onRuntimeEvent: ports.onRuntimeEvent, onSpawn: ports.onSpawn, timeoutMs: ports.remainingMs() });
12
+ ports.onInvocationEnd(result);
13
+ if (ports.isCancelled())
14
+ return { ...result, runtimeStatus: 'cancelled', failed: true };
15
+ if (result.runtimeStatus !== 'paused')
16
+ return result;
17
+ const pending = result.pendingInterrupts ?? [];
18
+ const reason = result.pendingQuestion?.question ?? result.pendingApproval?.reason ?? pending.map(item => {
19
+ const value = item.value;
20
+ return `${item.nodePath}: ${typeof value?.prompt === 'string' ? value.prompt : item.kind}`;
21
+ }).join('\n');
22
+ if (!pending.length)
23
+ throw new Error('Core paused without an addressable interrupt');
24
+ const decision = await ports.awaitHumanDecision(reason, pending);
25
+ if (decision.action === 'stop' || ports.isCancelled())
26
+ return { ...result, runtimeStatus: 'cancelled', failed: true };
27
+ const selected = decision.interruptId ? pending.find(item => item.id === decision.interruptId) : pending.length === 1 ? pending[0] : undefined;
28
+ if (!selected)
29
+ throw new Error('Select the pending question or approval to resume');
30
+ if (selected.kind !== 'question' && decision.approve !== true)
31
+ throw new Error('Explicit approval is required to resume this interrupt');
32
+ continuation = selected.kind === 'question'
33
+ ? { resume: true, answer: decision.text, interruptId: selected.id }
34
+ : { resume: true, approve: [selected.id], interruptId: selected.id };
35
+ }
36
+ }