specrails-desktop 2.57.0 → 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 (336) 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 +130 -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/core-execution.js +49 -3
  191. package/server/dist/db/migrations.js +101 -0
  192. package/server/dist/desktop-db.js +28 -0
  193. package/server/dist/desktop-router.js +2 -0
  194. package/server/dist/mcp/guide.js +6 -3
  195. package/server/dist/mcp/tools/jobs.js +14 -1
  196. package/server/dist/mcp/tools/loops.js +17 -8
  197. package/server/dist/mcp/tools/rails.js +3 -3
  198. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +313 -9
  199. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls-router.js +34 -1
  200. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +203 -21
  201. package/server/dist/modules/agent-runtime/runtime/agent-runtime-effective-config.js +46 -1
  202. package/server/dist/modules/agent-runtime/runtime/agent-runtime-engines.js +10 -0
  203. package/server/dist/modules/agent-runtime/runtime/agent-runtime-loader.js +68 -3
  204. package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +16 -8
  205. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-gc.js +81 -0
  206. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-lock.js +37 -0
  207. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package.js +30 -24
  208. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-host.js +87 -0
  209. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-quarantine.js +231 -0
  210. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-records.js +32 -0
  211. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention.js +102 -0
  212. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +12 -7
  213. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +57 -13
  214. package/server/dist/modules/agents/runtime/agent-catalog.js +32 -0
  215. package/server/dist/modules/agents/runtime/agent-refine-manager.js +12 -0
  216. package/server/dist/modules/agents/runtime/agent-role-descriptor.js +64 -0
  217. package/server/dist/modules/agents/runtime/profiles-router.js +26 -34
  218. package/server/dist/modules/builder/runtime/milestone-chain.js +121 -73
  219. package/server/dist/modules/delivery/runtime/definition-fork.js +158 -0
  220. package/server/dist/modules/delivery/runtime/delivery-evidence.js +56 -10
  221. package/server/dist/modules/delivery/runtime/isolated-settlement-reconstruction.js +99 -0
  222. package/server/dist/modules/delivery/runtime/isolated-settlement-store.js +53 -0
  223. package/server/dist/modules/delivery/runtime/multi-repo-execution-store.js +3 -1
  224. package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +388 -203
  225. package/server/dist/modules/delivery/runtime/rail-launch-parser.js +3 -2
  226. package/server/dist/modules/delivery/runtime/rail-pr-store.js +6 -1
  227. package/server/dist/modules/delivery/runtime/rails-router.js +66 -10
  228. package/server/dist/modules/execution/runtime/queue-manager.js +11 -2
  229. package/server/dist/modules/loops/runtime/builtin-loops.js +70 -0
  230. package/server/dist/modules/loops/runtime/definition-cancellation.js +31 -0
  231. package/server/dist/modules/loops/runtime/legacy-launch-telemetry.js +78 -0
  232. package/server/dist/modules/loops/runtime/loop-command-catalog.js +13 -12
  233. package/server/dist/modules/loops/runtime/loop-compat.js +219 -0
  234. package/server/dist/modules/loops/runtime/loop-core-factory.js +42 -0
  235. package/server/dist/modules/loops/runtime/loop-definition-controls.js +48 -0
  236. package/server/dist/modules/loops/runtime/loop-definition-events.js +145 -0
  237. package/server/dist/modules/loops/runtime/loop-definition-recovery.js +106 -0
  238. package/server/dist/modules/loops/runtime/loop-definition-run.js +36 -0
  239. package/server/dist/modules/loops/runtime/loop-definition.js +246 -0
  240. package/server/dist/modules/loops/runtime/loop-effect.js +10 -1
  241. package/server/dist/modules/loops/runtime/loop-executors.js +63 -3
  242. package/server/dist/modules/loops/runtime/loop-factory.js +35 -15
  243. package/server/dist/modules/loops/runtime/loop-graph.js +84 -8
  244. package/server/dist/modules/loops/runtime/loop-migration.js +36 -0
  245. package/server/dist/modules/loops/runtime/loop-preview.js +10 -0
  246. package/server/dist/modules/loops/runtime/loop-run-manager.js +206 -13
  247. package/server/dist/modules/loops/runtime/loop-runs-store.js +271 -7
  248. package/server/dist/modules/loops/runtime/loop-templates.js +205 -45
  249. package/server/dist/modules/loops/runtime/loops-router.js +246 -29
  250. package/server/dist/modules/loops/runtime/loops-store.js +140 -20
  251. package/server/dist/modules/missions/runtime/agent-operator-prompt.js +14 -11
  252. package/server/dist/modules/specs/runtime/spec-addenda.js +28 -0
  253. package/server/dist/project-registry.js +87 -51
  254. package/server/dist/project-router-jobs.js +17 -6
  255. package/server/dist/project-router-loop-runs.js +200 -0
  256. package/server/dist/project-router-spending.js +17 -0
  257. package/server/dist/project-router.js +8 -6
  258. package/server/dist/schemas/agent-runtime.schema.json +93 -1
  259. package/server/dist/schemas/workflow-definition.schema.json +312 -0
  260. package/server/dist/vitest-setup.js +22 -0
  261. package/server/dist/worktree-overlay.js +1 -1
  262. package/client/dist/assets/AgentsPage-DfHWgP_x.js +0 -87
  263. package/client/dist/assets/DesktopAnalyticsPage-tc0p4jJW.js +0 -1
  264. package/client/dist/assets/InteractiveJobComposer-DFPpdg2D.js +0 -19
  265. package/client/dist/assets/JobDetailModal-BdOv-l-M.js +0 -1
  266. package/client/dist/assets/LoopBuilderPage-BemvUOxy.js +0 -7
  267. package/client/dist/assets/LoopPreviewModal-C6hNAeYN.js +0 -1
  268. package/client/dist/assets/LoopsPage-BZr0quvh.js +0 -1
  269. package/client/dist/assets/TemplatePreviewModal-Cejcquah.js +0 -1
  270. package/client/dist/assets/agentRuntime-BUimiX__.js +0 -1
  271. package/client/dist/assets/agentRuntime-BcN97sAB.js +0 -1
  272. package/client/dist/assets/agentRuntime-Ckyg-ahT.js +0 -1
  273. package/client/dist/assets/agentRuntime-DJCwyX4t.js +0 -1
  274. package/client/dist/assets/agentRuntime-DVzPJOl0.js +0 -1
  275. package/client/dist/assets/agentRuntime-V9AZTG3c.js +0 -1
  276. package/client/dist/assets/agentRuntime-bAAjw1vw.js +0 -1
  277. package/client/dist/assets/agentRuntime-v4qKAYH-.js +0 -1
  278. package/client/dist/assets/agentstudio-BNUth0uj.js +0 -1
  279. package/client/dist/assets/agentstudio-BNdB5hkr.js +0 -1
  280. package/client/dist/assets/agentstudio-C7-bt8FO.js +0 -1
  281. package/client/dist/assets/agentstudio-C8uNchpV.js +0 -1
  282. package/client/dist/assets/agentstudio-CKabyMlg.js +0 -1
  283. package/client/dist/assets/agentstudio-CN2pokQu.js +0 -1
  284. package/client/dist/assets/agentstudio-y2dVDXeM.js +0 -1
  285. package/client/dist/assets/analytics-BlypLLR6.js +0 -1
  286. package/client/dist/assets/builder-765NeYTp.js +0 -1
  287. package/client/dist/assets/builder-8MG3V6ea.js +0 -1
  288. package/client/dist/assets/builder-B-1UBs5K.js +0 -1
  289. package/client/dist/assets/builder-BTiGnPT0.js +0 -1
  290. package/client/dist/assets/builder-Cx9Rz-7u.js +0 -1
  291. package/client/dist/assets/builder-DKFMELBl.js +0 -1
  292. package/client/dist/assets/builder-OcmKrC0h.js +0 -1
  293. package/client/dist/assets/builder-P9qVBXFZ.js +0 -1
  294. package/client/dist/assets/common-B5AZNYMH.js +0 -1
  295. package/client/dist/assets/common-BFZ0c_vN.js +0 -1
  296. package/client/dist/assets/common-BttQx3z4.js +0 -1
  297. package/client/dist/assets/common-CM65k_-e.js +0 -1
  298. package/client/dist/assets/common-D4mvFFlu.js +0 -1
  299. package/client/dist/assets/common-TZ9Ny96S.js +0 -1
  300. package/client/dist/assets/common-nFpSF4GC.js +0 -1
  301. package/client/dist/assets/dashboard-B9uWNGO2.js +0 -1
  302. package/client/dist/assets/dashboard-BIwmO9wc.js +0 -1
  303. package/client/dist/assets/dashboard-BV4vOZkR.js +0 -1
  304. package/client/dist/assets/dashboard-Bf6MPVsM.js +0 -1
  305. package/client/dist/assets/dashboard-C8klWVvZ.js +0 -1
  306. package/client/dist/assets/dashboard-D1ORhPF0.js +0 -1
  307. package/client/dist/assets/dashboard-DSubvs9x.js +0 -1
  308. package/client/dist/assets/dashboard-px_vlRx_.js +0 -1
  309. package/client/dist/assets/index-C51x61Bz.css +0 -2
  310. package/client/dist/assets/loops-B7YpGizO.js +0 -1
  311. package/client/dist/assets/loops-BomD2J-h.js +0 -1
  312. package/client/dist/assets/loops-BrQHSQw3.js +0 -1
  313. package/client/dist/assets/loops-BvBwvTPA.js +0 -1
  314. package/client/dist/assets/loops-CODk6Slz.js +0 -1
  315. package/client/dist/assets/loops-DC6k-Ym-.js +0 -1
  316. package/client/dist/assets/loops-DSEg8-uY.js +0 -1
  317. package/client/dist/assets/loops-DhCoXD_v.js +0 -1
  318. package/client/dist/assets/settings-BsehKpty.js +0 -1
  319. package/client/dist/assets/settings-CQWob3SB.js +0 -1
  320. package/client/dist/assets/settings-CgCl0hzy.js +0 -1
  321. package/client/dist/assets/settings-DLRLd22L.js +0 -1
  322. package/client/dist/assets/settings-Dn5prAFl.js +0 -1
  323. package/client/dist/assets/settings-HZSu-K4l.js +0 -1
  324. package/client/dist/assets/settings-p4OVIyRh.js +0 -1
  325. package/client/dist/assets/settings-rddaldUp.js +0 -1
  326. package/client/dist/assets/useRuntimeRuns-Dqmz9fS9.js +0 -1
  327. package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  328. package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +0 -126
  329. package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +0 -114
  330. package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  331. package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  332. package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  333. package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  334. package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  335. package/server/dist/modules/loops/runtime/loop-templates-ported.js +0 -675
  336. /package/client/dist/assets/{LoopBuilderPage-DtxX27Jz.css → loop-layout-DtxX27Jz.css} +0 -0
