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
|
@@ -3,14 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AGENT_SEARCH_INDEX_DDL = void 0;
|
|
6
7
|
exports.setProjectProvidersMirror = setProjectProvidersMirror;
|
|
7
8
|
exports.getDesktopDbPath = getDesktopDbPath;
|
|
9
|
+
exports.rebuildAgentSearchIndex = rebuildAgentSearchIndex;
|
|
8
10
|
exports.initDesktopDb = initDesktopDb;
|
|
9
11
|
exports.listProjects = listProjects;
|
|
10
12
|
exports.getProject = getProject;
|
|
11
13
|
exports.getProjectBySlug = getProjectBySlug;
|
|
12
14
|
exports.getProjectByPath = getProjectByPath;
|
|
13
15
|
exports.addProject = addProject;
|
|
16
|
+
exports.listProjectRepositories = listProjectRepositories;
|
|
17
|
+
exports.addProjectRepository = addProjectRepository;
|
|
18
|
+
exports.updateProjectRepository = updateProjectRepository;
|
|
19
|
+
exports.removeProjectRepository = removeProjectRepository;
|
|
14
20
|
exports.removeProject = removeProject;
|
|
15
21
|
exports.touchProject = touchProject;
|
|
16
22
|
exports.getDesktopSetting = getDesktopSetting;
|
|
@@ -38,6 +44,9 @@ const path_1 = __importDefault(require("path"));
|
|
|
38
44
|
const os_1 = __importDefault(require("os"));
|
|
39
45
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
40
46
|
const secure_fs_1 = require("./util/secure-fs");
|
|
47
|
+
const sqlite_migrations_1 = require("./util/sqlite-migrations");
|
|
48
|
+
const crypto_1 = require("crypto");
|
|
49
|
+
const project_repositories_1 = require("./project-repositories");
|
|
41
50
|
function parseProviders(raw, primary) {
|
|
42
51
|
if (raw) {
|
|
43
52
|
try {
|
|
@@ -63,9 +72,11 @@ function setProjectProvidersMirror(fn) {
|
|
|
63
72
|
_detectedMirrorSupplier = fn;
|
|
64
73
|
}
|
|
65
74
|
const PRIMARY_PREFERENCE = ['claude', 'codex', 'gemini', 'kimi'];
|
|
66
|
-
function mapProjectRow(raw) {
|
|
75
|
+
function mapProjectRow(db, raw) {
|
|
67
76
|
if (!raw)
|
|
68
77
|
return undefined;
|
|
78
|
+
const repositories = listProjectRepositories(db, raw.id);
|
|
79
|
+
const membership = { repositories, primaryRepositoryId: repositories.find((repository) => repository.isPrimary)?.id };
|
|
69
80
|
const provider = (raw.provider ?? 'claude');
|
|
70
81
|
const stored = parseProviders(raw.providers, provider);
|
|
71
82
|
const detected = _detectedMirrorSupplier?.();
|
|
@@ -74,9 +85,9 @@ function mapProjectRow(raw) {
|
|
|
74
85
|
let primary = mirrored.includes(provider) ? provider : undefined;
|
|
75
86
|
if (!primary)
|
|
76
87
|
primary = PRIMARY_PREFERENCE.find((p) => mirrored.includes(p)) ?? mirrored[0];
|
|
77
|
-
return { ...raw, provider: primary, providers: [...mirrored] };
|
|
88
|
+
return { ...raw, ...membership, provider: primary, providers: [...mirrored] };
|
|
78
89
|
}
|
|
79
|
-
return { ...raw, provider, providers: stored };
|
|
90
|
+
return { ...raw, ...membership, provider, providers: stored };
|
|
80
91
|
}
|
|
81
92
|
// ─── Desktop DB path ──────────────────────────────────────────────────────────
|
|
82
93
|
function getDesktopDbPath() {
|
|
@@ -102,9 +113,8 @@ function migrateLegacyDbFile(dbPath) {
|
|
|
102
113
|
// is renamed first, a crash in the (sub-ms) window between renames leaves
|
|
103
114
|
// `desktop.sqlite` with no adjacent WAL -> un-checkpointed commits in the
|
|
104
115
|
// orphaned `hub.sqlite-wal` are silently discarded on next open. Moving the
|
|
105
|
-
// sidecars first
|
|
106
|
-
//
|
|
107
|
-
// guard above re-runs the whole migration on the next launch).
|
|
116
|
+
// sidecars first keeps the new main file from being opened without its
|
|
117
|
+
// WAL. An interrupted migration resumes before opening either main file.
|
|
108
118
|
for (const suffix of ['-wal', '-shm']) {
|
|
109
119
|
if (fs_1.default.existsSync(legacyPath + suffix)) {
|
|
110
120
|
fs_1.default.renameSync(legacyPath + suffix, dbPath + suffix);
|
|
@@ -114,12 +124,62 @@ function migrateLegacyDbFile(dbPath) {
|
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
126
|
catch (err) {
|
|
117
|
-
|
|
127
|
+
// A failed rename must never fall through into creating an empty catalog.
|
|
128
|
+
// Tolerate only a second startup that already completed the same rename.
|
|
129
|
+
if (!fs_1.default.existsSync(legacyPath) && fs_1.default.existsSync(dbPath))
|
|
130
|
+
return;
|
|
131
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
132
|
+
throw new Error(`Could not migrate the existing project database from hub.sqlite to desktop.sqlite: ${detail}`, { cause: err });
|
|
118
133
|
}
|
|
119
134
|
}
|
|
120
135
|
function getProjectDbPath(slug) {
|
|
121
136
|
return path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', slug, 'jobs.sqlite');
|
|
122
137
|
}
|
|
138
|
+
// ─── Mission search index ─────────────────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* DDL for the mission full-text index (migration 24). Kept as a constant so
|
|
141
|
+
* `rebuildAgentSearchIndex` and tests can reason about the same schema. The
|
|
142
|
+
* column order matters: `content` is column 3, which is what `snippet()` and
|
|
143
|
+
* `highlight()` address in `agent-store.ts`.
|
|
144
|
+
*/
|
|
145
|
+
exports.AGENT_SEARCH_INDEX_DDL = `
|
|
146
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS agent_messages_fts USING fts5(
|
|
147
|
+
conversation_id UNINDEXED,
|
|
148
|
+
message_id UNINDEXED,
|
|
149
|
+
role UNINDEXED,
|
|
150
|
+
content,
|
|
151
|
+
tokenize='trigram remove_diacritics 1'
|
|
152
|
+
);
|
|
153
|
+
CREATE TRIGGER IF NOT EXISTS agent_messages_fts_ai AFTER INSERT ON agent_messages BEGIN
|
|
154
|
+
INSERT INTO agent_messages_fts (conversation_id, message_id, role, content)
|
|
155
|
+
VALUES (new.conversation_id, new.id, new.role, new.content);
|
|
156
|
+
END;
|
|
157
|
+
CREATE TRIGGER IF NOT EXISTS agent_messages_fts_ad AFTER DELETE ON agent_messages BEGIN
|
|
158
|
+
DELETE FROM agent_messages_fts WHERE message_id = old.id;
|
|
159
|
+
END;
|
|
160
|
+
CREATE TRIGGER IF NOT EXISTS agent_messages_fts_au AFTER UPDATE OF content, role, conversation_id ON agent_messages BEGIN
|
|
161
|
+
DELETE FROM agent_messages_fts WHERE message_id = old.id;
|
|
162
|
+
INSERT INTO agent_messages_fts (conversation_id, message_id, role, content)
|
|
163
|
+
VALUES (new.conversation_id, new.id, new.role, new.content);
|
|
164
|
+
END;
|
|
165
|
+
`;
|
|
166
|
+
/**
|
|
167
|
+
* Re-derive the whole mission search index from `agent_messages`. Idempotent
|
|
168
|
+
* and cheap (the corpus is a few MB at most); the migration calls it once, and
|
|
169
|
+
* any future bulk writer that bypasses SQL triggers (an import, a repair tool)
|
|
170
|
+
* must call it afterwards. Runs in one transaction so a reader never observes
|
|
171
|
+
* a half-empty index.
|
|
172
|
+
*/
|
|
173
|
+
function rebuildAgentSearchIndex(db) {
|
|
174
|
+
const tx = db.transaction(() => {
|
|
175
|
+
db.exec('DELETE FROM agent_messages_fts');
|
|
176
|
+
db.exec(`
|
|
177
|
+
INSERT INTO agent_messages_fts (conversation_id, message_id, role, content)
|
|
178
|
+
SELECT conversation_id, id, role, content FROM agent_messages
|
|
179
|
+
`);
|
|
180
|
+
});
|
|
181
|
+
tx();
|
|
182
|
+
}
|
|
123
183
|
// ─── Schema migrations ────────────────────────────────────────────────────────
|
|
124
184
|
// NOTE: existing migrations below intentionally keep the legacy `hub_settings`
|
|
125
185
|
// table name — they are append-only history and must stay byte-identical for
|
|
@@ -133,8 +193,6 @@ function applyDesktopMigrations(db) {
|
|
|
133
193
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
134
194
|
);
|
|
135
195
|
`);
|
|
136
|
-
const appliedVersions = new Set(db.prepare('SELECT version FROM schema_migrations').all()
|
|
137
|
-
.map((r) => r.version));
|
|
138
196
|
const migrations = [
|
|
139
197
|
// Migration 1: projects and hub_settings tables
|
|
140
198
|
() => {
|
|
@@ -484,23 +542,102 @@ function applyDesktopMigrations(db) {
|
|
|
484
542
|
CREATE INDEX IF NOT EXISTS idx_blueprint_messages_conv ON blueprint_messages(conversation_id, created_at);
|
|
485
543
|
`);
|
|
486
544
|
},
|
|
545
|
+
// 23: durable Builder snapshots (harden-project-builder-snapshots). The
|
|
546
|
+
// blueprint used to live ONLY in client memory — the persisted assistant
|
|
547
|
+
// rows were stripped of the blueprint-draft JSON — so a closed panel, a
|
|
548
|
+
// refresh, or a silently rejected block lost everything the model had
|
|
549
|
+
// produced (and billed). Now every ACCEPTED snapshot is stored on the
|
|
550
|
+
// conversation (normalized + exact raw payload), the model's unstripped
|
|
551
|
+
// reply is kept per message for forensics/re-parsing, the last rejection
|
|
552
|
+
// is recorded so a manual repair can resume after a restart, and a commit
|
|
553
|
+
// links the conversation to the project it created (so the resume list
|
|
554
|
+
// only offers unfinished blueprints).
|
|
555
|
+
() => {
|
|
556
|
+
db.exec(`
|
|
557
|
+
ALTER TABLE blueprint_conversations ADD COLUMN blueprint_json TEXT;
|
|
558
|
+
ALTER TABLE blueprint_conversations ADD COLUMN raw_blueprint_json TEXT;
|
|
559
|
+
ALTER TABLE blueprint_conversations ADD COLUMN snapshot_updated_at TEXT;
|
|
560
|
+
ALTER TABLE blueprint_conversations ADD COLUMN snapshot_issue_json TEXT;
|
|
561
|
+
ALTER TABLE blueprint_conversations ADD COLUMN committed_project_id TEXT;
|
|
562
|
+
ALTER TABLE blueprint_messages ADD COLUMN raw_content TEXT;
|
|
563
|
+
`);
|
|
564
|
+
},
|
|
565
|
+
// 24: mission search (search-missions-in-palette). A standard FTS5 table
|
|
566
|
+
// (NOT external-content: `agent_messages` has a TEXT primary key, so its
|
|
567
|
+
// rowid is implicit and a VACUUM may renumber it — an external-content
|
|
568
|
+
// index keyed on that rowid would silently desync) carrying its own copy of
|
|
569
|
+
// every message body plus UNINDEXED conversation/message/role columns so a
|
|
570
|
+
// search never has to join back. The trigram tokenizer gives substring
|
|
571
|
+
// semantics ("etris" finds "Tetris") with an index and `snippet()`;
|
|
572
|
+
// `remove_diacritics 1` makes "mision" find "misión". Three triggers keep
|
|
573
|
+
// it in sync so every existing writer stays untouched; the one-off rebuild
|
|
574
|
+
// indexes the history already on disk.
|
|
575
|
+
() => {
|
|
576
|
+
db.exec(exports.AGENT_SEARCH_INDEX_DDL);
|
|
577
|
+
rebuildAgentSearchIndex(db);
|
|
578
|
+
},
|
|
579
|
+
// 25: Builder decision cards (premium-milestone-progress follow-up): a user
|
|
580
|
+
// turn sent from a one-click card ("surprise me", "approve") keeps its
|
|
581
|
+
// intent so the thread renders the settled decision card in place of the
|
|
582
|
+
// prompt bubble — across resumes and locale switches.
|
|
583
|
+
() => {
|
|
584
|
+
db.exec(`ALTER TABLE blueprint_messages ADD COLUMN intent TEXT;`);
|
|
585
|
+
},
|
|
586
|
+
// 26: logical projects retain their primary identity and one shared backlog.
|
|
587
|
+
() => {
|
|
588
|
+
db.exec(`
|
|
589
|
+
CREATE TABLE project_repositories (
|
|
590
|
+
id TEXT PRIMARY KEY,
|
|
591
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
592
|
+
name TEXT NOT NULL,
|
|
593
|
+
path TEXT NOT NULL,
|
|
594
|
+
canonical_key TEXT NOT NULL,
|
|
595
|
+
git_identity TEXT,
|
|
596
|
+
is_primary INTEGER NOT NULL DEFAULT 0 CHECK (is_primary IN (0, 1)),
|
|
597
|
+
kind TEXT NOT NULL CHECK (kind IN ('git', 'folder')),
|
|
598
|
+
integration_branch TEXT,
|
|
599
|
+
added_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
600
|
+
UNIQUE(project_id, canonical_key)
|
|
601
|
+
);
|
|
602
|
+
CREATE UNIQUE INDEX project_repositories_primary ON project_repositories(project_id) WHERE is_primary = 1;
|
|
603
|
+
CREATE UNIQUE INDEX project_repositories_git_identity ON project_repositories(project_id, git_identity) WHERE git_identity IS NOT NULL;
|
|
604
|
+
`);
|
|
605
|
+
const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, is_primary, kind, added_at) VALUES (?, ?, ?, ?, ?, 1, ?, ?)`);
|
|
606
|
+
for (const project of db.prepare('SELECT id, name, path, added_at FROM projects').all()) {
|
|
607
|
+
insert.run(`primary-${project.id}`, project.id, project.name, project.path, (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(project.path)), (0, project_repositories_1.legacyRepositoryKind)(project.path), project.added_at);
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
// 27: durable mission inputs. Pending corrections survive a process restart
|
|
611
|
+
// as visible interrupted user messages, without silently replaying actions.
|
|
612
|
+
() => {
|
|
613
|
+
db.exec(`
|
|
614
|
+
CREATE TABLE agent_inputs (
|
|
615
|
+
id TEXT PRIMARY KEY,
|
|
616
|
+
conversation_id TEXT NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
|
617
|
+
queue_id TEXT NOT NULL,
|
|
618
|
+
text TEXT NOT NULL,
|
|
619
|
+
options_json TEXT NOT NULL DEFAULT '{}',
|
|
620
|
+
enqueue_payload TEXT NOT NULL,
|
|
621
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'delivered', 'cancelled', 'interrupted')),
|
|
622
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
623
|
+
message_id TEXT REFERENCES agent_messages(id) ON DELETE SET NULL,
|
|
624
|
+
UNIQUE(conversation_id, queue_id)
|
|
625
|
+
);
|
|
626
|
+
CREATE INDEX idx_agent_inputs_pending ON agent_inputs(conversation_id, created_at) WHERE status = 'pending';
|
|
627
|
+
CREATE UNIQUE INDEX idx_agent_inputs_message ON agent_inputs(message_id) WHERE message_id IS NOT NULL;
|
|
628
|
+
`);
|
|
629
|
+
},
|
|
630
|
+
// 28: provider receipt is independent of transcript settlement. Existing
|
|
631
|
+
// delivered inputs were accepted, but have no evidence of being read.
|
|
632
|
+
() => {
|
|
633
|
+
db.exec(`
|
|
634
|
+
ALTER TABLE agent_inputs ADD COLUMN receipt TEXT NOT NULL DEFAULT 'sent'
|
|
635
|
+
CHECK (receipt IN ('sent', 'received', 'read'));
|
|
636
|
+
UPDATE agent_inputs SET receipt = 'received' WHERE status = 'delivered';
|
|
637
|
+
`);
|
|
638
|
+
},
|
|
487
639
|
];
|
|
488
|
-
|
|
489
|
-
const version = i + 1;
|
|
490
|
-
if (!appliedVersions.has(version)) {
|
|
491
|
-
// Run each migration body and its version INSERT atomically (mirrors
|
|
492
|
-
// db.ts applyMigrations). SQLite DDL is transactional, so a crash/failure
|
|
493
|
-
// mid-migration rolls back the whole body instead of leaving a half-applied
|
|
494
|
-
// schema with the version unrecorded — which previously re-ran a bare
|
|
495
|
-
// `ALTER TABLE ADD COLUMN` on next startup and bricked app launch forever
|
|
496
|
-
// with 'duplicate column name'.
|
|
497
|
-
const tx = db.transaction(() => {
|
|
498
|
-
migrations[i]();
|
|
499
|
-
db.prepare('INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)').run(version);
|
|
500
|
-
});
|
|
501
|
-
tx();
|
|
502
|
-
}
|
|
503
|
-
}
|
|
640
|
+
(0, sqlite_migrations_1.applyNumberedMigrations)(db, migrations);
|
|
504
641
|
}
|
|
505
642
|
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
506
643
|
function initDesktopDb(dbPath = getDesktopDbPath()) {
|
|
@@ -511,29 +648,35 @@ function initDesktopDb(dbPath = getDesktopDbPath()) {
|
|
|
511
648
|
// Rebrand compat: pick up a pre-rename hub.sqlite before opening.
|
|
512
649
|
migrateLegacyDbFile(dbPath);
|
|
513
650
|
const db = new better_sqlite3_1.default(dbPath);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
651
|
+
try {
|
|
652
|
+
db.pragma('journal_mode = WAL');
|
|
653
|
+
db.pragma('foreign_keys = ON');
|
|
654
|
+
// Wait up to 5s on a lock instead of throwing SQLITE_BUSY, and cap the WAL.
|
|
655
|
+
db.pragma('busy_timeout = 5000');
|
|
656
|
+
db.pragma('journal_size_limit = 10000000'); // ~10 MB
|
|
657
|
+
applyDesktopMigrations(db);
|
|
658
|
+
// H-13: desktop.sqlite stores webhook HMAC secrets in plaintext — restrict it
|
|
659
|
+
// (and its WAL sidecars) to owner read/write.
|
|
660
|
+
if (dbPath !== ':memory:')
|
|
661
|
+
(0, secure_fs_1.secureDbFile)(dbPath);
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
db.close();
|
|
665
|
+
throw err;
|
|
666
|
+
}
|
|
524
667
|
return db;
|
|
525
668
|
}
|
|
526
669
|
function listProjects(db) {
|
|
527
|
-
return db.prepare('SELECT * FROM projects ORDER BY added_at ASC').all().map((r) => mapProjectRow(r));
|
|
670
|
+
return db.prepare('SELECT * FROM projects ORDER BY added_at ASC').all().map((r) => mapProjectRow(db, r));
|
|
528
671
|
}
|
|
529
672
|
function getProject(db, id) {
|
|
530
|
-
return mapProjectRow(db.prepare('SELECT * FROM projects WHERE id = ?').get(id));
|
|
673
|
+
return mapProjectRow(db, db.prepare('SELECT * FROM projects WHERE id = ?').get(id));
|
|
531
674
|
}
|
|
532
675
|
function getProjectBySlug(db, slug) {
|
|
533
|
-
return mapProjectRow(db.prepare('SELECT * FROM projects WHERE slug = ?').get(slug));
|
|
676
|
+
return mapProjectRow(db, db.prepare('SELECT * FROM projects WHERE slug = ?').get(slug));
|
|
534
677
|
}
|
|
535
678
|
function getProjectByPath(db, projectPath) {
|
|
536
|
-
return mapProjectRow(db.prepare('SELECT * FROM projects WHERE path = ?').get(projectPath));
|
|
679
|
+
return mapProjectRow(db, db.prepare('SELECT * FROM projects WHERE path = ?').get(projectPath));
|
|
537
680
|
}
|
|
538
681
|
function addProject(db, project) {
|
|
539
682
|
const dbPath = getProjectDbPath(project.slug);
|
|
@@ -542,12 +685,65 @@ function addProject(db, project) {
|
|
|
542
685
|
: [project.provider ?? 'claude'];
|
|
543
686
|
// Primary provider = explicit override, else the first selected provider.
|
|
544
687
|
const provider = project.provider ?? providers[0];
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
688
|
+
// Low-level callers may register unavailable legacy roots. HTTP validates all
|
|
689
|
+
// roots first; membership and project rows are still committed atomically here.
|
|
690
|
+
const roots = [(0, project_repositories_1.inspectRepositoryPath)({ path: project.path, name: project.name }, false), ...(project.repositories ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))];
|
|
691
|
+
(0, project_repositories_1.assertDistinctRepositories)(roots);
|
|
692
|
+
db.transaction(() => {
|
|
693
|
+
db.prepare(`
|
|
694
|
+
INSERT INTO projects (id, slug, name, path, db_path, provider, providers)
|
|
695
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
696
|
+
`).run(project.id, project.slug, project.name, project.path, dbPath, provider, JSON.stringify(providers));
|
|
697
|
+
const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, is_primary, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
698
|
+
roots.forEach((root, index) => insert.run(index === 0 ? `primary-${project.id}` : (0, crypto_1.randomUUID)(), project.id, root.name ?? path_1.default.basename(root.path), index === 0 ? project.path : root.path, root.canonicalKey, root.gitIdentity, index === 0 ? 1 : 0, root.kind, root.integrationBranch ?? null));
|
|
699
|
+
})();
|
|
549
700
|
return getProject(db, project.id);
|
|
550
701
|
}
|
|
702
|
+
function listProjectRepositories(db, projectId) {
|
|
703
|
+
return db.prepare('SELECT * FROM project_repositories WHERE project_id = ? ORDER BY is_primary DESC, added_at, rowid').all(projectId).map((row) => ({
|
|
704
|
+
id: row.id, projectId: row.project_id, name: row.name, path: row.path,
|
|
705
|
+
isPrimary: row.is_primary === 1, kind: row.kind, integrationBranch: row.integration_branch,
|
|
706
|
+
addedAt: row.added_at, available: (0, project_repositories_1.repositoryAvailable)(row.path),
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
function addProjectRepository(db, projectId, input) {
|
|
710
|
+
if (!getProject(db, projectId))
|
|
711
|
+
throw new project_repositories_1.RepositoryValidationError('Project not found', 'project_not_found', 404);
|
|
712
|
+
const root = (0, project_repositories_1.inspectRepositoryPath)(input);
|
|
713
|
+
const existing = listProjectRepositories(db, projectId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false));
|
|
714
|
+
(0, project_repositories_1.assertDistinctRepositories)([...existing, root]);
|
|
715
|
+
const id = (0, crypto_1.randomUUID)();
|
|
716
|
+
db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
717
|
+
.run(id, projectId, root.name ?? path_1.default.basename(root.path), root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null);
|
|
718
|
+
return listProjectRepositories(db, projectId).find((repo) => repo.id === id);
|
|
719
|
+
}
|
|
720
|
+
function updateProjectRepository(db, projectId, repositoryId, input) {
|
|
721
|
+
const repositories = listProjectRepositories(db, projectId);
|
|
722
|
+
const current = repositories.find((repo) => repo.id === repositoryId);
|
|
723
|
+
if (!current)
|
|
724
|
+
throw new project_repositories_1.RepositoryValidationError('Repository does not belong to this project', 'repository_not_found', 404);
|
|
725
|
+
if (current.isPrimary && input.path !== undefined)
|
|
726
|
+
throw new project_repositories_1.RepositoryValidationError('The primary project path cannot be relocated here', 'primary_repository_protected', 409);
|
|
727
|
+
const next = { ...current, ...input };
|
|
728
|
+
const root = (0, project_repositories_1.inspectRepositoryPath)(next, input.path !== undefined);
|
|
729
|
+
if (input.path === undefined && !(0, project_repositories_1.repositoryAvailable)(current.path)) {
|
|
730
|
+
root.kind = current.kind;
|
|
731
|
+
root.gitIdentity = db.prepare('SELECT git_identity FROM project_repositories WHERE id = ?').get(repositoryId).git_identity;
|
|
732
|
+
}
|
|
733
|
+
(0, project_repositories_1.assertDistinctRepositories)([...repositories.filter((repo) => repo.id !== repositoryId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false)), root]);
|
|
734
|
+
db.prepare('UPDATE project_repositories SET name = ?, path = ?, canonical_key = ?, git_identity = ?, kind = ?, integration_branch = ? WHERE project_id = ? AND id = ?')
|
|
735
|
+
.run(root.name ?? current.name, input.path === undefined ? current.path : root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, projectId, repositoryId);
|
|
736
|
+
return listProjectRepositories(db, projectId).find((repo) => repo.id === repositoryId);
|
|
737
|
+
}
|
|
738
|
+
/** Removes the association only. Operational reference guards belong to ProjectRegistry. */
|
|
739
|
+
function removeProjectRepository(db, projectId, repositoryId) {
|
|
740
|
+
const current = listProjectRepositories(db, projectId).find((repo) => repo.id === repositoryId);
|
|
741
|
+
if (!current)
|
|
742
|
+
throw new project_repositories_1.RepositoryValidationError('Repository does not belong to this project', 'repository_not_found', 404);
|
|
743
|
+
if (current.isPrimary)
|
|
744
|
+
throw new project_repositories_1.RepositoryValidationError('The primary repository cannot be removed', 'primary_repository_protected', 409);
|
|
745
|
+
db.prepare('DELETE FROM project_repositories WHERE project_id = ? AND id = ?').run(projectId, repositoryId);
|
|
746
|
+
}
|
|
551
747
|
function removeProject(db, id) {
|
|
552
748
|
db.prepare('DELETE FROM projects WHERE id = ?').run(id);
|
|
553
749
|
// No FK on agent_conversations.pinned_project_id — unpin so orphaned agent
|
|
@@ -10,9 +10,11 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
const net_1 = __importDefault(require("net"));
|
|
12
12
|
const dns_1 = __importDefault(require("dns"));
|
|
13
|
+
const project_repositories_1 = require("./project-repositories");
|
|
13
14
|
const desktop_db_1 = require("./desktop-db");
|
|
14
15
|
const webhook_manager_1 = require("./webhook-manager");
|
|
15
16
|
const core_update_manager_1 = require("./core-update-manager");
|
|
17
|
+
const framework_reseed_1 = require("./framework-reseed");
|
|
16
18
|
const specrails_tech_client_1 = require("./specrails-tech-client");
|
|
17
19
|
const core_compat_1 = require("./core-compat");
|
|
18
20
|
const providers_1 = require("./providers");
|
|
@@ -453,7 +455,7 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
453
455
|
});
|
|
454
456
|
// POST /api/projects — register a new project by path
|
|
455
457
|
router.post('/projects', async (req, res) => {
|
|
456
|
-
const { path: projectPath, name, provider, providers: providersRaw } = req.body ?? {};
|
|
458
|
+
const { path: projectPath, name, provider, providers: providersRaw, repositories: repositoriesRaw } = req.body ?? {};
|
|
457
459
|
if (!projectPath || typeof projectPath !== 'string') {
|
|
458
460
|
res.status(400).json({ error: 'path is required' });
|
|
459
461
|
return;
|
|
@@ -545,6 +547,23 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
545
547
|
return;
|
|
546
548
|
}
|
|
547
549
|
const canonicalPath = canonicalizePath(resolvedPath);
|
|
550
|
+
let repositories = [];
|
|
551
|
+
try {
|
|
552
|
+
if (repositoriesRaw !== undefined && !Array.isArray(repositoriesRaw))
|
|
553
|
+
throw new project_repositories_1.RepositoryValidationError('repositories must be an array');
|
|
554
|
+
repositories = (repositoriesRaw ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input));
|
|
555
|
+
const primary = (0, project_repositories_1.inspectRepositoryPath)({ path: canonicalPath });
|
|
556
|
+
(0, project_repositories_1.assertDistinctRepositories)([primary, ...repositories.map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))]);
|
|
557
|
+
if (repositories.some((repository) => !isPathSafe(repository.path)))
|
|
558
|
+
throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
|
|
559
|
+
}
|
|
560
|
+
catch (err) {
|
|
561
|
+
if (err instanceof project_repositories_1.RepositoryValidationError) {
|
|
562
|
+
res.status(err.status).json({ error: err.message, code: err.code });
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
throw err;
|
|
566
|
+
}
|
|
548
567
|
// LOW-04: Reject registration of system-critical directories
|
|
549
568
|
if (!isPathSafe(canonicalPath)) {
|
|
550
569
|
res.status(400).json({ error: 'Registering system directories is not allowed' });
|
|
@@ -568,6 +587,7 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
568
587
|
path: canonicalPath,
|
|
569
588
|
provider: providers[0],
|
|
570
589
|
providers,
|
|
590
|
+
repositories,
|
|
571
591
|
});
|
|
572
592
|
broadcast({
|
|
573
593
|
type: 'desktop.project_added',
|
|
@@ -593,7 +613,10 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
593
613
|
catch (err) {
|
|
594
614
|
const message = err.message ?? '';
|
|
595
615
|
// SQLite UNIQUE constraint violation means path or slug already registered
|
|
596
|
-
if (
|
|
616
|
+
if (err instanceof project_repositories_1.RepositoryValidationError) {
|
|
617
|
+
res.status(err.status).json({ error: err.message, code: err.code, details: err.details });
|
|
618
|
+
}
|
|
619
|
+
else if (message.includes('UNIQUE')) {
|
|
597
620
|
res.status(409).json({ error: 'A project with this path is already registered' });
|
|
598
621
|
}
|
|
599
622
|
else {
|
|
@@ -602,6 +625,68 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
602
625
|
}
|
|
603
626
|
}
|
|
604
627
|
});
|
|
628
|
+
// The logical project keeps one backlog; these routes only edit membership.
|
|
629
|
+
const sendRepositoryError = (err, res) => {
|
|
630
|
+
if (err instanceof project_repositories_1.RepositoryValidationError)
|
|
631
|
+
res.status(err.status).json({ error: err.message, code: err.code, details: err.details });
|
|
632
|
+
else {
|
|
633
|
+
console.error('[desktop] repository operation failed:', err);
|
|
634
|
+
res.status(500).json({ error: 'Could not update project repositories' });
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
const broadcastRepositories = () => broadcast({ type: 'desktop.projects', projects: (0, desktop_db_1.listProjects)(registry.desktopDb), timestamp: new Date().toISOString() });
|
|
638
|
+
router.get('/projects/:projectId/repositories', (req, res) => {
|
|
639
|
+
const project = (0, desktop_db_1.listProjects)(registry.desktopDb).find((item) => item.id === req.params.projectId);
|
|
640
|
+
if (!project) {
|
|
641
|
+
res.status(404).json({ error: 'Project not found' });
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
res.json({ repositories: (0, project_repositories_1.getProjectRepositories)(project), primaryRepositoryId: project.primaryRepositoryId });
|
|
645
|
+
});
|
|
646
|
+
router.post('/projects/:projectId/repositories', (req, res) => {
|
|
647
|
+
try {
|
|
648
|
+
const input = (0, project_repositories_1.inspectRepositoryPath)(req.body);
|
|
649
|
+
if (!isPathSafe(input.path))
|
|
650
|
+
throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
|
|
651
|
+
const repository = registry.addRepository(req.params.projectId, input);
|
|
652
|
+
broadcastRepositories();
|
|
653
|
+
res.status(201).json({ repository, project: registry.getProjectRow(req.params.projectId) });
|
|
654
|
+
}
|
|
655
|
+
catch (err) {
|
|
656
|
+
sendRepositoryError(err, res);
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
router.patch('/projects/:projectId/repositories/:repositoryId', (req, res) => {
|
|
660
|
+
try {
|
|
661
|
+
const body = req.body ?? {};
|
|
662
|
+
const input = {};
|
|
663
|
+
for (const key of ['path', 'name', 'integrationBranch'])
|
|
664
|
+
if (Object.prototype.hasOwnProperty.call(body, key))
|
|
665
|
+
Object.assign(input, { [key]: body[key] });
|
|
666
|
+
if (input.path !== undefined) {
|
|
667
|
+
const inspected = (0, project_repositories_1.inspectRepositoryPath)(input);
|
|
668
|
+
if (!isPathSafe(inspected.path))
|
|
669
|
+
throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
|
|
670
|
+
input.path = inspected.path;
|
|
671
|
+
}
|
|
672
|
+
const repository = registry.updateRepository(req.params.projectId, req.params.repositoryId, input);
|
|
673
|
+
broadcastRepositories();
|
|
674
|
+
res.json({ repository, project: registry.getProjectRow(req.params.projectId) });
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
sendRepositoryError(err, res);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
router.delete('/projects/:projectId/repositories/:repositoryId', (req, res) => {
|
|
681
|
+
try {
|
|
682
|
+
registry.removeRepository(req.params.projectId, req.params.repositoryId);
|
|
683
|
+
broadcastRepositories();
|
|
684
|
+
res.json({ ok: true, project: registry.getProjectRow(req.params.projectId) });
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
sendRepositoryError(err, res);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
605
690
|
// POST /api/projects/:id/assemble-retry — re-run the silent workspace
|
|
606
691
|
// assemble for the providers that failed (or every detected provider when
|
|
607
692
|
// nothing is recorded as failed). 202; progress rides project.assemble_progress.
|
|
@@ -679,14 +764,24 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
679
764
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
680
765
|
return;
|
|
681
766
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
767
|
+
try {
|
|
768
|
+
const projectId = typeof req.query.projectId === 'string' ? req.query.projectId : undefined;
|
|
769
|
+
const project = (0, project_repositories_1.resolveRepositoryProject)((0, desktop_db_1.listProjects)(registry.desktopDb), queryPath, projectId);
|
|
770
|
+
if (!project) {
|
|
771
|
+
res.status(404).json({ error: 'No project registered for this path' });
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const context = registry.getContext(project.id);
|
|
775
|
+
if (!context) {
|
|
776
|
+
res.status(503).json({ error: 'Project is registered but unavailable', code: 'project_unavailable', project });
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
registry.touchProject(project.id);
|
|
780
|
+
res.json({ project: context.project });
|
|
781
|
+
}
|
|
782
|
+
catch (err) {
|
|
783
|
+
sendRepositoryError(err, res);
|
|
687
784
|
}
|
|
688
|
-
registry.touchProject(ctx.project.id);
|
|
689
|
-
res.json({ project: ctx.project });
|
|
690
785
|
});
|
|
691
786
|
// GET /api/settings — get app-level settings
|
|
692
787
|
router.get('/settings', (_req, res) => {
|
|
@@ -1184,6 +1279,11 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
1184
1279
|
// and not members of the WsMessage union; cast at this single boundary.
|
|
1185
1280
|
broadcast: (msg) => broadcast(msg),
|
|
1186
1281
|
providers: () => registry.installedProvidersUnion(),
|
|
1282
|
+
reseed: version => (0, framework_reseed_1.reseedStaleWorkspaces)(registry.listContexts().map(context => ({
|
|
1283
|
+
id: context.project.id,
|
|
1284
|
+
slug: context.project.slug,
|
|
1285
|
+
path: context.project.path,
|
|
1286
|
+
})), version),
|
|
1187
1287
|
});
|
|
1188
1288
|
// GET /api/core-update/status — current/bundled/latest versions, no network.
|
|
1189
1289
|
router.get('/core-update/status', (_req, res) => {
|
package/server/dist/dev-ports.js
CHANGED
|
@@ -24,12 +24,17 @@ function resolveServerPort(argv, env = process.env) {
|
|
|
24
24
|
return port;
|
|
25
25
|
}
|
|
26
26
|
function resolveWebDevPorts(env = process.env) {
|
|
27
|
-
const serverPort =
|
|
27
|
+
const serverPort = resolveServerPort([], env);
|
|
28
28
|
const clientPort = parseDevPort(env.SPECRAILS_DEV_CLIENT_PORT, exports.DEFAULT_DEV_CLIENT_PORT);
|
|
29
|
+
if (serverPort === clientPort) {
|
|
30
|
+
throw new Error('SPECRAILS_DEV_CLIENT_PORT must differ from the Specrails API port (SPECRAILS_DEV_SERVER_PORT or SPECRAILS_PORT).');
|
|
31
|
+
}
|
|
29
32
|
return {
|
|
30
33
|
serverPort,
|
|
31
34
|
clientPort,
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
// index.ts binds IPv4 loopback. localhost can resolve to another app on ::1
|
|
36
|
+
// using the same numeric port, including a frontend returning HTML for /api.
|
|
37
|
+
serverOrigin: `http://127.0.0.1:${serverPort}`,
|
|
38
|
+
wsUrl: `ws://127.0.0.1:${serverPort}`,
|
|
34
39
|
};
|
|
35
40
|
}
|