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
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Server-derived milestone progress (premium-milestone-progress).
|
|
3
|
+
//
|
|
4
|
+
// The Builder sidebar used to count `status === 'done'` on a board it fetched
|
|
5
|
+
// only when its flyout opened — so a delivered M1 read `0/8` forever (every
|
|
6
|
+
// ask-first delivery parks its specs at `on_review`; `done` arrives only after a
|
|
7
|
+
// merge) and nothing ever updated live. This module is the ONE progress model:
|
|
8
|
+
// derived on the server from durable rows only (ticket store, rail_pr_deliveries,
|
|
9
|
+
// live rail runs, the launch chain), returned by `GET /:projectId/blueprint`
|
|
10
|
+
// and broadcast as `blueprint.milestone_progress` on every relevant mutation.
|
|
11
|
+
//
|
|
12
|
+
// Honesty contract: every count is a fact from a row. `failed` means "this
|
|
13
|
+
// spec is back at todo and its NEWEST delivery unit failed" — never a guess.
|
|
14
|
+
// The derived `state` is what surfaces render; the blueprint's stored status is
|
|
15
|
+
// a record (persisted `done` once, never auto-reverted).
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.MilestoneProgressBroadcaster = exports.MILESTONE_PROGRESS_DEBOUNCE_MS = exports.MILESTONE_PROGRESS_TRIGGERS = void 0;
|
|
18
|
+
exports.milestoneLabelFor = milestoneLabelFor;
|
|
19
|
+
exports.deriveMilestoneProgress = deriveMilestoneProgress;
|
|
20
|
+
exports.resolveBlueprintWorkspace = resolveBlueprintWorkspace;
|
|
21
|
+
exports.readMilestoneProgress = readMilestoneProgress;
|
|
22
|
+
exports.markMilestoneDone = markMilestoneDone;
|
|
23
|
+
const blueprint_render_1 = require("./blueprint-render");
|
|
24
|
+
const ticket_store_1 = require("./ticket-store");
|
|
25
|
+
const rail_pr_store_1 = require("./rail-pr-store");
|
|
26
|
+
const rails_store_1 = require("./rails-store");
|
|
27
|
+
const loop_runs_store_1 = require("./loop-runs-store");
|
|
28
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
29
|
+
const workspace_manager_1 = require("./workspace-manager");
|
|
30
|
+
function milestoneLabelFor(n) {
|
|
31
|
+
return `M${n}`;
|
|
32
|
+
}
|
|
33
|
+
function deliveryFailedForTicket(d, ticketId) {
|
|
34
|
+
if (d.decision === 'implementation_failed')
|
|
35
|
+
return true;
|
|
36
|
+
const unit = d.units.find((u) => u.ticketId === ticketId);
|
|
37
|
+
if (!unit)
|
|
38
|
+
return d.implementationOutcome === 'failed';
|
|
39
|
+
if (unit.implementationOutcome === 'failed')
|
|
40
|
+
return true;
|
|
41
|
+
if (unit.deliveryOutcome === 'blocked')
|
|
42
|
+
return true;
|
|
43
|
+
return unit.implementationOutcome == null && unit.succeeded === false;
|
|
44
|
+
}
|
|
45
|
+
/** Pure derivation — see the module header for the semantics. */
|
|
46
|
+
function deriveMilestoneProgress(input) {
|
|
47
|
+
const deliveries = [...input.deliveries].sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0);
|
|
48
|
+
const railNames = new Map(input.rails.map((r) => [r.railIndex, r.name]));
|
|
49
|
+
return input.blueprint.milestones.map((m, index) => {
|
|
50
|
+
const n = index + 1;
|
|
51
|
+
const label = milestoneLabelFor(n);
|
|
52
|
+
const mine = input.tickets.filter((t) => Array.isArray(t.labels) && t.labels.includes(label));
|
|
53
|
+
const ids = new Set(mine.map((t) => t.id));
|
|
54
|
+
const count = (status) => mine.filter((t) => t.status === status).length;
|
|
55
|
+
const counts = {
|
|
56
|
+
total: mine.length,
|
|
57
|
+
done: count('done'),
|
|
58
|
+
onReview: count('on_review'),
|
|
59
|
+
inProgress: count('in_progress'),
|
|
60
|
+
todo: count('todo'),
|
|
61
|
+
failed: 0,
|
|
62
|
+
};
|
|
63
|
+
for (const t of mine) {
|
|
64
|
+
if (t.status !== 'todo')
|
|
65
|
+
continue;
|
|
66
|
+
const newest = deliveries.find((d) => d.ticketIds.includes(t.id));
|
|
67
|
+
if (newest && deliveryFailedForTicket(newest, t.id))
|
|
68
|
+
counts.failed += 1;
|
|
69
|
+
}
|
|
70
|
+
const chain = input.chains.find((c) => c.milestoneN === n) ?? null;
|
|
71
|
+
const chunkByRail = new Map();
|
|
72
|
+
for (const l of chain?.launched ?? [])
|
|
73
|
+
chunkByRail.set(l.railIndex, l.chunk);
|
|
74
|
+
const rails = new Map();
|
|
75
|
+
for (const run of input.activeRuns) {
|
|
76
|
+
const carried = run.ticketIds.filter((id) => ids.has(id));
|
|
77
|
+
if (carried.length === 0)
|
|
78
|
+
continue;
|
|
79
|
+
rails.set(run.railIndex, {
|
|
80
|
+
railIndex: run.railIndex,
|
|
81
|
+
name: railNames.get(run.railIndex) ?? null,
|
|
82
|
+
ticketIds: carried,
|
|
83
|
+
active: true,
|
|
84
|
+
runId: run.runId,
|
|
85
|
+
startedAt: run.startedAt,
|
|
86
|
+
delivery: null,
|
|
87
|
+
chunkIndex: chunkByRail.get(run.railIndex) ?? null,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const d of deliveries) {
|
|
91
|
+
if ((0, rail_pr_store_1.isTerminalPrDecision)(d.decision))
|
|
92
|
+
continue;
|
|
93
|
+
const carried = d.ticketIds.filter((id) => ids.has(id));
|
|
94
|
+
if (carried.length === 0)
|
|
95
|
+
continue;
|
|
96
|
+
const existing = rails.get(d.railIndex);
|
|
97
|
+
if (existing) {
|
|
98
|
+
if (!existing.delivery)
|
|
99
|
+
existing.delivery = d;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
rails.set(d.railIndex, {
|
|
103
|
+
railIndex: d.railIndex,
|
|
104
|
+
name: railNames.get(d.railIndex) ?? null,
|
|
105
|
+
ticketIds: carried,
|
|
106
|
+
active: false,
|
|
107
|
+
runId: null,
|
|
108
|
+
startedAt: d.createdAt,
|
|
109
|
+
delivery: d,
|
|
110
|
+
chunkIndex: chunkByRail.get(d.railIndex) ?? null,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const railList = [...rails.values()].sort((a, b) => {
|
|
114
|
+
const ca = a.chunkIndex ?? Number.MAX_SAFE_INTEGER;
|
|
115
|
+
const cb = b.chunkIndex ?? Number.MAX_SAFE_INTEGER;
|
|
116
|
+
return ca !== cb ? ca - cb : a.railIndex - b.railIndex;
|
|
117
|
+
});
|
|
118
|
+
const chainLive = chain !== null && (chain.status === 'running' || chain.status === 'waiting');
|
|
119
|
+
const state = counts.total > 0 && counts.done === counts.total ? 'done'
|
|
120
|
+
: counts.total > 0 && counts.todo + counts.inProgress === 0 && counts.onReview > 0 ? 'delivered'
|
|
121
|
+
: counts.inProgress > 0 || chainLive ? 'running'
|
|
122
|
+
: counts.total > 0 ? 'committed'
|
|
123
|
+
: m.status;
|
|
124
|
+
return {
|
|
125
|
+
id: m.id,
|
|
126
|
+
n,
|
|
127
|
+
title: m.title,
|
|
128
|
+
storedStatus: m.status,
|
|
129
|
+
state,
|
|
130
|
+
counts,
|
|
131
|
+
rails: railList,
|
|
132
|
+
chain,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// ─── Reading the durable sources ─────────────────────────────────────────────
|
|
137
|
+
/** The workspace holding the blueprint pair (same resolution as the route). */
|
|
138
|
+
function resolveBlueprintWorkspace(project) {
|
|
139
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
140
|
+
if (exec.relocated && exec.workspaceDir)
|
|
141
|
+
return exec.workspaceDir;
|
|
142
|
+
return project.slug ? (0, workspace_manager_1.workspacePathFor)(project.slug) : null;
|
|
143
|
+
}
|
|
144
|
+
function parseTicketIds(raw) {
|
|
145
|
+
if (!raw)
|
|
146
|
+
return [];
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(raw);
|
|
149
|
+
return Array.isArray(parsed) ? parsed.filter((v) => typeof v === 'number') : [];
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Read every durable source and derive; null when the project has no blueprint. */
|
|
156
|
+
function readMilestoneProgress(src) {
|
|
157
|
+
const workspace = src.workspaceDir();
|
|
158
|
+
const blueprint = workspace ? (0, blueprint_render_1.readBlueprint)(workspace) : null;
|
|
159
|
+
if (!blueprint)
|
|
160
|
+
return null;
|
|
161
|
+
let tickets = [];
|
|
162
|
+
try {
|
|
163
|
+
const store = (0, ticket_store_1.readStore)(src.ticketsPath());
|
|
164
|
+
tickets = Object.values(store.tickets).map((t) => ({ id: t.id, status: t.status, labels: t.labels ?? [] }));
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
tickets = [];
|
|
168
|
+
}
|
|
169
|
+
let deliveries = [];
|
|
170
|
+
try {
|
|
171
|
+
deliveries = [...(0, rail_pr_store_1.listActivePrDeliveries)(src.db), ...(0, rail_pr_store_1.listTerminalPrDeliveries)(src.db)].map(rail_pr_store_1.toPrDeliverySnapshot);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
deliveries = [];
|
|
175
|
+
}
|
|
176
|
+
const activeRuns = [];
|
|
177
|
+
const seenRuns = new Set();
|
|
178
|
+
for (const [runId, meta] of src.railLoopRuns?.entries() ?? []) {
|
|
179
|
+
seenRuns.add(runId);
|
|
180
|
+
let startedAt = null;
|
|
181
|
+
try {
|
|
182
|
+
startedAt = (0, loop_runs_store_1.getLoopRun)(src.db, runId)?.started_at ?? null;
|
|
183
|
+
}
|
|
184
|
+
catch { /* fall through */ }
|
|
185
|
+
activeRuns.push({ runId, railIndex: meta.railIndex, ticketIds: meta.ticketIds, startedAt });
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
for (const run of (0, loop_runs_store_1.listActiveLoopRuns)(src.db, src.projectId)) {
|
|
189
|
+
if (run.rail_index == null || seenRuns.has(run.id))
|
|
190
|
+
continue;
|
|
191
|
+
seenRuns.add(run.id);
|
|
192
|
+
const ids = parseTicketIds(run.ticket_ids_json);
|
|
193
|
+
activeRuns.push({
|
|
194
|
+
runId: run.id,
|
|
195
|
+
railIndex: run.rail_index,
|
|
196
|
+
ticketIds: ids.length > 0 ? ids : run.ticket_id != null ? [run.ticket_id] : [],
|
|
197
|
+
startedAt: run.started_at ?? null,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch { /* DB unavailable ⇒ in-memory maps only */ }
|
|
202
|
+
for (const [jobId, meta] of src.railJobs?.entries() ?? []) {
|
|
203
|
+
if (seenRuns.has(jobId))
|
|
204
|
+
continue;
|
|
205
|
+
activeRuns.push({ runId: jobId, railIndex: meta.railIndex, ticketIds: meta.ticketIds, startedAt: null });
|
|
206
|
+
}
|
|
207
|
+
let rails = [];
|
|
208
|
+
try {
|
|
209
|
+
rails = (0, rails_store_1.getRails)(src.db).map((r) => ({ railIndex: r.railIndex, name: r.name ?? null }));
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
rails = [];
|
|
213
|
+
}
|
|
214
|
+
let chains = [];
|
|
215
|
+
try {
|
|
216
|
+
chains = src.chains?.() ?? [];
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
chains = [];
|
|
220
|
+
}
|
|
221
|
+
return { blueprint, progress: deriveMilestoneProgress({ blueprint, tickets, deliveries, activeRuns, rails, chains }) };
|
|
222
|
+
}
|
|
223
|
+
/** Persist `status: 'done'` on one milestone (idempotent). Returns the
|
|
224
|
+
* updated blueprint, or null when nothing was written. */
|
|
225
|
+
function markMilestoneDone(workspaceDir, milestoneId) {
|
|
226
|
+
if (!workspaceDir)
|
|
227
|
+
return null;
|
|
228
|
+
const blueprint = (0, blueprint_render_1.readBlueprint)(workspaceDir);
|
|
229
|
+
if (!blueprint)
|
|
230
|
+
return null;
|
|
231
|
+
const milestone = blueprint.milestones.find((m) => m.id === milestoneId);
|
|
232
|
+
if (!milestone || milestone.status === 'done')
|
|
233
|
+
return null;
|
|
234
|
+
milestone.status = 'done';
|
|
235
|
+
(0, blueprint_render_1.writeBlueprintPair)(workspaceDir, blueprint);
|
|
236
|
+
return blueprint;
|
|
237
|
+
}
|
|
238
|
+
// ─── Broadcaster ─────────────────────────────────────────────────────────────
|
|
239
|
+
/** WS message types whose arrival means the milestone progress may have
|
|
240
|
+
* changed. Everything else (log frames, queue ticks…) is ignored. */
|
|
241
|
+
exports.MILESTONE_PROGRESS_TRIGGERS = new Set([
|
|
242
|
+
'ticket_created',
|
|
243
|
+
'ticket_updated',
|
|
244
|
+
'ticket_deleted',
|
|
245
|
+
'rail.pr_state',
|
|
246
|
+
'rail.updated',
|
|
247
|
+
'rail.removed',
|
|
248
|
+
'rail.job_started',
|
|
249
|
+
'rail.job_completed',
|
|
250
|
+
'rail.job_stopped',
|
|
251
|
+
'loop.run_completed',
|
|
252
|
+
'loop.run_stopped',
|
|
253
|
+
'milestone.chain_changed',
|
|
254
|
+
]);
|
|
255
|
+
exports.MILESTONE_PROGRESS_DEBOUNCE_MS = 150;
|
|
256
|
+
/**
|
|
257
|
+
* Per-project debounced progress broadcaster. `observe(msg)` is the ONE tap
|
|
258
|
+
* (wired inside the project's bound broadcast): any trigger schedules a flush;
|
|
259
|
+
* a flush re-reads the durable sources, persists newly-completed milestones,
|
|
260
|
+
* and emits `blueprint.milestone_progress` (+ `blueprint.milestone_completed`).
|
|
261
|
+
* A project with no blueprint is remembered as absent so the tap costs nothing
|
|
262
|
+
* on ordinary projects until `invalidate()` (a blueprint commit) resets it.
|
|
263
|
+
*/
|
|
264
|
+
class MilestoneProgressBroadcaster {
|
|
265
|
+
deps;
|
|
266
|
+
timer = null;
|
|
267
|
+
knownAbsent = false;
|
|
268
|
+
disposed = false;
|
|
269
|
+
constructor(deps) {
|
|
270
|
+
this.deps = deps;
|
|
271
|
+
}
|
|
272
|
+
observe(msg) {
|
|
273
|
+
if (this.disposed)
|
|
274
|
+
return;
|
|
275
|
+
if (!exports.MILESTONE_PROGRESS_TRIGGERS.has(msg.type))
|
|
276
|
+
return;
|
|
277
|
+
this.schedule();
|
|
278
|
+
}
|
|
279
|
+
schedule() {
|
|
280
|
+
if (this.disposed || this.knownAbsent)
|
|
281
|
+
return;
|
|
282
|
+
if (this.timer)
|
|
283
|
+
clearTimeout(this.timer);
|
|
284
|
+
this.timer = setTimeout(() => { this.timer = null; this.flush(); }, this.deps.debounceMs ?? exports.MILESTONE_PROGRESS_DEBOUNCE_MS);
|
|
285
|
+
this.timer.unref?.();
|
|
286
|
+
}
|
|
287
|
+
/** Forget the "no blueprint" memo (call after a blueprint lands). */
|
|
288
|
+
invalidate() {
|
|
289
|
+
this.knownAbsent = false;
|
|
290
|
+
}
|
|
291
|
+
/** Synchronous read + broadcast. Returns the snapshot (null = no blueprint). */
|
|
292
|
+
flush() {
|
|
293
|
+
if (this.disposed)
|
|
294
|
+
return null;
|
|
295
|
+
if (this.timer) {
|
|
296
|
+
clearTimeout(this.timer);
|
|
297
|
+
this.timer = null;
|
|
298
|
+
}
|
|
299
|
+
let snapshot;
|
|
300
|
+
try {
|
|
301
|
+
snapshot = this.deps.read();
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
console.error('[milestone-progress] read failed:', err);
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
if (!snapshot) {
|
|
308
|
+
this.knownAbsent = true;
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const completed = [];
|
|
312
|
+
for (const m of snapshot.progress) {
|
|
313
|
+
if (m.state !== 'done' || m.storedStatus === 'done')
|
|
314
|
+
continue;
|
|
315
|
+
let written = false;
|
|
316
|
+
try {
|
|
317
|
+
written = this.deps.persistDone(m.id);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
console.error('[milestone-progress] persist done failed:', err);
|
|
321
|
+
}
|
|
322
|
+
if (written) {
|
|
323
|
+
m.storedStatus = 'done';
|
|
324
|
+
completed.push(m);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const timestamp = new Date(this.deps.now?.() ?? Date.now()).toISOString();
|
|
328
|
+
try {
|
|
329
|
+
this.deps.broadcast({ type: 'blueprint.milestone_progress', projectId: this.deps.projectId, progress: snapshot.progress, timestamp });
|
|
330
|
+
}
|
|
331
|
+
catch { /* durable rows are authoritative */ }
|
|
332
|
+
for (const m of completed) {
|
|
333
|
+
try {
|
|
334
|
+
this.deps.broadcast({ type: 'blueprint.milestone_completed', projectId: this.deps.projectId, milestoneId: m.id, n: m.n, title: m.title, timestamp });
|
|
335
|
+
}
|
|
336
|
+
catch { /* ignore */ }
|
|
337
|
+
}
|
|
338
|
+
return snapshot;
|
|
339
|
+
}
|
|
340
|
+
dispose() {
|
|
341
|
+
this.disposed = true;
|
|
342
|
+
if (this.timer) {
|
|
343
|
+
clearTimeout(this.timer);
|
|
344
|
+
this.timer = null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
exports.MilestoneProgressBroadcaster = MilestoneProgressBroadcaster;
|
|
@@ -51,24 +51,27 @@ function _resetAclColumnCacheForTests() {
|
|
|
51
51
|
_aclColumnReady = false;
|
|
52
52
|
}
|
|
53
53
|
/** The set of project ids this device may access, or `null` when unrestricted
|
|
54
|
-
* (all projects).
|
|
55
|
-
*
|
|
54
|
+
* (all projects). Only the legacy NULL/unset value grants all projects;
|
|
55
|
+
* malformed persisted restrictions fail closed. */
|
|
56
56
|
function getAllowedProjects(db, deviceId) {
|
|
57
57
|
ensureAclColumn(db);
|
|
58
58
|
const row = db
|
|
59
59
|
.prepare('SELECT allowed_projects FROM mobile_devices WHERE id = ?')
|
|
60
60
|
.get(deviceId);
|
|
61
|
-
if (!row
|
|
61
|
+
if (!row)
|
|
62
|
+
return new Set();
|
|
63
|
+
if (row.allowed_projects == null || row.allowed_projects === '')
|
|
62
64
|
return null;
|
|
63
65
|
try {
|
|
64
66
|
const parsed = JSON.parse(row.allowed_projects);
|
|
65
67
|
if (!Array.isArray(parsed))
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
return new Set();
|
|
69
|
+
if (parsed.some(x => typeof x !== 'string'))
|
|
70
|
+
return new Set();
|
|
71
|
+
return new Set(parsed);
|
|
69
72
|
}
|
|
70
73
|
catch {
|
|
71
|
-
return
|
|
74
|
+
return new Set();
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
/** Restrict a device to a specific set of project ids. An empty array clears the
|
|
@@ -16,6 +16,7 @@ const mobile_devices_1 = require("./mobile-devices");
|
|
|
16
16
|
const mobile_webrtc_peer_1 = require("./mobile-webrtc-peer");
|
|
17
17
|
const mobile_signal_reconnect_1 = require("./mobile-signal-reconnect");
|
|
18
18
|
const mobile_webrtc_1 = require("./mobile-webrtc");
|
|
19
|
+
const agent_store_1 = require("../agent-store");
|
|
19
20
|
// Lifecycle owner of the second HTTPS+WSS listener (default :4202), hard-isolated
|
|
20
21
|
// from the main server. Off by default; started on enable or boot-if-enabled.
|
|
21
22
|
const DEFAULT_PORT = 4202;
|
|
@@ -144,6 +145,7 @@ class MobileGateway {
|
|
|
144
145
|
// projects (null ⇒ unrestricted/all-projects, the default for paired devices).
|
|
145
146
|
const bridge = new mobile_ws_1.MobileWsBridge({
|
|
146
147
|
allowedProjectsFor: (deviceId) => (0, mobile_devices_1.getAllowedProjects)(this._db, deviceId),
|
|
148
|
+
missionProjectFor: (conversationId) => (0, agent_store_1.getAgentConversation)(this._db, conversationId)?.pinned_project_id ?? null,
|
|
147
149
|
});
|
|
148
150
|
bridge.start();
|
|
149
151
|
const port = this.configuredPort();
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MISSION_ACCESS_REASON = exports.MOBILE_PROTOCOL_VERSION = void 0;
|
|
4
|
+
exports.mobileCapabilities = mobileCapabilities;
|
|
5
|
+
exports.missionSnapshot = missionSnapshot;
|
|
6
|
+
exports.createMobileMissionsRouter = createMobileMissionsRouter;
|
|
7
|
+
const node_string_decoder_1 = require("node:string_decoder");
|
|
8
|
+
const express_1 = require("express");
|
|
9
|
+
const agent_store_1 = require("../agent-store");
|
|
10
|
+
const desktop_db_1 = require("../desktop-db");
|
|
11
|
+
const mobile_devices_1 = require("./mobile-devices");
|
|
12
|
+
const mobile_redact_1 = require("./mobile-redact");
|
|
13
|
+
const ID = /^[A-Za-z0-9_-]{1,160}$/;
|
|
14
|
+
const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
15
|
+
const pick = (value, keys) => Object.fromEntries(Object.entries(record(value)).filter(([key]) => keys.includes(key)));
|
|
16
|
+
exports.MOBILE_PROTOCOL_VERSION = 2;
|
|
17
|
+
exports.MISSION_ACCESS_REASON = 'Mission agents can access more than their pinned project. Missions require an all-projects device grant; your permitted Specs and Rails remain available.';
|
|
18
|
+
function mobileCapabilities(db, deviceId) {
|
|
19
|
+
const missions = process.env.SPECRAILS_AGENT_CHAT !== 'false' && (0, mobile_devices_1.getAllowedProjects)(db, deviceId) === null;
|
|
20
|
+
return { protocolVersion: exports.MOBILE_PROTOCOL_VERSION, features: { missions, missionControl: missions, missionQueue: missions, missionProcesses: missions, repositories: true },
|
|
21
|
+
...(!missions ? { missionsUnavailableReason: process.env.SPECRAILS_AGENT_CHAT === 'false' ? 'Missions are disabled on Desktop.' : exports.MISSION_ACCESS_REASON } : {}) };
|
|
22
|
+
}
|
|
23
|
+
function conversationView(value) {
|
|
24
|
+
return pick(value, ['id', 'title', 'provider', 'model', 'pinned_project_id', 'tier_level', 'reasoning_effort', 'created_at', 'updated_at']);
|
|
25
|
+
}
|
|
26
|
+
/** Explicit mobile DTO: provider session IDs, attachment paths, tool inputs and
|
|
27
|
+
* arbitrary context metadata never enter the mission wire contract. */
|
|
28
|
+
function missionSnapshot(value) {
|
|
29
|
+
const data = record(value);
|
|
30
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
31
|
+
// Shared UTF-8 budget for all text, with pending inputs and the current
|
|
32
|
+
// response reserved first. Metadata is bounded separately by row counts.
|
|
33
|
+
let remaining = 480_000;
|
|
34
|
+
let truncated = messages.length > 200;
|
|
35
|
+
const take = (raw, maximum) => {
|
|
36
|
+
const bytes = Buffer.from(raw);
|
|
37
|
+
const budget = Math.min(remaining, maximum);
|
|
38
|
+
const text = bytes.length <= budget ? raw : new node_string_decoder_1.StringDecoder('utf8').write(bytes.subarray(0, budget));
|
|
39
|
+
remaining -= Buffer.byteLength(text);
|
|
40
|
+
if (text.length < raw.length)
|
|
41
|
+
truncated = true;
|
|
42
|
+
return text;
|
|
43
|
+
};
|
|
44
|
+
const live = record(data.live), liveRaw = String(live.streamingText ?? '');
|
|
45
|
+
const streamingText = take(liveRaw, 80_000);
|
|
46
|
+
const pending = Array.isArray(data.pendingMessages) ? data.pendingMessages : [];
|
|
47
|
+
const pendingMessages = pending.slice(0, 50).map(value => {
|
|
48
|
+
const raw = String(record(value).text ?? ''), text = take(raw, 4_000);
|
|
49
|
+
return { ...pick(value, ['queueId', 'deliveryMode', 'timestamp']), text, textTruncated: text.length < raw.length };
|
|
50
|
+
});
|
|
51
|
+
const bounded = [];
|
|
52
|
+
for (const message of messages.slice(-200).reverse()) {
|
|
53
|
+
const raw = String(record(message).content ?? '');
|
|
54
|
+
let card;
|
|
55
|
+
if (record(message).role === 'system') {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = record(JSON.parse(raw));
|
|
58
|
+
if (parsed.kind === 'pr_decision')
|
|
59
|
+
card = { ...pick(parsed, ['kind', 'railIndex', 'ticketIds', 'decision', 'implementationOutcome', 'deliveryOutcome', 'prNumber']), repositoryDeliveries: (Array.isArray(parsed.repositoryDeliveries) ? parsed.repositoryDeliveries : []).slice(0, 30).map(value => pick(value, ['repositoryId', 'repositoryName', 'decision'])) };
|
|
60
|
+
}
|
|
61
|
+
catch { /* Non-card system rows do not expose internal provider payloads. */ }
|
|
62
|
+
}
|
|
63
|
+
if (remaining === 0) {
|
|
64
|
+
truncated = true;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const content = record(message).role === 'system' ? '' : take(raw, 40_000);
|
|
68
|
+
bounded.unshift({ ...pick(message, ['id', 'role', 'created_at', 'delivery_status', 'delivery_receipt']), content, ...(card ? { card } : {}) });
|
|
69
|
+
}
|
|
70
|
+
return { conversation: conversationView(data.conversation), messages: bounded, truncated,
|
|
71
|
+
pendingMessages, pendingTruncated: pending.length > 50,
|
|
72
|
+
live: { ...pick(live, ['isStreaming', 'startedAt']), streamingText, textTruncated: streamingText.length < liveRaw.length } };
|
|
73
|
+
}
|
|
74
|
+
function createMobileMissionsRouter({ db, upstream }) {
|
|
75
|
+
const router = (0, express_1.Router)();
|
|
76
|
+
const segment = (value) => typeof value === 'string' ? value : '';
|
|
77
|
+
const send = async (res, method, path, body, shape) => {
|
|
78
|
+
try {
|
|
79
|
+
const result = await upstream(method, path, body);
|
|
80
|
+
res.status(result.status).json((0, mobile_redact_1.redact)(result.status >= 200 && result.status < 300 && shape ? shape(result.json) : result.json));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
res.status(502).json({ error: 'Desktop is unreachable. Retry when connected.' });
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
router.get('/capabilities', (req, res) => res.json(mobileCapabilities(db, req.mobileDevice.id)));
|
|
87
|
+
router.get('/mission-models', (req, res) => {
|
|
88
|
+
const provider = segment(req.query.provider);
|
|
89
|
+
if (!/^[a-z0-9_-]{1,40}$/.test(provider)) {
|
|
90
|
+
res.status(400).json({ error: 'A valid provider is required' });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
void send(res, 'GET', `/api/agent/models?provider=${encodeURIComponent(provider)}`);
|
|
94
|
+
});
|
|
95
|
+
router.get('/projects/:pid/repositories', (req, res) => {
|
|
96
|
+
const pid = segment(req.params.pid);
|
|
97
|
+
if (!(0, desktop_db_1.getProject)(db, pid)) {
|
|
98
|
+
res.status(404).json({ error: 'Unknown project' });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
res.json({ repositories: (0, desktop_db_1.listProjectRepositories)(db, pid).map(repository => pick(repository, ['id', 'name', 'isPrimary', 'kind', 'integrationBranch'])) });
|
|
102
|
+
});
|
|
103
|
+
router.use('/projects/:pid/missions', (req, res, next) => {
|
|
104
|
+
const device = req.mobileDevice;
|
|
105
|
+
// A conversation pin is an execution default, not an agent sandbox. Deny
|
|
106
|
+
// restricted devices rather than leaking repinned history or escalating tools.
|
|
107
|
+
if (!mobileCapabilities(db, device.id).features.missions) {
|
|
108
|
+
res.status(403).json({ error: exports.MISSION_ACCESS_REASON });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!(0, desktop_db_1.getProject)(db, segment(req.params.pid))) {
|
|
112
|
+
res.status(404).json({ error: 'Unknown project' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
next();
|
|
116
|
+
});
|
|
117
|
+
const identity = (req, res) => {
|
|
118
|
+
const pid = segment(req.params.pid), cid = segment(req.params.cid);
|
|
119
|
+
if (!ID.test(pid) || !ID.test(cid)) {
|
|
120
|
+
res.status(400).json({ error: 'Invalid mission identity' });
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const conversation = (0, agent_store_1.getAgentConversation)(db, cid);
|
|
124
|
+
if (!conversation || conversation.pinned_project_id !== pid) {
|
|
125
|
+
res.status(404).json({ error: 'Mission not found in this project' });
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return { pid, cid, base: `/api/agent/conversations/${encodeURIComponent(cid)}` };
|
|
129
|
+
};
|
|
130
|
+
router.get('/projects/:pid/missions', async (req, res) => {
|
|
131
|
+
const pid = segment(req.params.pid);
|
|
132
|
+
const rawLimit = req.query.limit === undefined ? 100 : Number(req.query.limit);
|
|
133
|
+
if (!Number.isSafeInteger(rawLimit) || rawLimit < 1 || rawLimit > 200) {
|
|
134
|
+
res.status(400).json({ error: 'limit must be 1–200' });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Query the project before applying the limit: active projects must not
|
|
138
|
+
// disappear behind another project's most recent conversations.
|
|
139
|
+
const rows = db.prepare('SELECT id FROM agent_conversations WHERE pinned_project_id = ? ORDER BY updated_at DESC, id DESC LIMIT ?').all(pid, rawLimit + 1);
|
|
140
|
+
try {
|
|
141
|
+
const active = await upstream('GET', '/api/agent/active-turns');
|
|
142
|
+
if (active.status !== 200) {
|
|
143
|
+
res.status(active.status).json((0, mobile_redact_1.redact)(active.json));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const activeIds = new Set((Array.isArray(record(active.json).turns) ? record(active.json).turns : []).map(value => record(value).conversationId));
|
|
147
|
+
res.json((0, mobile_redact_1.redact)({ conversations: rows.slice(0, rawLimit).map(row => ({ ...conversationView((0, agent_store_1.getAgentConversation)(db, row.id)), isStreaming: activeIds.has(row.id) })), truncated: rows.length > rawLimit }));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
res.status(502).json({ error: 'Desktop is unreachable' });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
router.post('/projects/:pid/missions', (req, res) => {
|
|
154
|
+
const b = record(req.body);
|
|
155
|
+
const body = { ...pick(b, ['provider', 'model', 'reasoningEffort']), pinnedProjectId: segment(req.params.pid), tierLevel: Number.isInteger(b.tierLevel) && Number(b.tierLevel) >= 0 && Number(b.tierLevel) <= 3 ? b.tierLevel : 0 };
|
|
156
|
+
void send(res, 'POST', '/api/agent/conversations', body, data => ({ conversation: conversationView(record(data).conversation) }));
|
|
157
|
+
});
|
|
158
|
+
router.get('/projects/:pid/missions/:cid', (req, res) => {
|
|
159
|
+
const id = identity(req, res);
|
|
160
|
+
if (id)
|
|
161
|
+
void send(res, 'GET', id.base, undefined, missionSnapshot);
|
|
162
|
+
});
|
|
163
|
+
router.post('/projects/:pid/missions/:cid/send', (req, res) => {
|
|
164
|
+
const id = identity(req, res);
|
|
165
|
+
if (!id)
|
|
166
|
+
return;
|
|
167
|
+
const b = record(req.body);
|
|
168
|
+
if (typeof b.text !== 'string' || !b.text.trim() || b.text.length > 40_000 || !ID.test(segment(b.queueId))) {
|
|
169
|
+
res.status(400).json({ error: 'A message and stable queueId are required (maximum 40000 characters)' });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (b.deliveryMode !== undefined && b.deliveryMode !== 'queue' && b.deliveryMode !== 'steer') {
|
|
173
|
+
res.status(400).json({ error: 'deliveryMode must be queue or steer' });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
void send(res, 'POST', `${id.base}/send`, { text: b.text, queueId: b.queueId, deliveryMode: b.deliveryMode ?? 'queue' }, data => pick(data, ['accepted', 'queued', 'duplicate', 'removed', 'deliveryMode']));
|
|
177
|
+
});
|
|
178
|
+
router.post('/projects/:pid/missions/:cid/abort', (req, res) => { const id = identity(req, res); if (id)
|
|
179
|
+
void send(res, 'POST', `${id.base}/abort`, {}); });
|
|
180
|
+
for (const action of ['edit', 'steer', 'remove']) {
|
|
181
|
+
const route = `/projects/:pid/missions/:cid/queue/:queueId${action === 'steer' ? '/steer' : ''}`;
|
|
182
|
+
const method = action === 'edit' ? 'patch' : action === 'steer' ? 'post' : 'delete';
|
|
183
|
+
router[method](route, (req, res) => {
|
|
184
|
+
const id = identity(req, res);
|
|
185
|
+
if (!id)
|
|
186
|
+
return;
|
|
187
|
+
const queueId = segment(req.params.queueId), text = record(req.body).text;
|
|
188
|
+
if (!ID.test(queueId) || (action === 'edit' && (typeof text !== 'string' || !text.trim() || text.length > 40_000))) {
|
|
189
|
+
res.status(400).json({ error: 'Invalid queued message' });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
void send(res, method.toUpperCase(), `${id.base}/queue/${encodeURIComponent(queueId)}${action === 'steer' ? '/steer' : ''}`, action === 'edit' ? { text } : undefined);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
router.get('/projects/:pid/missions/:cid/processes', (req, res) => {
|
|
196
|
+
const id = identity(req, res);
|
|
197
|
+
if (id)
|
|
198
|
+
void send(res, 'GET', `/api/projects/${encodeURIComponent(id.pid)}/background-processes?chatId=${encodeURIComponent(id.cid)}&includeFinished=true`);
|
|
199
|
+
});
|
|
200
|
+
for (const method of ['get', 'delete'])
|
|
201
|
+
router[method](`/projects/:pid/missions/:cid/processes/:processId${method === 'get' ? '/logs' : ''}`, (req, res) => {
|
|
202
|
+
const id = identity(req, res);
|
|
203
|
+
if (!id)
|
|
204
|
+
return;
|
|
205
|
+
const processId = segment(req.params.processId), osPid = Number(req.query.pid);
|
|
206
|
+
if (!ID.test(processId) || !Number.isSafeInteger(osPid) || osPid < 1) {
|
|
207
|
+
res.status(400).json({ error: 'Execution identity and numeric pid are required' });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const query = new URLSearchParams({ chatId: id.cid, processId, ...(method === 'get' ? { limit: '1000' } : {}) });
|
|
211
|
+
void send(res, method.toUpperCase(), `/api/projects/${encodeURIComponent(id.pid)}/background-processes/${osPid}${method === 'get' ? '/logs' : ''}?${query}`);
|
|
212
|
+
});
|
|
213
|
+
return router;
|
|
214
|
+
}
|
|
@@ -19,7 +19,8 @@ const os_1 = __importDefault(require("os"));
|
|
|
19
19
|
// key allow-list (rule 1) stays as defence-in-depth.
|
|
20
20
|
//
|
|
21
21
|
// Applied to EVERY proxied REST JSON body and EVERY outbound WS payload.
|
|
22
|
-
const SENSITIVE_KEYS = new Set(['path', 'db_path', 'dbPath', 'absolutePath', 'cwd', 'filePath', 'projectPath'
|
|
22
|
+
const SENSITIVE_KEYS = new Set(['path', 'db_path', 'dbPath', 'absolutePath', 'cwd', 'filePath', 'projectPath',
|
|
23
|
+
'token', 'token_hash', 'session_id', 'authorization', 'apiKey', 'api_key', 'password', 'secret', 'privateKey', 'private_key', 'env', 'environment']);
|
|
23
24
|
const ABS_PATH_PLACEHOLDER = '[path]';
|
|
24
25
|
// Absolute-path tokens to scrub AFTER the $HOME→`~` rewrite. A token is a run of
|
|
25
26
|
// non-whitespace characters; we match those that look like an absolute path:
|
|
@@ -33,6 +34,11 @@ const ABS_PATH_PLACEHOLDER = '[path]';
|
|
|
33
34
|
// paths are already collapsed by stripHome and never reach here.
|
|
34
35
|
// The leading boundary keeps embedded `://` URLs and mid-word slashes intact.
|
|
35
36
|
const ABS_PATH_RE = /(^|[\s"'`(=:,])([A-Za-z]:[\\/][^\s"'`)]*|\\\\[^\s"'`)]+|\/[^\s"'`)/]+\/[^\s"'`)]*)/g;
|
|
37
|
+
// Quotes bound the complete path, including a final filename with spaces.
|
|
38
|
+
// Without quotes, spaces are reliable only inside components followed by a
|
|
39
|
+
// separator; leave trailing prose intact rather than guessing its endpoint.
|
|
40
|
+
const QUOTED_WINDOWS_PATH_RE = /(["'`])((?:[A-Za-z]:[\\/]|\\\\)[^\r\n]*?)\1/g;
|
|
41
|
+
const WINDOWS_COMPONENT_PATH_RE = /(^|[\s"'`(=:,])((?:[A-Za-z]:[\\/]|\\\\)(?:[^\\/\r\n"'`()<>|:;,!?]*[\\/])*[^\s\\/"'`()<>|:;,!?]*)/g;
|
|
36
42
|
function homeDir() {
|
|
37
43
|
try {
|
|
38
44
|
return os_1.default.homedir();
|
|
@@ -47,7 +53,12 @@ function stripHome(s) {
|
|
|
47
53
|
const home = homeDir();
|
|
48
54
|
if (!home)
|
|
49
55
|
return s;
|
|
50
|
-
|
|
56
|
+
const windowsHome = /^[A-Za-z]:[\\/]|^\\\\/.test(home);
|
|
57
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
58
|
+
const pattern = windowsHome ? home.split(/[\\/]/).map(escape).join('[\\\\/]') : escape(home);
|
|
59
|
+
// A different account whose name merely starts with ours is not our HOME.
|
|
60
|
+
const boundary = '(?=$|[\\\\/\\s"\'`),;:])';
|
|
61
|
+
return s.replace(new RegExp(pattern + boundary, windowsHome || process.platform === 'win32' ? 'gi' : 'g'), '~');
|
|
51
62
|
}
|
|
52
63
|
/** Replace absolute-path tokens (POSIX `/…`, Windows `X:\…`, UNC `\\…`) with a
|
|
53
64
|
* placeholder. Runs AFTER stripHome so `~`-rooted paths are already collapsed and
|
|
@@ -55,7 +66,10 @@ function stripHome(s) {
|
|
|
55
66
|
function scrubAbsolutePaths(s) {
|
|
56
67
|
// Keep the matched leading delimiter, replace the path token with the
|
|
57
68
|
// placeholder. URLs (`://`) are excluded by the path-branch char classes.
|
|
58
|
-
return s
|
|
69
|
+
return s
|
|
70
|
+
.replace(QUOTED_WINDOWS_PATH_RE, (_match, quote) => `${quote}${ABS_PATH_PLACEHOLDER}${quote}`)
|
|
71
|
+
.replace(WINDOWS_COMPONENT_PATH_RE, (_match, lead) => `${lead}${ABS_PATH_PLACEHOLDER}`)
|
|
72
|
+
.replace(ABS_PATH_RE, (_match, lead) => `${lead}${ABS_PATH_PLACEHOLDER}`);
|
|
59
73
|
}
|
|
60
74
|
function scrubString(s) {
|
|
61
75
|
return scrubAbsolutePaths(stripHome(s));
|