@@ -0,0 +1,81 @@
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.collectUnreferencedRuntimePackages = collectUnreferencedRuntimePackages;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_crypto_1 = require("node:crypto");
10
+ const agent_runtime_package_lock_1 = require("./agent-runtime-package-lock");
11
+ const DIGEST = /^[a-f0-9]{64}$/;
12
+ const STAGED = /^\.staged-[a-f0-9-]{36}$/;
13
+ const GARBAGE = /^\.garbage-[a-f0-9]{64}-[a-f0-9-]{36}$/;
14
+ /** Collects only expired, unreferenced package directories. Every run pin is
15
+ * inspected before the first mutation; an unknown pin protects the whole cache.
16
+ * Callers must use the same package lock when publishing new retained pins. */
17
+ function collectUnreferencedRuntimePackages(pipelineRoot, options) {
18
+ if (!Number.isSafeInteger(options.minimumAgeMs) || options.minimumAgeMs < 86_400_000 || !Number.isFinite(options.now))
19
+ throw new Error('Package retention requires an age of at least one day and a valid clock');
20
+ if (!node_fs_1.default.existsSync(pipelineRoot))
21
+ return [];
22
+ if (node_fs_1.default.lstatSync(pipelineRoot).isSymbolicLink() || !node_fs_1.default.statSync(pipelineRoot).isDirectory())
23
+ throw new Error('Pipeline storage must be a real directory');
24
+ const cache = node_path_1.default.join(pipelineRoot, 'runtime-packages');
25
+ if (!node_fs_1.default.existsSync(cache))
26
+ return [];
27
+ return (0, agent_runtime_package_lock_1.withRuntimePackageLock)(cache, () => {
28
+ const referenced = new Set();
29
+ for (const entry of node_fs_1.default.readdirSync(pipelineRoot, { withFileTypes: true })) {
30
+ if (entry.name === 'runtime-packages')
31
+ continue;
32
+ if (entry.isSymbolicLink())
33
+ throw new Error('Cannot collect packages across an unknown pipeline link');
34
+ if (!entry.isDirectory())
35
+ continue;
36
+ const directory = node_path_1.default.join(pipelineRoot, entry.name);
37
+ if (entry.name === '.retention') {
38
+ if (node_fs_1.default.readdirSync(directory).length)
39
+ throw new Error('Runtime quarantine must be recovered before package collection');
40
+ continue;
41
+ }
42
+ const pinFile = node_path_1.default.join(directory, 'desktop-runtime-package.json');
43
+ if (!node_fs_1.default.existsSync(pinFile)) {
44
+ if (node_fs_1.default.existsSync(node_path_1.default.join(directory, 'agent-runtime-request.json')))
45
+ throw new Error('A saved execution has no proven original package pin');
46
+ continue;
47
+ }
48
+ const stat = node_fs_1.default.lstatSync(pinFile);
49
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16_384)
50
+ throw new Error('Invalid runtime package pin');
51
+ const pin = JSON.parse(node_fs_1.default.readFileSync(pinFile, 'utf8'));
52
+ if (pin.schemaVersion !== 1 || typeof pin.integrity !== 'string' || !DIGEST.test(pin.integrity) ||
53
+ pin.root !== node_path_1.default.join(cache, pin.integrity) || typeof pin.cli !== 'string' || !pin.cli || node_path_1.default.isAbsolute(pin.cli) || pin.cli.split(/[\\/]/).includes('..'))
54
+ throw new Error('Runtime package pin scope is invalid');
55
+ referenced.add(pin.integrity);
56
+ }
57
+ const eligible = [];
58
+ for (const entry of node_fs_1.default.readdirSync(cache, { withFileTypes: true })) {
59
+ if (!DIGEST.test(entry.name) && !GARBAGE.test(entry.name) && !STAGED.test(entry.name))
60
+ continue;
61
+ if (!entry.isDirectory() || entry.isSymbolicLink())
62
+ throw new Error('Runtime package storage contains an invalid entry');
63
+ if (referenced.has(entry.name))
64
+ continue;
65
+ const stat = node_fs_1.default.statSync(node_path_1.default.join(cache, entry.name));
66
+ if (stat.mtimeMs > options.now || options.now - stat.mtimeMs < options.minimumAgeMs)
67
+ continue;
68
+ eligible.push(entry.name);
69
+ }
70
+ for (const name of options.dryRun ? [] : eligible) {
71
+ const original = node_path_1.default.join(cache, name);
72
+ // A crashed deletion cannot leave a partially deleted digest available to
73
+ // a future publisher. The private garbage name is safe to retry later.
74
+ const garbage = GARBAGE.test(name) || STAGED.test(name) ? original : node_path_1.default.join(cache, `.garbage-${name}-${(0, node_crypto_1.randomUUID)()}`);
75
+ if (garbage !== original)
76
+ node_fs_1.default.renameSync(original, garbage);
77
+ node_fs_1.default.rmSync(garbage, { recursive: true });
78
+ }
79
+ return eligible.sort();
80
+ });
81
+ }
@@ -0,0 +1,37 @@
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.withRuntimePackageLock = withRuntimePackageLock;
7
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ /** SQLite's OS-backed write reservation serializes package publication and
11
+ * collection across Desktop processes and releases automatically after a crash.
12
+ * No timestamps, guessed stale owners or advisory PID files authorize deletion. */
13
+ function withRuntimePackageLock(cache, operation) {
14
+ node_fs_1.default.mkdirSync(cache, { recursive: true, mode: 0o700 });
15
+ if (node_fs_1.default.lstatSync(cache).isSymbolicLink() || !node_fs_1.default.statSync(cache).isDirectory())
16
+ throw new Error('Runtime package cache must be a real directory');
17
+ const lock = node_path_1.default.join(cache, '.maintenance.sqlite');
18
+ try {
19
+ node_fs_1.default.writeFileSync(lock, '', { flag: 'wx', mode: 0o600 });
20
+ }
21
+ catch (error) {
22
+ if (error.code !== 'EEXIST')
23
+ throw error;
24
+ }
25
+ const stat = node_fs_1.default.lstatSync(lock);
26
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1)
27
+ throw new Error('Runtime package maintenance lock is invalid');
28
+ const database = new better_sqlite3_1.default(lock, { timeout: 0 });
29
+ try {
30
+ // No asynchronous work may escape this transaction. Retention's host/Core
31
+ // inspection happens before package collection; copying is synchronous.
32
+ return database.transaction(operation).immediate();
33
+ }
34
+ finally {
35
+ database.close();
36
+ }
37
+ }
@@ -3,9 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.collectUnreferencedRuntimePackages = void 0;
6
7
  exports.runtimeEntryFingerprint = runtimeEntryFingerprint;
