specrails-desktop 2.36.0 → 2.42.2
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 +94 -247
- package/client/dist/assets/ActivityFeedPage-DoEMumQS.js +1 -0
- package/client/dist/assets/AgentBrowserCapture-DIh4K0gh.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-mbq4WEoO.js +2 -0
- package/client/dist/assets/AgentModeCodePane-CKh7zuA4.js +2 -0
- package/client/dist/assets/AgentModeJobsPane-BDokPiLJ.js +1 -0
- package/client/dist/assets/AgentsPage-BFVAZbBj.js +87 -0
- package/client/dist/assets/AnalyticsPage-3Y9LuWwz.js +1 -0
- package/client/dist/assets/CodePage-C9KyuV_C.js +3 -0
- package/client/dist/assets/DesktopAnalyticsPage-Bhi54bXQ.js +1 -0
- package/client/dist/assets/{DocsDialog-C76VdBJC.js → DocsDialog-Dj6mt4ga.js} +2 -2
- package/client/dist/assets/{DocsPage-ziMlj6B6.js → DocsPage-24yWglNI.js} +1 -1
- package/client/dist/assets/ExportDropdown-ophapHrT.js +1 -0
- package/client/dist/assets/InteractiveJobComposer-C1ucmxQh.js +19 -0
- package/client/dist/assets/JobDetailModal-DNWmgVHD.js +1 -0
- package/client/dist/assets/JobDetailPage-BcajZyuu.js +1 -0
- package/client/dist/assets/JobsPage-B9nTZw6e.js +1 -0
- package/client/dist/assets/LoopBuilderPage-Oc48iOFW.js +7 -0
- package/client/dist/assets/LoopPreviewModal-ae0Fr6SU.js +1 -0
- package/client/dist/assets/LoopsPage-CUEqzUBI.js +1 -0
- package/client/dist/assets/MinimizedChatsContext-Deg39peL.js +1 -0
- package/client/dist/assets/PluginsPage-C1xtARIx.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-JZzrXFBN.js +1 -0
- package/client/dist/assets/RepositoryDeliveries-CSDsiVUz.js +5 -0
- package/client/dist/assets/RepositoryScopeSelector-D-RCaknl.js +1 -0
- package/client/dist/assets/ReviewPacketPage-x5EfGTiL.js +1 -0
- package/client/dist/assets/TemplatePreviewModal-BSyuZxGq.js +1 -0
- package/client/dist/assets/TicketDetailModalContext-DqyF0so-.js +14 -0
- package/client/dist/assets/Trans-SnMX4MhW.js +1 -0
- package/client/dist/assets/agent-BdT0SB1D.js +1 -0
- package/client/dist/assets/agent-Bh9K7iEt.js +1 -0
- package/client/dist/assets/agent-C4ibdFk9.js +1 -0
- package/client/dist/assets/agent-CSFY22Zo.js +1 -0
- package/client/dist/assets/agent-DAnqxUn8.js +1 -0
- package/client/dist/assets/agent-DTQiu0Mi.js +1 -0
- package/client/dist/assets/agent-T54WuR_Y.js +1 -0
- package/client/dist/assets/agent-i75WDZOA.js +1 -0
- package/client/dist/assets/browser-05vRDg_y.js +1 -0
- package/client/dist/assets/browser-Bulb-cB1.js +1 -0
- package/client/dist/assets/browser-C7f-Jsg1.js +1 -0
- package/client/dist/assets/browser-CG0PE1j_.js +1 -0
- package/client/dist/assets/browser-CMzaqAjo.js +1 -0
- package/client/dist/assets/browser-DMajYdNX.js +1 -0
- package/client/dist/assets/browser-DxcOHrdP.js +1 -0
- package/client/dist/assets/browser-ObE7xVo3.js +1 -0
- package/client/dist/assets/builder-765NeYTp.js +1 -0
- package/client/dist/assets/builder-8MG3V6ea.js +1 -0
- package/client/dist/assets/builder-B-1UBs5K.js +1 -0
- package/client/dist/assets/builder-BTiGnPT0.js +1 -0
- package/client/dist/assets/builder-Cx9Rz-7u.js +1 -0
- package/client/dist/assets/builder-DKFMELBl.js +1 -0
- package/client/dist/assets/builder-OcmKrC0h.js +1 -0
- package/client/dist/assets/builder-P9qVBXFZ.js +1 -0
- package/client/dist/assets/{button-CJqFnUOL.js → button-DCBqEaZl.js} +1 -1
- package/client/dist/assets/{code-CkUuW4m9.js → code-B37GRg9C.js} +1 -1
- package/client/dist/assets/{code-B0O8-p8k.js → code-BBOEZbf9.js} +1 -1
- package/client/dist/assets/{code-B1VLZDhS.js → code-BRm-jcyx.js} +1 -1
- package/client/dist/assets/{code-Bpj4Ddml.js → code-CF7w62CR.js} +1 -1
- package/client/dist/assets/{code-BykBZ5-e.js → code-CLuvJt2r.js} +1 -1
- package/client/dist/assets/{code-B86IMAfz.js → code-D0RCm59f.js} +1 -1
- package/client/dist/assets/code-DaZ-ZwCT.js +1 -0
- package/client/dist/assets/{code-cAqaqMTx.js → code-iIHkGaCL.js} +1 -1
- package/client/dist/assets/commands-0XjAftla.js +1 -0
- package/client/dist/assets/commands-BAWSYHjQ.js +1 -0
- package/client/dist/assets/commands-BBwowgiE.js +1 -0
- package/client/dist/assets/commands-BacjIZLN.js +1 -0
- package/client/dist/assets/commands-Bs62IAZ3.js +1 -0
- package/client/dist/assets/commands-CLPdh-Hh.js +1 -0
- package/client/dist/assets/commands-DlbFzgrp.js +1 -0
- package/client/dist/assets/commands-LjPSsi5F.js +1 -0
- package/client/dist/assets/common-B5AZNYMH.js +1 -0
- package/client/dist/assets/common-BFZ0c_vN.js +1 -0
- package/client/dist/assets/common-BrlwEnL7.js +1 -0
- package/client/dist/assets/common-BttQx3z4.js +1 -0
- package/client/dist/assets/common-CM65k_-e.js +1 -0
- package/client/dist/assets/common-D4mvFFlu.js +1 -0
- package/client/dist/assets/common-TZ9Ny96S.js +1 -0
- package/client/dist/assets/common-nFpSF4GC.js +1 -0
- package/client/dist/assets/dashboard-B14uYXkG.js +1 -0
- package/client/dist/assets/dashboard-BK_mez-z.js +1 -0
- package/client/dist/assets/dashboard-BfBir_6o.js +1 -0
- package/client/dist/assets/dashboard-Bt0cCzeG.js +1 -0
- package/client/dist/assets/dashboard-BwkjxI6H.js +1 -0
- package/client/dist/assets/{dashboard-C8WOEiPu.js → dashboard-C3bDKf7s.js} +1 -1
- package/client/dist/assets/dashboard-CdvjGfog.js +1 -0
- package/client/dist/assets/dashboard-fRrP0Kv0.js +1 -0
- package/client/dist/assets/dialog-SRw01_QU.js +45 -0
- package/client/dist/assets/dist-js-CJ_2XX1Y.js +1 -0
- package/client/dist/assets/formatDistanceToNow-DSj5DprF.js +1 -0
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-YhD8rIEs.js +1 -0
- package/client/dist/assets/index-DwAmL7m5.css +2 -0
- package/client/dist/assets/index-EzrkUb8B.js +76 -0
- package/client/dist/assets/{jira-api-B1-MUehg.js → jira-api-CSCqGr-Q.js} +1 -1
- package/client/dist/assets/{jobs-CN_zv4Gr.js → jobs-B8rrzfyZ.js} +1 -1
- package/client/dist/assets/{jobs-DUlWSYq2.js → jobs-BS6RTurf.js} +1 -1
- package/client/dist/assets/{jobs-Bpks6doU.js → jobs-BTT0Bsks.js} +1 -1
- package/client/dist/assets/{jobs-Bgq-PZkC.js → jobs-CezU69AE.js} +1 -1
- package/client/dist/assets/{jobs-SwYS2M-B.js → jobs-ClFJae0s.js} +1 -1
- package/client/dist/assets/{jobs-BAls3bWq.js → jobs-DJSFIFVy.js} +1 -1
- package/client/dist/assets/{jobs-DhxKgWFq.js → jobs-DhgGr8vm.js} +1 -1
- package/client/dist/assets/{jobs-B_TeSQxb.js → jobs-DzMjUcaN.js} +1 -1
- package/client/dist/assets/mcp-6rMoZfEO.js +1 -0
- package/client/dist/assets/mcp-BD8sL5mP.js +1 -0
- package/client/dist/assets/mcp-BkcA3I5s.js +1 -0
- package/client/dist/assets/mcp-CCpSelOh.js +1 -0
- package/client/dist/assets/mcp-CUAN6ORK.js +1 -0
- package/client/dist/assets/mcp-CqbRxRTE.js +1 -0
- package/client/dist/assets/mcp-DP2YfVhO.js +1 -0
- package/client/dist/assets/mcp-DUVQamDz.js +1 -0
- package/client/dist/assets/narration-B-ynWTpn.js +1 -0
- package/client/dist/assets/narration-Bj_laZwg.js +1 -0
- package/client/dist/assets/narration-BxHpmMv_.js +1 -0
- package/client/dist/assets/narration-C1Px0-0j.js +1 -0
- package/client/dist/assets/narration-CAXhGbzM.js +1 -0
- package/client/dist/assets/narration-DQ1l_Kq6.js +1 -0
- package/client/dist/assets/narration-XISXwqZd.js +1 -0
- package/client/dist/assets/narration-kwzfglQo.js +1 -0
- package/client/dist/assets/nav-1-Hh2kOz.js +1 -0
- package/client/dist/assets/nav-B34QkESm.js +1 -0
- package/client/dist/assets/nav-Bap_UXAq.js +1 -0
- package/client/dist/assets/{nav-pYuXq7sf.js → nav-BwxXIB24.js} +1 -1
- package/client/dist/assets/nav-Ch-zHBG5.js +1 -0
- package/client/dist/assets/nav-CygC_WN-.js +1 -0
- package/client/dist/assets/{nav-iwjCg5FR.js → nav-KnnLQtOQ.js} +1 -1
- package/client/dist/assets/nav-m00uxYUG.js +1 -0
- package/client/dist/assets/packet-BQ2tXfJI.js +1 -0
- package/client/dist/assets/packet-BdpOJ1Cy.js +1 -0
- package/client/dist/assets/packet-C3Ievb9R.js +1 -0
- package/client/dist/assets/packet-C4zJIG0Y.js +1 -0
- package/client/dist/assets/packet-Cx-zRuWn.js +1 -0
- package/client/dist/assets/packet-DUTGX52M.js +1 -0
- package/client/dist/assets/packet-DhsKtUPB.js +1 -0
- package/client/dist/assets/packet-z3LJ8Zyy.js +1 -0
- package/client/dist/assets/project-repositories-DYhTvwQ1.js +1 -0
- package/client/dist/assets/provider-capabilities-B-wOHfLP.js +1 -0
- package/client/dist/assets/settings-BBt2xtDQ.js +1 -0
- package/client/dist/assets/settings-BJB9RKJj.js +1 -0
- package/client/dist/assets/settings-CHwQR2rB.js +1 -0
- package/client/dist/assets/settings-CrMi3kSi.js +1 -0
- package/client/dist/assets/settings-DKyhDGD1.js +1 -0
- package/client/dist/assets/settings-Deupji9c.js +1 -0
- package/client/dist/assets/settings-DqRMmwAM.js +1 -0
- package/client/dist/assets/settings-J4rj6vo-.js +1 -0
- package/client/dist/assets/setup-2xGgu4Sk.js +1 -0
- package/client/dist/assets/setup-Ba11l2JA.js +1 -0
- package/client/dist/assets/setup-BzW4kMok.js +1 -0
- package/client/dist/assets/setup-COzwYhkJ.js +1 -0
- package/client/dist/assets/setup-CRVULhtF.js +1 -0
- package/client/dist/assets/setup-DgvlxwpE.js +1 -0
- package/client/dist/assets/setup-Dw_wYeEY.js +1 -0
- package/client/dist/assets/setup-ZPtEeJPW.js +1 -0
- package/client/dist/assets/spending-BqbdPm_4.js +1 -0
- package/client/dist/assets/square-Ci-PFcFe.js +1 -0
- package/client/dist/assets/{terminal-BFRZnMPP.js → terminal-BAzpHtpY.js} +1 -1
- package/client/dist/assets/{terminal-IahE4LW9.js → terminal-C7dHeR3x.js} +1 -1
- package/client/dist/assets/{terminal-tITa81iz.js → terminal-CMla5Qsz.js} +1 -1
- package/client/dist/assets/{terminal-BZiIu3rf.js → terminal-CdNXH-n9.js} +1 -1
- package/client/dist/assets/{terminal-ScjyXNxQ.js → terminal-Cw5bZsmV.js} +1 -1
- package/client/dist/assets/{terminal-C2NAd7BW.js → terminal-DFUP4eNv.js} +1 -1
- package/client/dist/assets/{terminal-d7GadXbN.js → terminal-DGqljeK_.js} +1 -1
- package/client/dist/assets/{terminal-QpXZU2Bn.js → terminal-cU70NodO.js} +1 -1
- package/client/dist/assets/useDesktop-RSMTyy7V.js +1 -0
- package/client/dist/assets/useSharedWebSocket-BOdS5_Iz.js +2 -0
- package/client/dist/index.html +34 -35
- package/docs/README.md +3 -0
- package/docs/agent-live-steering.md +57 -0
- package/docs/ci-cd.md +57 -0
- package/docs/code-explorer.md +48 -0
- package/docs/codex.md +12 -1
- package/docs/features/detachable-mission-windows.md +59 -0
- package/docs/guide/de/integrations/5-mcp-server.md +13 -9
- package/docs/guide/de/integrations/6-agent-chat.md +1 -0
- package/docs/guide/en/integrations/5-mcp-server.md +13 -9
- package/docs/guide/en/integrations/6-agent-chat.md +1 -0
- package/docs/guide/es/integrations/5-mcp-server.md +13 -9
- package/docs/guide/es/integrations/6-agent-chat.md +1 -0
- package/docs/guide/fr/integrations/5-mcp-server.md +13 -9
- package/docs/guide/fr/integrations/6-agent-chat.md +1 -0
- package/docs/guide/it/integrations/5-mcp-server.md +13 -9
- package/docs/guide/it/integrations/6-agent-chat.md +1 -0
- package/docs/guide/ja/integrations/5-mcp-server.md +13 -9
- package/docs/guide/ja/integrations/6-agent-chat.md +1 -0
- package/docs/guide/pt/integrations/5-mcp-server.md +13 -9
- package/docs/guide/pt/integrations/6-agent-chat.md +1 -0
- package/docs/guide/zh/integrations/5-mcp-server.md +13 -9
- package/docs/guide/zh/integrations/6-agent-chat.md +1 -0
- package/docs/internals/README.md +1 -0
- package/docs/internals/browser-capture-performance.md +55 -3
- package/docs/internals/browser-login-popups.md +45 -0
- package/docs/internals/browser-native-retina-audit.md +39 -0
- package/docs/internals/ci-cd-audit.md +39 -0
- package/docs/internals/core-runtime-updates.md +68 -0
- package/docs/internals/embedded-browser-native-webview-evaluation.md +2 -0
- package/docs/internals/implementation-reliability-audit.md +93 -0
- package/docs/internals/interactive-jobs.md +110 -6
- package/docs/internals/mcp-mission-audit.md +76 -0
- package/docs/internals/project-builder.md +404 -42
- package/docs/internals/safe-pr-review-flow.md +52 -10
- package/docs/internals/startup-recovery-audit.md +58 -0
- package/docs/mcp.md +74 -41
- package/docs/mission-processes.md +59 -0
- package/docs/multi-repo-projects.md +51 -0
- package/docs/platforms/windows-parity.md +70 -0
- package/docs/platforms/windows.md +13 -11
- package/docs/running-pipelines.md +14 -0
- package/mcp-bridge/dist/specrails-mcp.js +7186 -0
- package/package.json +9 -4
- package/server/dist/agent-chat-manager.js +537 -68
- package/server/dist/agent-chat-router.js +87 -6
- package/server/dist/agent-context-resolver.js +129 -25
- package/server/dist/agent-defaults.js +19 -11
- package/server/dist/agent-input-store.js +206 -0
- package/server/dist/agent-mcp-config.js +54 -15
- package/server/dist/agent-operator-prompt.js +255 -65
- package/server/dist/agent-spec-framing.js +202 -0
- package/server/dist/agent-steering.js +218 -0
- package/server/dist/agent-store.js +153 -0
- package/server/dist/api-not-found.js +8 -0
- package/server/dist/background-process-control.js +164 -0
- package/server/dist/background-process-service.js +61 -0
- package/server/dist/background-process-store.js +242 -0
- package/server/dist/background-windows-bootstrap.js +238 -0
- package/server/dist/blueprint-chat-manager.js +476 -62
- package/server/dist/blueprint-commit.js +21 -1
- package/server/dist/blueprint-draft-parser.js +122 -17
- package/server/dist/blueprint-generation.js +238 -0
- package/server/dist/blueprint-operator-prompt.js +91 -63
- package/server/dist/blueprint-router.js +55 -3
- package/server/dist/blueprint-spec-fixtures.js +85 -0
- package/server/dist/blueprint-spec-quality.js +57 -16
- package/server/dist/blueprint-store.js +92 -1
- package/server/dist/browser-capture-manager.js +86 -30
- package/server/dist/browser-playwright.js +168 -45
- package/server/dist/browser-viewport.js +26 -0
- package/server/dist/chat-manager.js +23 -15
- package/server/dist/chromium-archive.cjs +83 -0
- package/server/dist/chromium-resolver.js +72 -64
- package/server/dist/code-activity.js +145 -0
- package/server/dist/code-explorer-router.js +714 -114
- package/server/dist/core-compat.js +28 -1
- package/server/dist/core-execution.js +111 -0
- package/server/dist/core-package.js +4 -16
- package/server/dist/core-runtime.js +202 -0
- package/server/dist/core-update-manager.js +161 -60
- package/server/dist/core-update-state.js +29 -0
- package/server/dist/db.js +66 -35
- package/server/dist/delivery-evidence.js +142 -39
- package/server/dist/desktop-db.js +239 -43
- package/server/dist/desktop-router.js +109 -9
- package/server/dist/dev-ports.js +8 -3
- package/server/dist/file-provenance.js +34 -22
- package/server/dist/file-story-manager.js +62 -64
- package/server/dist/file-story.js +118 -35
- package/server/dist/file-summary-generator.js +46 -5
- package/server/dist/file-summary-manager.js +355 -106
- package/server/dist/framework-manager.js +98 -110
- package/server/dist/framework-reseed.js +76 -10
- package/server/dist/git-diagnostics.js +2 -2
- package/server/dist/headroom-manager.js +38 -3
- package/server/dist/host-control.js +52 -0
- package/server/dist/index.js +43 -2
- package/server/dist/interactive-job-session.js +149 -16
- package/server/dist/internal-api.js +48 -0
- package/server/dist/jira/jira-materializer.js +1 -0
- package/server/dist/json-tolerant.js +174 -0
- package/server/dist/legacy-migration.js +4 -2
- package/server/dist/loop-command-catalog.js +83 -21
- package/server/dist/loop-constants.js +6 -1
- package/server/dist/loop-decider.js +11 -1
- package/server/dist/loop-executors.js +194 -45
- package/server/dist/loop-factory.js +11 -4
- package/server/dist/loop-graph.js +90 -4
- package/server/dist/loop-run-manager.js +301 -69
- package/server/dist/loop-shell-invocation.js +21 -0
- package/server/dist/loop-step-idle.js +55 -0
- package/server/dist/loop-templates.js +31 -7
- package/server/dist/mcp/agent-capability.js +37 -4
- package/server/dist/mcp/guide.js +149 -36
- package/server/dist/mcp/mcp-admin-router.js +20 -4
- package/server/dist/mcp/mcp-server.js +89 -43
- package/server/dist/mcp/mcp-tiers.js +6 -5
- package/server/dist/mcp/mcp-token.js +3 -1
- package/server/dist/mcp/resources.js +5 -4
- package/server/dist/mcp/tools/catalog.js +4 -0
- package/server/dist/mcp/tools/code.js +125 -14
- package/server/dist/mcp/tools/context.js +153 -0
- package/server/dist/mcp/tools/git.js +15 -5
- package/server/dist/mcp/tools/jobs.js +84 -71
- package/server/dist/mcp/tools/loops.js +17 -6
- package/server/dist/mcp/tools/meta.js +74 -27
- package/server/dist/mcp/tools/mission.js +29 -0
- package/server/dist/mcp/tools/projects.js +22 -6
- package/server/dist/mcp/tools/rails.js +48 -11
- package/server/dist/mcp/tools/specs.js +86 -14
- package/server/dist/mcp/tools/support.js +1 -1
- package/server/dist/mcp/tools/types.js +117 -43
- package/server/dist/mcp/tools/watch.js +144 -51
- package/server/dist/milestone-chain-store.js +164 -0
- package/server/dist/milestone-chain.js +487 -0
- package/server/dist/milestone-progress.js +348 -0
- package/server/dist/mobile/mobile-devices.js +10 -7
- package/server/dist/mobile/mobile-gateway.js +2 -0
- package/server/dist/mobile/mobile-missions.js +214 -0
- package/server/dist/mobile/mobile-redact.js +17 -3
- package/server/dist/mobile/mobile-router.js +46 -4
- package/server/dist/mobile/mobile-ws.js +49 -4
- package/server/dist/multi-repo-bases.js +56 -0
- package/server/dist/multi-repo-checkout.js +101 -0
- package/server/dist/multi-repo-delivery.js +100 -0
- package/server/dist/multi-repo-execution-store.js +65 -0
- package/server/dist/multi-repo-execution.js +453 -0
- package/server/dist/offline-assemble.js +10 -8
- package/server/dist/openspec-runtime-plugin-commands.json +18 -0
- package/server/dist/openspec-runtime-plugin.js +120 -0
- package/server/dist/plugins/codex-spawn.js +49 -0
- package/server/dist/plugins/serena/manifest.js +2 -2
- package/server/dist/plugins/serena/templates/instructions.md +16 -0
- package/server/dist/pr-publisher.js +1 -1
- package/server/dist/profiles-router.js +3 -18
- package/server/dist/project-code-discovery.js +142 -0
- package/server/dist/project-git.js +19 -21
- package/server/dist/project-registry.js +272 -20
- package/server/dist/project-repositories.js +157 -0
- package/server/dist/project-repository-provenance.js +31 -0
- package/server/dist/project-router-background-processes.js +105 -0
- package/server/dist/project-router-git.js +75 -16
- package/server/dist/project-router-jobs.js +2 -126
- package/server/dist/project-router-loop-runs.js +72 -2
- package/server/dist/project-router-tickets.js +53 -8
- package/server/dist/project-router.js +239 -38
- package/server/dist/provider-detection.js +18 -6
- package/server/dist/provider-limit.js +64 -0
- package/server/dist/providers/claude-adapter.js +26 -1
- package/server/dist/providers/claude-background-tasks.js +18 -0
- package/server/dist/providers/claude-live-session.js +383 -0
- package/server/dist/providers/codex-adapter.js +19 -0
- package/server/dist/providers/codex-live-session.js +545 -0
- package/server/dist/providers/gemini-adapter.js +1 -0
- package/server/dist/providers/live-session-types.js +12 -0
- package/server/dist/providers/live-session.js +13 -0
- package/server/dist/providers/terminal-result.js +24 -0
- package/server/dist/queue-manager.js +2 -0
- package/server/dist/rail-isolated-launch.js +141 -16
- package/server/dist/rail-pr-decision.js +364 -99
- package/server/dist/rail-pr-store.js +24 -10
- package/server/dist/rail-worktree-release.js +34 -2
- package/server/dist/rail-worktrees-store.js +4 -4
- package/server/dist/rails-router.js +249 -55
- package/server/dist/repo-lock.js +92 -17
- package/server/dist/review-packet.js +23 -13
- package/server/dist/schemas/file-summary.v1.json +59 -59
- package/server/dist/schemas/profile.v1.json +137 -137
- package/server/dist/setup-manager.js +52 -8
- package/server/dist/shell-integration/bash-shim.bash +45 -0
- package/server/dist/shell-integration/fish-shim.fish +36 -0
- package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
- package/server/dist/shell-integration/zsh-shim.zsh +47 -0
- package/server/dist/smash-runner.js +1 -0
- package/server/dist/spawn-lifecycle.js +44 -27
- package/server/dist/spec-contract-prompt.js +143 -0
- package/server/dist/spec-draft-parser.js +6 -1
- package/server/dist/spec-models.js +1 -0
- package/server/dist/stuck-run-detector.js +1 -0
- package/server/dist/terminal-manager.js +21 -1
- package/server/dist/terminal-shell-integration.js +4 -1
- package/server/dist/transient-children.js +619 -230
- package/server/dist/util/cli-prompt.js +78 -5
- package/server/dist/util/sqlite-migrations.js +20 -0
- package/server/dist/util/win-spawn.js +18 -3
- package/server/dist/util/windows-provider-shell.js +29 -0
- package/server/dist/verification-sentinel.js +14 -0
- package/server/dist/vitest-setup.js +2 -0
- package/server/dist/windows-job-supervisor.js +133 -0
- package/server/dist/workspace-manager.js +9 -30
- package/server/dist/worktree-manager.js +17 -2
- package/server/dist/worktree-overlay.js +3 -1
- package/client/dist/assets/ActivityFeedPage-tAQcWs1R.js +0 -1
- package/client/dist/assets/AgentBrowserCapture-BX8BNae5.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-BVAqCL0K.js +0 -2
- package/client/dist/assets/AgentModeCodePane-9XfqQc9c.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-BCjLsPhc.js +0 -1
- package/client/dist/assets/AgentsPage-C8J_42sm.js +0 -87
- package/client/dist/assets/AnalyticsPage-BtQuyfJm.js +0 -1
- package/client/dist/assets/CodePage-BZ2MwdCu.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-Bh_29V4g.js +0 -1
- package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-BCZiSfzc.js +0 -19
- package/client/dist/assets/JobDetailModal-NAJmuYIT.js +0 -1
- package/client/dist/assets/JobDetailPage-fE0ZycUX.js +0 -1
- package/client/dist/assets/JobsPage-z5M_HVJG.js +0 -1
- package/client/dist/assets/LoopBuilderPage-j1FQS47c.js +0 -7
- package/client/dist/assets/LoopPreviewModal-DhDJjdCe.js +0 -1
- package/client/dist/assets/LoopsPage-Ll7zORZb.js +0 -1
- package/client/dist/assets/MinimizedChatsContext-Bd3OqR_S.js +0 -1
- package/client/dist/assets/PluginsPage-DN4kGvJt.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-OgsgxjzF.js +0 -1
- package/client/dist/assets/ReviewPacketPage-DFog4sXn.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-L2XxqiOD.js +0 -1
- package/client/dist/assets/TicketDetailModal-CsF-AWZX.js +0 -14
- package/client/dist/assets/Trans-Bsz9_gyq.js +0 -1
- package/client/dist/assets/agent-BQgKv_2F.js +0 -1
- package/client/dist/assets/agent-C1NRDJqL.js +0 -1
- package/client/dist/assets/agent-CLNleiy6.js +0 -1
- package/client/dist/assets/agent-CmceArkQ.js +0 -1
- package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
- package/client/dist/assets/agent-DB1JeE64.js +0 -1
- package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
- package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
- package/client/dist/assets/auth-D_5bQv5W.js +0 -1
- package/client/dist/assets/ban-E4EaZe3a.js +0 -1
- package/client/dist/assets/bot-DzuuuC5R.js +0 -1
- package/client/dist/assets/browser-BgqHDMXP.js +0 -1
- package/client/dist/assets/browser-CsKJtrpv.js +0 -1
- package/client/dist/assets/browser-Cu58EpZU.js +0 -1
- package/client/dist/assets/browser-CvXOJm-j.js +0 -1
- package/client/dist/assets/browser-DOrFh1c7.js +0 -1
- package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
- package/client/dist/assets/browser-LPgxvJu2.js +0 -1
- package/client/dist/assets/browser-eL-3MIEI.js +0 -1
- package/client/dist/assets/builder-0IWtosUm.js +0 -1
- package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
- package/client/dist/assets/builder-BnqaaBkT.js +0 -1
- package/client/dist/assets/builder-BxxfGgAO.js +0 -1
- package/client/dist/assets/builder-C5IIgWsq.js +0 -1
- package/client/dist/assets/builder-CHuahItu.js +0 -1
- package/client/dist/assets/builder-CjWosXL_.js +0 -1
- package/client/dist/assets/builder-CpCIOoKj.js +0 -1
- package/client/dist/assets/code-CHPkOkDS.js +0 -1
- package/client/dist/assets/commands-6tYQih5m.js +0 -1
- package/client/dist/assets/commands-BK2QinXN.js +0 -1
- package/client/dist/assets/commands-C2Nca76h.js +0 -1
- package/client/dist/assets/commands-CKdLihNX.js +0 -1
- package/client/dist/assets/commands-CYIBTAlc.js +0 -1
- package/client/dist/assets/commands-CjUtYJIM.js +0 -1
- package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
- package/client/dist/assets/commands-D_ZHYQug.js +0 -1
- package/client/dist/assets/common-6MLbD-jM.js +0 -1
- package/client/dist/assets/common-BKGEu4rm.js +0 -1
- package/client/dist/assets/common-BNbnv8cE.js +0 -1
- package/client/dist/assets/common-BqGWtDbt.js +0 -1
- package/client/dist/assets/common-DBtTzeYm.js +0 -1
- package/client/dist/assets/common-DEW3Nr8n.js +0 -1
- package/client/dist/assets/common-DHsv9N5d.js +0 -1
- package/client/dist/assets/common-mkrEcW-Y.js +0 -1
- package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
- package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
- package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
- package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
- package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
- package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
- package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
- package/client/dist/assets/dialog-CdWCb02j.js +0 -45
- package/client/dist/assets/dist-bVqfYmby.js +0 -1
- package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
- package/client/dist/assets/formatDistanceToNow-DQCvMgUc.js +0 -1
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-C16cxSe1.js +0 -1
- package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
- package/client/dist/assets/i18n-hnX2DPmo.js +0 -2
- package/client/dist/assets/index-DIhMEAVw.js +0 -66
- package/client/dist/assets/index-DwygcU7s.css +0 -2
- package/client/dist/assets/mcp-B8X8wBLu.js +0 -1
- package/client/dist/assets/mcp-C2AojZZN.js +0 -1
- package/client/dist/assets/mcp-D8BT78_L.js +0 -1
- package/client/dist/assets/mcp-Df5RWBNk.js +0 -1
- package/client/dist/assets/mcp-Dj7BgMQW.js +0 -1
- package/client/dist/assets/mcp-Q7iT7rcz.js +0 -1
- package/client/dist/assets/mcp-YlvNvJbW.js +0 -1
- package/client/dist/assets/mcp-mKFpTSRb.js +0 -1
- package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
- package/client/dist/assets/narration-BGuiEDj3.js +0 -1
- package/client/dist/assets/narration-BuSoqRmv.js +0 -1
- package/client/dist/assets/narration-CB9t29fy.js +0 -1
- package/client/dist/assets/narration-CU3x8FHD.js +0 -1
- package/client/dist/assets/narration-CWeXhfP5.js +0 -1
- package/client/dist/assets/narration-DRaDnhT5.js +0 -1
- package/client/dist/assets/narration-DtEbQgCw.js +0 -1
- package/client/dist/assets/nav-ApJVg3xu.js +0 -1
- package/client/dist/assets/nav-BKNky1Bg.js +0 -1
- package/client/dist/assets/nav-C6vWWKYx.js +0 -1
- package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
- package/client/dist/assets/nav-CsmFBIDf.js +0 -1
- package/client/dist/assets/nav-DjkmQJjd.js +0 -1
- package/client/dist/assets/packet-B1IxaL5f.js +0 -1
- package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
- package/client/dist/assets/packet-By8fqYxW.js +0 -1
- package/client/dist/assets/packet-CZdvA44E.js +0 -1
- package/client/dist/assets/packet-CrjHRQCP.js +0 -1
- package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
- package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
- package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
- package/client/dist/assets/play-DNlrawbS.js +0 -1
- package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
- package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
- package/client/dist/assets/settings-C1-7cXzC.js +0 -1
- package/client/dist/assets/settings-CAFaT38b.js +0 -1
- package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
- package/client/dist/assets/settings-DHh7_IF5.js +0 -1
- package/client/dist/assets/settings-DQXAmdcv.js +0 -1
- package/client/dist/assets/settings-guzlhpup.js +0 -1
- package/client/dist/assets/settings-sZfTPMEH.js +0 -1
- package/client/dist/assets/setup-3jWaoVUO.js +0 -1
- package/client/dist/assets/setup-BjgRneb2.js +0 -1
- package/client/dist/assets/setup-BzFIjv-f.js +0 -1
- package/client/dist/assets/setup-C4VfQsxE.js +0 -1
- package/client/dist/assets/setup-DfUkpDbN.js +0 -1
- package/client/dist/assets/setup-ECe90Pmu.js +0 -1
- package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
- package/client/dist/assets/setup-jwfLMiM2.js +0 -1
- package/client/dist/assets/spending-EC9xqio-.js +0 -1
- package/client/dist/assets/useDesktop-BQVt8lFd.js +0 -1
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.LoopRunManager = void 0;
|
|
7
4
|
exports.truncate = truncate;
|
|
8
5
|
exports.resolveRunVars = resolveRunVars;
|
|
9
6
|
exports.extractChangeId = extractChangeId;
|
|
10
7
|
exports.recoverOrphanLoopStepAccounting = recoverOrphanLoopStepAccounting;
|
|
11
|
-
const
|
|
8
|
+
const multi_repo_execution_store_1 = require("./multi-repo-execution-store");
|
|
9
|
+
const win_spawn_1 = require("./util/win-spawn");
|
|
12
10
|
const db_1 = require("./db");
|
|
13
11
|
const interactive_job_session_1 = require("./interactive-job-session");
|
|
14
12
|
const loop_graph_1 = require("./loop-graph");
|
|
15
13
|
const loop_command_catalog_1 = require("./loop-command-catalog");
|
|
16
14
|
const loop_constants_1 = require("./loop-constants");
|
|
15
|
+
const loop_step_idle_1 = require("./loop-step-idle");
|
|
17
16
|
const loop_decider_1 = require("./loop-decider");
|
|
18
17
|
const loop_runs_store_1 = require("./loop-runs-store");
|
|
19
18
|
const ai_invocations_1 = require("./ai-invocations");
|
|
19
|
+
const provider_limit_1 = require("./provider-limit");
|
|
20
20
|
const ids_1 = require("./ids");
|
|
21
21
|
const providers_1 = require("./providers");
|
|
22
22
|
const runtime_1 = require("./providers/runtime");
|
|
23
23
|
const result_event_1 = require("./result-event");
|
|
24
24
|
const rails_store_1 = require("./rails-store");
|
|
25
|
+
const verification_sentinel_1 = require("./verification-sentinel");
|
|
25
26
|
const IMPLEMENT_CMD_TOKEN_RE = /\{\{\s*cmd:(?:implement|batch)\s*\}\}/;
|
|
26
27
|
const IMPLEMENT_CMD_TEXT_RE = /(?:^|\s)(?:\/(?:specrails:|sr:)?(?:implement|batch-implement)|\$(?:implement|batch-implement))\b/;
|
|
27
28
|
const LOOP_BLOCKED_LINE_RE = /(?:^|\n)\s*LOOP_BLOCKED:\s*(.+?)(?:\n|$)/i;
|
|
@@ -53,6 +54,44 @@ function aiStepBlockedReason(text) {
|
|
|
53
54
|
}
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
57
|
+
/** The interactive step's timeout failure reason (matched by isResumeMiss). */
|
|
58
|
+
const AI_STEP_TIMEOUT_ERROR = 'AI step timed out';
|
|
59
|
+
/** Whole-step zero-work judgment. The interactive path reports it
|
|
60
|
+
* authoritatively (`zeroWork` set at session settle); the one-shot path is
|
|
61
|
+
* derived from the result's accumulated signals — one-shot `text` accumulates
|
|
62
|
+
* ONLY from assistant text-delta events, so non-empty text ⇒ assistant events
|
|
63
|
+
* were seen. A hard-failed one-shot (non-zero exit) is a crash, not zero-work. */
|
|
64
|
+
function aiStepZeroWork(res) {
|
|
65
|
+
if (res.zeroWork !== undefined)
|
|
66
|
+
return res.zeroWork;
|
|
67
|
+
if (res.failed)
|
|
68
|
+
return false;
|
|
69
|
+
return (0, interactive_job_session_1.isZeroWorkSettle)({
|
|
70
|
+
numTurns: res.numTurns ?? 0,
|
|
71
|
+
tokensIn: res.tokensIn ?? 0,
|
|
72
|
+
tokensOut: res.tokensOut ?? 0,
|
|
73
|
+
tokensCacheRead: res.tokensCacheRead ?? 0,
|
|
74
|
+
tokensCacheCreate: res.tokensCacheCreate ?? 0,
|
|
75
|
+
sawAssistantEvent: res.text.trim().length > 0,
|
|
76
|
+
resultText: res.text.trim() ? res.text : null,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** A RESUMED step whose settle is zero-work for a reason other than an
|
|
80
|
+
* unresolvable command or the step timeout: the CLI answered with session
|
|
81
|
+
* bookkeeping (no model call — a transcript the resume could not load, a
|
|
82
|
+
* session torn down mid-flight) and the prompt never ran. Worth exactly one
|
|
83
|
+
* fresh retry; an `Unknown command:` would fail again identically. */
|
|
84
|
+
function isResumeMiss(res) {
|
|
85
|
+
if (res.stalled)
|
|
86
|
+
return false;
|
|
87
|
+
if (!aiStepZeroWork(res))
|
|
88
|
+
return false;
|
|
89
|
+
if (interactive_job_session_1.UNKNOWN_COMMAND_RE.test(res.text.trim()))
|
|
90
|
+
return false;
|
|
91
|
+
if (res.errorText === AI_STEP_TIMEOUT_ERROR)
|
|
92
|
+
return false;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
56
95
|
const HISTORY_MAX_CHARS = 1500;
|
|
57
96
|
/** Caps for the `loop_step` detail fields (`template` / `command`) — the
|
|
58
97
|
* payload persists per event, so a runaway prompt must not bloat the events
|
|
@@ -85,7 +124,7 @@ const RUN_TOKEN_RE = /\{\{\s*run\.(\w+)\s*\}\}/g;
|
|
|
85
124
|
/** First ACTIVE `openspec/changes/<id>` path mentioned in a step's output.
|
|
86
125
|
* Archive paths (`openspec/changes/archive/...`) are historical destinations,
|
|
87
126
|
* not runnable change ids for later `{{run.changeId}}` commands. */
|
|
88
|
-
const CHANGE_ID_RE = /openspec
|
|
127
|
+
const CHANGE_ID_RE = /openspec[\\/]+changes[\\/]+(?!archive(?:[\\/]|$))([A-Za-z0-9._-]+)(?=[\\/]|\s|$)/g;
|
|
89
128
|
/** Replace `{{run.<name>}}` with the captured value; uncaptured → '' (never a
|
|
90
129
|
* leaked literal token). Applied AFTER `{{cmd:*}}` and `{{spec.*}}`. */
|
|
91
130
|
function resolveRunVars(text, vars) {
|
|
@@ -318,6 +357,7 @@ class LoopRunManager {
|
|
|
318
357
|
broadcast;
|
|
319
358
|
executors;
|
|
320
359
|
now;
|
|
360
|
+
options;
|
|
321
361
|
_disposed = false;
|
|
322
362
|
_cancelled = new Set();
|
|
323
363
|
/** The currently-spawned child per run, so cancel/stop can actually KILL a
|
|
@@ -331,11 +371,15 @@ class LoopRunManager {
|
|
|
331
371
|
_interactiveSteps = new Map();
|
|
332
372
|
_activeStepRecovery = new Map();
|
|
333
373
|
_pausedHumanDecisions = new Map();
|
|
334
|
-
constructor(db, broadcast, executors, now = () => Date.now()
|
|
374
|
+
constructor(db, broadcast, executors, now = () => Date.now(),
|
|
375
|
+
/** `idleTimeoutMs`: override the env-resolved loop-step idle watchdog
|
|
376
|
+
* (tests). Undefined ⇒ resolveLoopStepIdleTimeoutMs() per run. */
|
|
377
|
+
options = {}) {
|
|
335
378
|
this.db = db;
|
|
336
379
|
this.broadcast = broadcast;
|
|
337
380
|
this.executors = executors;
|
|
338
381
|
this.now = now;
|
|
382
|
+
this.options = options;
|
|
339
383
|
}
|
|
340
384
|
/** WebSocket delivery is advisory. A disconnected/misbehaving listener must
|
|
341
385
|
* never change launch, accounting, traversal, or terminal outcomes. */
|
|
@@ -365,7 +409,7 @@ class LoopRunManager {
|
|
|
365
409
|
const child = this._activeChild.get(runId);
|
|
366
410
|
if (child?.pid) {
|
|
367
411
|
try {
|
|
368
|
-
(0,
|
|
412
|
+
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL', () => { });
|
|
369
413
|
}
|
|
370
414
|
catch { /* already gone */ }
|
|
371
415
|
}
|
|
@@ -473,7 +517,7 @@ class LoopRunManager {
|
|
|
473
517
|
for (const child of this._activeChild.values()) {
|
|
474
518
|
if (child?.pid) {
|
|
475
519
|
try {
|
|
476
|
-
(0,
|
|
520
|
+
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL', () => { });
|
|
477
521
|
}
|
|
478
522
|
catch { /* gone */ }
|
|
479
523
|
}
|
|
@@ -489,6 +533,24 @@ class LoopRunManager {
|
|
|
489
533
|
this._pausedHumanDecisions.clear();
|
|
490
534
|
}
|
|
491
535
|
async run(req) {
|
|
536
|
+
// Later fresh steps must verify the admitted spec, even if its caller or
|
|
537
|
+
// backlog changes while the implementation is running.
|
|
538
|
+
req = { ...req, graph: structuredClone(req.graph) };
|
|
539
|
+
if (req.spec)
|
|
540
|
+
req = { ...req, spec: structuredClone(req.spec) };
|
|
541
|
+
if (req.executionManifest)
|
|
542
|
+
req = { ...req, executionManifest: structuredClone(req.executionManifest) };
|
|
543
|
+
(0, loop_graph_1.assertLoopShellRepositoryScope)(req.graph, req.executionManifest?.selectedRepositoryIds ?? (req.repositoryId ? [req.repositoryId] : []));
|
|
544
|
+
if (req.executionManifest) {
|
|
545
|
+
for (const node of req.graph.nodes) {
|
|
546
|
+
if (node.type !== 'shell')
|
|
547
|
+
continue;
|
|
548
|
+
const repositoryId = node.data?.repositoryId;
|
|
549
|
+
if (typeof repositoryId !== 'string' || !req.executionManifest.repositories.some((repo) => repo.repositoryId === repositoryId)) {
|
|
550
|
+
throw new Error(`Shell step ${node.id} requires an explicit selected repositoryId for this execution`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
492
554
|
if (this._disposed)
|
|
493
555
|
throw new Error('LoopRunManager is shut down');
|
|
494
556
|
const adapter = (0, providers_1.getAdapter)(req.provider);
|
|
@@ -512,6 +574,14 @@ class LoopRunManager {
|
|
|
512
574
|
const aiStepTimeoutMs = req.graph.config.aiStepTimeoutMinutes != null
|
|
513
575
|
? req.graph.config.aiStepTimeoutMinutes * 60_000
|
|
514
576
|
: undefined;
|
|
577
|
+
// Inactivity bound for every AI step (loop-step-idle): an UNTIMED step is
|
|
578
|
+
// still bounded by silence, so a wedged provider never leaves the run
|
|
579
|
+
// `running` forever. 0 ⇒ disabled.
|
|
580
|
+
const idleTimeoutMs = this.options.idleTimeoutMs ?? (0, loop_step_idle_1.resolveLoopStepIdleTimeoutMs)({
|
|
581
|
+
...process.env,
|
|
582
|
+
// Preserve installations configured with the previous watchdog name.
|
|
583
|
+
SPECRAILS_LOOP_STEP_IDLE_TIMEOUT_MS: process.env.SPECRAILS_LOOP_STEP_IDLE_TIMEOUT_MS ?? process.env.SPECRAILS_LOOP_INACTIVITY_MS,
|
|
584
|
+
});
|
|
515
585
|
// Optional cost cap (USD). Like maxIterations/timeout, it's a BETWEEN-STEPS
|
|
516
586
|
// guard: per-step cost is only known when that step's process exits, so the
|
|
517
587
|
// loop is stopped before the NEXT step once the accumulated total crosses the
|
|
@@ -549,6 +619,8 @@ class LoopRunManager {
|
|
|
549
619
|
iterationLimit: maxIterations,
|
|
550
620
|
startedAt: launchStartedAt,
|
|
551
621
|
});
|
|
622
|
+
if (req.executionManifest)
|
|
623
|
+
this.db.prepare('UPDATE loop_runs SET execution_manifest = ? WHERE id = ?').run(JSON.stringify(req.executionManifest), runId);
|
|
552
624
|
(0, db_1.createJob)(this.db, {
|
|
553
625
|
id: runId,
|
|
554
626
|
command: jobCommand,
|
|
@@ -598,8 +670,9 @@ class LoopRunManager {
|
|
|
598
670
|
// emission (see LoopStepEventPayload).
|
|
599
671
|
const emitStep = (kind, title, nodeId, iteration, detail) => {
|
|
600
672
|
stepNum += 1;
|
|
601
|
-
|
|
602
|
-
|
|
673
|
+
const startedAtMs = this.now();
|
|
674
|
+
openStep = { index: stepNum, nodeId, startMs: startedAtMs };
|
|
675
|
+
const payload = { index: stepNum, kind, title, nodeId, iteration, startedAtMs, ...(detail ?? {}) };
|
|
603
676
|
emitRunEvent('loop_step', payload);
|
|
604
677
|
logLine(`\n━━━━━━ Step ${stepNum} · ${title} ━━━━━━`);
|
|
605
678
|
};
|
|
@@ -619,6 +692,8 @@ class LoopRunManager {
|
|
|
619
692
|
exitCode: input.exitCode ?? null,
|
|
620
693
|
durationMs: input.durationMs ?? Math.max(0, this.now() - openStep.startMs),
|
|
621
694
|
...(input.decision ? { decision: input.decision } : {}),
|
|
695
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
696
|
+
...(input.idleMs != null ? { idleMs: input.idleMs } : {}),
|
|
622
697
|
};
|
|
623
698
|
openStep = null;
|
|
624
699
|
emitRunEvent('loop_step_end', payload);
|
|
@@ -691,6 +766,8 @@ class LoopRunManager {
|
|
|
691
766
|
const byId = (0, loop_graph_1.nodesById)(req.graph);
|
|
692
767
|
const start = (0, loop_graph_1.findStartNode)(req.graph);
|
|
693
768
|
let outcome = 'failed';
|
|
769
|
+
let stallReason;
|
|
770
|
+
let providerLimitHit;
|
|
694
771
|
let iteration = 0;
|
|
695
772
|
let totalCost = 0;
|
|
696
773
|
let totalTokens = 0;
|
|
@@ -720,6 +797,10 @@ class LoopRunManager {
|
|
|
720
797
|
// RESUMED steps already carry prior context in their session, so re-appending
|
|
721
798
|
// the history there is redundant tokens; this flag keeps it off for them.
|
|
722
799
|
let justContinued = false;
|
|
800
|
+
// Transport/test failures are engine facts. A model's STOP cannot turn a
|
|
801
|
+
// failed pass (or a failed final archive command) into a successful run.
|
|
802
|
+
// A continue starts a fresh repair pass which can establish success again.
|
|
803
|
+
let passFailed = false;
|
|
723
804
|
// Fail-open honesty: flips true the first time a cost-bearing step yields no
|
|
724
805
|
// priced cost while a cap is set (non-Claude estimate / unknown model / a
|
|
725
806
|
// failed step) → the cap can under-count, so we warn once in the run log.
|
|
@@ -930,7 +1011,7 @@ class LoopRunManager {
|
|
|
930
1011
|
outcome = 'stopped';
|
|
931
1012
|
break;
|
|
932
1013
|
}
|
|
933
|
-
if (this.now()
|
|
1014
|
+
if (this.now() >= deadline) {
|
|
934
1015
|
outcome = 'failed';
|
|
935
1016
|
break;
|
|
936
1017
|
}
|
|
@@ -962,17 +1043,23 @@ class LoopRunManager {
|
|
|
962
1043
|
nodeId = succs[0]?.id;
|
|
963
1044
|
break;
|
|
964
1045
|
case 'end':
|
|
965
|
-
outcome = node.data?.outcome === 'failure' ? 'failed' : 'success';
|
|
1046
|
+
outcome = node.data?.outcome === 'failure' || passFailed ? 'failed' : 'success';
|
|
966
1047
|
settled = true;
|
|
967
1048
|
break;
|
|
968
1049
|
case 'ai-step': {
|
|
1050
|
+
// Some roles must inspect the previous step without inheriting its
|
|
1051
|
+
// provider conversation (notably Revision's mutator → reviewer
|
|
1052
|
+
// boundary). The node still receives durable disk state, its fully
|
|
1053
|
+
// rendered prompt, and bounded prior-step history.
|
|
1054
|
+
if (node.data?.freshSession === true)
|
|
1055
|
+
aiSessionId = undefined;
|
|
969
1056
|
// Expand magic commands FIRST — `{{cmd:implement}}` becomes the
|
|
970
1057
|
// NATIVE per-provider invocation (claude `/specrails:implement #<id>
|
|
971
1058
|
// --yes`, codex `$implement #<id> --yes`) — then resolve `{{spec.*}}`
|
|
972
1059
|
// data tokens and finally `{{const:*}}` library constants.
|
|
973
1060
|
const rawTemplate = String(node.data?.prompt ?? '');
|
|
974
1061
|
const expanded = (0, loop_constants_1.resolveConstants)(resolveRunVars((0, loop_graph_1.interpolateSpec)((0, loop_command_catalog_1.expandCommands)(rawTemplate, { provider: nodeProvider, ticketIds: req.spec?.ticketIds, specId: req.spec?.id }), req.spec), runVars), constMap);
|
|
975
|
-
const base = withReviewContinuationContext(expanded, rawTemplate, req.spec);
|
|
1062
|
+
const base = [(0, multi_repo_execution_store_1.executionManifestPrompt)(req.executionManifest), withReviewContinuationContext(expanded, rawTemplate, req.spec)].filter(Boolean).join('\n\n');
|
|
976
1063
|
// Inject the cross-iteration history only when there's no live session
|
|
977
1064
|
// to carry it (a fresh pass) OR right after a Decider 'continue' (so the
|
|
978
1065
|
// step sees the verdict). A mid-body resumed step already has it.
|
|
@@ -984,69 +1071,171 @@ class LoopRunManager {
|
|
|
984
1071
|
// were noise once the step explorer landed; the log now opens on real
|
|
985
1072
|
// output). `template` is omitted when rendering changed nothing (a
|
|
986
1073
|
// plain free-text prompt would just duplicate `command`).
|
|
987
|
-
|
|
1074
|
+
const stepTitle = `🤖 ${nodeLabel || 'AI Step'} (${nodeProvider}/${nodeModel}${nodeEffort ? `, effort: ${nodeEffort}` : ''})`;
|
|
1075
|
+
const stepDetail = {
|
|
988
1076
|
...(rawTemplate && rawTemplate !== base ? { template: capText(rawTemplate, STEP_TEMPLATE_CAP) } : {}),
|
|
989
1077
|
...(base ? { command: capText(base, STEP_COMMAND_CAP) } : {}),
|
|
990
|
-
}
|
|
1078
|
+
};
|
|
1079
|
+
emitStep('ai-step', stepTitle, node.id, iteration + 1, stepDetail);
|
|
991
1080
|
// BUG-32: capture the REAL start instant BEFORE the await — the row is
|
|
992
1081
|
// bucketed/ordered by started_at, which must be the turn-start, not the
|
|
993
1082
|
// finish time (this.now() evaluated after the await would be the finish).
|
|
994
|
-
|
|
995
|
-
|
|
1083
|
+
let aiStepStart = new Date(this.now()).toISOString();
|
|
1084
|
+
let aiRecoveryKey = stageAiAccounting('ai', node.id, `loop:${runId}`, aiStepStart);
|
|
996
1085
|
// Interactive upgrade (default for persistent-stdin providers): the
|
|
997
1086
|
// executors return a resident-session plan when the kill-switch is on
|
|
998
1087
|
// and the provider is capable (claude); null falls through to the
|
|
999
1088
|
// byte-identical one-shot spawn. The plan mirrors the one-shot's
|
|
1000
1089
|
// cwd/env/argv derivation; the engine owns the session lifecycle.
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1090
|
+
// One attempt of this step. `sessionId` is the ONLY thing that
|
|
1091
|
+
// differs between the resumed attempt and its fresh retry below.
|
|
1092
|
+
const runAttempt = async (sessionId) => {
|
|
1093
|
+
const remainingMs = deadline - this.now();
|
|
1094
|
+
const effectiveTimeoutMs = Number.isFinite(remainingMs)
|
|
1095
|
+
? Math.max(1, Math.min(aiStepTimeoutMs === 0 ? Infinity : (aiStepTimeoutMs ?? 15 * 60_000), remainingMs))
|
|
1096
|
+
: aiStepTimeoutMs;
|
|
1097
|
+
const coreRun = { runId, spec: req.spec, goal: req.spec ? undefined : JSON.stringify({ loop: req.loopName, graph: req.graph }), repositoryId: req.repositoryId, verificationStep: node.data?.requireVerificationPass === true || /\{\{\s*cmd:(?:verify|revision-verify|opsx:verify)\s*\}\}/.test(rawTemplate) };
|
|
1098
|
+
const interactivePlan = this.executors.planInteractiveAiStep?.({
|
|
1099
|
+
coreRun,
|
|
1100
|
+
provider: nodeProvider,
|
|
1101
|
+
model: nodeModel,
|
|
1102
|
+
effort: nodeEffort,
|
|
1103
|
+
profileName: req.profileName,
|
|
1104
|
+
cwd: req.cwd,
|
|
1105
|
+
repoDir: req.repoDir, executionManifest: req.executionManifest,
|
|
1106
|
+
sessionId,
|
|
1107
|
+
aiStepTimeoutMs: effectiveTimeoutMs,
|
|
1108
|
+
idleTimeoutMs,
|
|
1109
|
+
}) ?? null;
|
|
1110
|
+
return interactivePlan
|
|
1111
|
+
? this._runInteractiveAiStep({
|
|
1112
|
+
runId,
|
|
1113
|
+
projectId: req.projectId,
|
|
1114
|
+
plan: interactivePlan,
|
|
1115
|
+
prompt: [interactivePlan.promptPrefix, prompt].filter(Boolean).join('\n\n'),
|
|
1116
|
+
fallbackModel: nodeModel,
|
|
1117
|
+
nextEventSeq: takeSeq,
|
|
1118
|
+
recoveryStepKey: aiRecoveryKey,
|
|
1119
|
+
})
|
|
1120
|
+
: this.executors.runAiStep({ coreRun, prompt, sessionId, provider: nodeProvider, model: nodeModel, effort: nodeEffort, profileName: req.profileName, cwd: req.cwd, repoDir: req.repoDir, executionManifest: req.executionManifest, onLine: logLine, onRawLine, onSpawn: (c) => this._activeChild.set(runId, c), aiStepTimeoutMs: effectiveTimeoutMs, idleTimeoutMs });
|
|
1121
|
+
};
|
|
1122
|
+
let res = await runAttempt(aiSessionId);
|
|
1021
1123
|
if (this._disposed)
|
|
1022
1124
|
return neverAfterDispose();
|
|
1023
1125
|
this._activeChild.delete(runId);
|
|
1126
|
+
// Idle stall (loop-step-idle): the provider produced no stream
|
|
1127
|
+
// activity for the idle budget and the watchdog tore the step down.
|
|
1128
|
+
// Close this attempt as `stalled`, then retry the SAME step ONCE by
|
|
1129
|
+
// resuming its session (the worktree + transcript carry the partial
|
|
1130
|
+
// work). A second stall fails the step and settles the run `stalled`
|
|
1131
|
+
// instead of leaving it `running` forever. A user cancel is not a
|
|
1132
|
+
// stall — the loop head settles it 'stopped' below.
|
|
1133
|
+
if (res.stalled && !this._cancelled.has(runId)) {
|
|
1134
|
+
const stalledSession = res.sessionId ?? aiSessionId;
|
|
1135
|
+
const idleMin = Math.max(1, Math.round(idleTimeoutMs / 60_000));
|
|
1136
|
+
emitStepEnd({ status: 'stalled', reason: 'idle_timeout', idleMs: idleTimeoutMs, durationMs: res.durationMs });
|
|
1137
|
+
// Each provider process owns independent accounting and recovery
|
|
1138
|
+
// frontiers. Settle the first attempt before staging its retry.
|
|
1139
|
+
record(`loop:${runId}`, { ...res, failed: true }, aiStepStart, aiRecoveryKey);
|
|
1140
|
+
this._activeStepRecovery.delete(runId);
|
|
1141
|
+
if (this.now() >= deadline || (maxCostUsd !== undefined && totalCost >= maxCostUsd)) {
|
|
1142
|
+
outcome = this.now() >= deadline ? 'failed' : 'max-cost';
|
|
1143
|
+
logLine('■ Step stalled; the run deadline or cost cap prevents another attempt.', 'stderr');
|
|
1144
|
+
settled = true;
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
logLine(`↻ Step stalled — no provider output for ${idleMin} min. Retrying this step once${stalledSession ? ` by resuming session ${stalledSession}` : ''}.`, 'stderr');
|
|
1148
|
+
emitStep('ai-step', stepTitle, node.id, iteration + 1, { ...stepDetail, attempt: 2 });
|
|
1149
|
+
aiStepStart = new Date(this.now()).toISOString();
|
|
1150
|
+
aiRecoveryKey = stageAiAccounting('ai', node.id, `loop:${runId}`, aiStepStart);
|
|
1151
|
+
res = await runAttempt(stalledSession);
|
|
1152
|
+
if (this._disposed)
|
|
1153
|
+
return neverAfterDispose();
|
|
1154
|
+
this._activeChild.delete(runId);
|
|
1155
|
+
if (res.stalled && !this._cancelled.has(runId)) {
|
|
1156
|
+
emitStepEnd({ status: 'stalled', reason: 'idle_timeout', idleMs: idleTimeoutMs, durationMs: res.durationMs });
|
|
1157
|
+
logLine(`■ Step stalled twice — no provider output for ${idleMin} min on the resumed attempt either; stopping the run.`, 'stderr');
|
|
1158
|
+
history.push(`AI Step: ${truncate(res.text)}`);
|
|
1159
|
+
record(`loop:${runId}`, { ...res, failed: true }, aiStepStart, aiRecoveryKey);
|
|
1160
|
+
this._activeStepRecovery.delete(runId);
|
|
1161
|
+
stallReason = 'idle_timeout';
|
|
1162
|
+
outcome = 'stalled';
|
|
1163
|
+
settled = true;
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
// Resume miss (loop run 5c958db2): a step that `--resume`d the
|
|
1168
|
+
// previous step's session and settled with ZERO work — no model
|
|
1169
|
+
// call, no `Unknown command:` text — was answered by the CLI's
|
|
1170
|
+
// session bookkeeping (a transcript the resume could not load, a
|
|
1171
|
+
// session torn down mid-flight), never by the model. The prompt did
|
|
1172
|
+
// not run, so retry it ONCE in a fresh session instead of failing
|
|
1173
|
+
// the step and spinning the loop through verify/fix on nothing.
|
|
1174
|
+
// Unknown commands, timeouts and cancels are NOT resume misses.
|
|
1175
|
+
if (aiSessionId && !this._cancelled.has(runId) && isResumeMiss(res)) {
|
|
1176
|
+
logLine(`↻ Resuming session ${aiSessionId} produced no work — retrying this step in a fresh session.`, 'stderr');
|
|
1177
|
+
aiSessionId = undefined;
|
|
1178
|
+
res = await runAttempt(undefined);
|
|
1179
|
+
if (this._disposed)
|
|
1180
|
+
return neverAfterDispose();
|
|
1181
|
+
this._activeChild.delete(runId);
|
|
1182
|
+
}
|
|
1024
1183
|
// Zero-work strictness (run 01f41203): a settle that consumed NO
|
|
1025
1184
|
// model work — the claude CLI's synthetic `Unknown command:` result
|
|
1026
1185
|
// frame (num_turns 0, no assistant events, zero usage tokens) —
|
|
1027
1186
|
// means the step's command never actually ran. A step that didn't
|
|
1028
1187
|
// run is FAILED, never 'ok'. The interactive path evaluates the
|
|
1029
1188
|
// predicate at session settle (res.zeroWork set); for the one-shot
|
|
1030
|
-
// path the engine derives it
|
|
1031
|
-
//
|
|
1032
|
-
|
|
1033
|
-
const oneShotZeroWork = res.zeroWork === undefined &&
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1189
|
+
// path the engine derives it from the result's accumulated signals
|
|
1190
|
+
// (aiStepZeroWork).
|
|
1191
|
+
const zeroWork = aiStepZeroWork(res);
|
|
1192
|
+
const oneShotZeroWork = res.zeroWork === undefined && zeroWork;
|
|
1193
|
+
// Provider usage/rate limit (run 52124009): the CLI answers with the
|
|
1194
|
+
// limit notice as the reply. Iterating on it burns cycles and hides
|
|
1195
|
+
// the real cause — stop the run at once with the provider's own
|
|
1196
|
+
// words and the reset hint, and tell every surface.
|
|
1197
|
+
const providerLimit = !this._cancelled.has(runId)
|
|
1198
|
+
? ((0, provider_limit_1.classifyProviderLimit)(res.errorText) ?? (0, provider_limit_1.classifyProviderLimit)(res.text))
|
|
1199
|
+
: null;
|
|
1200
|
+
if (providerLimit) {
|
|
1201
|
+
const described = (0, provider_limit_1.describeProviderLimit)(providerLimit);
|
|
1202
|
+
emitStepEnd({ status: 'failed', reason: 'provider_limit', durationMs: res.durationMs });
|
|
1203
|
+
logLine(`■ Provider limit reached (${req.provider}): ${described}. Stopping the loop now — relaunch or Recover & retry after the reset.`, 'stderr');
|
|
1204
|
+
history.push(`AI Step: ${truncate(res.text)}`);
|
|
1205
|
+
record(`loop:${runId}`, { ...res, failed: true }, aiStepStart, aiRecoveryKey);
|
|
1206
|
+
this._activeStepRecovery.delete(runId);
|
|
1207
|
+
this._emit({
|
|
1208
|
+
type: 'loop.provider_limit',
|
|
1209
|
+
projectId: req.projectId,
|
|
1210
|
+
runId,
|
|
1211
|
+
provider: req.provider,
|
|
1212
|
+
kind: providerLimit.kind,
|
|
1213
|
+
message: providerLimit.message,
|
|
1214
|
+
resetsAt: providerLimit.resetsAt,
|
|
1215
|
+
timestamp: new Date().toISOString(),
|
|
1216
|
+
});
|
|
1217
|
+
stallReason = 'provider_limit';
|
|
1218
|
+
providerLimitHit = providerLimit;
|
|
1219
|
+
outcome = 'stalled';
|
|
1220
|
+
settled = true;
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1043
1223
|
const blockedReason = aiStepBlockedReason(res.text);
|
|
1044
|
-
const
|
|
1224
|
+
const requiresVerification = node.data?.requireVerificationPass === true
|
|
1225
|
+
|| /\{\{cmd:(?:verify|revision-verify|opsx:verify)\}\}/.test(rawTemplate);
|
|
1226
|
+
const verificationFailed = requiresVerification
|
|
1227
|
+
&& (0, verification_sentinel_1.parseVerificationSentinel)(res.text).verdict !== 'pass';
|
|
1228
|
+
const stepFailed = res.failed === true || zeroWork || blockedReason !== null || verificationFailed;
|
|
1229
|
+
passFailed ||= res.failed === true || zeroWork || (verificationFailed && !blockedReason);
|
|
1045
1230
|
const stepErrorText = res.errorText ?? (zeroWork
|
|
1046
1231
|
? `zero work performed — the command never ran${res.text.trim() ? `: ${res.text.trim()}` : ''}`
|
|
1047
1232
|
: blockedReason
|
|
1048
1233
|
? `blocked — ${blockedReason}`
|
|
1049
|
-
:
|
|
1234
|
+
: verificationFailed
|
|
1235
|
+
? 'verification did not finish with VERIFICATION: PASS'
|
|
1236
|
+
: undefined);
|
|
1237
|
+
if (verificationFailed && !blockedReason)
|
|
1238
|
+
logLine(`✖ ${stepErrorText}`, 'stderr');
|
|
1050
1239
|
// Make the failure reason land visibly INSIDE the step's log
|
|
1051
1240
|
// segment (the interactive session already surfaced its own note at
|
|
1052
1241
|
// settle; the Template/Command flat-log lines are gone, so without
|
|
@@ -1067,7 +1256,9 @@ class LoopRunManager {
|
|
|
1067
1256
|
// would otherwise make the next step `--resume` a dead session.
|
|
1068
1257
|
if (res.sessionId && (!stepFailed || (blockedReason !== null && !res.failed && !zeroWork)))
|
|
1069
1258
|
aiSessionId = res.sessionId;
|
|
1070
|
-
|
|
1259
|
+
else if (stepFailed)
|
|
1260
|
+
aiSessionId = undefined;
|
|
1261
|
+
history.push(`AI Step${stepFailed ? ` FAILED (${stepErrorText ?? 'provider invocation failed'})` : ''}: ${truncate(res.text)}`);
|
|
1071
1262
|
record(`loop:${runId}`, { ...res, failed: stepFailed }, aiStepStart, aiRecoveryKey);
|
|
1072
1263
|
this._activeStepRecovery.delete(runId);
|
|
1073
1264
|
if (blockedReason) {
|
|
@@ -1101,7 +1292,11 @@ class LoopRunManager {
|
|
|
1101
1292
|
// (its `Unknown command:` text is a CLI synthetic, not model output),
|
|
1102
1293
|
// so a persistently-unresolvable command aborts the run identically
|
|
1103
1294
|
// instead of grinding to the cap.
|
|
1104
|
-
if (
|
|
1295
|
+
if (this._cancelled.has(runId)) {
|
|
1296
|
+
// A user cancel tears the step down with no output — that is not
|
|
1297
|
+
// a provider failure; the loop head settles the run 'stopped'.
|
|
1298
|
+
}
|
|
1299
|
+
else if (stepFailed && (zeroWork || res.resultIsError || !res.text.trim())) {
|
|
1105
1300
|
consecutiveAiFailures += 1;
|
|
1106
1301
|
if (consecutiveAiFailures >= AI_FAILFAST_THRESHOLD) {
|
|
1107
1302
|
logLine(`Loop aborted: provider appears down — ${consecutiveAiFailures} AI steps failed with no output and no successful AI call in between${stepErrorText ? ` — ${stepErrorText}` : ''}`, 'stderr');
|
|
@@ -1138,12 +1333,14 @@ class LoopRunManager {
|
|
|
1138
1333
|
const command = (0, loop_constants_1.resolveConstants)(resolveRunVars((0, loop_graph_1.interpolateSpec)(String(node.data?.command ?? ''), req.spec), runVars), constMap);
|
|
1139
1334
|
emitStep('shell', `⚡ ${nodeLabel || 'Shell'}`, node.id, iteration + 1);
|
|
1140
1335
|
logLine(`$ ${command}`);
|
|
1141
|
-
const
|
|
1336
|
+
const shellRepo = req.executionManifest?.repositories.find((repo) => repo.repositoryId === node.data?.repositoryId)?.worktreePath ?? req.repoDir;
|
|
1337
|
+
const sh = await this.executors.runShell({ command, cwd: req.executionManifest ? shellRepo ?? req.cwd : req.cwd, repoDir: shellRepo, timeoutMs: Math.min(10 * 60_000, Math.max(1, deadline - this.now())), onLine: logLine, onSpawn: (c) => this._activeChild.set(runId, c) });
|
|
1142
1338
|
if (this._disposed)
|
|
1143
1339
|
return neverAfterDispose();
|
|
1144
1340
|
this._activeChild.delete(runId);
|
|
1145
1341
|
logLine(`(exit ${sh.exitCode})`);
|
|
1146
1342
|
emitStepEnd({ status: sh.exitCode === 0 ? 'ok' : 'failed', exitCode: sh.exitCode, durationMs: sh.durationMs });
|
|
1343
|
+
passFailed ||= sh.exitCode !== 0;
|
|
1147
1344
|
totalDuration += sh.durationMs ?? 0;
|
|
1148
1345
|
(0, loop_runs_store_1.updateLoopRunCounters)(this.db, runId, {
|
|
1149
1346
|
iterationCount: iteration,
|
|
@@ -1169,23 +1366,27 @@ class LoopRunManager {
|
|
|
1169
1366
|
// BUG-32: capture the real Decider start BEFORE the await (see AI step).
|
|
1170
1367
|
const deciderStart = new Date(this.now()).toISOString();
|
|
1171
1368
|
const deciderRecoveryKey = stageAiAccounting('decider', node.id, `loop:${runId}:decider`, deciderStart);
|
|
1172
|
-
|
|
1369
|
+
let dec = await this.executors.runDecider({
|
|
1173
1370
|
systemPrompt: (0, loop_decider_1.buildDeciderSystemPrompt)(),
|
|
1174
1371
|
// Give the Decider the spec so it can verify completeness against the
|
|
1175
1372
|
// FULL scope instead of trusting a step's self-reported success.
|
|
1176
|
-
userPrompt: (0, loop_decider_1.buildDeciderUserPrompt)({ goal, history, spec: req.spec
|
|
1373
|
+
userPrompt: [(0, multi_repo_execution_store_1.executionManifestPrompt)(req.executionManifest), (0, loop_decider_1.buildDeciderUserPrompt)({ goal, history, spec: req.spec })].filter(Boolean).join('\n\n'),
|
|
1177
1374
|
provider: nodeProvider,
|
|
1178
1375
|
model: nodeModel,
|
|
1179
1376
|
effort: nodeEffort,
|
|
1180
1377
|
cwd: req.cwd,
|
|
1181
|
-
repoDir: req.repoDir,
|
|
1378
|
+
repoDir: req.repoDir, executionManifest: req.executionManifest,
|
|
1182
1379
|
onLine: logLine,
|
|
1183
1380
|
onRawLine,
|
|
1184
1381
|
onSpawn: (c) => this._activeChild.set(runId, c),
|
|
1382
|
+
timeoutMs: Math.min(3 * 60_000, Math.max(1, deadline - this.now())),
|
|
1185
1383
|
});
|
|
1186
1384
|
if (this._disposed)
|
|
1187
1385
|
return neverAfterDispose();
|
|
1188
1386
|
this._activeChild.delete(runId);
|
|
1387
|
+
if ((!dec.parsed || passFailed) && !dec.blocked && !dec.continue) {
|
|
1388
|
+
dec = { ...dec, continue: true, reasoning: `A required step failed in this pass; repair and verify before completion. ${dec.reasoning}` };
|
|
1389
|
+
}
|
|
1189
1390
|
// A parseable Decider verdict means this AI invocation (SAME
|
|
1190
1391
|
// provider/model) succeeded → the provider is alive, so clear any
|
|
1191
1392
|
// ai-step failure streak. This stops a false fail-fast abort when an
|
|
@@ -1257,14 +1458,16 @@ class LoopRunManager {
|
|
|
1257
1458
|
// (e.g. verify keeps passing a baseline while the feature is never
|
|
1258
1459
|
// written) — abort `stalled` rather than cycle to the cap.
|
|
1259
1460
|
if (dec.continue && this.executors.repoStateHash) {
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1461
|
+
const dirs = req.executionManifest?.repositories.map((repo) => repo.worktreePath) ?? [req.repoDir ?? req.cwd];
|
|
1462
|
+
const hashes = dirs.map((dir) => this.executors.repoStateHash(dir));
|
|
1463
|
+
const hash = hashes.some((value) => value === null) ? null : JSON.stringify(hashes);
|
|
1262
1464
|
// null = couldn't read the tree → skip (never let null match null).
|
|
1263
1465
|
if (hash !== null) {
|
|
1264
1466
|
if (hash === lastRepoHash) {
|
|
1265
1467
|
consecutiveNoProgress += 1;
|
|
1266
1468
|
if (consecutiveNoProgress >= NO_PROGRESS_LIMIT) {
|
|
1267
1469
|
logLine(`\n■ Loop stalled — ${consecutiveNoProgress + 1} iterations changed nothing in the working tree; stopping instead of cycling.`, 'stderr');
|
|
1470
|
+
stallReason = 'no_progress';
|
|
1268
1471
|
outcome = 'stalled';
|
|
1269
1472
|
settled = true;
|
|
1270
1473
|
break;
|
|
@@ -1277,9 +1480,17 @@ class LoopRunManager {
|
|
|
1277
1480
|
}
|
|
1278
1481
|
}
|
|
1279
1482
|
if (dec.continue) {
|
|
1483
|
+
// Enforce the cap before another expensive implementation pass,
|
|
1484
|
+
// not after that extra pass reaches the next decider.
|
|
1485
|
+
if (iteration >= maxIterations) {
|
|
1486
|
+
outcome = 'max-iterations';
|
|
1487
|
+
settled = true;
|
|
1488
|
+
break;
|
|
1489
|
+
}
|
|
1280
1490
|
const target = branchTarget('continue') ?? conts[0]?.id;
|
|
1281
|
-
if (target) {
|
|
1491
|
+
if (target && byId.get(target)?.type !== 'end') {
|
|
1282
1492
|
nodeId = target;
|
|
1493
|
+
passFailed = false;
|
|
1283
1494
|
// The next step acts on this verdict → let it see the history.
|
|
1284
1495
|
justContinued = true;
|
|
1285
1496
|
// New iteration of an iterate-per-item loop → drop the resumed AI
|
|
@@ -1291,7 +1502,7 @@ class LoopRunManager {
|
|
|
1291
1502
|
aiSessionId = undefined;
|
|
1292
1503
|
}
|
|
1293
1504
|
else {
|
|
1294
|
-
outcome = '
|
|
1505
|
+
outcome = 'failed';
|
|
1295
1506
|
settled = true;
|
|
1296
1507
|
}
|
|
1297
1508
|
}
|
|
@@ -1307,7 +1518,14 @@ class LoopRunManager {
|
|
|
1307
1518
|
break;
|
|
1308
1519
|
}
|
|
1309
1520
|
case 'condition':
|
|
1310
|
-
//
|
|
1521
|
+
// Multiple branches have no implemented condition evaluator. Never
|
|
1522
|
+
// silently skip required branches and report a successful run.
|
|
1523
|
+
if (succs.length !== 1) {
|
|
1524
|
+
logLine('Condition branching is unsupported; replace it with explicit sequential steps or a Decider.', 'stderr');
|
|
1525
|
+
outcome = 'failed';
|
|
1526
|
+
settled = true;
|
|
1527
|
+
break;
|
|
1528
|
+
}
|
|
1311
1529
|
nodeId = succs[0]?.id;
|
|
1312
1530
|
break;
|
|
1313
1531
|
default:
|
|
@@ -1315,7 +1533,7 @@ class LoopRunManager {
|
|
|
1315
1533
|
}
|
|
1316
1534
|
// Ran off the end of the graph without an explicit End node.
|
|
1317
1535
|
if (nodeId === undefined && !settled) {
|
|
1318
|
-
outcome = '
|
|
1536
|
+
outcome = 'failed';
|
|
1319
1537
|
settled = true;
|
|
1320
1538
|
}
|
|
1321
1539
|
}
|
|
@@ -1455,6 +1673,8 @@ class LoopRunManager {
|
|
|
1455
1673
|
outcome,
|
|
1456
1674
|
iterations: iteration,
|
|
1457
1675
|
totalCostUsd: usageTelemetryAvailable ? totalCost : null,
|
|
1676
|
+
...(outcome === 'stalled' && stallReason ? { stallReason } : {}),
|
|
1677
|
+
...(providerLimitHit ? { providerLimit: providerLimitHit } : {}),
|
|
1458
1678
|
};
|
|
1459
1679
|
}
|
|
1460
1680
|
/**
|
|
@@ -1486,6 +1706,9 @@ class LoopRunManager {
|
|
|
1486
1706
|
return new Promise((resolve) => {
|
|
1487
1707
|
let timer = null;
|
|
1488
1708
|
let timedOut = false;
|
|
1709
|
+
// Set by the session's wedge detector BEFORE it aborts: tells a stall
|
|
1710
|
+
// (no raw output for the idle budget) apart from any other 'crashed'.
|
|
1711
|
+
let stalled = false;
|
|
1489
1712
|
const session = new interactive_job_session_1.InteractiveJobSession({
|
|
1490
1713
|
jobId: input.runId,
|
|
1491
1714
|
projectId: input.projectId,
|
|
@@ -1493,8 +1716,13 @@ class LoopRunManager {
|
|
|
1493
1716
|
adapter: input.plan.adapter,
|
|
1494
1717
|
broadcast: this.broadcast,
|
|
1495
1718
|
settleMode: 'auto',
|
|
1496
|
-
//
|
|
1497
|
-
//
|
|
1719
|
+
// Inactivity bound (loop-step-idle): the session's zombie timer IS the
|
|
1720
|
+
// step's idle watchdog — reset on any raw child output, silence for the
|
|
1721
|
+
// whole budget folds the in-flight turn and settles 'crashed' with the
|
|
1722
|
+
// `stalled` flag set so the engine retries the step once by resume.
|
|
1723
|
+
// Absent (0) ⇒ the step timeout below is the only watchdog.
|
|
1724
|
+
zombieTimeoutMs: input.plan.idleTimeoutMs ?? input.plan.inactivityTimeoutMs ?? 0,
|
|
1725
|
+
onZombieTimeout: () => { stalled = true; },
|
|
1498
1726
|
nextEventSeq: input.nextEventSeq,
|
|
1499
1727
|
persistTurnUsage: (usage, completedEventSeq, checkpoint) => {
|
|
1500
1728
|
const checkpointTurn = this.db.transaction(() => {
|
|
@@ -1529,7 +1757,7 @@ class LoopRunManager {
|
|
|
1529
1757
|
// command:` frame) is a FAILED step — the command never ran. Judged
|
|
1530
1758
|
// by the session itself over the accumulated totals (whole-session,
|
|
1531
1759
|
// so a multi-turn step ending on one synthetic frame is unaffected).
|
|
1532
|
-
const failed = info.reason === 'crashed' || info.zeroWork;
|
|
1760
|
+
const failed = info.reason === 'crashed' || info.zeroWork || info.resultIsError;
|
|
1533
1761
|
resolve({
|
|
1534
1762
|
// The last turn's `result` payload — the same terminal text the
|
|
1535
1763
|
// one-shot path captures (feeds history/changeId/fail-fast).
|
|
@@ -1554,9 +1782,13 @@ class LoopRunManager {
|
|
|
1554
1782
|
zeroWork: info.zeroWork,
|
|
1555
1783
|
errorText: failed
|
|
1556
1784
|
? (info.reason === 'crashed'
|
|
1557
|
-
? (timedOut ?
|
|
1558
|
-
:
|
|
1785
|
+
? (stalled ? loop_step_idle_1.AI_STEP_STALLED_ERROR : timedOut ? AI_STEP_TIMEOUT_ERROR : info.resultIsError && info.resultText ? info.resultText : 'interactive step session crashed')
|
|
1786
|
+
: info.zeroWork
|
|
1787
|
+
? `zero work performed — the command never ran${info.resultText ? `: ${info.resultText}` : ''}`
|
|
1788
|
+
: `provider returned an error result${info.resultText ? ` — ${info.resultText}` : ''}`)
|
|
1559
1789
|
: undefined,
|
|
1790
|
+
...(stalled && info.reason === 'crashed' ? { stalled: true } : {}),
|
|
1791
|
+
...(info.resultIsError ? { resultIsError: true } : {}),
|
|
1560
1792
|
});
|
|
1561
1793
|
},
|
|
1562
1794
|
});
|