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
@@ -15,9 +15,10 @@ function withDefaultRoleModels(body) {
15
15
  if (!agents || typeof agents !== 'object' || !Array.isArray(providers))
16
16
  return body;
17
17
  // Only LOCAL roles: a CLI role saved without a model keeps meaning "provider default" (never frozen into the file).
18
+ const roles = body.roles && typeof body.roles === 'object' && !Array.isArray(body.roles) ? structuredClone(body.roles) : undefined;
18
19
  const fixer = body.fixer && typeof body.fixer === 'object' ? structuredClone(body.fixer) : undefined;
19
- const filled = (0, agent_runtime_effective_config_1.fillDefaultRoleModels)({ agents: structuredClone(agents), providers: providers, ...(fixer ? { fixer: fixer } : {}) }, { localOnly: true });
20
- return { ...body, agents: filled.agents, ...(filled.fixer ? { fixer: filled.fixer } : {}) };
20
+ const filled = (0, agent_runtime_effective_config_1.fillDefaultRoleModels)({ agents: structuredClone(agents), providers: providers, ...(roles ? { roles: roles } : {}), ...(fixer ? { fixer: fixer } : {}) }, { localOnly: true });
21
+ return { ...body, agents: filled.agents, ...(filled.roles ? { roles: filled.roles } : {}), ...(filled.fixer ? { fixer: filled.fixer } : {}) };
21
22
  }
22
23
  const loop_role_engines_1 = require("../../loops/runtime/loop-role-engines");
23
24
  function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
@@ -62,12 +63,15 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
62
63
  const project = ctx(_req).project;
63
64
  const configured = (0, node_fs_1.existsSync)((0, agent_runtime_settings_1.agentRuntimeConfigPath)(project));
64
65
  const config = (0, agent_runtime_settings_1.loadAgentRuntimeConfig)(project);
65
- let efficiencyAvailable = false;
66
+ let efficiencyAvailable = false, openRolesAvailable = false;
66
67
  try {
67
- efficiencyAvailable = (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).api?.capabilities?.efficientRoleExecution === 1;
68
+ const capabilities = (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).api?.capabilities;
69
+ efficiencyAvailable = capabilities?.efficientRoleExecution === 1;
70
+ openRolesAvailable = capabilities?.openRoles === 1;
68
71
  }
69
72
  catch { /* Ordinary settings remain readable without capability support. */ }
70
- res.json({ configured, config, runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null, efficiencyAvailable });
73
+ res.json({ configured, config, runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null, efficiencyAvailable, openRolesAvailable,
74
+ workflowRoleDefaults: openRolesAvailable && config ? (0, agent_runtime_effective_config_1.workflowRoleDefaults)(config) : {} });
71
75
  }
72
76
  catch (err) {
73
77
  const validation = err instanceof agent_runtime_settings_1.AgentRuntimeConfigError;
@@ -127,7 +131,7 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
127
131
  res.status(503).json({ error: 'runtime_unavailable', message: 'Update Core to an installation with the agent runtime before enabling it' });
128
132
  return;
129
133
  }
130
- let efficiencyAvailable = false;
134
+ let efficiencyAvailable = false, openRolesAvailable = false;
131
135
  if (config.enabled && runtimeAvailable) {
132
136
  try {
133
137
  const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
@@ -135,6 +139,7 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
135
139
  runtime.validateRuntimeConfig(coreConfig);
136
140
  (0, agent_runtime_loader_1.validateRequestedRoleEfforts)(runtime, coreConfig);
137
141
  efficiencyAvailable = runtime.api?.capabilities?.efficientRoleExecution === 1;
142
+ openRolesAvailable = runtime.api?.capabilities?.openRoles === 1;
138
143
  }
139
144
  catch {
140
145
  res.status(503).json({ error: 'runtime_incompatible', message: 'The installed Core runtime could not validate this configuration; update Core and retry' });
@@ -142,7 +147,7 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
142
147
  }
143
148
  }
144
149
  const saved = (0, agent_runtime_settings_1.saveAgentRuntimeConfig)(ctx(req).project, config);