7
8
  exports.retainAgentRuntime = retainAgentRuntime;
8
9
  exports.resolveRetainedAgentRuntime = resolveRetainedAgentRuntime;
10
+ const agent_runtime_package_lock_1 = require("./agent-runtime-package-lock");
11
+ var agent_runtime_package_gc_1 = require("./agent-runtime-package-gc");
12
+ Object.defineProperty(exports, "collectUnreferencedRuntimePackages", { enumerable: true, get: function () { return agent_runtime_package_gc_1.collectUnreferencedRuntimePackages; } });
9
13
  const node_crypto_1 = require("node:crypto");
10
14
  const node_fs_1 = __importDefault(require("node:fs"));
11
15
  const node_module_1 = require("node:module");
@@ -155,37 +159,39 @@ function retainAgentRuntime(cli, contextPath) {
155
159
  throw new Error('Core CLI escapes its package');
156
160
  const cache = node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.dirname(contextPath)), 'runtime-packages');
157
161
  node_fs_1.default.mkdirSync(cache, { recursive: true, mode: 0o700 });
158
- const staged = node_path_1.default.join(cache, '.staged-' + (0, node_crypto_1.randomUUID)());
159
- try {
160
- copyInstalledPackage(root, staged);
161
- const integrity = treeDigest(staged);
162
- const retained = node_path_1.default.join(cache, integrity);
163
- if (node_fs_1.default.existsSync(retained)) {
164
- if (treeDigest(retained) !== integrity)
165
- throw new Error('Retained Core runtime integrity changed');
166
- }
167
- else {
162
+ return (0, agent_runtime_package_lock_1.withRuntimePackageLock)(cache, () => {
163
+ const staged = node_path_1.default.join(cache, '.staged-' + (0, node_crypto_1.randomUUID)());
164
+ try {
165
+ copyInstalledPackage(root, staged);
166
+ const integrity = treeDigest(staged);
167
+ const retained = node_path_1.default.join(cache, integrity);
168
+ if (node_fs_1.default.existsSync(retained)) {
169
+ if (treeDigest(retained) !== integrity)
170
+ throw new Error('Retained Core runtime integrity changed');
171
+ }
172
+ else {
173
+ try {
174
+ node_fs_1.default.renameSync(staged, retained);
175
+ }
176
+ catch (error) {
177
+ if (!node_fs_1.default.existsSync(retained) || treeDigest(retained) !== integrity)
178
+ throw error;
179
+ }
180
+ }
181
+ const pin = { schemaVersion: 1, packageVersion: JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(root, 'package.json'), 'utf8')).version, integrity, root: retained, cli: relativeCli };
168
182
  try {
169
- node_fs_1.default.renameSync(staged, retained);
183
+ node_fs_1.default.writeFileSync(pinFile, JSON.stringify(pin) + '\n', { flag: 'wx', mode: 0o600 });
170
184
  }
171
185
  catch (error) {
172
- if (!node_fs_1.default.existsSync(retained) || treeDigest(retained) !== integrity)
186
+ if (error.code !== 'EEXIST' || resolveRetainedAgentRuntime(contextPath) !== node_path_1.default.join(retained, relativeCli))
173
187
  throw error;
174
188
  }
189
+ return node_path_1.default.join(retained, relativeCli);
175
190
  }
176
- const pin = { schemaVersion: 1, packageVersion: JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(root, 'package.json'), 'utf8')).version, integrity, root: retained, cli: relativeCli };
177
- try {
178
- node_fs_1.default.writeFileSync(pinFile, JSON.stringify(pin) + '\n', { flag: 'wx', mode: 0o600 });
179
- }
180
- catch (error) {
181
- if (error.code !== 'EEXIST' || resolveRetainedAgentRuntime(contextPath) !== node_path_1.default.join(retained, relativeCli))
182
- throw error;
191
+ finally {
192
+ node_fs_1.default.rmSync(staged, { recursive: true, force: true });
183
193
  }
184
- return node_path_1.default.join(retained, relativeCli);
185
- }
186
- finally {
187
- node_fs_1.default.rmSync(staged, { recursive: true, force: true });
188
- }
194
+ });
189
195
  }
