specrails-desktop 2.46.0 → 2.48.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.
- package/README.md +2 -0
- package/client/dist/assets/{ActivityFeedPage-CsHQPpvl.js → ActivityFeedPage-CGq6QhP5.js} +1 -1
- package/client/dist/assets/AgentBrowserCapture-C4GZ-kdf.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-_IPmL0ZS.js +2 -0
- package/client/dist/assets/AgentModeCodePane-B5WG5DOP.js +2 -0
- package/client/dist/assets/{AgentModeJobsPane-rnD-EPMd.js → AgentModeJobsPane-w1N_HIXh.js} +1 -1
- package/client/dist/assets/AgentsPage-CmwnnT46.js +87 -0
- package/client/dist/assets/AnalyticsPage-BUMsKtsJ.js +1 -0
- package/client/dist/assets/CodePage-PjgywJPS.js +3 -0
- package/client/dist/assets/DesktopAnalyticsPage-BXY1pH4y.js +1 -0
- package/client/dist/assets/DocsDialog-ccQETHKB.js +11 -0
- package/client/dist/assets/DocsPage-DzEgplB3.js +11 -0
- package/client/dist/assets/{ExportDropdown-pWZGYCv3.js → ExportDropdown-B8bBGopr.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-CEwGkJ8F.js +19 -0
- package/client/dist/assets/JobDetailModal-BYXmuTDe.js +1 -0
- package/client/dist/assets/JobDetailPage-CWp6MwJj.js +1 -0
- package/client/dist/assets/JobsPage-D1O1uyG8.js +1 -0
- package/client/dist/assets/{LoopBuilderPage-CtmOE4-b.js → LoopBuilderPage-DQE-7Vby.js} +2 -2
- package/client/dist/assets/{LoopPreviewModal-BYeLjQbr.js → LoopPreviewModal-Bk7zIXD5.js} +1 -1
- package/client/dist/assets/LoopsPage-CqG7mx56.js +1 -0
- package/client/dist/assets/MinimizedChatsContext-UpnqccPx.js +1 -0
- package/client/dist/assets/PluginsPage-CjgVvmpW.js +2 -0
- package/client/dist/assets/{ProjectSettingsDialog-DsCRIc4v.js → ProjectSettingsDialog-D6vBCoay.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-BQm_3TO8.js → RepositoryDeliveries-C36ipKp_.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-DnAFtjTz.js → RepositoryScopeSelector-0Gz_fR1E.js} +1 -1
- package/client/dist/assets/ReviewPacketPage-Dr9RYs7q.js +1 -0
- package/client/dist/assets/TemplatePreviewModal-sCMHYFJo.js +1 -0
- package/client/dist/assets/TicketDetailModalContext-C5GgJlEt.js +14 -0
- package/client/dist/assets/{Trans-BwLgX6n7.js → Trans-9pHTy2fH.js} +1 -1
- package/client/dist/assets/{addspec-D2YIQ0GU.js → addspec-BOvgmcgk.js} +1 -1
- package/client/dist/assets/{addspec-B_TPJt-R.js → addspec-Clmyfn_R.js} +1 -1
- package/client/dist/assets/{addspec-A1lDQa4M.js → addspec-CsNaBpHp.js} +1 -1
- package/client/dist/assets/{addspec-CQBjzt24.js → addspec-D2tvWmmV.js} +1 -1
- package/client/dist/assets/{addspec-CLYLvaVP.js → addspec-D3nFwvD0.js} +1 -1
- package/client/dist/assets/{addspec-BPpZ_olp.js → addspec-DVLEhK3w.js} +1 -1
- package/client/dist/assets/{addspec-iFdQ6D99.js → addspec-DXbK-tb0.js} +1 -1
- package/client/dist/assets/{addspec-BHZd0N5F.js → addspec-OHG8DFEn.js} +1 -1
- package/client/dist/assets/agentRuntime-BUimiX__.js +1 -0
- package/client/dist/assets/agentRuntime-BcN97sAB.js +1 -0
- package/client/dist/assets/agentRuntime-Ckyg-ahT.js +1 -0
- package/client/dist/assets/agentRuntime-DJCwyX4t.js +1 -0
- package/client/dist/assets/agentRuntime-DVzPJOl0.js +1 -0
- package/client/dist/assets/agentRuntime-V9AZTG3c.js +1 -0
- package/client/dist/assets/agentRuntime-bAAjw1vw.js +1 -0
- package/client/dist/assets/agentRuntime-v4qKAYH-.js +1 -0
- package/client/dist/assets/{agents-45GbINfh.js → agents-CA5eT1d0.js} +1 -1
- package/client/dist/assets/{agents-DMKFStHL.js → agents-CAgy-5uS.js} +1 -1
- package/client/dist/assets/{agents-IUOgqT5t.js → agents-CNm6Qzlv.js} +1 -1
- package/client/dist/assets/{agents-ms7TwG16.js → agents-CTwbx-fb.js} +1 -1
- package/client/dist/assets/{agents-DaYcxAr1.js → agents-DEGV1Vao.js} +1 -1
- package/client/dist/assets/{agents-BHp0_FQt.js → agents-HsDF1aQU.js} +1 -1
- package/client/dist/assets/{agents-Bvyi3y6R.js → agents-IQjdA-z9.js} +1 -1
- package/client/dist/assets/{agents-rzPnqoBR.js → agents-RkGoteXh.js} +1 -1
- package/client/dist/assets/analytics-BNgZ1Nj7.js +1 -0
- package/client/dist/assets/analytics-BlypLLR6.js +1 -0
- package/client/dist/assets/analytics-CoEzWPx4.js +1 -0
- package/client/dist/assets/analytics-DFh5lSOl.js +1 -0
- package/client/dist/assets/analytics-DZp-DS2U.js +1 -0
- package/client/dist/assets/analytics-DjLlE7jS.js +1 -0
- package/client/dist/assets/analytics-jzCkOec3.js +1 -0
- package/client/dist/assets/analytics-nZIjjtCc.js +1 -0
- package/client/dist/assets/{dashboard-BK_mez-z.js → dashboard-C29Qwa-L.js} +1 -1
- package/client/dist/assets/{dashboard-BwkjxI6H.js → dashboard-CSJzf5OT.js} +1 -1
- package/client/dist/assets/{dashboard-C3bDKf7s.js → dashboard-CTfEtkqX.js} +1 -1
- package/client/dist/assets/{dashboard-Bt0cCzeG.js → dashboard-Cr4D8oMn.js} +1 -1
- package/client/dist/assets/{dashboard-fRrP0Kv0.js → dashboard-Csf1yGSN.js} +1 -1
- package/client/dist/assets/{dashboard-CdvjGfog.js → dashboard-D40HP2ks.js} +1 -1
- package/client/dist/assets/dashboard-DBAP2_SQ.js +1 -0
- package/client/dist/assets/{dashboard-B14uYXkG.js → dashboard-DMklepmz.js} +1 -1
- package/client/dist/assets/file-text-CFT-xXKN.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-CeZv93iR.js → formatDistanceToNow-w_2U5pm0.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxAkjvJb.js → getTimezoneOffsetInMilliseconds-BRymg1Kk.js} +1 -1
- package/client/dist/assets/index-D0ScFZvR.css +2 -0
- package/client/dist/assets/index-DORCrgbI.js +77 -0
- package/client/dist/assets/jira-api-BqHbSdxK.js +1 -0
- package/client/dist/assets/jobs-C6qmvUAi.js +1 -0
- package/client/dist/assets/jobs-C9j2PuwW.js +1 -0
- package/client/dist/assets/jobs-DLHdDkHc.js +1 -0
- package/client/dist/assets/jobs-DSp9DoD_.js +1 -0
- package/client/dist/assets/jobs-DTQO08nJ.js +1 -0
- package/client/dist/assets/jobs-DoFaEl9g.js +1 -0
- package/client/dist/assets/jobs-Dpw6AEOP.js +1 -0
- package/client/dist/assets/jobs-DrF8Do8s.js +1 -0
- package/client/dist/assets/{narration-BSldQfjd.js → narration--hUhjM5v.js} +1 -1
- package/client/dist/assets/{narration-DKLjXIFI.js → narration-CHI06czm.js} +1 -1
- package/client/dist/assets/{narration-4OSg0b5-.js → narration-DjKB3pQZ.js} +1 -1
- package/client/dist/assets/{narration-BPMOQPFe.js → narration-DtCYvRLM.js} +1 -1
- package/client/dist/assets/{narration-D-e9ssxA.js → narration-DvLQBW9U.js} +1 -1
- package/client/dist/assets/{narration-DnrKC3mF.js → narration-eSx3gR0W.js} +1 -1
- package/client/dist/assets/{narration-Dypjxeel.js → narration-n3hOA7LY.js} +1 -1
- package/client/dist/assets/{narration-CvQdWdR5.js → narration-thJOeT2-.js} +1 -1
- package/client/dist/assets/packet-BNW8ODQw.js +1 -0
- package/client/dist/assets/packet-CXYvfdjy.js +1 -0
- package/client/dist/assets/packet-CtIlB8YE.js +1 -0
- package/client/dist/assets/packet-DA1SJkMH.js +1 -0
- package/client/dist/assets/packet-DYcWfBip.js +1 -0
- package/client/dist/assets/packet-DseX9yKC.js +1 -0
- package/client/dist/assets/packet-_m5aOy_d.js +1 -0
- package/client/dist/assets/packet-qOemUAm3.js +1 -0
- package/client/dist/assets/{project-repositories-BvBj9XH9.js → project-repositories-BsCs1PPA.js} +1 -1
- package/client/dist/assets/provider-capabilities-Vi1ZGvAK.js +1 -0
- package/client/dist/assets/settings-BsehKpty.js +1 -0
- package/client/dist/assets/settings-CQWob3SB.js +1 -0
- package/client/dist/assets/settings-CgCl0hzy.js +1 -0
- package/client/dist/assets/settings-DLRLd22L.js +1 -0
- package/client/dist/assets/settings-Dn5prAFl.js +1 -0
- package/client/dist/assets/settings-HZSu-K4l.js +1 -0
- package/client/dist/assets/{settings-DEgcA0jd.js → settings-p4OVIyRh.js} +1 -1
- package/client/dist/assets/settings-rddaldUp.js +1 -0
- package/client/dist/assets/setup-B5FHuxvB.js +1 -0
- package/client/dist/assets/setup-BihzXadx.js +1 -0
- package/client/dist/assets/setup-BtC6Hg4K.js +1 -0
- package/client/dist/assets/setup-C9Fy9PKQ.js +1 -0
- package/client/dist/assets/setup-CROyqD5N.js +1 -0
- package/client/dist/assets/setup-DpqzaEEA.js +1 -0
- package/client/dist/assets/setup-Dwzt5nO1.js +1 -0
- package/client/dist/assets/setup-zLEDnG7N.js +1 -0
- package/client/dist/assets/{spending-DNM6NAoA.js → spending-hcgxHP4O.js} +1 -1
- package/client/dist/assets/{useDesktop-mxHVQOdx.js → useDesktop-CAisjKWC.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-DnxHj8jP.js → useSharedWebSocket-BHIfW4QT.js} +2 -2
- package/client/dist/assets/wrench-BfmNO9Vw.js +1 -0
- package/client/dist/index.html +27 -26
- package/docs/guide/de/integrations/7-local-engines.md +37 -0
- package/docs/guide/en/integrations/7-local-engines.md +37 -0
- package/docs/guide/es/integrations/7-local-engines.md +37 -0
- package/docs/guide/fr/integrations/7-local-engines.md +37 -0
- package/docs/guide/it/integrations/7-local-engines.md +37 -0
- package/docs/guide/ja/integrations/7-local-engines.md +37 -0
- package/docs/guide/pt/integrations/7-local-engines.md +37 -0
- package/docs/guide/zh/integrations/7-local-engines.md +37 -0
- package/docs/internals/adding-a-provider.md +12 -0
- package/docs/internals/local-agent-runner.md +153 -0
- package/docs/internals/review-packet.md +8 -0
- package/docs/local-providers.md +215 -0
- package/docs/plans/implementation-efficiency-artifact.json +18 -0
- package/docs/plans/implementation-efficiency-evaluation.json +1817 -0
- package/docs/plans/implementation-efficiency-evaluation.md +41 -0
- package/docs/plans/implementation-efficiency-progress.md +48 -0
- package/docs/plans/implementation-efficiency-release.md +41 -0
- package/docs/plans/implementation-efficiency.md +124 -0
- package/local-runner/dist/specrails-local-runner.js +15815 -0
- package/package.json +7 -5
- package/server/dist/agent-chat-manager.js +8 -1
- package/server/dist/agent-chat-router.js +5 -2
- package/server/dist/agent-fence-promotion.js +53 -0
- package/server/dist/agent-mcp-config.js +4 -1
- package/server/dist/agent-runtime-bridge.js +35 -26
- package/server/dist/agent-runtime-controls-router.js +20 -1
- package/server/dist/agent-runtime-controls.js +94 -12
- package/server/dist/agent-runtime-effective-config.js +53 -0
- package/server/dist/agent-runtime-events.js +46 -0
- package/server/dist/agent-runtime-history.js +47 -0
- package/server/dist/agent-runtime-loader.js +84 -5
- package/server/dist/agent-runtime-metrics.js +77 -1
- package/server/dist/agent-runtime-package.js +205 -0
- package/server/dist/agent-runtime-settings-router.js +96 -5
- package/server/dist/agent-runtime-settings.js +163 -7
- package/server/dist/agent-spec-framing.js +10 -4
- package/server/dist/binary-probe.js +12 -0
- package/server/dist/blueprint-router.js +3 -2
- package/server/dist/chat-manager.js +5 -1
- package/server/dist/desktop-router.js +87 -4
- package/server/dist/index.js +13 -0
- package/server/dist/interactive-job-session.js +3 -1
- package/server/dist/local-engine-detection.js +118 -0
- package/server/dist/loop-executors.js +4 -3
- package/server/dist/loop-role-engines.js +129 -0
- package/server/dist/loop-run-manager.js +14 -6
- package/server/dist/mcp/tools/app-settings.js +30 -4
- package/server/dist/mcp/tools/rails.js +2 -1
- package/server/dist/pr-body.js +11 -2
- package/server/dist/pricing.js +15 -0
- package/server/dist/provider-detection.js +63 -1
- package/server/dist/provider-selection.js +26 -1
- package/server/dist/providers/index.js +4 -1
- package/server/dist/providers/local-adapter-registry.js +56 -0
- package/server/dist/providers/local-adapter.js +426 -0
- package/server/dist/providers/registry.js +39 -4
- package/server/dist/rail-isolated-launch.js +4 -3
- package/server/dist/rails-router.js +90 -8
- package/server/dist/result-event.js +17 -0
- package/server/dist/review-packet.js +1 -0
- package/server/dist/schemas/agent-runtime.schema.json +28 -3
- package/server/dist/schemas/fixtures/runtime-efficiency-summary.v1.json +790 -0
- package/server/dist/setup-manager.js +5 -1
- package/server/dist/spec-models.js +14 -2
- package/server/dist/worktree-node-modules.js +97 -65
- package/client/dist/assets/AgentBrowserCapture-BVcCK7x0.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-C5F3m29G.js +0 -2
- package/client/dist/assets/AgentModeCodePane-C5QuKsu0.js +0 -2
- package/client/dist/assets/AgentsPage-DiyJWZcU.js +0 -87
- package/client/dist/assets/AnalyticsPage-Dkmb0dnd.js +0 -1
- package/client/dist/assets/CodePage-C0dH99-t.js +0 -3
- package/client/dist/assets/DesktopAnalyticsPage-DuTjr2n8.js +0 -1
- package/client/dist/assets/DocsDialog-BVZJZHVR.js +0 -11
- package/client/dist/assets/DocsPage-D6KHTv8V.js +0 -11
- package/client/dist/assets/InteractiveJobComposer-Ck3PQxjN.js +0 -19
- package/client/dist/assets/JobDetailModal-DkoMH3ie.js +0 -1
- package/client/dist/assets/JobDetailPage-CBVivcl2.js +0 -1
- package/client/dist/assets/JobsPage-CzxEeca9.js +0 -1
- package/client/dist/assets/LoopsPage-6iW7g25k.js +0 -1
- package/client/dist/assets/MinimizedChatsContext-B6wVevPq.js +0 -1
- package/client/dist/assets/PluginsPage-Bf1z3WPF.js +0 -2
- package/client/dist/assets/ReviewPacketPage-CaXAwtR7.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-9aRFpJ_J.js +0 -1
- package/client/dist/assets/TicketDetailModalContext-B1X5glzV.js +0 -14
- package/client/dist/assets/agentRuntime-BUagD2I_.js +0 -1
- package/client/dist/assets/agentRuntime-BkL70WtX.js +0 -1
- package/client/dist/assets/agentRuntime-CWXVqgqE.js +0 -1
- package/client/dist/assets/agentRuntime-CvNBdUAe.js +0 -1
- package/client/dist/assets/agentRuntime-D4RmeuNY.js +0 -1
- package/client/dist/assets/agentRuntime-DTidBWKg.js +0 -1
- package/client/dist/assets/agentRuntime-DhKYfyaU.js +0 -1
- package/client/dist/assets/agentRuntime-LL8_GLx9.js +0 -1
- package/client/dist/assets/analytics-47pYygtV.js +0 -1
- package/client/dist/assets/analytics-B0vt1g5U.js +0 -1
- package/client/dist/assets/analytics-BMzCvrCU.js +0 -1
- package/client/dist/assets/analytics-BlFN3Zwx.js +0 -1
- package/client/dist/assets/analytics-CZYzL-nR.js +0 -1
- package/client/dist/assets/analytics-DAsb9MJ8.js +0 -1
- package/client/dist/assets/analytics-FXQlQ2Yt.js +0 -1
- package/client/dist/assets/analytics-RnFCNkah.js +0 -1
- package/client/dist/assets/dashboard-BfBir_6o.js +0 -1
- package/client/dist/assets/file-text-BcLCfcWZ.js +0 -1
- package/client/dist/assets/index-Cfh9aaep.css +0 -2
- package/client/dist/assets/index-DYoyDWim.js +0 -76
- package/client/dist/assets/jira-api-ChjVGJx_.js +0 -1
- package/client/dist/assets/jobs-5PQJbdOf.js +0 -1
- package/client/dist/assets/jobs-B2B08Oy9.js +0 -1
- package/client/dist/assets/jobs-BJZtBbwb.js +0 -1
- package/client/dist/assets/jobs-BlWEsQb7.js +0 -1
- package/client/dist/assets/jobs-ConfD8xD.js +0 -1
- package/client/dist/assets/jobs-DMPU64ms.js +0 -1
- package/client/dist/assets/jobs-DojASiTp.js +0 -1
- package/client/dist/assets/jobs-kVHkNpB-.js +0 -1
- package/client/dist/assets/packet-BQ2tXfJI.js +0 -1
- package/client/dist/assets/packet-BdpOJ1Cy.js +0 -1
- package/client/dist/assets/packet-C3Ievb9R.js +0 -1
- package/client/dist/assets/packet-C4zJIG0Y.js +0 -1
- package/client/dist/assets/packet-Cx-zRuWn.js +0 -1
- package/client/dist/assets/packet-DUTGX52M.js +0 -1
- package/client/dist/assets/packet-DhsKtUPB.js +0 -1
- package/client/dist/assets/packet-z3LJ8Zyy.js +0 -1
- package/client/dist/assets/provider-capabilities-CXipwrXJ.js +0 -1
- package/client/dist/assets/settings-B3rWEwOx.js +0 -1
- package/client/dist/assets/settings-BcVoiMyw.js +0 -1
- package/client/dist/assets/settings-BlYvaQ7w.js +0 -1
- package/client/dist/assets/settings-CD5uEBiL.js +0 -1
- package/client/dist/assets/settings-CyhS0DLJ.js +0 -1
- package/client/dist/assets/settings-DCMFUGDs.js +0 -1
- package/client/dist/assets/settings-DmbYKyMJ.js +0 -1
- package/client/dist/assets/setup-2xGgu4Sk.js +0 -1
- package/client/dist/assets/setup-Ba11l2JA.js +0 -1
- package/client/dist/assets/setup-BzW4kMok.js +0 -1
- package/client/dist/assets/setup-COzwYhkJ.js +0 -1
- package/client/dist/assets/setup-CRVULhtF.js +0 -1
- package/client/dist/assets/setup-DgvlxwpE.js +0 -1
- package/client/dist/assets/setup-Dw_wYeEY.js +0 -1
- package/client/dist/assets/setup-ZPtEeJPW.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specrails-desktop",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.48.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"server/dist",
|
|
18
18
|
"client/dist",
|
|
19
19
|
"mcp-bridge/dist",
|
|
20
|
+
"local-runner/dist",
|
|
20
21
|
"docs",
|
|
21
22
|
".claude/commands/specrails"
|
|
22
23
|
],
|
|
@@ -25,20 +26,21 @@
|
|
|
25
26
|
"dev:server": "tsx watch server/index.ts",
|
|
26
27
|
"dev:client": "cd client && npm run dev",
|
|
27
28
|
"dev:desktop": "tauri dev",
|
|
28
|
-
"dev:desktop:prepare": "npm run build:sidecar && npm run build:mcp-bridge && npm run dev:client",
|
|
29
|
+
"dev:desktop:prepare": "npm run build:sidecar && npm run build:mcp-bridge && npm run build:local-runner && npm run dev:client",
|
|
29
30
|
"build:server": "node -e \"require('fs').rmSync('server/dist',{recursive:true,force:true})\" && tsc --project tsconfig.build.json --noCheck && node scripts/copy-server-assets.mjs",
|
|
30
31
|
"build:cli": "tsc --project tsconfig.cli.json",
|
|
31
|
-
"build": "npm run build:server && npm run build --prefix client && npm run build:cli && npm run build:mcp-bridge",
|
|
32
|
+
"build": "npm run build:server && npm run build --prefix client && npm run build:cli && npm run build:mcp-bridge && npm run build:local-runner",
|
|
32
33
|
"prepack": "npm run build",
|
|
33
34
|
"test:scripts": "node scripts/test-scripts.mjs",
|
|
34
35
|
"check:package": "node scripts/check-package.mjs",
|
|
35
36
|
"build:sidecar": "node scripts/build-sidecar.mjs",
|
|
36
37
|
"build:mcp-bridge": "node scripts/build-mcp-bridge.mjs",
|
|
37
|
-
"build:
|
|
38
|
+
"build:local-runner": "node scripts/build-local-runner.mjs",
|
|
39
|
+
"build:desktop": "npm run build:server && cd client && npm run build && cd .. && npm run build:sidecar && npm run build:mcp-bridge && npm run build:local-runner && tauri build",
|
|
38
40
|
"assemble-runtimes": "node scripts/assemble-runtimes-local.mjs",
|
|
39
41
|
"build:desktop:local": "npm run assemble-runtimes && npm run build:desktop",
|
|
40
42
|
"generate-icons": "tauri icon src-tauri/icons/icon.svg",
|
|
41
|
-
"typecheck": "tsc --noEmit && tsc -p tsconfig.cli.json --noEmit && tsc -p mcp-bridge/tsconfig.json --noEmit && cd client && tsc --noEmit",
|
|
43
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.cli.json --noEmit && tsc -p mcp-bridge/tsconfig.json --noEmit && tsc -p local-runner/tsconfig.json --noEmit && cd client && tsc --noEmit",
|
|
42
44
|
"check-core-compat": "tsx scripts/check-core-compat.ts",
|
|
43
45
|
"test": "vitest run && tsx scripts/check-core-compat.ts",
|
|
44
46
|
"test:watch": "vitest",
|
|
@@ -13,6 +13,7 @@ const promises_1 = require("node:fs/promises");
|
|
|
13
13
|
const win_spawn_1 = require("./util/win-spawn");
|
|
14
14
|
const providers_1 = require("./providers");
|
|
15
15
|
const runtime_1 = require("./providers/runtime");
|
|
16
|
+
const registry_1 = require("./providers/registry");
|
|
16
17
|
const spawn_lifecycle_1 = require("./spawn-lifecycle");
|
|
17
18
|
const live_session_1 = require("./providers/live-session");
|
|
18
19
|
const live_session_types_1 = require("./providers/live-session-types");
|
|
@@ -415,7 +416,13 @@ class AgentChatManager {
|
|
|
415
416
|
// Per-turn dynamic context (pinned project, permission level, provider) rides
|
|
416
417
|
// the USER turn, never the system prompt — byte-stability contract (see
|
|
417
418
|
// agent-operator-prompt.ts).
|
|
418
|
-
|
|
419
|
+
// Local (OpenAI-compatible) engines are usually small models that drop the
|
|
420
|
+
// exact fence tags and ask the user for the #noframe waiver — restate the
|
|
421
|
+
// two rules they break most, on the USER turn (system prompt stays byte-stable).
|
|
422
|
+
const localProtocolNote = (0, registry_1.isLocalAdapterId)(adapter.id)
|
|
423
|
+
? ' | Protocol: fenced protocol blocks MUST be tagged exactly problem-frame, spec-draft or options (never json); never ask the user to type #noframe'
|
|
424
|
+
: '';
|
|
425
|
+
const turnSettings = `Permission level: ${tierLevel} (${['observe', 'edit', 'operate', 'autonomous'][tierLevel]}) | Provider: ${adapter.id} | Model: ${model}${reasoningEffort ? ` | Reasoning effort: ${reasoningEffort}` : ''}${localProtocolNote}`;
|
|
419
426
|
const contextPrefix = conversation.pinned_project_id
|
|
420
427
|
? `[Active project: projectId="${conversation.pinned_project_id}" | ${turnSettings}. Use this project for project-scoped tools; changing the execution target requires changing the conversation pin.]`
|
|
421
428
|
: `[No project pinned (Home) | ${turnSettings}]`;
|
|
@@ -9,6 +9,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const multer_1 = __importDefault(require("multer"));
|
|
10
10
|
const express_1 = require("express");
|
|
11
11
|
const providers_1 = require("./providers");
|
|
12
|
+
const provider_selection_1 = require("./provider-selection");
|
|
12
13
|
const agent_tier_1 = require("./agent-tier");
|
|
13
14
|
const attachment_manager_1 = require("./attachment-manager");
|
|
14
15
|
const agent_store_1 = require("./agent-store");
|
|
@@ -121,7 +122,7 @@ function createAgentChatRouter(deps) {
|
|
|
121
122
|
});
|
|
122
123
|
// Per-provider model catalog for the header model selector.
|
|
123
124
|
router.get('/models', (req, res) => {
|
|
124
|
-
const provider = validProvider(req.query.provider) ??
|
|
125
|
+
const provider = validProvider(req.query.provider) ?? (0, provider_selection_1.defaultMachineProvider)();
|
|
125
126
|
const adapter = (0, providers_1.getAdapter)(provider);
|
|
126
127
|
const requestedModel = (0, providers_1.isModelAvailableForAdapter)(adapter, req.query.model)
|
|
127
128
|
? req.query.model
|
|
@@ -171,7 +172,9 @@ function createAgentChatRouter(deps) {
|
|
|
171
172
|
});
|
|
172
173
|
router.post('/conversations', (req, res) => {
|
|
173
174
|
const body = (req.body ?? {});
|
|
174
|
-
|
|
175
|
+
// No explicit provider ⇒ the machine's primary (a local-only machine lands
|
|
176
|
+
// on its local engine, never on an uninstalled claude).
|
|
177
|
+
let provider = (0, provider_selection_1.defaultMachineProvider)();
|
|
175
178
|
if (body.provider !== undefined) {
|
|
176
179
|
const v = validProvider(body.provider);
|
|
177
180
|
if (!v) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Protocol fence promotion (small-model tolerance) ─────────────────────────
|
|
3
|
+
// The operator protocol asks for fenced blocks tagged EXACTLY `options`,
|
|
4
|
+
// `problem-frame` and `spec-draft`. Small / local models routinely emit the
|
|
5
|
+
// right JSON under a generic ```json (or bare ```) fence instead, which the
|
|
6
|
+
// strict parsers ignore — so no chips, no framing card, and commit_draft then
|
|
7
|
+
// refuses because no frame was ever registered. This pass re-tags a CLOSED
|
|
8
|
+
// generic fence whose body unambiguously has one protocol shape. Anything
|
|
9
|
+
// ambiguous is left untouched; fences already carrying a protocol tag are
|
|
10
|
+
// never rewritten. Mirror: server/agent-fence-promotion.ts (keep in sync).
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.detectProtocolShape = detectProtocolShape;
|
|
13
|
+
exports.promoteAgentProtocolFences = promoteAgentProtocolFences;
|
|
14
|
+
const GENERIC_FENCE_RE = /```(?:json|jsonc|JSON)?[^\S\n]*\n([\s\S]*?)\n[^\S\n]*```/g;
|
|
15
|
+
function isRecord(v) {
|
|
16
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
17
|
+
}
|
|
18
|
+
/** Detect which protocol block a parsed JSON body is, or null. */
|
|
19
|
+
function detectProtocolShape(value) {
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
if (value.length < 2 || value.length > 8)
|
|
22
|
+
return null;
|
|
23
|
+
return value.every((s) => typeof s === 'string' && s.trim() && s.trim().length <= 80) ? 'options' : null;
|
|
24
|
+
}
|
|
25
|
+
if (!isRecord(value))
|
|
26
|
+
return null;
|
|
27
|
+
if (isRecord(value.restated) && isRecord(value.alternative) && typeof value.discriminator === 'string')
|
|
28
|
+
return 'problem-frame';
|
|
29
|
+
if (typeof value.title === 'string' && typeof value.description === 'string'
|
|
30
|
+
&& ('acceptanceCriteria' in value || 'labels' in value || 'priority' in value))
|
|
31
|
+
return 'spec-draft';
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/** Re-tag generic fences that carry a protocol-shaped JSON body. */
|
|
35
|
+
function promoteAgentProtocolFences(text) {
|
|
36
|
+
if (!text || !text.includes('```'))
|
|
37
|
+
return text ?? '';
|
|
38
|
+
GENERIC_FENCE_RE.lastIndex = 0;
|
|
39
|
+
return text.replace(GENERIC_FENCE_RE, (whole, body, offset) => {
|
|
40
|
+
// A fence preceded by another backtick is a quoted/long fence — never touch.
|
|
41
|
+
if (offset > 0 && text[offset - 1] === '`')
|
|
42
|
+
return whole;
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(body);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return whole;
|
|
49
|
+
}
|
|
50
|
+
const shape = detectProtocolShape(parsed);
|
|
51
|
+
return shape ? '```' + shape + '\n' + body + '\n```' : whole;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -306,7 +306,10 @@ function codexExternalOverrides(server) {
|
|
|
306
306
|
function prepareAgentMcp(opts) {
|
|
307
307
|
const adapter = (0, providers_1.getAdapter)(opts.adapterId);
|
|
308
308
|
const external = (opts.external ?? []).filter((s) => s.name !== 'specrails');
|
|
309
|
-
|
|
309
|
+
// claude AND local (OpenAI-compatible) runners take the per-conversation
|
|
310
|
+
// `--mcp-config <file>` (design D8): the bundled runner starts the same
|
|
311
|
+
// stdio servers and exposes their tools as `mcp__<server>__<tool>`.
|
|
312
|
+
if (adapter.id === 'claude' || (0, providers_1.isLocalAdapterId)(adapter.id)) {
|
|
310
313
|
const extraArgs = buildAgentMcpArgs(opts);
|
|
311
314
|
if (!extraArgs.length)
|
|
312
315
|
throw new Error('The bundled Specrails MCP bridge is unavailable.');
|
|
@@ -3,15 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RUNTIME_HOST_ENV_KEYS = void 0;
|
|
4
4
|
exports.runtimeChangeName = runtimeChangeName;
|
|
5
5
|
exports.runAgentRuntimeInvocation = runAgentRuntimeInvocation;
|
|
6
|
+
const agent_runtime_history_1 = require("./agent-runtime-history");
|
|
6
7
|
const agent_runtime_repositories_1 = require("./agent-runtime-repositories");
|
|
7
8
|
const node_util_1 = require("node:util");
|
|
8
|
-
const
|
|
9
|
+
const agent_runtime_effective_config_1 = require("./agent-runtime-effective-config");
|
|
9
10
|
const node_child_process_1 = require("node:child_process");
|
|
10
11
|
const node_crypto_1 = require("node:crypto");
|
|
11
12
|
const node_fs_1 = require("node:fs");
|
|
12
13
|
const node_path_1 = require("node:path");
|
|
13
14
|
const node_readline_1 = require("node:readline");
|
|
14
15
|
const agent_runtime_loader_1 = require("./agent-runtime-loader");
|
|
16
|
+
const agent_runtime_package_1 = require("./agent-runtime-package");
|
|
15
17
|
const agent_runtime_settings_1 = require("./agent-runtime-settings");
|
|
16
18
|
const core_node_runtime_1 = require("./core-node-runtime");
|
|
17
19
|
const win_spawn_1 = require("./util/win-spawn");
|
|
@@ -42,7 +44,8 @@ function saveHostContext(options) {
|
|
|
42
44
|
/** Core owns the complete agent workflow in one managed process. The existing
|
|
43
45
|
* rail's cancellation and worktree ownership remain in Desktop. */
|
|
44
46
|
async function runAgentRuntimeInvocation(options) {
|
|
45
|
-
const
|
|
47
|
+
const selectedCli = options.resume ? (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(options.contextPath) : (0, agent_runtime_loader_1.findCoreAgentRuntimeCli)();
|
|
48
|
+
let cli = selectedCli;
|
|
46
49
|
if (!cli)
|
|
47
50
|
throw new Error('Programmatic agent runtime is enabled but its Core CLI is unavailable. Build or bundle the compatible Core runtime.');
|
|
48
51
|
if (!options.resume && (!options.configPath || !options.change))
|
|
@@ -50,35 +53,36 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
50
53
|
const admittedContext = JSON.parse((0, node_fs_1.readFileSync)(options.contextPath, 'utf8'));
|
|
51
54
|
if (typeof admittedContext.runId !== 'string')
|
|
52
55
|
throw new Error('Core context is missing its run identity');
|
|
53
|
-
if (!options.resume)
|
|
54
|
-
saveHostContext(options);
|
|
55
56
|
const args = [cli, options.resume ? 'resume' : 'run', '--context', options.contextPath];
|
|
56
57
|
if (!options.resume) {
|
|
57
58
|
if (!Array.isArray(admittedContext.repositories) || !admittedContext.repositories.length || admittedContext.repositories.some(repo => !repo || typeof repo.id !== 'string' || !repo.id))
|
|
58
59
|
throw new Error('Core context is missing its repository scope');
|
|
59
|
-
const
|
|
60
|
+
const source = (0, node_fs_1.existsSync)(options.configPath) ? 'project-role' : 'default';
|
|
61
|
+
const { config, origins } = (0, agent_runtime_effective_config_1.resolveEffectiveRuntimeConfig)((0, agent_runtime_settings_1.loadRuntimeConfigFile)(options.configPath, options.defaultProvider), {
|
|
62
|
+
repositoryIds: admittedContext.repositories.map(repo => repo.id), source, providerOverride: options.providerOverride,
|
|
63
|
+
});
|
|
60
64
|
config.rolePrompts = { ...(await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).rolePromptDefaults(), ...(0, agent_runtime_settings_1.loadRuntimeRolePrompts)() };
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
// Core rejects unknown connection keys: drop the desktop-only local-engine
|
|
66
|
+
// fields (label/defaultModel/rates/supportsReasoningEffort) before Core sees it.
|
|
67
|
+
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
68
|
+
config.providers = (0, agent_runtime_settings_1.stripDesktopConnectionFields)(config.providers, (0, agent_runtime_settings_1.coreConnectionFieldGates)(runtime.api?.capabilities));
|
|
69
|
+
if (runtime.api?.capabilities?.configurableGuardrails !== 1)
|
|
70
|
+
delete config.guardrails;
|
|
71
|
+
const override = options.providerOverride;
|
|
72
|
+
runtime.validateRuntimeConfig(JSON.parse(JSON.stringify(config)));
|
|
73
|
+
(0, agent_runtime_loader_1.validateRequestedRoleEfforts)(runtime, config);
|
|
74
|
+
cli = (0, agent_runtime_package_1.retainAgentRuntime)(cli, options.contextPath);
|
|
75
|
+
args[0] = cli;
|
|
76
|
+
saveHostContext(options);
|
|
77
|
+
const selectionFile = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-runtime-selection.json');
|
|
78
|
+
const selection = JSON.stringify({ schemaVersion: 1, runId: admittedContext.runId, providerOverride: override ?? null, origins }) + '\n';
|
|
79
|
+
try {
|
|
80
|
+
(0, node_fs_1.writeFileSync)(selectionFile, selection, { flag: 'wx', mode: 0o600 });
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error.code !== 'EEXIST' || (0, node_fs_1.readFileSync)(selectionFile, 'utf8') !== selection)
|
|
84
|
+
throw new Error('Runtime selection provenance changed; start a new run');
|
|
77
85
|
}
|
|
78
|
-
const selected = new Set(admittedContext.repositories.map(repo => repo.id));
|
|
79
|
-
// Project settings cover every repository; a ticket may select only a subset.
|
|
80
|
-
// Freeze that subset for admission without changing the project settings.
|
|
81
|
-
config.verification = config.verification.filter(command => selected.has(command.repositoryId));
|
|
82
86
|
const scopedPath = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-runtime-config.json');
|
|
83
87
|
const serialized = JSON.stringify(config, null, 2) + '\n';
|
|
84
88
|
try {
|
|
@@ -95,7 +99,7 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
95
99
|
const frozenPath = (0, node_path_1.join)((0, node_path_1.dirname)(options.contextPath), 'desktop-runtime-config.json');
|
|
96
100
|
if ((0, node_fs_1.existsSync)(frozenPath)) {
|
|
97
101
|
const frozen = JSON.parse((0, node_fs_1.readFileSync)(frozenPath, 'utf8'));
|
|
98
|
-
for (const [role, assignment] of Object.entries(frozen.agents)) {
|
|
102
|
+
for (const [role, assignment] of [...Object.entries(frozen.agents), ...(frozen.fixer ? [['fixer', frozen.fixer]] : [])]) {
|
|
99
103
|
try {
|
|
100
104
|
options.onLine?.(`[runtime] ${role}: ${assignment.provider}/${assignment.model ?? 'provider default'}\n`);
|
|
101
105
|
}
|
|
@@ -112,6 +116,7 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
112
116
|
args.push('--answer', options.answer);
|
|
113
117
|
}
|
|
114
118
|
const started = Date.now();
|
|
119
|
+
(0, agent_runtime_history_1.writeRuntimeHistory)(options.contextPath, { status: 'running' });
|
|
115
120
|
return new Promise((resolve) => {
|
|
116
121
|
let result;
|
|
117
122
|
let stderr = '', summary = '', invalidProtocol = false, timedOut = false;
|
|
@@ -220,6 +225,10 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
220
225
|
? `Workflow awaits an answer in Agent Runtime settings: ${result.pendingQuestion.question.trim().slice(0, 500)}`
|
|
221
226
|
: 'Workflow awaits approval in Agent Runtime settings')
|
|
222
227
|
: failed ? stderr || 'Core exited without a successful programmatic workflow result' : undefined));
|
|
228
|
+
try {
|
|
229
|
+
(0, agent_runtime_history_1.writeRuntimeHistory)(options.contextPath, invalidProtocol || timedOut || observerError || !result ? { status: 'failed', error: errorText } : { ...result });
|
|
230
|
+
}
|
|
231
|
+
catch { /* Projection failure cannot replay a completed execution. */ }
|
|
223
232
|
resolve({
|
|
224
233
|
text: summary || (failed ? errorText ?? '' : 'Programmatic implementation verified and archived.'),
|
|
225
234
|
provider: 'agent-runtime', model: 'per-role', failed, errorText,
|
|
@@ -38,7 +38,9 @@ function registerAgentRuntimeControlRoutes({ router, ctx }) {
|
|
|
38
38
|
}
|
|
39
39
|
const context = ctx(req);
|
|
40
40
|
const latest = context.db.prepare('SELECT id FROM loop_runs WHERE rail_index = ? ORDER BY started_at DESC, rowid DESC LIMIT 1').get(railIndex);
|
|
41
|
-
|
|
41
|
+
// A dismissed continuation — or a green one with nothing left to decide — leaves the rail card (the run stays in the history list below).
|
|
42
|
+
const summary = latest && (0, agent_runtime_paths_1.hasAgentRuntimeRequest)(context.project, latest.id) ? await controls(req).summary(latest.id) : undefined;
|
|
43
|
+
res.json({ runs: summary && (0, agent_runtime_controls_1.pinsRailCard)(summary) ? [summary] : [] });
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
res.json({ runs: await controls(req).list() });
|
|
@@ -56,6 +58,14 @@ function registerAgentRuntimeControlRoutes({ router, ctx }) {
|
|
|
56
58
|
res.status(500).json({ error: 'runtime_status_failed' });
|
|
57
59
|
}
|
|
58
60
|
});
|
|
61
|
+
router.get('/:projectId/agent-runtime/runs/:runId/evidence', async (req, res) => {
|
|
62
|
+
try {
|
|
63
|
+
res.json(await controls(req).evidence(String(req.params.runId), req.query));
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
res.status(error instanceof agent_runtime_controls_1.RuntimeControlError ? error.statusCode : 503).json({ error: 'evidence_unavailable', message: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.message : 'The original runtime evidence is unavailable' });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
59
69
|
router.post('/:projectId/agent-runtime/runs/:runId/resume', async (req, res) => {
|
|
60
70
|
try {
|
|
61
71
|
await controls(req).resume(String(req.params.runId), (0, agent_runtime_controls_1.validateRuntimeResumeInput)(req.body));
|
|
@@ -74,6 +84,15 @@ function registerAgentRuntimeControlRoutes({ router, ctx }) {
|
|
|
74
84
|
res.status(error instanceof agent_runtime_controls_1.RuntimeControlError ? error.statusCode : 500).json({ message: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.message : 'Could not prepare delivery' });
|
|
75
85
|
}
|
|
76
86
|
});
|
|
87
|
+
router.post('/:projectId/agent-runtime/runs/:runId/dismiss', (req, res) => {
|
|
88
|
+
try {
|
|
89
|
+
controls(req).dismiss(String(req.params.runId));
|
|
90
|
+
res.json({ dismissed: true });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
res.status(error instanceof agent_runtime_controls_1.RuntimeControlError ? error.statusCode : 500).json({ error: error instanceof agent_runtime_controls_1.RuntimeControlError ? error.code : 'runtime_dismiss_failed', message: error instanceof Error ? error.message : 'Could not dismiss the execution' });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
77
96
|
router.post('/:projectId/agent-runtime/runs/:runId/cancel', (req, res) => {
|
|
78
97
|
try {
|
|
79
98
|
controls(req).cancel(String(req.params.runId));
|
|
@@ -4,8 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AgentRuntimeControls = exports.RuntimeControlError = void 0;
|
|
7
|
+
exports.pinsRailCard = pinsRailCard;
|
|
7
8
|
exports.validateRuntimeResumeInput = validateRuntimeResumeInput;
|
|
8
9
|
exports.readAgentRuntimeStatus = readAgentRuntimeStatus;
|
|
10
|
+
const agent_runtime_events_1 = require("./agent-runtime-events");
|
|
11
|
+
const agent_runtime_history_1 = require("./agent-runtime-history");
|
|
9
12
|
const agent_runtime_settlement_1 = require("./agent-runtime-settlement");
|
|
10
13
|
const agent_runtime_metrics_1 = require("./agent-runtime-metrics");
|
|
11
14
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -13,7 +16,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
13
16
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
18
|
const node_util_1 = require("node:util");
|
|
16
|
-
const
|
|
19
|
+
const agent_runtime_package_1 = require("./agent-runtime-package");
|
|
17
20
|
const agent_runtime_bridge_1 = require("./agent-runtime-bridge");
|
|
18
21
|
const core_node_runtime_1 = require("./core-node-runtime");
|
|
19
22
|
const win_spawn_1 = require("./util/win-spawn");
|
|
@@ -23,8 +26,23 @@ const multi_repo_execution_store_1 = require("./multi-repo-execution-store");
|
|
|
23
26
|
const db_1 = require("./db");
|
|
24
27
|
const loop_run_manager_1 = require("./loop-run-manager");
|
|
25
28
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
26
|
-
const STEP_IDS = ['architect', 'developer', 'verify', 'reviewer', 'archive'];
|
|
29
|
+
const STEP_IDS = ['architect', 'developer', 'fixer', 'verify', 'reviewer', 'archive'];
|
|
27
30
|
const ANSWER_LIMIT = 20_000;
|
|
31
|
+
/**
|
|
32
|
+
* Whether a continuation still deserves the rail card. Anything that needs a
|
|
33
|
+
* human (blocked, failed, interrupted, a pending question or approval, a
|
|
34
|
+
* settle still owed) pins the rail; a run that SUCCEEDED and offers no action
|
|
35
|
+
* does not — its evidence lives in the job log and the Settings history, and
|
|
36
|
+
* the delivery decision (when one exists) has its own strip. Observed: a rail
|
|
37
|
+
* kept a "Completed" card with only Dismiss on it after every green run.
|
|
38
|
+
*/
|
|
39
|
+
function pinsRailCard(summary) {
|
|
40
|
+
if (summary.dismissed)
|
|
41
|
+
return false;
|
|
42
|
+
if (summary.active || summary.canResume || summary.canSettle || summary.canCancel || summary.pendingQuestion || summary.pendingApproval || summary.recoverableSteps.length)
|
|
43
|
+
return true;
|
|
44
|
+
return summary.status !== 'succeeded';
|
|
45
|
+
}
|
|
28
46
|
class RuntimeControlError extends Error {
|
|
29
47
|
statusCode;
|
|
30
48
|
code;
|
|
@@ -56,7 +74,13 @@ function openQuestion(state) {
|
|
|
56
74
|
}
|
|
57
75
|
/** Status is a read-only Core CLI operation. It never invokes a provider. */
|
|
58
76
|
async function readAgentRuntimeStatus(contextPath, cwd, env) {
|
|
59
|
-
|
|
77
|
+
let cli;
|
|
78
|
+
try {
|
|
79
|
+
cli = (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(contextPath);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw new RuntimeControlError(409, 'original_runtime_unavailable', error instanceof Error ? error.message : 'The original runtime package is unavailable');
|
|
83
|
+
}
|
|
60
84
|
if (!cli)
|
|
61
85
|
throw new RuntimeControlError(503, 'runtime_unavailable', 'Update Core to inspect or resume agent runtime executions');
|
|
62
86
|
const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), [cli, 'status', '--context', contextPath, '--compact'], {
|
|
@@ -65,7 +89,12 @@ async function readAgentRuntimeStatus(contextPath, cwd, env) {
|
|
|
65
89
|
const result = JSON.parse(stdout);
|
|
66
90
|
if (result.type !== 'runtime-status' || result.state === undefined)
|
|
67
91
|
throw new Error('Core returned an invalid runtime status');
|
|
68
|
-
|
|
92
|
+
let selection;
|
|
93
|
+
try {
|
|
94
|
+
selection = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-selection.json'), 'utf8'));
|
|
95
|
+
}
|
|
96
|
+
catch { /* Original hosts may not record selection origins. */ }
|
|
97
|
+
return result.state ? { ...result.state, efficiencySummary: (0, agent_runtime_metrics_1.applyRuntimeSelectionOrigins)((0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(result.efficiencySummary), selection, result.state.runId) } : null;
|
|
69
98
|
}
|
|
70
99
|
/** One controller per ProjectContext; Core's durable lease remains the final
|
|
71
100
|
* cross-process guard. Resume never constructs a new context or worktree. */
|
|
@@ -82,7 +111,7 @@ class AgentRuntimeControls {
|
|
|
82
111
|
this.ctx = ctx;
|
|
83
112
|
this.dependencies = dependencies;
|
|
84
113
|
}
|
|
85
|
-
context(runId) {
|
|
114
|
+
context(runId, historical = false) {
|
|
86
115
|
if (!SAFE_ID.test(runId))
|
|
87
116
|
throw new RuntimeControlError(400, 'invalid_run_id', 'Invalid runtime run ID');
|
|
88
117
|
const execution = (0, workspace_resolution_1.resolveProjectExecution)(this.ctx.project);
|
|
@@ -98,7 +127,7 @@ class AgentRuntimeControls {
|
|
|
98
127
|
if (frozen.runId !== runId || node_fs_1.default.realpathSync(frozen.backlogRoot) !== backlogRoot || !Array.isArray(frozen.repositories) || !frozen.repositories.length || !frozen.repositories.some((repository) => repository.path === frozen.artifactRoot))
|
|
99
128
|
throw new Error();
|
|
100
129
|
for (const root of [frozen.artifactRoot, ...frozen.repositories.map((repository) => repository.path)]) {
|
|
101
|
-
if (!node_path_1.default.isAbsolute(root) || node_fs_1.default.realpathSync(root) !== root || !node_fs_1.default.statSync(root).isDirectory())
|
|
130
|
+
if (!node_path_1.default.isAbsolute(root) || (!historical && (node_fs_1.default.realpathSync(root) !== root || !node_fs_1.default.statSync(root).isDirectory())))
|
|
102
131
|
throw new Error();
|
|
103
132
|
}
|
|
104
133
|
}
|
|
@@ -121,6 +150,21 @@ class AgentRuntimeControls {
|
|
|
121
150
|
Object.assign(env, host.env, { SPECRAILS_EXECUTION_CONTEXT: file });
|
|
122
151
|
return { file, frozen, cwd: host.cwd, env };
|
|
123
152
|
}
|
|
153
|
+
async evidence(runId, query) {
|
|
154
|
+
const { file, frozen, env } = this.context(runId, true);
|
|
155
|
+
const flags = [];
|
|
156
|
+
for (const [key, value] of Object.entries(query)) {
|
|
157
|
+
if (!['id', 'section', 'sourceId', 'cursor', 'limit'].includes(key) || typeof value !== 'string' || value.length > 1024)
|
|
158
|
+
throw new RuntimeControlError(400, 'invalid_evidence_query', 'Invalid evidence query');
|
|
159
|
+
flags.push('--' + (key === 'sourceId' ? 'source-id' : key), value);
|
|
160
|
+
}
|
|
161
|
+
const cli = (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(file);
|
|
162
|
+
const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), [cli, 'evidence', '--context', file, ...flags], { cwd: frozen.backlogRoot, env: (0, win_spawn_1.windowsSpawnEnv)(env), windowsHide: true, timeout: 15000, maxBuffer: 4 * 1024 * 1024, encoding: 'utf8' });
|
|
163
|
+
const result = JSON.parse(stdout);
|
|
164
|
+
if (result.schemaVersion !== 1 || typeof result.available !== 'boolean' || typeof result.truncated !== 'boolean')
|
|
165
|
+
throw new RuntimeControlError(503, 'evidence_unavailable', 'Core evidence is unavailable or incompatible');
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
124
168
|
async list() {
|
|
125
169
|
const directory = node_path_1.default.join((0, workspace_resolution_1.resolveProjectExecution)(this.ctx.project).specrailsDir, 'pipeline');
|
|
126
170
|
if (!node_fs_1.default.existsSync(directory))
|
|
@@ -140,7 +184,7 @@ class AgentRuntimeControls {
|
|
|
140
184
|
// key, keeping idle settings panes from spawning CLI processes repeatedly.
|
|
141
185
|
const checkpoint = node_path_1.default.join(node_path_1.default.dirname(file), 'agent-workflow', runId, 'checkpoint.json');
|
|
142
186
|
const stat = node_fs_1.default.existsSync(checkpoint) ? node_fs_1.default.statSync(checkpoint) : null;
|
|
143
|
-
const fingerprint = stat ?
|
|
187
|
+
const fingerprint = stat ? (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(checkpoint)).digest('hex') : null;
|
|
144
188
|
const cached = this.statusCache.get(runId);
|
|
145
189
|
const state = fingerprint && cached?.fingerprint === fingerprint ? cached.state : await this.dependencies.status(file, cwd, env);
|
|
146
190
|
if (!state || state.runId !== runId)
|
|
@@ -149,15 +193,29 @@ class AgentRuntimeControls {
|
|
|
149
193
|
this.statusCache.set(runId, { fingerprint, state });
|
|
150
194
|
const parent = (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId);
|
|
151
195
|
const active = this.active.has(runId) || parent?.status === 'running' || parent?.status === 'paused';
|
|
196
|
+
let projection = null;
|
|
197
|
+
try {
|
|
198
|
+
projection = (0, agent_runtime_history_1.readRuntimeHistory)(file);
|
|
199
|
+
}
|
|
200
|
+
catch { /* Invalid advisory history cannot replace live state. */ }
|
|
201
|
+
const superseding = !active && projection && ['failed', 'cancelled', 'running'].includes(projection.status) && Date.parse(projection.updatedAt) > Date.parse(state.updatedAt ?? '1970-01-01') ? projection : null;
|
|
152
202
|
const recoverableSteps = Object.entries(state.steps).filter(([, step]) => ['running', 'interrupted'].includes(step.status)).map(([id]) => id);
|
|
153
|
-
return { runId, status: state.status === 'running' && !active ? 'interrupted' : state.status, nextStep: state.nextStep,
|
|
154
|
-
updatedAt: state.updatedAt, error: this.errors.get(runId) ?? state.error, pendingApproval: state.pendingApproval,
|
|
203
|
+
return { runId, status: superseding ? (superseding.status === 'running' ? 'interrupted' : superseding.status) : state.status === 'running' && !active ? 'interrupted' : state.status, nextStep: state.nextStep,
|
|
204
|
+
updatedAt: superseding?.updatedAt ?? state.updatedAt, error: this.errors.get(runId) ?? superseding?.error ?? state.error, pendingApproval: state.pendingApproval,
|
|
155
205
|
traceId: state.traceId, pendingQuestion: openQuestion(state),
|
|
156
|
-
metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(state.metrics),
|
|
157
|
-
canSettle: !active && state.status === 'succeeded' && this.ctx.db.prepare('SELECT status FROM jobs WHERE id = ?').get(runId)?.status !== 'completed',
|
|
158
|
-
recoverableSteps, active, canCancel: this.active.has(runId), canResume: !active && parent?.status === 'completed' && state.status !== 'succeeded'
|
|
206
|
+
metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(superseding ? superseding.metrics : state.metrics), efficiencySummary: (0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(superseding ? superseding.efficiencySummary : state.efficiencySummary),
|
|
207
|
+
canSettle: !superseding && !active && state.status === 'succeeded' && this.ctx.db.prepare('SELECT status FROM jobs WHERE id = ?').get(runId)?.status !== 'completed',
|
|
208
|
+
recoverableSteps, active, canCancel: this.active.has(runId), canResume: !active && parent?.status === 'completed' && state.status !== 'succeeded',
|
|
209
|
+
canDismiss: !active, dismissed: this.isDismissed(runId) };
|
|
159
210
|
}
|
|
160
211
|
catch (error) {
|
|
212
|
+
try {
|
|
213
|
+
const { file } = this.context(runId, true);
|
|
214
|
+
const historical = (0, agent_runtime_history_1.readRuntimeHistory)(file);
|
|
215
|
+
if (historical)
|
|
216
|
+
return { ...historical, runId, status: historical.status === 'running' ? 'interrupted' : historical.status, historical: true, active: this.active.has(runId), canResume: false, canSettle: false, canCancel: this.active.has(runId), recoverableSteps: [], error: error instanceof RuntimeControlError ? error.message : 'Historical result; live runtime verification is unavailable' };
|
|
217
|
+
}
|
|
218
|
+
catch { /* No trustworthy historical projection is available. */ }
|
|
161
219
|
return { runId, status: 'unavailable', nextStep: null, active: this.active.has(runId), canResume: false, canCancel: this.active.has(runId), recoverableSteps: [], error: error instanceof RuntimeControlError ? error.message : 'Could not inspect the saved runtime execution' };
|
|
162
220
|
}
|
|
163
221
|
}
|
|
@@ -246,6 +304,12 @@ class AgentRuntimeControls {
|
|
|
246
304
|
eventType = parsed.type;
|
|
247
305
|
}
|
|
248
306
|
catch { /* Keep raw diagnostics available to recovery. */ }
|
|
307
|
+
if (eventType === 'runtime-efficiency-event') {
|
|
308
|
+
const validated = (0, agent_runtime_events_1.runtimeEfficiencyEventLine)(line, runId);
|
|
309
|
+
if (!validated || (0, agent_runtime_events_1.isRecordedRuntimeEfficiencyEvent)(this.ctx.db, runId, validated))
|
|
310
|
+
return;
|
|
311
|
+
line = validated;
|
|
312
|
+
}
|
|
249
313
|
this.ctx.db.transaction(() => {
|
|
250
314
|
(0, db_1.appendEvent)(this.ctx.db, runId, ++sequence, { event_type: eventType, source: 'stdout', payload: line });
|
|
251
315
|
(0, loop_runs_store_1.updateLoopStepActivityCheckpoint)(this.ctx.db, runId, stepKey, undefined, Date.now());
|
|
@@ -315,6 +379,24 @@ class AgentRuntimeControls {
|
|
|
315
379
|
this.ctx.broadcast?.({ type: 'runtime.continuation', projectId: this.ctx.project.id, jobId: runId, railIndex: (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId)?.rail_index ?? null, active: false });
|
|
316
380
|
}
|
|
317
381
|
}
|
|
382
|
+
/** Marker next to the frozen context: the rail card stops showing this run; history/log stay intact. */
|
|
383
|
+
dismissMarker(runId) { return node_path_1.default.join(node_path_1.default.dirname(this.context(runId).file), 'desktop-runtime-dismissed'); }
|
|
384
|
+
isDismissed(runId) { try {
|
|
385
|
+
return node_fs_1.default.existsSync(this.dismissMarker(runId));
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return false;
|
|
389
|
+
} }
|
|
390
|
+
dismiss(runId) {
|
|
391
|
+
if (this.active.has(runId))
|
|
392
|
+
throw new RuntimeControlError(409, 'runtime_run_active', 'Stop the execution before dismissing it');
|
|
393
|
+
const parent = (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId);
|
|
394
|
+
if (parent && (parent.status === 'running' || parent.status === 'paused'))
|
|
395
|
+
throw new RuntimeControlError(409, 'runtime_run_active', 'Stop the rail run before dismissing it');
|
|
396
|
+
node_fs_1.default.writeFileSync(this.dismissMarker(runId), new Date().toISOString() + '\n', { mode: 0o600 });
|
|
397
|
+
this.statusCache.delete(runId);
|
|
398
|
+
this.ctx.broadcast?.({ type: 'runtime.continuation', projectId: this.ctx.project.id, jobId: runId, railIndex: parent?.rail_index ?? null, active: false });
|
|
399
|
+
}
|
|
318
400
|
cancel(runId) {
|
|
319
401
|
const active = this.active.get(runId);
|
|
320
402
|
if (!active)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveEffectiveRuntimeConfig = resolveEffectiveRuntimeConfig;
|
|
4
|
+
exports.fillDefaultRoleModels = fillDefaultRoleModels;
|
|
5
|
+
const providers_1 = require("./providers");
|
|
6
|
+
/** A pure admission resolver; only deliberate launch intent overrides a project role. */
|
|
7
|
+
function resolveEffectiveRuntimeConfig(input, options) {
|
|
8
|
+
const config = structuredClone(input);
|
|
9
|
+
const override = options.providerOverride;
|
|
10
|
+
if (override) {
|
|
11
|
+
const provider = config.providers.find(entry => entry.id === override.provider);
|
|
12
|
+
if (!provider)
|
|
13
|
+
throw new Error(`Selected runtime provider is not configured: ${override.provider}`);
|
|
14
|
+
for (const role of ['architect', 'developer', 'reviewer', 'fixer']) {
|
|
15
|
+
const current = role === 'fixer' ? config.fixer : config.agents[role];
|
|
16
|
+
if (!current)
|
|
17
|
+
continue;
|
|
18
|
+
const changedProvider = current.provider !== provider.id;
|
|
19
|
+
const changedModel = override.model !== undefined && override.model !== current.model;
|
|
20
|
+
const next = {
|
|
21
|
+
...current, provider: provider.id,
|
|
22
|
+
model: override.model ?? (changedProvider ? undefined : current.model),
|
|
23
|
+
effort: override.effort ?? (changedProvider || changedModel ? undefined : current.effort),
|
|
24
|
+
escalation: changedProvider || changedModel ? undefined : current.escalation,
|
|
25
|
+
};
|
|
26
|
+
if (role === 'fixer')
|
|
27
|
+
config.fixer = next;
|
|
28
|
+
else
|
|
29
|
+
config.agents[role] = next;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
fillDefaultRoleModels(config);
|
|
33
|
+
config.verification = config.verification.filter(check => options.repositoryIds.includes(check.repositoryId));
|
|
34
|
+
return { config, origins: { architect: override ? 'explicit-launch-override' : options.source, developer: override ? 'explicit-launch-override' : options.source, reviewer: override ? 'explicit-launch-override' : options.source } };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Every role gets a concrete model: CLI roles the adapter default, local
|
|
38
|
+
* (OpenAI-compatible) roles the connection's default (stored `defaultModel`,
|
|
39
|
+
* else the first discovered model). Core requires a model on API providers, so
|
|
40
|
+
* this runs before launch AND before the compatibility check — otherwise
|
|
41
|
+
* picking a local engine with "provider default" failed the check with
|
|
42
|
+
* "Role architect requires a model for its API provider".
|
|
43
|
+
*/
|
|
44
|
+
function fillDefaultRoleModels(config, options = {}) {
|
|
45
|
+
for (const agent of [...Object.values(config.agents), ...(config.fixer ? [config.fixer] : [])]) {
|
|
46
|
+
const provider = config.providers.find(entry => entry.id === agent.provider);
|
|
47
|
+
if (!agent.model && provider?.kind === 'cli' && !options.localOnly)
|
|
48
|
+
agent.model = (0, providers_1.getAdapter)(provider.cli).defaultModel();
|
|
49
|
+
if (!agent.model && provider?.kind === 'openai-compatible' && (0, providers_1.hasAdapter)(provider.id) && (0, providers_1.isLocalAdapterId)(provider.id))
|
|
50
|
+
agent.model = (0, providers_1.getAdapter)(provider.id).defaultModel();
|
|
51
|
+
}
|
|
52
|
+
return config;
|
|
53
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runtimeEfficiencyEventLine = runtimeEfficiencyEventLine;
|
|
4
|
+
exports.isRecordedRuntimeEfficiencyEvent = isRecordedRuntimeEfficiencyEvent;
|
|
5
|
+
/** Efficiency events are advisory measurements, never workflow or delivery authority. */
|
|
6
|
+
function runtimeEfficiencyEventLine(line, runId) {
|
|
7
|
+
if (line.length > 16384)
|
|
8
|
+
return null;
|
|
9
|
+
let event;
|
|
10
|
+
try {
|
|
11
|
+
event = JSON.parse(line);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (event?.type !== 'runtime-efficiency-event' || event.schemaVersion !== 1 || event.runId !== runId || typeof event.eventId !== 'string' || !event.eventId.startsWith(runId + ':') || event.eventId.length > 256 || typeof event.attemptId !== 'string' || event.attemptId.length > 256 || !['role-context', 'role-route', 'check-started', 'check-finished', 'check-reused', 'check-invalidated'].includes(String(event.kind)))
|
|
17
|
+
return null;
|
|
18
|
+
if (!event.payload || typeof event.payload !== 'object' || Array.isArray(event.payload))
|
|
19
|
+
return null;
|
|
20
|
+
const raw = event.payload, payload = {};
|
|
21
|
+
for (const key of ['invocationId', 'provider', 'model', 'requestedEffort', 'kind', 'tier', 'routeReason', 'contextMode', 'status', 'executionId', 'repositoryId', 'checkId', 'label', 'reason']) {
|
|
22
|
+
const value = raw[key];
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (value !== null && (typeof value !== 'string' || value.length > 256))
|
|
26
|
+
return null;
|
|
27
|
+
payload[key] = value;
|
|
28
|
+
}
|
|
29
|
+
for (const key of ['ordinal', 'promptBytes', 'contextBytes', 'handoffBytes', 'durationMs', 'toolCalls']) {
|
|
30
|
+
if (raw[key] === undefined)
|
|
31
|
+
continue;
|
|
32
|
+
if (typeof raw[key] !== 'number' || !Number.isFinite(raw[key]) || raw[key] < 0)
|
|
33
|
+
return null;
|
|
34
|
+
payload[key] = raw[key];
|
|
35
|
+
}
|
|
36
|
+
if (raw.exitCode !== undefined) {
|
|
37
|
+
if (typeof raw.exitCode !== 'number' || !Number.isSafeInteger(raw.exitCode))
|
|
38
|
+
return null;
|
|
39
|
+
payload.exitCode = raw.exitCode;
|
|
40
|
+
}
|
|
41
|
+
return JSON.stringify({ type: event.type, schemaVersion: 1, eventId: event.eventId, runId, attemptId: event.attemptId, kind: event.kind, payload });
|
|
42
|
+
}
|
|
43
|
+
function isRecordedRuntimeEfficiencyEvent(db, runId, line) {
|
|
44
|
+
const event = JSON.parse(line);
|
|
45
|
+
return Boolean(db.prepare("SELECT 1 FROM events WHERE job_id = ? AND event_type = 'runtime-efficiency-event' AND CASE WHEN json_valid(payload) THEN json_extract(payload, '$.eventId') END = ? LIMIT 1").get(runId, event.eventId));
|
|
46
|
+
}
|