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
package/server/dist/index.js
CHANGED
|
@@ -44,12 +44,16 @@ const framework_manager_1 = require("./framework-manager");
|
|
|
44
44
|
const artifact_registry_1 = require("./artifact-registry");
|
|
45
45
|
const path_resolver_1 = require("./path-resolver");
|
|
46
46
|
const dev_ports_1 = require("./dev-ports");
|
|
47
|
+
const api_not_found_1 = require("./api-not-found");
|
|
47
48
|
const process_admission_1 = require("./process-admission");
|
|
49
|
+
const host_control_1 = require("./host-control");
|
|
50
|
+
const transient_children_1 = require("./transient-children");
|
|
48
51
|
// Side-effect import: registers every bundled ProviderAdapter (claude, codex,
|
|
49
52
|
// future providers) so `getAdapter`/`hasAdapter`/`listAdapters` are populated
|
|
50
53
|
// before any manager constructs a project context. See
|
|
51
54
|
// openspec/changes/add-multi-provider-support/specs/multi-provider-architecture/spec.md.
|
|
52
55
|
require("./providers");
|
|
56
|
+
const hostControlToken = (0, host_control_1.consumeHostControlToken)();
|
|
53
57
|
const inheritedPathBeforeResolve = (process.env.PATH ?? '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean).length;
|
|
54
58
|
// Backfill SystemRoot/ComSpec/etc into process.env BEFORE anything spawns, so a
|
|
55
59
|
// stripped GUI-launch sidecar env never breaks cmd.exe-mediated spawns (PTY,
|
|
@@ -155,6 +159,7 @@ function corsMiddleware(req, res, next) {
|
|
|
155
159
|
next();
|
|
156
160
|
}
|
|
157
161
|
app.use(corsMiddleware);
|
|
162
|
+
app.use('/api/host', (0, host_control_1.createHostControlRouter)({ token: hostControlToken, onShutdown: shutdown }));
|
|
158
163
|
// Provider traffic uses the desktop listener as a stable relay. Mount before
|
|
159
164
|
// every body parser so large/streaming request bodies and Codex event streams
|
|
160
165
|
// pass through byte-for-byte; the relay verifies the connected Headroom backend
|
|
@@ -504,9 +509,17 @@ function applyPtyWsRateLimiting(ws) {
|
|
|
504
509
|
// ─── Super-mode bootstrap ─────────────────────────────────────────────────────
|
|
505
510
|
{
|
|
506
511
|
const registry = new project_registry_1.ProjectRegistry(broadcast, undefined, port);
|
|
512
|
+
try {
|
|
513
|
+
(0, transient_children_1.initializeBackgroundProcessPersistence)(path_1.default.join(path_1.default.dirname(registry.desktopDb.name), 'background-processes.sqlite'));
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
// A log-store failure must not take mission chat or the project catalog
|
|
517
|
+
// offline. New background launches report the persistence error directly.
|
|
518
|
+
console.error('[background-process] persistent history unavailable:', error);
|
|
519
|
+
}
|
|
507
520
|
registry.loadAll();
|
|
508
521
|
_registry = registry;
|
|
509
|
-
_getProjectCount = () => registry.
|
|
522
|
+
_getProjectCount = () => registry.listProjects().length;
|
|
510
523
|
// ─── Bundled framework update channel (Phase 6) ─────────────────────────────
|
|
511
524
|
// After the registry + projects have loaded, run a single framework
|
|
512
525
|
// version-check. On FIRST run it materializes the bundled framework to
|
|
@@ -606,6 +619,15 @@ function applyPtyWsRateLimiting(ws) {
|
|
|
606
619
|
broadcast,
|
|
607
620
|
}));
|
|
608
621
|
if (mcpManager.isEnabledSetting()) {
|
|
622
|
+
// MCP is on by default, so mint + persist the scoped token at boot: the
|
|
623
|
+
// stdio bridge reads ~/.specrails/mcp.token locally, and on a fresh install
|
|
624
|
+
// nothing else would have written it until Settings ▸ MCP was opened.
|
|
625
|
+
try {
|
|
626
|
+
(0, mcp_1.getMcpToken)();
|
|
627
|
+
}
|
|
628
|
+
catch (err) {
|
|
629
|
+
console.warn('[mcp] could not prepare the scoped token at boot:', err);
|
|
630
|
+
}
|
|
609
631
|
mcpManager.start().catch((err) => console.error('[mcp] boot start failed:', err));
|
|
610
632
|
}
|
|
611
633
|
// ─── App-global agent chat (drives the app via its own MCP) ────────────────
|
|
@@ -660,7 +682,7 @@ function applyPtyWsRateLimiting(ws) {
|
|
|
660
682
|
state.subscribedProjectId = frame.projectId;
|
|
661
683
|
});
|
|
662
684
|
// Send app state init
|
|
663
|
-
const projects = registry.
|
|
685
|
+
const projects = registry.listProjects();
|
|
664
686
|
ws.send(JSON.stringify({
|
|
665
687
|
type: 'desktop.projects',
|
|
666
688
|
projects,
|
|
@@ -680,6 +702,7 @@ app.use((err, _req, res, _next) => {
|
|
|
680
702
|
}
|
|
681
703
|
});
|
|
682
704
|
// ─── Serve built React client (production) ────────────────────────────────────
|
|
705
|
+
app.use(['/api', '/hooks'], api_not_found_1.apiNotFound);
|
|
683
706
|
const clientDist = path_1.default.resolve(__dirname, '../../client/dist');
|
|
684
707
|
if (fs_1.default.existsSync(clientDist)) {
|
|
685
708
|
app.use(express_1.default.static(clientDist));
|
|
@@ -699,6 +722,13 @@ server.on('error', (err) => {
|
|
|
699
722
|
server.listen(port, '127.0.0.1', () => {
|
|
700
723
|
console.log(`specrails web manager running on http://127.0.0.1:${port}`);
|
|
701
724
|
writePidFile();
|
|
725
|
+
// Milestone launch chains launch chunks over loopback REST, so a chain whose
|
|
726
|
+
// chunk settled while the server was down can only replay once we listen.
|
|
727
|
+
for (const ctx of _registry?.listContexts() ?? []) {
|
|
728
|
+
void ctx.milestoneChains?.recoverOnStartup().catch((err) => {
|
|
729
|
+
console.error(`[milestone-chain] startup recovery failed for ${ctx.project.id}:`, err);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
702
732
|
// Sweep stale shell-integration shim directories left behind by previous runs.
|
|
703
733
|
try {
|
|
704
734
|
const removed = (0, terminal_shell_integration_1.cleanupStaleShimDirs)();
|
|
@@ -744,6 +774,17 @@ async function shutdown() {
|
|
|
744
774
|
Promise.resolve().then(() => _agentChatManager?.shutdown()),
|
|
745
775
|
Promise.resolve().then(() => _blueprintChatManager?.shutdown()),
|
|
746
776
|
]);
|
|
777
|
+
// The shell can exit before its application descendants. Keep the event
|
|
778
|
+
// loop alive for the owned-group escalation before server.close exits us.
|
|
779
|
+
const remainingBackground = await (0, transient_children_1.awaitBackgroundProcessesStopped)(6000);
|
|
780
|
+
if (remainingBackground.length)
|
|
781
|
+
console.error('[shutdown] background applications still alive after stop deadline:', remainingBackground.map(({ pid, processId, error }) => ({ pid, processId, error })));
|
|
782
|
+
try {
|
|
783
|
+
(0, transient_children_1.closeBackgroundProcessPersistence)();
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
console.error('[shutdown] background process history could not be flushed:', error);
|
|
787
|
+
}
|
|
747
788
|
try {
|
|
748
789
|
await _headroomManager?.shutdown();
|
|
749
790
|
}
|
|
@@ -40,6 +40,9 @@ const result_event_1 = require("./result-event");
|
|
|
40
40
|
const db_1 = require("./db");
|
|
41
41
|
const stream_display_1 = require("./util/stream-display");
|
|
42
42
|
const runtime_1 = require("./providers/runtime");
|
|
43
|
+
const terminal_result_1 = require("./providers/terminal-result");
|
|
44
|
+
const claude_background_tasks_1 = require("./providers/claude-background-tasks");
|
|
45
|
+
const loop_constants_1 = require("./loop-constants");
|
|
43
46
|
/** Claude's stream-json result frames do not carry a turn id. Hash a canonical
|
|
44
47
|
* representation of the real frame instead: an exact/semantic retransmission
|
|
45
48
|
* remains identifiable even after the next stdin turn has been armed, while a
|
|
@@ -58,6 +61,11 @@ function canonicalJson(value) {
|
|
|
58
61
|
function resultFrameSignature(frame) {
|
|
59
62
|
return (0, node_crypto_1.createHash)('sha256').update(canonicalJson(frame)).digest('hex');
|
|
60
63
|
}
|
|
64
|
+
/** Bound unproductive reply/continue cycles for the same unfinished workers.
|
|
65
|
+
* A blocking collection can take as long as the owner's ordinary step budget;
|
|
66
|
+
* repeatedly returning without joining the workers cannot spin forever. */
|
|
67
|
+
const MAX_BACKGROUND_HANDOFFS = 3;
|
|
68
|
+
const MAX_TOTAL_BACKGROUND_HANDOFFS = 12;
|
|
61
69
|
/** The claude CLI's synthetic no-op marker: the result text of a frame emitted
|
|
62
70
|
* for a command the CLI could not resolve (nothing was sent to the model). */
|
|
63
71
|
exports.UNKNOWN_COMMAND_RE = /^Unknown command:/;
|
|
@@ -120,6 +128,7 @@ class InteractiveJobSession {
|
|
|
120
128
|
_onSettle;
|
|
121
129
|
_settleMode;
|
|
122
130
|
_zombieTimeoutMs;
|
|
131
|
+
_onZombieTimeoutHook;
|
|
123
132
|
_quiescentEofGraceMs;
|
|
124
133
|
/** Armed by the quiescent graceful teardown: SIGTERM escalation if the child
|
|
125
134
|
* ignores the stdin EOF. Cleared on close/settle/dispose. */
|
|
@@ -158,11 +167,16 @@ class InteractiveJobSession {
|
|
|
158
167
|
_sessionId = null;
|
|
159
168
|
/** Last completed turn's `result` payload (output chaining — see SettleInfo). */
|
|
160
169
|
_resultText = null;
|
|
170
|
+
_resultIsError = false;
|
|
161
171
|
/** True once ANY assistant-derived event was observed across the whole
|
|
162
172
|
* session life (isModelWorkEvent). Feeds the zero-work settle predicate:
|
|
163
173
|
* real turns always emit assistant events; the synthetic `Unknown command:`
|
|
164
174
|
* frame never does. */
|
|
165
175
|
_sawModelWork = false;
|
|
176
|
+
/** Authoritative live roster, never a reason to complete an automatic step. */
|
|
177
|
+
_liveBackgroundTasks = [];
|
|
178
|
+
_backgroundHandoffs = 0;
|
|
179
|
+
_totalBackgroundHandoffs = 0;
|
|
166
180
|
/** Previous turn's CUMULATIVE `total_cost_usd` / `num_turns` reading. The
|
|
167
181
|
* persistent stream-json transport reports both cumulatively per turn, so we
|
|
168
182
|
* accumulate deltas against these baselines (HIGH-2). Reset to 0 when the
|
|
@@ -199,6 +213,7 @@ class InteractiveJobSession {
|
|
|
199
213
|
this._onSettle = deps.onSettle;
|
|
200
214
|
this._settleMode = deps.settleMode ?? 'finalize';
|
|
201
215
|
this._zombieTimeoutMs = deps.zombieTimeoutMs ?? 0;
|
|
216
|
+
this._onZombieTimeoutHook = deps.onZombieTimeout;
|
|
202
217
|
this._quiescentEofGraceMs = deps.quiescentEofGraceMs ?? 5000;
|
|
203
218
|
this._nextEventSeq = deps.nextEventSeq ?? null;
|
|
204
219
|
this._persistTurnUsage = deps.persistTurnUsage ?? null;
|
|
@@ -208,12 +223,37 @@ class InteractiveJobSession {
|
|
|
208
223
|
}
|
|
209
224
|
/** Spawn the resident child and run the first turn (the freestyle prompt). */
|
|
210
225
|
start(spec, firstPrompt) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
// Keep slash-command expansion intact: the lifecycle rule belongs in the
|
|
227
|
+
// system context, not in the command's argument string. Preserve any
|
|
228
|
+
// existing appended system prompt supplied by the queue/loop owner.
|
|
229
|
+
const args = [...spec.args];
|
|
230
|
+
if (this._adapter.id === 'claude' && this._settleMode === 'auto') {
|
|
231
|
+
const index = args.indexOf('--append-system-prompt');
|
|
232
|
+
if (index >= 0)
|
|
233
|
+
args[index + 1] = `${args[index + 1] ?? ''}\n\n${loop_constants_1.FOREGROUND_RULE}`;
|
|
234
|
+
else
|
|
235
|
+
args.push('--append-system-prompt', loop_constants_1.FOREGROUND_RULE);
|
|
236
|
+
}
|
|
237
|
+
let child;
|
|
238
|
+
try {
|
|
239
|
+
child = this._spawn(spec.binary, args, {
|
|
240
|
+
env: spec.env ?? process.env,
|
|
241
|
+
// stdin MUST be piped — it is the per-turn transport.
|
|
242
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
243
|
+
cwd: spec.cwd,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
// Local preparation (including managed skills) can fail synchronously,
|
|
248
|
+
// before there is a child to emit error/close. Settle through the normal
|
|
249
|
+
// owner callback so no interactive slot or step timer leaks.
|
|
250
|
+
this._resultText = `Could not start ${this._adapter.id}: ${error instanceof Error ? error.message : String(error)}`;
|
|
251
|
+
this._resultIsError = true;
|
|
252
|
+
this._persistLog('stderr', this._resultText);
|
|
253
|
+
this._emitLog('stderr', this._resultText);
|
|
254
|
+
this._settle('crashed');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
217
257
|
this._child = child;
|
|
218
258
|
this._childClosed = false;
|
|
219
259
|
this._stdinFailed = false;
|
|
@@ -272,7 +312,7 @@ class InteractiveJobSession {
|
|
|
272
312
|
* an immediate prompt only after a confirmed stdin write. Returns false if the
|
|
273
313
|
* session is gone OR the write could not be delivered (stdin destroyed/EPIPE). */
|
|
274
314
|
send(text) {
|
|
275
|
-
if (this._disposed || this._finalizing || !this._child)
|
|
315
|
+
if (this._disposed || this._settled || this._finalizing || !this._child)
|
|
276
316
|
return false;
|
|
277
317
|
const queued = this._streaming;
|
|
278
318
|
if (queued) {
|
|
@@ -392,6 +432,8 @@ class InteractiveJobSession {
|
|
|
392
432
|
* write; false when stdin is gone/destroyed or the write throws (EPIPE) — the
|
|
393
433
|
* caller must NOT treat an unconfirmed turn as accepted. */
|
|
394
434
|
_writeTurn(text) {
|
|
435
|
+
if (this._disposed || this._settled || this._finalizing || this._terminationStarted)
|
|
436
|
+
return false;
|
|
395
437
|
const child = this._child;
|
|
396
438
|
if (!child || !child.stdin || child.stdin.destroyed || this._stdinFailed)
|
|
397
439
|
return false;
|
|
@@ -431,20 +473,39 @@ class InteractiveJobSession {
|
|
|
431
473
|
parsed = JSON.parse(line);
|
|
432
474
|
}
|
|
433
475
|
catch { /* plain text */ }
|
|
476
|
+
// Keep the live roster current before deciding whether a result completes
|
|
477
|
+
// the step or needs the parent to join its unfinished workers.
|
|
478
|
+
this._trackBackgroundTasks(parsed);
|
|
479
|
+
// parseStreamEvents is pure, so classifying the frame through the adapter
|
|
480
|
+
// BEFORE the drop logic is safe — and it is the adapter that knows a claude
|
|
481
|
+
// `result` frame carrying `origin.kind === 'task-notification'` is a
|
|
482
|
+
// CLI-internal notification turn, not this turn's terminal result.
|
|
483
|
+
const adapterEvents = (0, runtime_1.parseStreamEvents)(this._adapter, line);
|
|
434
484
|
// A late retransmission can arrive after the next queued prompt has already
|
|
435
|
-
// been written. Drop it BEFORE
|
|
436
|
-
//
|
|
437
|
-
//
|
|
485
|
+
// been written. Drop it BEFORE event persistence: adding it to the next
|
|
486
|
+
// turn's event buffer corrupts live totals, while persisting it beyond the
|
|
487
|
+
// prior checkpoint would make crash recovery count it again.
|
|
438
488
|
if (parsed?.type === 'result') {
|
|
439
489
|
// A terminal frame with no active turn is causally unassignable. Drop it
|
|
440
|
-
// before
|
|
490
|
+
// before persistence so raw recovery cannot count it.
|
|
441
491
|
if (!this._awaitingResult)
|
|
442
492
|
return;
|
|
493
|
+
if (!adapterEvents.some((ev) => ev.kind === 'result')) {
|
|
494
|
+
// Notification turn (loop run 5c958db2): the CLI answered its OWN
|
|
495
|
+
// orphaned-background-task notice, not the prompt we wrote — the real
|
|
496
|
+
// turn is still coming. Closing the turn here auto-settled the session
|
|
497
|
+
// and tore the child down mid-thought as a zero-work failure. Leave the
|
|
498
|
+
// turn armed, and do not persist the frame as a `result` row either
|
|
499
|
+
// (crash recovery counts persisted result rows as completed turns).
|
|
500
|
+
const note = `↷ ${this._adapter.id} reported a background-task notification (not this turn's result) — continuing.`;
|
|
501
|
+
this._persistLog('stdout', note);
|
|
502
|
+
this._emitLog('stdout', note);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
443
505
|
const signature = resultFrameSignature(parsed);
|
|
444
506
|
if (this._acceptedResultSignatures.has(signature))
|
|
445
507
|
return;
|
|
446
508
|
}
|
|
447
|
-
const adapterEvents = (0, runtime_1.parseStreamEvents)(this._adapter, line);
|
|
448
509
|
for (const adapterEvent of adapterEvents) {
|
|
449
510
|
this._turnEvents.push(adapterEvent);
|
|
450
511
|
if (!this._sawModelWork && isModelWorkEvent(adapterEvent))
|
|
@@ -486,6 +547,47 @@ class InteractiveJobSession {
|
|
|
486
547
|
}
|
|
487
548
|
}
|
|
488
549
|
}
|
|
550
|
+
/** Mirror claude's `system/background_tasks_changed` roster. Any other frame
|
|
551
|
+
* (or a non-claude provider, which never emits it) leaves the roster as is. */
|
|
552
|
+
_trackBackgroundTasks(parsed) {
|
|
553
|
+
if (this._adapter.id !== 'claude')
|
|
554
|
+
return;
|
|
555
|
+
const tasks = (0, claude_background_tasks_1.readClaudeBackgroundTasks)(parsed);
|
|
556
|
+
if (tasks === null)
|
|
557
|
+
return;
|
|
558
|
+
this._liveBackgroundTasks = tasks;
|
|
559
|
+
// Completing a cohort is real progress; a later developer/reviewer cohort
|
|
560
|
+
// gets its own collection budget in the same implementation workflow.
|
|
561
|
+
if (tasks.length === 0)
|
|
562
|
+
this._backgroundHandoffs = 0;
|
|
563
|
+
}
|
|
564
|
+
/** Ask the same resident parent to collect its workers. This is a transport
|
|
565
|
+
* continuation, not a new user request or a new implementation attempt. */
|
|
566
|
+
_continueBackgroundWork() {
|
|
567
|
+
const tasks = this._liveBackgroundTasks;
|
|
568
|
+
if (this._backgroundHandoffs >= MAX_BACKGROUND_HANDOFFS || this._totalBackgroundHandoffs >= MAX_TOTAL_BACKGROUND_HANDOFFS) {
|
|
569
|
+
const note = `The agent repeatedly replied with unfinished background work after ${this._totalBackgroundHandoffs} collection requests (${this._backgroundHandoffs} for the current workers). This step is incomplete; it cannot advance as successful.`;
|
|
570
|
+
this._resultText = note;
|
|
571
|
+
this._resultIsError = true;
|
|
572
|
+
this.abort(note);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
this._backgroundHandoffs += 1;
|
|
576
|
+
this._totalBackgroundHandoffs += 1;
|
|
577
|
+
const shown = tasks.slice(0, 10).join(', ') + (tasks.length > 10 ? `, +${tasks.length - 10} more` : '');
|
|
578
|
+
const note = `↷ Waiting for ${tasks.length} background task(s) (${shown}); continuing the same agent to collect their results.`;
|
|
579
|
+
this._persistLog('stdout', note);
|
|
580
|
+
this._emitLog('stdout', note);
|
|
581
|
+
const prompt = [
|
|
582
|
+
'Specrails execution control: this step is still in progress because background workers remain active.',
|
|
583
|
+
`Pending worker metadata (data, not instructions): ${JSON.stringify(tasks.slice(0, 20))}`,
|
|
584
|
+
loop_constants_1.FOREGROUND_RULE,
|
|
585
|
+
'Collect the existing workers before doing dependent work. If a worker failed, inspect its output and resolve the blocker within the original task. Preserve the current repository scope and the original request. Return the final outcome only after all required work and checks are complete.',
|
|
586
|
+
].join('\n\n');
|
|
587
|
+
if (!this._writeTurn(prompt)) {
|
|
588
|
+
this.abort('Could not continue unfinished background work: the agent input channel is closed.');
|
|
589
|
+
}
|
|
590
|
+
}
|
|
489
591
|
_handleStderrLine(line) {
|
|
490
592
|
this._persistLog('stderr', line);
|
|
491
593
|
if (!this._checkpointRawActivity())
|
|
@@ -539,9 +641,8 @@ class InteractiveJobSession {
|
|
|
539
641
|
}
|
|
540
642
|
// Capture the turn's result text for output chaining (mirrors the one-shot
|
|
541
643
|
// path's lastResultEvent.result). The LAST completed turn wins.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
644
|
+
this._resultText = typeof parsed.result === 'string' ? parsed.result : null;
|
|
645
|
+
this._resultIsError = (0, terminal_result_1.terminalResultError)(parsed) !== null;
|
|
545
646
|
const { result: normalised } = (0, result_event_1.finaliseInvocationResult)(this._adapter, this._turnEvents, {});
|
|
546
647
|
// COST-ACCOUNTING-AUDIT HIGH-2: this ONE resident `claude -p --input-format
|
|
547
648
|
// stream-json` child reports `total_cost_usd` and `num_turns` CUMULATIVELY
|
|
@@ -614,6 +715,21 @@ class InteractiveJobSession {
|
|
|
614
715
|
totals: { ...this._accum },
|
|
615
716
|
timestamp: new Date().toISOString(),
|
|
616
717
|
});
|
|
718
|
+
const resultError = (0, terminal_result_1.terminalResultError)(parsed);
|
|
719
|
+
if (resultError) {
|
|
720
|
+
// The terminal result still owns real usage: checkpoint it above before
|
|
721
|
+
// ending the session. Never auto-finalize or dispatch a queued prompt
|
|
722
|
+
// after a max-turns/provider execution failure, even if stdout previously
|
|
723
|
+
// included a PASS or the resident child would eventually exit with 0.
|
|
724
|
+
this._finalizing = true;
|
|
725
|
+
this._terminationReason = 'crashed';
|
|
726
|
+
this._clearZombieTimer();
|
|
727
|
+
const note = `✖ Provider turn failed — ${resultError}`;
|
|
728
|
+
this._persistLog('stderr', note);
|
|
729
|
+
this._emitLog('stderr', note);
|
|
730
|
+
this._terminateChildTree('crashed');
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
617
733
|
// Feed the next queued prompt (if any) now that the turn is idle. Deferred to a
|
|
618
734
|
// microtask so any stray/duplicate result for THIS just-settled turn that is
|
|
619
735
|
// sitting in the same synchronous stdout batch is processed (and rejected by
|
|
@@ -645,7 +761,10 @@ class InteractiveJobSession {
|
|
|
645
761
|
return;
|
|
646
762
|
if (this._streaming || this._pending.length > 0)
|
|
647
763
|
return;
|
|
648
|
-
this.
|
|
764
|
+
if (this._liveBackgroundTasks.length > 0)
|
|
765
|
+
this._continueBackgroundWork();
|
|
766
|
+
else
|
|
767
|
+
this._finalizeQuiescent();
|
|
649
768
|
});
|
|
650
769
|
}
|
|
651
770
|
}
|
|
@@ -815,11 +934,19 @@ class InteractiveJobSession {
|
|
|
815
934
|
const timeoutSec = Math.round(this._zombieTimeoutMs / 1000);
|
|
816
935
|
const note = `[zombie-detection] Interactive job ${this._jobId} produced no output for ${timeoutSec}s — auto-terminating`;
|
|
817
936
|
console.error(note);
|
|
937
|
+
try {
|
|
938
|
+
this._onZombieTimeoutHook?.();
|
|
939
|
+
}
|
|
940
|
+
catch { /* owner hook must never block teardown */ }
|
|
818
941
|
this.abort(note);
|
|
819
942
|
}
|
|
820
943
|
_settle(reason) {
|
|
821
944
|
if (this._settled)
|
|
822
945
|
return;
|
|
946
|
+
// A terminal error received during graceful teardown overrides an earlier
|
|
947
|
+
// finalize request, including a teardown timer armed before the error.
|
|
948
|
+
if (this._terminationReason === 'crashed')
|
|
949
|
+
reason = 'crashed';
|
|
823
950
|
this._settled = true;
|
|
824
951
|
// Fold any unfinished turn BEFORE snapshotting totals so a mid-turn
|
|
825
952
|
// finalize/crash keeps that turn's spend (CRIT-4).
|
|
@@ -867,6 +994,7 @@ class InteractiveJobSession {
|
|
|
867
994
|
estimated: this._inflightEstimated,
|
|
868
995
|
activeDurationMs: this._activeDurationMs,
|
|
869
996
|
resultText: this._resultText,
|
|
997
|
+
resultIsError: this._resultIsError,
|
|
870
998
|
zeroWork,
|
|
871
999
|
});
|
|
872
1000
|
}
|
|
@@ -882,6 +1010,11 @@ class InteractiveJobSession {
|
|
|
882
1010
|
* settles because its owner already flushed/reconciles the job separately.
|
|
883
1011
|
*/
|
|
884
1012
|
_terminateChildTree(reason) {
|
|
1013
|
+
// Close admission synchronously, before SIGTERM. A child may take seconds
|
|
1014
|
+
// to exit; neither a queued user prompt nor an auto-collection microtask
|
|
1015
|
+
// may start another turn in that window (including a late result frame).
|
|
1016
|
+
this._finalizing = true;
|
|
1017
|
+
this._clearZombieTimer();
|
|
885
1018
|
if (reason !== null && this._terminationReason === null)
|
|
886
1019
|
this._terminationReason = reason;
|
|
887
1020
|
const child = this._child;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Loopback client for the app's OWN REST API, authenticated with the master
|
|
3
|
+
// token held server-side. Precedent: the MCP domain tools (`apiCall`), which
|
|
4
|
+
// reuse every router's validation instead of re-implementing it. The milestone
|
|
5
|
+
// launch chain uses it to launch chunks through the ordinary rails launch
|
|
6
|
+
// route — the single authority for its ~15 guards — so a chunk launch behaves
|
|
7
|
+
// byte-identically to a dashboard launch and every 4xx becomes a typed reason.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createInternalApi = createInternalApi;
|
|
10
|
+
exports.internalApiError = internalApiError;
|
|
11
|
+
const auth_1 = require("./auth");
|
|
12
|
+
function createInternalApi(deps) {
|
|
13
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
14
|
+
const token = deps.token ?? auth_1.loadOrGenerateToken;
|
|
15
|
+
return {
|
|
16
|
+
async call(method, path, body) {
|
|
17
|
+
const res = await fetchImpl(`http://127.0.0.1:${deps.port}/api${path}`, {
|
|
18
|
+
method,
|
|
19
|
+
headers: {
|
|
20
|
+
Authorization: `Bearer ${token()}`,
|
|
21
|
+
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
22
|
+
},
|
|
23
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
24
|
+
});
|
|
25
|
+
const text = await res.text();
|
|
26
|
+
let data;
|
|
27
|
+
try {
|
|
28
|
+
data = text ? JSON.parse(text) : null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
data = text;
|
|
32
|
+
}
|
|
33
|
+
return { ok: res.ok, status: res.status, body: data };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Extract the router's `{ error, detail }` from a failed response body. */
|
|
38
|
+
function internalApiError(res) {
|
|
39
|
+
const body = res.body;
|
|
40
|
+
if (body && typeof body === 'object') {
|
|
41
|
+
const obj = body;
|
|
42
|
+
return {
|
|
43
|
+
error: typeof obj.error === 'string' ? obj.error : `http_${res.status}`,
|
|
44
|
+
...(typeof obj.detail === 'string' ? { detail: obj.detail } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return { error: `http_${res.status}`, ...(typeof body === 'string' && body ? { detail: body.slice(0, 200) } : {}) };
|
|
48
|
+
}
|
|
@@ -131,6 +131,7 @@ function mapIssueToTicket(issue, localId, conn, existing) {
|
|
|
131
131
|
labels: issue.fields.labels ?? [],
|
|
132
132
|
assignee: issue.fields.assignee?.displayName ?? issue.fields.assignee?.emailAddress ?? null,
|
|
133
133
|
prerequisites: existing?.prerequisites ?? [],
|
|
134
|
+
...(existing?.repositoryIds ? { repositoryIds: [...existing.repositoryIds] } : {}),
|
|
134
135
|
metadata: existing?.metadata ?? {},
|
|
135
136
|
comments: existing?.comments ?? [],
|
|
136
137
|
attachments: existing?.attachments,
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Tolerant JSON parsing for model-emitted payloads ─────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// A model writing a ~20 KB JSON snapshot by hand makes small, predictable
|
|
5
|
+
// mistakes: a raw newline inside a string, an unescaped inner quote, a
|
|
6
|
+
// trailing comma, a nested ```json fence, a stray comment. `JSON.parse`
|
|
7
|
+
// rejects all of them and the whole snapshot used to be dropped silently.
|
|
8
|
+
//
|
|
9
|
+
// `parseJsonTolerant` first tries strict `JSON.parse`; only when that fails
|
|
10
|
+
// does it run ONE deterministic repair pass (a small string-aware state
|
|
11
|
+
// machine — never a regex over the whole text, so repairs inside string
|
|
12
|
+
// literals can't corrupt structure) and parses again. The result says whether
|
|
13
|
+
// a repair was needed and which kinds were applied, so callers can log/badge
|
|
14
|
+
// it. It never throws.
|
|
15
|
+
//
|
|
16
|
+
// Client mirror: client/src/lib/json-tolerant.ts — keep the two in sync.
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.repairJsonText = repairJsonText;
|
|
19
|
+
exports.parseJsonTolerant = parseJsonTolerant;
|
|
20
|
+
const VALID_ESCAPES = '"\\/bfnrtu';
|
|
21
|
+
function isJsonWhitespace(ch) {
|
|
22
|
+
return ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n';
|
|
23
|
+
}
|
|
24
|
+
/** One string-aware pass over `source` fixing the common model mistakes. */
|
|
25
|
+
function repairJsonText(source) {
|
|
26
|
+
const repairs = new Set();
|
|
27
|
+
let text = source.trim();
|
|
28
|
+
// A nested code fence around the payload (```json … ``` inside the
|
|
29
|
+
// blueprint-draft block). Leading and trailing fences are stripped
|
|
30
|
+
// independently: the outer lazy fence regex may already have eaten one.
|
|
31
|
+
const leadingFence = /^```[A-Za-z0-9_-]*[ \t]*\r?\n/.exec(text);
|
|
32
|
+
if (leadingFence) {
|
|
33
|
+
text = text.slice(leadingFence[0].length);
|
|
34
|
+
repairs.add('nested_fence');
|
|
35
|
+
}
|
|
36
|
+
const trailingFence = /\r?\n[ \t]*```[ \t]*$/.exec(text);
|
|
37
|
+
if (trailingFence) {
|
|
38
|
+
text = text.slice(0, trailingFence.index);
|
|
39
|
+
repairs.add('nested_fence');
|
|
40
|
+
}
|
|
41
|
+
// Prose before/after the object ("Here is the snapshot: {...} Done.").
|
|
42
|
+
const first = text.indexOf('{');
|
|
43
|
+
const last = text.lastIndexOf('}');
|
|
44
|
+
if (first !== -1 && last > first && (first > 0 || last < text.length - 1)) {
|
|
45
|
+
text = text.slice(first, last + 1);
|
|
46
|
+
repairs.add('trimmed_wrapper');
|
|
47
|
+
}
|
|
48
|
+
let out = '';
|
|
49
|
+
let inString = false;
|
|
50
|
+
for (let i = 0; i < text.length; i++) {
|
|
51
|
+
const ch = text[i];
|
|
52
|
+
if (inString) {
|
|
53
|
+
if (ch === '\\') {
|
|
54
|
+
const next = text[i + 1];
|
|
55
|
+
if (next === undefined) {
|
|
56
|
+
repairs.add('dangling_escape');
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (VALID_ESCAPES.includes(next)) {
|
|
60
|
+
out += ch + next;
|
|
61
|
+
i += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// `\'` and friends are not JSON escapes — keep the character, drop the
|
|
65
|
+
// backslash (a raw newline after a backslash becomes a real escape).
|
|
66
|
+
out += next === '\n' ? '\\n' : next;
|
|
67
|
+
i += 1;
|
|
68
|
+
repairs.add('invalid_escape');
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (ch === '"') {
|
|
72
|
+
// Terminator, or a stray inner quote? A real terminator is followed
|
|
73
|
+
// (after whitespace) by a structural character or the end of input.
|
|
74
|
+
let j = i + 1;
|
|
75
|
+
while (j < text.length && isJsonWhitespace(text[j]))
|
|
76
|
+
j += 1;
|
|
77
|
+
const after = text[j];
|
|
78
|
+
if (after === undefined || after === ',' || after === '}' || after === ']' || after === ':') {
|
|
79
|
+
inString = false;
|
|
80
|
+
out += ch;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
out += '\\"';
|
|
84
|
+
repairs.add('inner_quote');
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === '\n') {
|
|
88
|
+
out += '\\n';
|
|
89
|
+
repairs.add('raw_newline');
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (ch === '\r') {
|
|
93
|
+
repairs.add('raw_newline');
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (ch === '\t') {
|
|
97
|
+
out += '\\t';
|
|
98
|
+
repairs.add('raw_control');
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const code = ch.charCodeAt(0);
|
|
102
|
+
if (code < 0x20) {
|
|
103
|
+
out += '\\u' + code.toString(16).padStart(4, '0');
|
|
104
|
+
repairs.add('raw_control');
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
out += ch;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Outside a string literal.
|
|
111
|
+
if (ch === '"') {
|
|
112
|
+
inString = true;
|
|
113
|
+
out += ch;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (ch === ',') {
|
|
117
|
+
let j = i + 1;
|
|
118
|
+
while (j < text.length && isJsonWhitespace(text[j]))
|
|
119
|
+
j += 1;
|
|
120
|
+
if (text[j] === '}' || text[j] === ']') {
|
|
121
|
+
repairs.add('trailing_comma');
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
out += ch;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (ch === '/' && text[i + 1] === '/') {
|
|
128
|
+
const end = text.indexOf('\n', i);
|
|
129
|
+
i = end === -1 ? text.length : end;
|
|
130
|
+
repairs.add('comment');
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (ch === '/' && text[i + 1] === '*') {
|
|
134
|
+
const end = text.indexOf('*/', i + 2);
|
|
135
|
+
i = end === -1 ? text.length : end + 1;
|
|
136
|
+
repairs.add('comment');
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
out += ch;
|
|
140
|
+
}
|
|
141
|
+
return { text: out, repairs: [...repairs] };
|
|
142
|
+
}
|
|
143
|
+
function describeParseError(err, text) {
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
const match = /position (\d+)/i.exec(message);
|
|
146
|
+
const position = match ? Number(match[1]) : null;
|
|
147
|
+
let excerpt = null;
|
|
148
|
+
if (position !== null && Number.isFinite(position)) {
|
|
149
|
+
const start = Math.max(0, position - 40);
|
|
150
|
+
const end = Math.min(text.length, position + 40);
|
|
151
|
+
excerpt = text.slice(start, end).replace(/\s+/g, ' ');
|
|
152
|
+
}
|
|
153
|
+
return { error: message, position, excerpt };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Strict `JSON.parse` first; on failure ONE repair pass, then parse again.
|
|
157
|
+
* `repaired` is true only when the strict parse failed AND the repaired text
|
|
158
|
+
* parsed. Never throws.
|
|
159
|
+
*/
|
|
160
|
+
function parseJsonTolerant(source) {
|
|
161
|
+
try {
|
|
162
|
+
return { ok: true, value: JSON.parse(source), repaired: false, repairs: [] };
|
|
163
|
+
}
|
|
164
|
+
catch (strictErr) {
|
|
165
|
+
const { text, repairs } = repairJsonText(source);
|
|
166
|
+
try {
|
|
167
|
+
const value = JSON.parse(text);
|
|
168
|
+
return { ok: true, value, repaired: true, repairs };
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return { ok: false, ...describeParseError(strictErr, source) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -227,8 +227,10 @@ function moveOverwriting(src, dest) {
|
|
|
227
227
|
fs.renameSync(src, dest);
|
|
228
228
|
}
|
|
229
229
|
catch {
|
|
230
|
-
// Cross-device fallback.
|
|
231
|
-
|
|
230
|
+
// Cross-device fallback. JS traversal avoids Node 22's native Unicode copy
|
|
231
|
+
// failure on Windows (nodejs/node#61878). The destination is cleared before
|
|
232
|
+
// this copy; remove the source state only after copying succeeds.
|
|
233
|
+
fs.cpSync(src, dest, { recursive: true, filter: () => true, mode: fs.constants.COPYFILE_FICLONE });
|
|
232
234
|
fs.rmSync(src, { recursive: true, force: true });
|
|
233
235
|
}
|
|
234
236
|
}
|