190
196
  function resolveRetainedAgentRuntime(contextPath) {
191
197
  const pinFile = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-package.json');
@@ -0,0 +1,87 @@
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.runtimeRetentionHostPorts = runtimeRetentionHostPorts;
7
+ exports.recoverHostRuntimeRetention = recoverHostRuntimeRetention;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_crypto_1 = require("node:crypto");
11
+ const loop_runs_store_1 = require("../../loops/runtime/loop-runs-store");
12
+ const agent_runtime_package_1 = require("./agent-runtime-package");
13
+ const agent_runtime_retention_quarantine_1 = require("./agent-runtime-retention-quarantine");
14
+ const agent_runtime_retention_records_1 = require("./agent-runtime-retention-records");
15
+ function terminalTime(value) {
16
+ if (!value)
17
+ return null;
18
+ return /^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d$/.test(value) ? value.replace(' ', 'T') + 'Z' : value;
19
+ }
20
+ function deliveryState(db, runId) {
21
+ const rows = db.prepare('SELECT run_ids,decision,operation_token FROM rail_pr_deliveries').all();
22
+ const relevant = rows.filter(row => {
23
+ const ids = JSON.parse(row.run_ids);
24
+ if (!Array.isArray(ids) || ids.some(id => typeof id !== 'string'))
25
+ throw new Error('Unknown delivery run ownership');
26
+ return ids.includes(runId);
27
+ });
28
+ return { pending: relevant.some(row => row.operation_token !== null || !['completed', 'merged', 'discarded', 'superseded'].includes(row.decision)),
29
+ discarded: relevant.length > 0 && relevant.every(row => ['discarded', 'superseded'].includes(row.decision)) };
30
+ }
31
+ function runtimeRetentionHostPorts(host, policy, now) {
32
+ const owner = 'retention:' + (0, node_crypto_1.randomUUID)();
33
+ return {
34
+ async runIds() {
35
+ // Children settle later than parents. Expire a child before reconsidering
36
+ // its parent's lineage; unexpired descendants continue protecting it.
37
+ return host.db.prepare("SELECT id FROM loop_runs WHERE project_id=? AND status='completed' ORDER BY finished_at DESC,id").all(host.projectId)
38
+ .map(row => row.id).filter(id => !(0, agent_runtime_retention_records_1.readRuntimeExpiration)(host.db, id) && node_fs_1.default.existsSync(node_path_1.default.join(host.pipelineRoot, id, 'agent-runtime-request.json')));
39
+ },
40
+ async reserve(runId) {
41
+ if (host.active(runId))
42
+ return null;
43
+ const scope = host.scope(runId);
44
+ const claim = (0, loop_runs_store_1.claimDefinitionExecution)(host.db, runId, { owner, repositoryMounts: scope.repositoryMounts });
45
+ if (!claim.ok)
46
+ return null;
47
+ try {
48
+ const state = await scope.inspect(), row = (0, loop_runs_store_1.getLoopRun)(host.db, runId);
49
+ if (!state || state.runId !== runId || !row || row.project_id !== host.projectId || (0, loop_runs_store_1.readDefinitionExecutionClaim)(host.db, runId)?.owner !== owner)
50
+ throw new Error('Runtime retention authority changed');
51
+ const job = host.db.prepare('SELECT status FROM jobs WHERE id=?').get(runId);
52
+ const delivery = deliveryState(host.db, runId);
53
+ const terminal = ['succeeded', 'cancelled', 'failed'].includes(state.status);
54
+ const settled = row.status === 'completed' && job?.status === 'completed' && row.final_outcome === 'success' && state.status === 'succeeded';
55
+ const discarded = row.status === 'completed' && delivery.discarded && terminal;
56
+ const fork = (0, loop_runs_store_1.readDefinitionForkTarget)(host.db, runId);
57
+ const evidence = {
58
+ runId, disposition: settled ? 'settled' : discarded ? 'discarded' : 'recoverable',
59
+ terminalAt: terminalTime(row.finished_at), active: host.active(runId) || row.status !== 'completed' || !terminal || state.lease?.active === true || state.engineVersion === 2 && state.lease === undefined,
60
+ humanPending: state.engineVersion === 2 && !Array.isArray(state.pendingInterrupts) || !!state.pendingQuestion || !!state.pendingApproval || !!state.pendingInterrupts?.length,
61
+ interruptedWrites: state.engineVersion === 2 && !Array.isArray(state.recoverableSteps) || !!state.recoverableSteps?.length || Object.values(state.steps).some(step => ['running', 'interrupted', 'paused'].includes(step.status)),
62
+ deliveryPending: delivery.pending || !job || !['completed', 'failed', 'canceled', 'cancelled'].includes(job.status),
63
+ forkPending: !!fork && !(0, agent_runtime_retention_records_1.readRuntimeExpiration)(host.db, fork),
64
+ };
65
+ return { evidence, ...(0, agent_runtime_retention_quarantine_1.runtimeJournalQuarantine)(host.db, host.pipelineRoot, {
66
+ runId, expiredAt: new Date(now).toISOString(), disposition: discarded ? 'discarded' : 'settled', previousStatus: state.status,
67
+ summary: { status: state.status, engineVersion: state.engineVersion ?? 1, nextStep: null, historical: true },
68
+ }), release: claim.release };
69
+ }
70
+ catch (error) {
71
+ claim.release();
72
+ throw error;
73
+ }
74
+ },
75
+ async collectUnreferencedPackages(dryRun) {
76
+ return policy.days === null ? [] : (0, agent_runtime_package_1.collectUnreferencedRuntimePackages)(host.pipelineRoot, { dryRun, minimumAgeMs: policy.days * 86_400_000, now });
77
+ },
78
+ };
79
+ }
80
+ function recoverHostRuntimeRetention(host) {
81
+ return (0, agent_runtime_retention_quarantine_1.recoverRuntimeQuarantine)(host.db, host.pipelineRoot, runId => {
82
+ if (host.active(runId))
83
+ return null;
84
+ const claim = (0, loop_runs_store_1.claimDefinitionExecution)(host.db, runId, { owner: 'retention-recovery:' + (0, node_crypto_1.randomUUID)(), repositoryMounts: [] });
85
+ return claim.ok ? claim.release : null;
86
+ });
87
+ }
@@ -0,0 +1,231 @@
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.runtimeJournalQuarantine = runtimeJournalQuarantine;
7
+ exports.recoverRuntimeQuarantine = recoverRuntimeQuarantine;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_crypto_1 = require("node:crypto");
11
+ const agent_runtime_retention_records_1 = require("./agent-runtime-retention-records");
12
+ /*
13
+ * Durable quarantine protocol. Every crash boundary leaves one recognizable state:
14
+ *
15
+ * 1. `.retention/.staging-<token>/` holds at most a partial intent. The original
16
+ * journal was not moved yet, so recovery deletes only the staging directory.
17
+ * 2. `.retention/<token>/intent.json` is published by an atomic directory rename.
18
+ * Before the journal move the original is intact and still has the recorded
19
+ * identity; recovery removes only the intent.
20
+ * 3. `<token>/run` is the moved journal (same inode). Without an expiration
21
+ * record recovery restores it; with an owning record it finishes deletion.
22
+ * 4. Deletion removes the journal first and the intent last, so an interrupted
23
+ * deletion always keeps the intent that authorizes it. A directory without
24
+ * an intent is removed only when it is empty.
25
+ * 5. Restoration moves the journal back first; before the intent is removed the
26
+ * original must still have the recorded identity. Replacement data is never
27
+ * deleted or overwritten.
28
+ */
29
+ const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
30
+ const TOKEN = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
31
+ const STAGING = /^\.staging-([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/;
32
+ function realDirectory(directory) {
33
+ const stat = node_fs_1.default.lstatSync(directory);
34
+ if (!stat.isDirectory() || stat.isSymbolicLink())
35
+ throw new Error('Runtime retention cannot cross a storage link');
36
+ }
37
+ function syncDirectory(directory) {
38
+ // Directory handles cannot be flushed on Windows; NTFS journals the rename itself.
39
+ if (process.platform === 'win32')
40
+ return;
41
+ const handle = node_fs_1.default.openSync(directory, 'r');
42
+ try {
43
+ node_fs_1.default.fsyncSync(handle);
44
+ }
45
+ finally {
46
+ node_fs_1.default.closeSync(handle);
47
+ }
48
+ }
49
+ function locations(pipeline, runId, token) {
50
+ if (!RUN_ID.test(runId) || !TOKEN.test(token))
51
+ throw new Error('Invalid runtime retention identity');
52
+ realDirectory(pipeline);
53
+ const root = node_path_1.default.join(pipeline, '.retention');
54
+ if (node_fs_1.default.existsSync(root))
55
+ realDirectory(root);
56
+ const directory = node_path_1.default.join(root, token);
57
+ if (node_fs_1.default.existsSync(directory))
58
+ realDirectory(directory);
59
+ return { original: node_path_1.default.join(pipeline, runId), root, directory, staging: node_path_1.default.join(root, `.staging-${token}`), journal: node_path_1.default.join(directory, 'run'), intent: node_path_1.default.join(directory, 'intent.json') };
60
+ }
61
+ function identity(directory) {
62
+ realDirectory(directory);
63
+ const stat = node_fs_1.default.statSync(directory, { bigint: true });
64
+ if (stat.ino === 0n)
65
+ throw new Error('Runtime journal filesystem identity is unavailable');
66
+ return { dev: String(stat.dev), ino: String(stat.ino) };
67
+ }
68
+ const sameIdentity = (a, b) => a.dev === b.dev && a.ino === b.ino;
69
+ function readIntent(file) {
70
+ const stat = node_fs_1.default.lstatSync(file);
71
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16_384)
72
+ throw new Error('Invalid runtime quarantine intent');
73
+ const intent = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
74
+ if (intent?.schemaVersion !== 1 || typeof intent.runId !== 'string' || typeof intent.token !== 'string' || !intent.journalIdentity ||
75
+ typeof intent.journalIdentity.dev !== 'string' || typeof intent.journalIdentity.ino !== 'string')
76
+ throw new Error('Runtime quarantine intent identity changed');
77
+ return intent;
78
+ }
79
+ function inspectIntent(files, runId, token) {
80
+ const intent = readIntent(files.intent);
81
+ if (intent.runId !== runId || intent.token !== token)
82
+ throw new Error('Runtime quarantine intent identity changed');
83
+ if (node_fs_1.default.readdirSync(files.directory).some(name => !['intent.json', 'run'].includes(name)))
84
+ throw new Error('Unknown files in runtime quarantine');
85
+ if (node_fs_1.default.existsSync(files.journal) && !sameIdentity(identity(files.journal), intent.journalIdentity))
86
+ throw new Error('Runtime quarantine journal was replaced');
87
+ return intent;
88
+ }
89
+ /** Journal first, intent last: every interruption keeps the deletion authority. */
90
+ function deleteQuarantine(files, runId, token) {
91
+ if (!node_fs_1.default.existsSync(files.directory))
92
+ return;
93
+ if (node_fs_1.default.existsSync(files.intent)) {
94
+ inspectIntent(files, runId, token);
95
+ node_fs_1.default.rmSync(files.journal, { recursive: true, force: true });
96
+ node_fs_1.default.rmSync(files.intent, { force: true });
97
+ }
98
+ // Fails with ENOTEMPTY rather than deleting data this protocol did not create.
99
+ node_fs_1.default.rmdirSync(files.directory);
100
+ }
101
+ /** Journal back first; the intent stays until the restored original is proven. */
102
+ function restoreQuarantine(files, intent) {
103
+ if (node_fs_1.default.existsSync(files.journal)) {
104
+ if (node_fs_1.default.existsSync(files.original))
105
+ throw new Error('Original runtime location was replaced; recovery must not overwrite it');
106
+ realDirectory(files.journal);
107
+ node_fs_1.default.renameSync(files.journal, files.original);
108
+ syncDirectory(node_path_1.default.dirname(files.original));
109
+ }
110
+ if (!node_fs_1.default.existsSync(files.original) || !sameIdentity(identity(files.original), intent.journalIdentity))
111
+ throw new Error('Restored runtime journal identity changed; quarantine retained for review');
112
+ if (node_fs_1.default.readdirSync(files.directory).some(name => name !== 'intent.json'))
113
+ throw new Error('Unknown files in runtime quarantine');
114
+ node_fs_1.default.rmSync(files.intent);
115
+ node_fs_1.default.rmdirSync(files.directory);
116
+ }
117
+ /** Host execution/delivery reservations must be held for the complete lifecycle.
118
+ * Only journal storage is moved; no path from frozen repository metadata is used. */
119
+ function runtimeJournalQuarantine(db, pipeline, record) {
120
+ const token = (0, node_crypto_1.randomUUID)(), files = locations(pipeline, record.runId, token);
121
+ const own = (value) => { if (value !== token)
122
+ throw new Error('Runtime quarantine ownership changed'); return locations(pipeline, record.runId, token); };
123
+ return {
124
+ async quarantine() {
125
+ realDirectory(files.original);
126
+ node_fs_1.default.mkdirSync(files.root, { recursive: true, mode: 0o700 });
127
+ realDirectory(files.root);
128
+ node_fs_1.default.mkdirSync(files.staging, { mode: 0o700 });
129
+ const intent = { schemaVersion: 1, runId: record.runId, token, journalIdentity: identity(files.original) };
130
+ const handle = node_fs_1.default.openSync(node_path_1.default.join(files.staging, 'intent.json'), 'wx', 0o600);
131
+ try {
132
+ node_fs_1.default.writeFileSync(handle, JSON.stringify(intent));
133
+ node_fs_1.default.fsyncSync(handle);
134
+ }
135
+ finally {
136
+ node_fs_1.default.closeSync(handle);
137
+ }
138
+ // Publication is atomic: recovery sees either staging or a complete intent.
139
+ node_fs_1.default.renameSync(files.staging, files.directory);
140
+ syncDirectory(files.root);
141
+ node_fs_1.default.renameSync(files.original, files.journal);
142
+ syncDirectory(pipeline);
143
+ return { token };
144
+ },
145
+ async expire(value) {
146
+ const target = own(value), intent = inspectIntent(target, record.runId, token);
147
+ if (!node_fs_1.default.existsSync(target.journal) || !sameIdentity(identity(target.journal), intent.journalIdentity))
148
+ throw new Error('Runtime quarantine journal is not owned');
149
+ (0, agent_runtime_retention_records_1.recordRuntimeExpiration)(db, { ...record, quarantineToken: token });
150
+ },
151
+ async restore(value) {
152
+ const target = own(value);
153
+ if ((0, agent_runtime_retention_records_1.readRuntimeExpiration)(db, record.runId))
154
+ throw new Error('Expired runtime cannot be restored as executable history');
155
+ restoreQuarantine(target, inspectIntent(target, record.runId, token));
156
+ },
157
+ async remove(value) {
158
+ const target = own(value), expired = (0, agent_runtime_retention_records_1.readRuntimeExpiration)(db, record.runId);
159
+ if (!expired || expired.quarantineToken !== token)
160
+ throw new Error('Runtime expiration is not durable');
161
+ deleteQuarantine(target, record.runId, token);
162
+ },
163
+ };
164
+ }
165
+ /** Recovery runs under a host reservation supplied for each intent. A live
166
+ * collection must return null; no age/PID heuristic takes over its ownership.
167
+ * One damaged entry is reported and left in place without blocking the others;
168
+ * package collection refuses to run while any quarantine entry remains. */
169
+ function recoverRuntimeQuarantine(db, pipeline, reserve) {
170
+ const result = { restored: [], removed: [], protected: [], errors: [] };
171
+ const root = node_path_1.default.join(pipeline, '.retention');
172
+ if (!node_fs_1.default.existsSync(root))
173
+ return result;
174
+ realDirectory(pipeline);
175
+ realDirectory(root);
176
+ for (const entry of node_fs_1.default.readdirSync(root, { withFileTypes: true })) {
177
+ try {
178
+ if (!entry.isDirectory() || entry.isSymbolicLink())
179
+ throw new Error(`Unknown runtime quarantine entry ${entry.name}`);
180
+ const directory = node_path_1.default.join(root, entry.name), staging = STAGING.exec(entry.name);
181
+ if (staging) {
182
+ // Unpublished: the original journal was never moved.
183
+ if (node_fs_1.default.readdirSync(directory).some(name => name !== 'intent.json'))
184
+ throw new Error('Unknown files in unpublished runtime quarantine');
185
+ node_fs_1.default.rmSync(node_path_1.default.join(directory, 'intent.json'), { force: true });
186
+ node_fs_1.default.rmdirSync(directory);
187
+ continue;
188
+ }
189
+ if (!TOKEN.test(entry.name))
190
+ throw new Error(`Unknown runtime quarantine entry ${entry.name}`);
191
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(directory, 'intent.json'))) {
192
+ // Deletion removed the intent last, or a pre-protocol crash left an empty
193
+ // directory. Anything else has no authority and is kept.
194
+ if (node_fs_1.default.readdirSync(directory).length)
195
+ throw new Error('Runtime quarantine has data without an intent');
196
+ node_fs_1.default.rmdirSync(directory);
197
+ continue;
198
+ }
199
+ const intent = readIntent(node_path_1.default.join(directory, 'intent.json'));
200
+ if (intent.token !== entry.name)
201
+ throw new Error('Runtime quarantine intent identity changed');
202
+ const files = locations(pipeline, intent.runId, intent.token);
203
+ inspectIntent(files, intent.runId, intent.token);
204
+ const release = reserve(intent.runId);
205
+ if (!release) {
206
+ result.protected.push(intent.runId);
207
+ continue;
208
+ }
209
+ try {
210
+ const expired = (0, agent_runtime_retention_records_1.readRuntimeExpiration)(db, intent.runId);
211
+ if (expired) {
212
+ if (expired.quarantineToken !== intent.token)
213
+ throw new Error('Runtime expiration does not own this quarantine');
214
+ deleteQuarantine(files, intent.runId, intent.token);
215
+ result.removed.push(intent.runId);
216
+ }
217
+ else {
218
+ restoreQuarantine(files, intent);
219
+ result.restored.push(intent.runId);
220
+ }
221
+ }
222
+ finally {
223
+ release();
224
+ }
225
+ }
226
+ catch (error) {
227
+ result.errors.push(`${entry.name}: ${error instanceof Error ? error.message : 'Runtime quarantine recovery failed'}`);
228
+ }
229
+ }
230
+ return result;
231
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readRuntimeExpiration = readRuntimeExpiration;
4
+ exports.recordRuntimeExpiration = recordRuntimeExpiration;
5
+ exports.readRuntimeRetentionPolicy = readRuntimeRetentionPolicy;
6
+ exports.saveRuntimeRetentionPolicy = saveRuntimeRetentionPolicy;
7
+ const agent_runtime_retention_1 = require("./agent-runtime-retention");
8
+ function readRuntimeExpiration(db, runId) {
9
+ const row = db.prepare('SELECT * FROM runtime_retention_records WHERE run_id=?').get(runId);
10
+ return row ? { runId: row.run_id, expiredAt: row.expired_at, disposition: row.disposition, previousStatus: row.previous_status, quarantineToken: row.quarantine_token, summary: JSON.parse(row.summary_json) } : null;
11
+ }
12
+ function recordRuntimeExpiration(db, value) {
13
+ db.transaction(() => {
14
+ const previous = readRuntimeExpiration(db, value.runId);
15
+ if (previous) {
16
+ if (previous.quarantineToken !== value.quarantineToken)
17
+ throw new Error('Runtime expiration identity changed');
18
+ return;
19
+ }
20
+ db.prepare('INSERT INTO runtime_retention_records(run_id,expired_at,disposition,previous_status,quarantine_token,summary_json) VALUES (?,?,?,?,?,?)')
21
+ .run(value.runId, value.expiredAt, value.disposition, value.previousStatus, value.quarantineToken, JSON.stringify(value.summary));
22
+ }).immediate();
23
+ }
24
+ function readRuntimeRetentionPolicy(db) {
25
+ const row = db.prepare('SELECT days FROM runtime_retention_policy WHERE id=1').get();
26
+ return row ? (0, agent_runtime_retention_1.validateRuntimeRetentionPolicy)(row) : { days: null };
27
+ }
28
+ function saveRuntimeRetentionPolicy(db, value) {
29
+ const policy = (0, agent_runtime_retention_1.validateRuntimeRetentionPolicy)(value);
30
+ db.prepare('INSERT INTO runtime_retention_policy(id,days) VALUES (1,?) ON CONFLICT(id) DO UPDATE SET days=excluded.days').run(policy.days);
31
+ return policy;
32
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateRuntimeRetentionPolicy = validateRuntimeRetentionPolicy;
4
+ exports.runtimeRetentionDecision = runtimeRetentionDecision;
5
+ exports.collectRuntimeRetention = collectRuntimeRetention;
6
+ const DAY_MS = 86_400_000;
7
+ function validateRuntimeRetentionPolicy(value) {
8
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(key => key !== 'days'))
9
+ throw new Error('Retention policy requires only days');
10
+ const days = value.days;
11
+ if (days !== null && (!Number.isSafeInteger(days) || days < 1 || days > 3650))
12
+ throw new Error('Retention days must be null or an integer from 1 to 3650');
13
+ return { days };
14
+ }
15
+ function runtimeRetentionDecision(evidence, policy, now) {
16
+ validateRuntimeRetentionPolicy(policy);
17
+ if (!Number.isFinite(now))
18
+ throw new Error('Retention requires a valid clock');
19
+ const reasons = [];
20
+ if (policy.days === null)
21
+ reasons.push('disabled');
22
+ if (evidence.disposition !== 'settled' && evidence.disposition !== 'discarded')
23
+ reasons.push('recoverable');
24
+ if (evidence.active !== false)
25
+ reasons.push('active');
26
+ if (evidence.humanPending !== false)
27
+ reasons.push('human_pending');
28
+ if (evidence.interruptedWrites !== false)
29
+ reasons.push('interrupted_writes');
30
+ if (evidence.deliveryPending !== false)
31
+ reasons.push('delivery_pending');
32
+ if (evidence.forkPending !== false)
33
+ reasons.push('fork_pending');
34
+ const at = evidence.terminalAt === null ? NaN : Date.parse(evidence.terminalAt);
35
+ if (!Number.isFinite(at) || at > now)
36
+ reasons.push('unknown_terminal_time');
37
+ else if (policy.days !== null && now - at < policy.days * DAY_MS)
38
+ reasons.push('within_retention');
39
+ return { runId: evidence.runId, collect: reasons.length === 0, reasons };
40
+ }
41
+ /** Failure boundaries are intentional: never delete before durable expiration,
42
+ * never resurrect an expired run when cleanup fails, and never collect packages
43
+ * after a run boundary whose integrity could not be established. */
44
+ async function collectRuntimeRetention(ports, policy, options) {
45
+ validateRuntimeRetentionPolicy(policy);
46
+ const report = { dryRun: options.dryRun, runs: [], packages: [], errors: [] };
47
+ let packageCollectionSafe = true;
48
+ for (const runId of [...new Set(await ports.runIds())]) {
49
+ if (policy.days === null) {
50
+ report.runs.push({ runId, collect: false, reasons: ['disabled'], state: 'protected' });
51
+ continue;
52
+ }
53
+ let reservation = null;
54
+ try {
55
+ reservation = await ports.reserve(runId);
56
+ if (!reservation) {
57
+ report.runs.push({ runId, collect: false, reasons: [], state: 'busy' });
58
+ continue;
59
+ }
60
+ if (reservation.evidence.runId !== runId)
61
+ throw new Error('Retention reservation identity changed');
62
+ const decision = runtimeRetentionDecision(reservation.evidence, policy, options.now);
63
+ if (!decision.collect || options.dryRun) {
64
+ report.runs.push({ ...decision, state: decision.collect ? 'eligible' : 'protected' });
65
+ continue;
66
+ }
67
+ const { token } = await reservation.quarantine();
68
+ try {
69
+ await reservation.expire(token);
70
+ }
71
+ catch (error) {
72
+ await reservation.restore(token);
73
+ throw error;
74
+ }
75
+ try {
76
+ await reservation.remove(token);
77
+ report.runs.push({ ...decision, state: 'expired' });
78
+ }
79
+ catch (error) {
80
+ // Restart recovery retries only deletion, never original-run execution.
81
+ report.runs.push({ ...decision, state: 'cleanup_pending', error: error instanceof Error ? error.message : 'Runtime cleanup failed' });
82
+ packageCollectionSafe = false;
83
+ }
84
+ }
85
+ catch (error) {
86
+ packageCollectionSafe = false;
87
+ report.runs.push({ runId, collect: false, reasons: [], state: 'error', error: error instanceof Error ? error.message : 'Runtime retention failed' });
88
+ }
89
+ finally {
90
+ reservation?.release();
91
+ }
92
+ }
93
+ if (packageCollectionSafe && policy.days !== null) {
94
+ try {
95
+ report.packages = await ports.collectUnreferencedPackages(options.dryRun);
96
+ }
97
+ catch (error) {
98
+ report.errors.push(error instanceof Error ? error.message : 'Package collection failed');
99
+ }
100
+ }
101
+ return report;
102
+ }