145
- res.json({ configured: true, config: saved, runtimeAvailable, efficiencyAvailable });
150
+ res.json({ configured: true, config: saved, runtimeAvailable, efficiencyAvailable, openRolesAvailable });
146
151
  }
147
152
  catch (err) {
148
153
  const validation = err instanceof agent_runtime_settings_1.AgentRuntimeConfigError;
@@ -3,7 +3,7 @@ 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.AgentRuntimeConfigError = exports.REVIEW_THRESHOLD_FLOORS = exports.MAX_OUTPUT_TOKENS_MAX = exports.MAX_OUTPUT_TOKENS_MIN = exports.CONTEXT_WINDOW_TOKENS_MAX = exports.CONTEXT_WINDOW_TOKENS_MIN = void 0;
6
+ exports.AgentRuntimeConfigError = exports.REVIEW_THRESHOLD_FLOORS = exports.MAX_OUTPUT_TOKENS_MAX = exports.MAX_OUTPUT_TOKENS_MIN = exports.CONTEXT_WINDOW_TOKENS_MAX = exports.CONTEXT_WINDOW_TOKENS_MIN = exports.RUNTIME_ROLE_ID = void 0;
7
7
  exports.stripDesktopConnectionFields = stripDesktopConnectionFields;
8
8
  exports.coreConnectionFieldGates = coreConnectionFieldGates;
9
9
  exports.validateAgentRuntimeConfig = validateAgentRuntimeConfig;
@@ -26,9 +26,14 @@ const node_path_1 = __importDefault(require("node:path"));
26
26
  const node_os_1 = __importDefault(require("node:os"));
27
27
  const node_crypto_1 = require("node:crypto");
28
28
  const ajv_1 = __importDefault(require("ajv"));
29
+ const node_util_1 = require("node:util");
29
30
  const agent_runtime_schema_json_1 = __importDefault(require("../../../schemas/agent-runtime.schema.json"));
30
31
  const workspace_resolution_1 = require("../../../workspace-resolution");
31
32
  const registry_1 = require("../../../providers/registry");
33
+ const agent_catalog_1 = require("../../agents/runtime/agent-catalog");
34
+ const agent_role_descriptor_1 = require("../../agents/runtime/agent-role-descriptor");
35
+ var agent_role_descriptor_2 = require("../../agents/runtime/agent-role-descriptor");
36
+ Object.defineProperty(exports, "RUNTIME_ROLE_ID", { enumerable: true, get: function () { return agent_role_descriptor_2.RUNTIME_ROLE_ID; } });
32
37
  const DESKTOP_CONNECTION_FIELDS = ['label', 'defaultModel', 'rates'];
33
38
  /** Core-owned small-model fields: known only to cores advertising `capabilities.compactAgentLoop`
34
39
  * (`supportsReasoningEffort` moved here: core forwards it as OpenAI `reasoning_effort`). */
@@ -145,7 +150,7 @@ function validateAgentRuntimeConfig(input) {
145
150
  }
146
151
  const config = input;
147
152
  if (config.rolePrompts !== undefined)
148
- validateRuntimeRolePrompts(config.rolePrompts);
153
+ validateRuntimeRolePrompts(config.rolePrompts, Object.keys(config.roles ?? {}));
149
154
  if (new Set(config.providers.map(({ id }) => id)).size !== config.providers.length)
150
155
  throw new AgentRuntimeConfigError('Provider IDs must be unique');
151
156
  for (const provider of config.providers) {
@@ -165,8 +170,8 @@ function validateAgentRuntimeConfig(input) {
165
170
  if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || provider.baseUrl.includes('\0'))
166
171
  throw new AgentRuntimeConfigError('Provider URLs must use HTTP(S) without credentials, query parameters or fragments');
167
172
  }
168
- for (const role of [...ROLES, ...(config.fixer ? ['fixer'] : [])]) {
169
- const agent = role === 'fixer' ? config.fixer : config.agents[role];
173
+ const assignments = [...Object.entries(config.agents), ...Object.entries(config.roles ?? {}), ...(config.fixer ? [['fixer', config.fixer]] : [])];
174
+ for (const [role, agent] of assignments) {
170
175
  const provider = config.providers.find(({ id }) => id === agent.provider);
171
176
  if (!provider)
172
177
  throw new AgentRuntimeConfigError(`Role ${role} references a provider that is not configured`);
@@ -191,6 +196,22 @@ function validateAgentRuntimeConfig(input) {
191
196
  if (agent.thinking !== undefined && agent.thinking !== 'on' && agent.thinking !== 'off')
192
197
  throw new AgentRuntimeConfigError(`Role ${role} thinking must be "on" or "off"`);
193
198
  }
199
+ const builtinPolicies = {
200
+ architect: { access: 'read', artifacts: 'all', openspecSkill: 'openspec-ff-change' },
201
+ developer: { access: 'write', artifacts: 'tasks-checkboxes', openspecSkill: 'openspec-apply-change' },
202
+ reviewer: { access: 'read', artifacts: 'none', openspecSkill: 'openspec-verify-change' },
203
+ };
204
+ for (const [id, descriptor] of Object.entries(config.roles ?? {})) {
205
+ if (!agent_role_descriptor_1.RUNTIME_ROLE_ID.test(id) || id === 'fixer')
206
+ throw new AgentRuntimeConfigError(`Invalid custom role ID: ${id}`);
207
+ if (descriptor.prompt !== undefined && (!descriptor.prompt.trim() || descriptor.prompt.includes('\0')))
208
+ throw new AgentRuntimeConfigError(`Role ${id} requires a nonblank prompt without null bytes`);
209
+ if (ROLES.includes(id)) {
210
+ const { access, artifacts, openspecSkill, prompt, ...assignment } = descriptor;
211
+ if (prompt !== undefined || !(0, node_util_1.isDeepStrictEqual)(assignment, config.agents[id]) || !(0, node_util_1.isDeepStrictEqual)({ access, artifacts, openspecSkill }, builtinPolicies[id]))
212
+ throw new AgentRuntimeConfigError(`Built-in role ${id} must retain its implicit assignment and access policy`);
213
+ }
214
+ }
194
215
  for (const [key, value] of Object.entries(config.limits ?? {})) {
195
216
  if (!Number.isFinite(value) || (key !== 'maxCostUsd' && !Number.isSafeInteger(value)))
196
217
  throw new AgentRuntimeConfigError('Workflow limits must be finite, safe numbers');
@@ -301,7 +322,7 @@ function loadRuntimeConfigFile(file, fallbackProvider) {
301
322
  throw new AgentRuntimeConfigError('Configure project role models before using API-only connections');
302
323
  for (const role of ROLES)
303
324
  config.agents[role].provider = selected.id;
304
- return validateAgentRuntimeConfig({ ...config, providers });
325
+ return withCustomAgentRoles(validateAgentRuntimeConfig({ ...config, providers }), file, fallbackProvider);
305
326
  }
306
327
  if (Array.isArray(parsed.providers)) {
307
328
  const legacy = validateAgentRuntimeConfig(parsed);
@@ -309,9 +330,9 @@ function loadRuntimeConfigFile(file, fallbackProvider) {
309
330
  let connection = providers.find(item => item.id === original.id);
310
331
  if (connection && JSON.stringify(connection) !== JSON.stringify(original)) {
311
332
  const id = `${original.id.slice(0, 117)}-${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify(original)).digest('hex').slice(0, 10)}`;
312
- for (const role of ROLES)
313
- if (legacy.agents[role].provider === original.id)
314
- legacy.agents[role].provider = id;
333
+ for (const role of [...Object.values(legacy.agents), ...Object.values(legacy.roles ?? {}), ...(legacy.fixer ? [legacy.fixer] : [])])
334
+ if (role.provider === original.id)
335
+ role.provider = id;
315
336
  connection = providers.find(item => item.id === id);
316
337
  if (!connection)
317
338
  providers.push({ ...original, id });
@@ -324,7 +345,24 @@ function loadRuntimeConfigFile(file, fallbackProvider) {
324
345
  atomicJson(file, settings);
325
346
  parsed = settings;
326
347
  }
327
- return validateAgentRuntimeConfig({ ...parsed, enabled: true, providers });
348
+ return withCustomAgentRoles(validateAgentRuntimeConfig({ ...parsed, enabled: true, providers }), file, fallbackProvider);
349
+ }
350
+ /** Agent Studio documents supply defaults; explicit project roles always win. */
351
+ function withCustomAgentRoles(config, file, fallbackProvider) {
352
+ const connection = config.providers.find(connection => connection.id === config.agents.architect.provider);
353
+ const provider = fallbackProvider && (0, registry_1.hasAdapter)(fallbackProvider) && (0, registry_1.adapterKind)(fallbackProvider) !== 'local'
354
+ ? fallbackProvider : connection?.kind === 'cli' ? connection.cli : 'claude';
355
+ try {
356
+ const documents = (0, agent_catalog_1.listNativeAgentFiles)(node_path_1.default.dirname(node_path_1.default.dirname(file)), provider).filter(entry => entry.id.startsWith('custom-'))
357
+ .map(entry => ({ id: entry.id, content: node_fs_1.default.readFileSync(entry.file, 'utf8') }));
358
+ if (!documents.length)
359
+ return config;
360
+ const roles = (0, agent_role_descriptor_1.projectCustomAgentRoles)(documents, config.agents.architect, config.roles);
361
+ return validateAgentRuntimeConfig({ ...config, roles });
362
+ }
363
+ catch (error) {
364
+ throw new AgentRuntimeConfigError(error instanceof Error ? error.message : 'Custom agent roles could not be loaded');
365
+ }
328
366
  }
329
367
  /** Missing settings use the programmatic engine. Malformed files fail closed. */
330
368
  function loadAgentRuntimeConfig(project) {
@@ -339,12 +377,12 @@ function saveAgentRuntimeConfig(project, input) {
339
377
  return config;
340
378
  }
341
379
  const PROMPT_ROLES = [...ROLES, 'fixer'];
342
- function validateRuntimeRolePrompts(input) {
380
+ function validateRuntimeRolePrompts(input, extraRoles = []) {
343
381
  if (!input || typeof input !== 'object' || Array.isArray(input))
344
382
  throw new AgentRuntimeConfigError('Role prompts must be an object');
345
383
  const prompts = {};
346
384
  for (const [role, text] of Object.entries(input)) {
347
- if (!PROMPT_ROLES.includes(role) || typeof text !== 'string' || !text.trim() || text.length > 20000 || text.includes('\0'))
385
+ if (![...PROMPT_ROLES, ...extraRoles].includes(role) || typeof text !== 'string' || !text.trim() || text.length > 20000 || text.includes('\0'))
348
386
  throw new AgentRuntimeConfigError('Role prompts must use architect, developer, reviewer or fixer with 1–20000 nonblank characters and no null bytes');
349
387
  prompts[role] = text;
350
388
  }
@@ -382,14 +420,20 @@ function validateRuntimeProviderOverride(value) {
382
420
  /** Older Core runtimes reject unknown top-level keys: drop `guardrails` unless the loaded Core advertises `configurableGuardrails`. */
383
421
  function forCoreRuntime(config, capabilities) {
384
422
  let out = config;
423
+ if (capabilities?.openRoles !== 1 && out.roles !== undefined) {
424
+ if (Object.keys(out.roles).length)
425
+ throw new AgentRuntimeConfigError('Update the paired Core runtime to use custom roles');
426
+ const { roles: _roles, ...rest } = out;
427
+ out = rest;
428
+ }
385
429
  if (capabilities?.configurableGuardrails !== 1 && out.guardrails !== undefined) {
386
430
  const { guardrails: _dropped, ...rest } = out;
387
431
  out = rest;
388
432
  }
389
433
  // The per-role thinking switch is known only to cores advertising `roleThinkingControl`.
390
- if (capabilities?.roleThinkingControl !== 1 && (Object.values(out.agents ?? {}).some(agent => agent?.thinking !== undefined) || out.fixer?.thinking !== undefined)) {
434
+ if (capabilities?.roleThinkingControl !== 1 && ([...Object.values(out.agents ?? {}), ...Object.values(out.roles ?? {})].some(agent => agent?.thinking !== undefined) || out.fixer?.thinking !== undefined)) {
391
435
  const strip = (agent) => { const { thinking: _t, ...rest } = agent; return rest; };
392
- out = { ...out, ...(out.agents ? { agents: Object.fromEntries(Object.entries(out.agents).map(([role, agent]) => [role, strip(agent)])) } : {}), ...(out.fixer ? { fixer: strip(out.fixer) } : {}) };
436
+ out = { ...out, ...(out.agents ? { agents: Object.fromEntries(Object.entries(out.agents).map(([role, agent]) => [role, strip(agent)])) } : {}), ...(out.fixer ? { fixer: strip(out.fixer) } : {}), ...(out.roles ? { roles: Object.fromEntries(Object.entries(out.roles).map(([id, role]) => [id, strip(role)])) } : {}) };
393
437
  }
394
438
  return out;
395
439
  }
@@ -0,0 +1,32 @@
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.nativeAgentFile = nativeAgentFile;
7
+ exports.nativeAgentsDirectory = nativeAgentsDirectory;
8
+ exports.listNativeAgentFiles = listNativeAgentFiles;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const providers_1 = require("../../../providers");
12
+ /** Provider-native role storage shared by Agent Studio and runtime projection. */
13
+ function nativeAgentFile(root, agentId, provider) {
14
+ const adapter = (0, providers_1.getAdapter)(provider);
15
+ return adapter.customRolePath?.(root, agentId) ?? node_path_1.default.join(root, adapter.projectDirName, 'agents', `${agentId}.md`);
16
+ }
17
+ function nativeAgentsDirectory(root, provider) {
18
+ const probe = nativeAgentFile(root, '__catalog_probe__', provider);
19
+ return node_path_1.default.basename(probe) === 'SKILL.md' ? node_path_1.default.dirname(node_path_1.default.dirname(probe)) : node_path_1.default.dirname(probe);
20
+ }
21
+ function listNativeAgentFiles(root, provider) {
22
+ const directory = nativeAgentsDirectory(root, provider);
23
+ if (!node_fs_1.default.existsSync(directory))
24
+ return [];
25
+ return node_fs_1.default.readdirSync(directory, { withFileTypes: true }).flatMap(entry => {
26
+ const id = entry.isDirectory() ? entry.name : entry.isFile() && entry.name.endsWith('.md') ? entry.name.slice(0, -3) : undefined;
27
+ if (!id)
28
+ return [];
29
+ const file = nativeAgentFile(root, id, provider);
30
+ return node_fs_1.default.existsSync(file) ? [{ id, file }] : [];
31
+ }).sort((a, b) => a.id.localeCompare(b.id));
32
+ }
@@ -12,6 +12,8 @@ const path_1 = __importDefault(require("path"));
12
12
  const crypto_1 = require("crypto");
13
13
  const tree_kill_1 = __importDefault(require("tree-kill"));
14
14
  const agent_generator_1 = require("./agent-generator");
15
+ const agent_role_descriptor_1 = require("./agent-role-descriptor");
16
+ const kimi_skill_prompt_1 = require("../../../providers/kimi-skill-prompt");
15
17
  const ai_invocations_1 = require("../../accounting/runtime/ai-invocations");
16
18
  const result_event_1 = require("../../accounting/runtime/result-event");
17
19
  const spawn_lifecycle_1 = require("../../execution/runtime/spawn-lifecycle");
@@ -199,6 +201,16 @@ class AgentRefineManager {
199
201
  if (draftName && draftName !== currentName) {
200
202
  return { ok: false, reason: 'name_changed' };
201
203
  }
204
+ // Force only bypasses concurrent disk edits, never the execution contract.
205
+ try {
206
+ (0, agent_role_descriptor_1.projectCustomAgentRole)({ id: session.agent_id, content: session.draft_body }, { provider: this._adapter.id });
207
+ if (this._adapter.id === 'kimi' && (0, kimi_skill_prompt_1.validateKimiRoleDocument)(session.draft_body, session.agent_id, `${session.agent_id}/SKILL.md`).length > 0) {
208
+ return { ok: false, reason: 'invalid_agent_role' };
209
+ }
210
+ }
211
+ catch {
212
+ return { ok: false, reason: 'invalid_agent_role' };
213
+ }
202
214
  fs_1.default.writeFileSync(file, session.draft_body, 'utf8');
203
215
  const maxVersion = this._db
204
216
  .prepare(`SELECT COALESCE(MAX(version), 0) AS v FROM agent_versions
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RUNTIME_ROLE_ID = void 0;
4
+ exports.projectCustomAgentRole = projectCustomAgentRole;
5
+ exports.projectCustomAgentRoles = projectCustomAgentRoles;
6
+ const js_yaml_1 = require("js-yaml");
7
+ exports.RUNTIME_ROLE_ID = /^[a-z][a-z0-9-]{0,63}$/;
8
+ const BUILTINS = new Set(['architect', 'developer', 'reviewer', 'fixer']);
9
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
10
+ /** Pure frontmatter-to-role mapping; source access never follows model/prompt wording. */
11
+ function projectCustomAgentRole(document, fallback) {
12
+ const id = document.id.startsWith('custom-') ? document.id.slice(7) : '';
13
+ if (!exports.RUNTIME_ROLE_ID.test(id) || BUILTINS.has(id))
14
+ throw new Error(`Custom agent ${document.id} must map to a non-built-in role matching ${exports.RUNTIME_ROLE_ID.source}`);
15
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/.exec(document.content);
16
+ if (!match && document.content.startsWith('---'))
17
+ throw new Error(`Custom agent ${document.id} has incomplete YAML frontmatter`);
18
+ const metadata = match ? (0, js_yaml_1.load)(match[1], { schema: js_yaml_1.JSON_SCHEMA }) : {};
19
+ if (!object(metadata))
20
+ throw new Error(`Custom agent ${document.id} has invalid frontmatter`);
21
+ if (metadata.name !== undefined && metadata.name !== document.id)
22
+ throw new Error(`Custom agent ${document.id} frontmatter name differs from its file identity`);
23
+ const prompt = (match ? match[2] : document.content).trim();
24
+ if (!prompt || prompt.length > 20_000 || prompt.includes('\0'))
25
+ throw new Error(`Custom agent ${document.id} instructions must contain 1–20,000 characters`);
26
+ const access = metadata.access ?? 'read', artifacts = metadata.artifacts ?? 'none';
27
+ if (!['read', 'write'].includes(String(access)) || !['none', 'tasks-checkboxes', 'all'].includes(String(artifacts)))
28
+ throw new Error(`Custom agent ${document.id} declares invalid source or artifact access`);
29
+ if (metadata.openspecSkill !== undefined && !['openspec-ff-change', 'openspec-apply-change', 'openspec-verify-change'].includes(String(metadata.openspecSkill)))
30
+ throw new Error(`Custom agent ${document.id} declares an unsupported OpenSpec skill`);
31
+ const engine = metadata.engine === undefined ? {} : metadata.engine;
32
+ if (!object(engine) || Object.keys(engine).some(key => !['provider', 'model', 'effort', 'thinking', 'maxTurns', 'escalation'].includes(key)))
33
+ throw new Error(`Custom agent ${document.id} engine must contain only provider, model, effort, thinking, maxTurns and escalation`);
34
+ const provider = engine.provider ?? fallback.provider;
35
+ const model = engine.model ?? (metadata.model === 'inherit' ? undefined : metadata.model) ?? fallback.model;
36
+ const effort = engine.effort ?? metadata.effort ?? fallback.effort;
37
+ if (typeof provider !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(provider) ||
38
+ model !== undefined && (typeof model !== 'string' || !model || model.length > 256 || /^-|[\r\n\0]/.test(model)) ||
39
+ effort !== undefined && (typeof effort !== 'string' || !/^[a-z][a-z0-9_-]{0,31}$/.test(effort)) ||
40
+ engine.thinking !== undefined && !['on', 'off'].includes(String(engine.thinking)) ||
41
+ engine.maxTurns !== undefined && (!Number.isSafeInteger(engine.maxTurns) || engine.maxTurns < 1))
42
+ throw new Error(`Custom agent ${document.id} has invalid engine settings`);
43
+ const escalation = engine.escalation;
44
+ if (escalation !== undefined && (!object(escalation) || Object.keys(escalation).some(key => !['model', 'effort'].includes(key)) ||
45
+ typeof escalation.model !== 'string' || !escalation.model || escalation.model.length > 256 || /^-|[\r\n\0]/.test(escalation.model) ||
46
+ escalation.effort !== undefined && (typeof escalation.effort !== 'string' || !/^[a-z][a-z0-9_-]{0,31}$/.test(escalation.effort)) ||
47
+ !model || escalation.model === model && escalation.effort === effort))
48
+ throw new Error(`Custom agent ${document.id} escalation requires a valid, different model or effort and an explicit base model`);
49
+ return { id, role: { provider, ...(model === undefined ? {} : { model: model }), ...(effort === undefined ? {} : { effort: effort }),
50
+ access: access, artifacts: artifacts, prompt,
51
+ ...(metadata.openspecSkill === undefined ? {} : { openspecSkill: metadata.openspecSkill }),
52
+ ...(engine.thinking === undefined ? {} : { thinking: engine.thinking }), ...(engine.maxTurns === undefined ? {} : { maxTurns: engine.maxTurns }),
53
+ ...(escalation === undefined ? {} : { escalation: escalation }) } };
54
+ }
55
+ function projectCustomAgentRoles(documents, fallback, explicit = {}) {
56
+ const roles = {};
57
+ for (const document of documents) {
58
+ const { id, role } = projectCustomAgentRole(document, fallback);
59
+ if (Object.hasOwn(roles, id))
60
+ throw new Error(`Duplicate custom agent role ${id}`);
61
+ roles[id] = { ...role, ...explicit[id] };
62
+ }
63
+ return { ...roles, ...Object.fromEntries(Object.entries(explicit).filter(([id]) => !Object.hasOwn(roles, id))) };
64
+ }
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.providerSupportsAgentStudioAutomation = providerSupportsAgentStudioAutomation;
7
7
  exports.createProfilesRouter = createProfilesRouter;
8
8
  const fs_1 = __importDefault(require("fs"));
9
+ const agent_catalog_1 = require("./agent-catalog");
10
+ const agent_role_descriptor_1 = require("./agent-role-descriptor");
9
11
  const core_runtime_1 = require("../../../core-runtime");
10
12
  const path_1 = __importDefault(require("path"));
11
13
  const express_1 = require("express");
@@ -60,38 +62,13 @@ function projectAdapter(project, provider) {
60
62
  return (0, providers_1.getAdapter)(provider ?? project.provider ?? 'claude');
61
63
  }
62
64
  function agentFile(project, agentId, provider) {
63
- const root = specRoot(project);
64
- const adapter = projectAdapter(project, provider);
65
- return adapter.customRolePath?.(root, agentId)
66
- ?? path_1.default.join(root, adapter.projectDirName, 'agents', `${agentId}.md`);
65
+ return (0, agent_catalog_1.nativeAgentFile)(specRoot(project), agentId, provider ?? project.provider ?? 'claude');
67
66
  }
68
- /** The provider-native roles catalog directory. */
69
67
  function agentsCatalogDir(project, provider) {
70
- const probe = agentFile(project, '__catalog_probe__', provider);
71
- // File-based roles use `<catalog>/<id>.md`; skill-based roles (Kimi) use
72
- // `<catalog>/<id>/SKILL.md`.
73
- return path_1.default.basename(probe) === 'SKILL.md'
74
- ? path_1.default.dirname(path_1.default.dirname(probe))
75
- : path_1.default.dirname(probe);
68
+ return (0, agent_catalog_1.nativeAgentsDirectory)(specRoot(project), provider ?? project.provider ?? 'claude');
76
69
  }
77
70
  function listAgentFiles(project, provider) {
78
- const dir = agentsCatalogDir(project, provider);
79
- if (!fs_1.default.existsSync(dir))
80
- return [];
81
- const out = [];
82
- for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
83
- const id = entry.isDirectory()
84
- ? entry.name
85
- : entry.isFile() && entry.name.endsWith('.md')
86
- ? entry.name.slice(0, -3)
87
- : null;
88
- if (!id)
89
- continue;
90
- const file = agentFile(project, id, provider);
91
- if (fs_1.default.existsSync(file))
92
- out.push({ id, file });
93
- }
94
- return out;
71
+ return (0, agent_catalog_1.listNativeAgentFiles)(specRoot(project), provider ?? project.provider ?? 'claude');
95
72
  }
96
73
  const AGENTS_SECTION_ENABLED = process.env.SPECRAILS_AGENTS_SECTION !== 'false';
97
74
  function handleError(res, err) {
@@ -135,9 +112,14 @@ function requireSafeStudioPolicy(res, provider) {
135
112
  * leaf (fs/path/js-yaml only), so this direct import does not create a router /
136
113
  * adapter cycle. */
137
114
  function validateCustomRoleBody(provider, agentId, body) {
138
- if (provider !== 'kimi')
139
- return [];
140
- return (0, kimi_skill_prompt_1.validateKimiRoleDocument)(body, agentId, `${agentId}/SKILL.md`);
115
+ const errors = provider === 'kimi' ? (0, kimi_skill_prompt_1.validateKimiRoleDocument)(body, agentId, `${agentId}/SKILL.md`) : [];
116
+ try {
117
+ (0, agent_role_descriptor_1.projectCustomAgentRole)({ id: agentId, content: body }, { provider });
118
+ }
119
+ catch (error) {
120
+ errors.push(error instanceof Error ? error.message : 'Invalid Core role descriptor');
121
+ }
122
+ return errors;
141
123
  }
142
124
  function createProfilesRouter() {
143
125
  const router = (0, express_1.Router)({ mergeParams: true });
@@ -419,8 +401,18 @@ function createProfilesRouter() {
419
401
  continue;
420
402
  let description;
421
403
  let model;
404
+ let projected = {};
422
405
  try {
423
406
  const body = fs_1.default.readFileSync(entry.file, 'utf8');
407
+ if (kind === 'custom') {
408
+ try {
409
+ const result = (0, agent_role_descriptor_1.projectCustomAgentRole)({ id, content: body }, { provider });
410
+ projected = { roleId: result.id, runtimeRoleDefaults: result.role };
411
+ }
412
+ catch (error) {
413
+ projected = { runtimeRoleError: error instanceof Error ? error.message : 'Invalid Core role descriptor' };
414
+ }
415
+ }
424
416
  if (provider === 'kimi') {
425
417
  // Use the same js-yaml metadata parser as validation/execution so
426
418
  // folded/literal descriptions and quoted scalars render correctly.
@@ -466,7 +458,7 @@ function createProfilesRouter() {
466
458
  catch {
467
459
  // ignore unreadable files
468
460
  }
469
- agents.push({ id, kind, description, model });
461
+ agents.push({ id, kind, description, model, ...projected });
470
462
  }
471
463
  agents.sort((a, b) => a.id.localeCompare(b.id));
472
464
  res.json({ agents });
@@ -517,7 +509,7 @@ function createProfilesRouter() {
517
509
  }
518
510
  const roleErrors = validateCustomRoleBody(provider, id, body);
519
511
  if (roleErrors.length > 0) {
520
- res.status(400).json({ error: 'invalid_kimi_skill', details: roleErrors });
512
+ res.status(400).json({ error: provider === 'kimi' ? 'invalid_kimi_skill' : 'invalid_agent_role', details: roleErrors });
521
513
  return;
522
514
  }
523
515
  const file = agentFile(project, id, provider);
@@ -556,7 +548,7 @@ function createProfilesRouter() {
556
548
  }
557
549
  const roleErrors = validateCustomRoleBody(provider, agentId, body);
558
550
  if (roleErrors.length > 0) {
559
- res.status(400).json({ error: 'invalid_kimi_skill', details: roleErrors });
551
+ res.status(400).json({ error: provider === 'kimi' ? 'invalid_kimi_skill' : 'invalid_agent_role', details: roleErrors });
560
552
  return;
561
553
  }
562
554
  const file = agentFile(project, agentId, provider);