specrails-desktop 2.60.0 → 2.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/dist/assets/ActivityFeedPage-C_pqc281.js +1 -0
- package/client/dist/assets/{AgentBrowserCapture-D4a3wYM1.js → AgentBrowserCapture-vz6rbDtu.js} +1 -1
- package/client/dist/assets/AgentModeAnalyticsPane-BHCcUahS.js +2 -0
- package/client/dist/assets/AgentModeCodePane-DhmxgG3W.js +2 -0
- package/client/dist/assets/{AgentModeJobsPane-25tQlLpF.js → AgentModeJobsPane-99fBBLri.js} +1 -1
- package/client/dist/assets/{AgentsPage-CUDp_sfw.js → AgentsPage-CTwm_6T8.js} +3 -3
- package/client/dist/assets/{AnalyticsPage-DXWMGn8b.js → AnalyticsPage-YD8MLoPa.js} +1 -1
- package/client/dist/assets/CodePage-CLJn2F2e.js +3 -0
- package/client/dist/assets/{DesktopAnalyticsPage-BkFBq0E6.js → DesktopAnalyticsPage-MWGQVbwp.js} +1 -1
- package/client/dist/assets/{DocsDialog-BL7uSbIJ.js → DocsDialog-XUCYKoq0.js} +1 -1
- package/client/dist/assets/{DocsPage-CsDLbmZX.js → DocsPage-dOLC8bXA.js} +1 -1
- package/client/dist/assets/{ExportDropdown-B4Olg1Fk.js → ExportDropdown-BvKQgwM9.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-DxOvwmBF.js +19 -0
- package/client/dist/assets/{JobDetailModal-DS_Owxs6.js → JobDetailModal-CkiubrkZ.js} +1 -1
- package/client/dist/assets/JobDetailPage-CNe4Wn7l.js +1 -0
- package/client/dist/assets/JobsPage-DSts_L_d.js +1 -0
- package/client/dist/assets/LoopBuilderPage-C9D9eer5.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-CH1r-KhU.js → LoopPreviewModal-Dv3dxHr_.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-CiZdI6aw.js → LoopWindowSurface-DN2lbSnL.js} +1 -1
- package/client/dist/assets/{LoopsPage-BwFujdGi.js → LoopsPage-DHgIreEW.js} +2 -2
- package/client/dist/assets/MinimizedChatsContext-Ds2-R6qJ.js +1 -0
- package/client/dist/assets/PluginsPage-D1EJ9oja.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-DKpa1wKk.js +1 -0
- package/client/dist/assets/{RepositoryDeliveries-NNorcGzT.js → RepositoryDeliveries-CW4eKG3x.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-D-jb4bI_.js → RepositoryScopeSelector-DrtPqvc_.js} +1 -1
- package/client/dist/assets/ReviewPacketPage-BtjPI0Cc.js +4 -0
- package/client/dist/assets/{TemplatePreviewModal-5CZdAZ0G.js → TemplatePreviewModal-DcFlhUxy.js} +1 -1
- package/client/dist/assets/TicketDetailModalContext-Dea22CI-.js +16 -0
- package/client/dist/assets/{Trans-wmTUDVyC.js → Trans-CAY2Rd2f.js} +1 -1
- package/client/dist/assets/agent-5bSixVzz.js +1 -0
- package/client/dist/assets/agent-BR4EHfhq.js +1 -0
- package/client/dist/assets/agent-C0IKkJP8.js +1 -0
- package/client/dist/assets/agent-C7li9ayC.js +1 -0
- package/client/dist/assets/agent-D0dRmEen.js +1 -0
- package/client/dist/assets/{agent-BjttZLIl.js → agent-DZVGHm7H.js} +1 -1
- package/client/dist/assets/agent-Dip4h2YE.js +1 -0
- package/client/dist/assets/agent-Pp-P-n1p.js +1 -0
- package/client/dist/assets/agentRuntime-B-myNBUP.js +1 -0
- package/client/dist/assets/agentRuntime-Bjt7Ij6_.js +1 -0
- package/client/dist/assets/agentRuntime-C_yLPKDy.js +1 -0
- package/client/dist/assets/agentRuntime-D2kYQqNO.js +1 -0
- package/client/dist/assets/agentRuntime-DvhEe1um.js +1 -0
- package/client/dist/assets/agentRuntime-DxC6-9rN.js +1 -0
- package/client/dist/assets/agentRuntime-TKQyg137.js +1 -0
- package/client/dist/assets/agentRuntime-WSv8G0pW.js +1 -0
- package/client/dist/assets/ban-E4EaZe3a.js +1 -0
- package/client/dist/assets/chevron-up-CwGUHd6D.js +1 -0
- package/client/dist/assets/dist-Bq10nS2m.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-Dax4zqPF.js → formatDistanceToNow-DlQY1Lm_.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-dpIo42JZ.js → getTimezoneOffsetInMilliseconds-COUeq65G.js} +1 -1
- package/client/dist/assets/index-BacrO_ub.css +2 -0
- package/client/dist/assets/index-CONHvEJw.js +83 -0
- package/client/dist/assets/{jira-api-CLrIvXFm.js → jira-api-C94VNHNY.js} +1 -1
- package/client/dist/assets/layers-eDMjG41I.js +1 -0
- package/client/dist/assets/{loops-Cm55CXJ4.js → loops-B2PhQLKR.js} +1 -1
- package/client/dist/assets/{loops-3IutojWt.js → loops-BqbF46st.js} +1 -1
- package/client/dist/assets/{loops-BAosWtXE.js → loops-CINnHbfI.js} +1 -1
- package/client/dist/assets/{loops-kIJJkaxf.js → loops-D-3XhH04.js} +1 -1
- package/client/dist/assets/{loops-DHQtnfze.js → loops-DKkEEjZ_.js} +1 -1
- package/client/dist/assets/{loops-D7tL_Saw.js → loops-DzWR5-v6.js} +1 -1
- package/client/dist/assets/{loops--KWr0X-w.js → loops-WyWDkhok.js} +1 -1
- package/client/dist/assets/{loops-BISDssL5.js → loops-scD5fIBf.js} +1 -1
- package/client/dist/assets/{narration-DvLQBW9U.js → narration-BEKMVxGm.js} +1 -1
- package/client/dist/assets/{narration-CHI06czm.js → narration-CjLwGKap.js} +1 -1
- package/client/dist/assets/{narration-DjKB3pQZ.js → narration-DANWuGsW.js} +1 -1
- package/client/dist/assets/{narration-eSx3gR0W.js → narration-DGeEDWiT.js} +1 -1
- package/client/dist/assets/{narration-DtCYvRLM.js → narration-DJgpGwIP.js} +1 -1
- package/client/dist/assets/{narration-n3hOA7LY.js → narration-DbHnz0qV.js} +1 -1
- package/client/dist/assets/{narration-thJOeT2-.js → narration-bZSsTZG8.js} +1 -1
- package/client/dist/assets/{narration--hUhjM5v.js → narration-i6aKJKEc.js} +1 -1
- package/client/dist/assets/{project-repositories-BkyT8AjU.js → project-repositories-Cc3oNv8X.js} +1 -1
- package/client/dist/assets/save-B6wGWwBR.js +1 -0
- package/client/dist/assets/{settings-Dn-pXukr.js → settings-BKJZ2KEM.js} +1 -1
- package/client/dist/assets/{settings-BBj_kZaD.js → settings-BV3jocIx.js} +1 -1
- package/client/dist/assets/{settings-COlH_P33.js → settings-BjM2Dw8_.js} +1 -1
- package/client/dist/assets/settings-CKu0LTib.js +1 -0
- package/client/dist/assets/{settings-D77mC_Lb.js → settings-DA77M48m.js} +1 -1
- package/client/dist/assets/{settings-ByVE7jdZ.js → settings-LfFnl1UH.js} +1 -1
- package/client/dist/assets/{settings-DZKy7MqN.js → settings-P1IQvhnL.js} +1 -1
- package/client/dist/assets/{settings-B5Hy_Kol.js → settings-eIMJjyK_.js} +1 -1
- package/client/dist/assets/{spending-Oov7p52A.js → spending-DemeFfrH.js} +1 -1
- package/client/dist/assets/{useDesktop-DpiS8Uov.js → useDesktop-9Lwoel_8.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-DBm1mul0.js → useRuntimeRuns-DL0xYoZ2.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-BZxKaVNT.js → useSharedWebSocket-BEHPeU1Y.js} +2 -2
- package/client/dist/assets/zap-CHo3aCD1.js +1 -0
- package/client/dist/index.html +26 -22
- package/docs/guide/de/integrations/6-agent-chat.md +11 -0
- package/docs/guide/en/integrations/6-agent-chat.md +11 -0
- package/docs/guide/es/integrations/6-agent-chat.md +11 -0
- package/docs/guide/fr/integrations/6-agent-chat.md +11 -0
- package/docs/guide/it/integrations/6-agent-chat.md +11 -0
- package/docs/guide/ja/integrations/6-agent-chat.md +11 -0
- package/docs/guide/pt/integrations/6-agent-chat.md +11 -0
- package/docs/guide/zh/integrations/6-agent-chat.md +11 -0
- package/docs/internals/README.md +1 -0
- package/docs/internals/agent-sessions.md +157 -0
- package/docs/internals/api-reference.md +33 -4
- package/docs/internals/configuration.md +36 -0
- package/docs/internals/core-runtime-updates.md +10 -3
- package/docs/internals/operations-runbook.md +11 -0
- package/docs/internals/programmatic-agent-runtime.md +2 -0
- package/docs/internals/source-map.md +40 -1
- package/mcp-bridge/dist/specrails-mcp.js +82 -15
- package/package.json +1 -1
- package/server/dist/agent-mcp-config.js +41 -0
- package/server/dist/core-compat.js +2 -0
- package/server/dist/desktop-db.js +101 -4
- package/server/dist/desktop-router.js +31 -2
- package/server/dist/index.js +58 -0
- package/server/dist/mcp/tools/app-settings.js +11 -0
- package/server/dist/mcp/tools/mission.js +45 -2
- package/server/dist/mcp/tools/specs.js +2 -2
- package/server/dist/modules/accounting/runtime/result-event.js +9 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +12 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +25 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-effective-config.js +2 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +6 -2
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +18 -6
- package/server/dist/modules/agent-sessions/adapters/host-process.js +73 -0
- package/server/dist/modules/agent-sessions/adapters/http.js +31 -0
- package/server/dist/modules/agent-sessions/adapters/rpc-client.js +147 -0
- package/server/dist/modules/agent-sessions/application/session-event-pump.js +99 -0
- package/server/dist/modules/agent-sessions/domain/availability.js +25 -0
- package/server/dist/modules/agent-sessions/domain/errors.js +23 -0
- package/server/dist/modules/agent-sessions/domain/host-state.js +80 -0
- package/server/dist/modules/agent-sessions/domain/projection.js +147 -0
- package/server/dist/modules/agent-sessions/domain/protocol.js +59 -0
- package/server/dist/modules/agent-sessions/domain/subagent-policy.js +34 -0
- package/server/dist/modules/agent-sessions/index.js +31 -0
- package/server/dist/modules/agent-sessions/ports.js +2 -0
- package/server/dist/modules/agent-sessions/runtime/core-sessions-availability.js +43 -0
- package/server/dist/modules/agent-sessions/runtime/session-host-registry.js +226 -0
- package/server/dist/modules/agents/runtime/agent-store.js +3 -1
- package/server/dist/modules/loops/runtime/loop-core-factory.js +2 -2
- package/server/dist/modules/loops/runtime/loop-definition.js +1 -0
- package/server/dist/modules/loops/runtime/loop-factory.js +1 -1
- package/server/dist/modules/loops/runtime/loop-graph.js +8 -1
- package/server/dist/modules/loops/runtime/loop-implement-recipe.js +41 -11
- package/server/dist/modules/loops/runtime/loop-run-manager.js +4 -0
- package/server/dist/modules/loops/runtime/loop-templates.js +3 -3
- package/server/dist/modules/missions/runtime/agent-chat-manager.js +137 -40
- package/server/dist/modules/missions/runtime/agent-chat-router.js +68 -0
- package/server/dist/modules/missions/runtime/agent-operator-prompt.js +8 -9
- package/server/dist/modules/missions/runtime/agent-session-store.js +133 -0
- package/server/dist/modules/missions/runtime/core-session-runner.js +204 -0
- package/server/dist/modules/missions/runtime/mission-core-sessions.js +286 -0
- package/server/dist/modules/missions/runtime/mission-session-projector.js +168 -0
- package/server/dist/modules/project-settings/adapters/http.js +14 -2
- package/server/dist/modules/project-settings/adapters/sqlite.js +30 -2
- package/server/dist/modules/project-settings/domain.js +24 -0
- package/server/dist/modules/project-settings/index.js +2 -1
- package/server/dist/modules/specs/runtime/spec-models.js +1 -1
- package/server/dist/plugins/codex-spawn.js +15 -7
- package/server/dist/project-registry.js +32 -0
- package/server/dist/project-router-settings.js +7 -1
- package/server/dist/providers/claude-adapter.js +6 -1
- package/server/dist/schemas/agent-runtime.schema.json +9 -7
- package/client/dist/assets/ActivityFeedPage-3J7fR8yB.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-Dps30tk9.js +0 -2
- package/client/dist/assets/AgentModeCodePane-CvLimey8.js +0 -2
- package/client/dist/assets/CodePage-D981zRHy.js +0 -3
- package/client/dist/assets/InteractiveJobComposer-Z6fl4791.js +0 -19
- package/client/dist/assets/JobDetailPage-1TUQ9vlE.js +0 -1
- package/client/dist/assets/JobsPage-BSrl8P7u.js +0 -1
- package/client/dist/assets/LoopBuilderPage-Dh5uk2Qy.js +0 -1
- package/client/dist/assets/MinimizedChatsContext-BD2bsRBe.js +0 -1
- package/client/dist/assets/PluginsPage-B8f49Mvn.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-D7q-O6h3.js +0 -1
- package/client/dist/assets/ReviewPacketPage-DpZSzF-Q.js +0 -4
- package/client/dist/assets/TicketDetailModalContext-BHmtj1As.js +0 -16
- package/client/dist/assets/agent--8QAfoXL.js +0 -1
- package/client/dist/assets/agent-3HxXkIWX.js +0 -1
- package/client/dist/assets/agent-CpFCz32T.js +0 -1
- package/client/dist/assets/agent-DVnw6ybO.js +0 -1
- package/client/dist/assets/agent-Z5s_GTP0.js +0 -1
- package/client/dist/assets/agent-hv57-zyG.js +0 -1
- package/client/dist/assets/agent-xGrZ_aN1.js +0 -1
- package/client/dist/assets/agentRuntime-7Ydcvr6P.js +0 -1
- package/client/dist/assets/agentRuntime-BHVw9orA.js +0 -1
- package/client/dist/assets/agentRuntime-DCPDaQcV.js +0 -1
- package/client/dist/assets/agentRuntime-DKTw4Ajg.js +0 -1
- package/client/dist/assets/agentRuntime-DVhij_i_.js +0 -1
- package/client/dist/assets/agentRuntime-Doss-T8o.js +0 -1
- package/client/dist/assets/agentRuntime-eZSMUlkM.js +0 -1
- package/client/dist/assets/agentRuntime-qSSU7fQk.js +0 -1
- package/client/dist/assets/index-Bfunvi6w.css +0 -2
- package/client/dist/assets/index-g8S62tpS.js +0 -83
- package/client/dist/assets/save-OsUqVn99.js +0 -1
- package/client/dist/assets/settings-Dbyd6gmj.js +0 -1
- package/client/dist/assets/zap-CUOlvtT1.js +0 -1
|
@@ -140,6 +140,17 @@ If a single provider or feature is breaking the app, you can turn it off with an
|
|
|
140
140
|
|
|
141
141
|
These are the most common ops escape hatches; see [Configuration → Environment variables](configuration.md#environment-variables) for the complete list of gates and flags.
|
|
142
142
|
|
|
143
|
+
### Missions on Core agent sessions
|
|
144
|
+
|
|
145
|
+
With `SPECRAILS_CORE_SESSIONS` at `auto` (the default) or `on`, missions run in a Core host
|
|
146
|
+
process per project scope (`specrails-core runtime host --stdio --scope …`).
|
|
147
|
+
The server log prefix is `[agent-sessions]`.
|
|
148
|
+
|
|
149
|
+
- **Status.** The server logs each scope's transitions: `starting`, `ready`, `restarting`, `degraded`. `GET /api/agent/session-hosts` lists them. A degraded scope sends new mission turns through the legacy transport, and the mission shows why, with a **Retry** button (`POST /api/agent/session-hosts/:scope/retry`). Crashes degrade a scope after repeated failures. Some causes degrade it at once, without restart attempts: another Desktop instance holding the journal (`journal_locked`), an incompatible Core (`protocol_mismatch`), or a Core without the host (`driver_unavailable`). Close the other instance or fix Core, then retry.
|
|
150
|
+
- **Session data.** Core owns the journals in `~/.specrails/sessions/<project>/sessions.sqlite`. Desktop's tables (`agent_session_cursors`, `agent_subagents`, `agent_subagent_events`) are a projection. If they look wrong, rebuild them for one mission with `POST /api/agent/conversations/:id/session/rebuild`. This never duplicates messages or spend.
|
|
151
|
+
- **Turn it off.** `SPECRAILS_CORE_SESSIONS=off` returns every mission to the legacy transports. Missions keep their history; sub-agent cards remain for past turns.
|
|
152
|
+
- **Live check (paid, opt-in).** `SPECRAILS_LIVE_PROVIDER_SMOKE=1 npx vitest run server/modules/missions/runtime/mission-core-sessions.live.test.ts` runs real Claude missions against the local `../specrails-core` build. Add `SPECRAILS_LIVE_PROVIDERS=claude,codex` to include Codex, and `SPECRAILS_LIVE_<PROVIDER>_MODEL` to pick a model the account can use.
|
|
153
|
+
|
|
143
154
|
### Per-project reset (keeps the registration)
|
|
144
155
|
|
|
145
156
|
To clear one project's job history, invocations, and telemetry pointers while keeping it registered, delete just that project's `jobs.sqlite` with the app stopped:
|
|
@@ -119,6 +119,8 @@ All routes are under `/api/projects/:projectId`:
|
|
|
119
119
|
| `POST /agent-runtime/runs/:runId/resume` | Accept `{}`, `{ "approve": ["archive"] }`, `{ "recover": ["developer"] }`, explicit `invalidate` phase IDs, or `{ "answer": "…" }` for a pending question (required while one is open) |
|
|
120
120
|
| `POST /agent-runtime/runs/:runId/cancel` | Cancel a continuation owned by this controller |
|
|
121
121
|
|
|
122
|
+
Host blockers (Core `hostBlockers: 1` / `setupCommands: 1`): the Implement recipe opts its `verify` nodes into host-owned environment repair and the `blocked` outcome. Core emits `[environment] <command> (<repository>)` and `Environment: installed …` lines on the `verification-output` channel, so they reach the readable job log through the same projection as checks. A precondition Core cannot satisfy (no network for a browser download, registry credentials, a failing `setup` command, a missing toolchain) ends the run at `host-blocked` with `completion.blocker = { kind, reason, command, args, cwd, requiredAction, evidenceId? }`; a fixer-declared `blocker` ends it at `fixer-blocked`. A developer-declared `blocker` ends the run at `developer-blocked` only for kinds the person must provide (`credential`, `network`, `environment-variable`); any other kind goes to host verification first: a host precondition ends at `host-blocked`, a reproduced failure goes to the fixer as a correction, and passing checks reach the reviewer, whose rejection of unfinished tasks is corrected within the same budget. The developer loop is bounded like corrections: when `tasks` finds approved tasks still unchecked, the developer runs again only while its last turn changed the candidate (`implementation-progress`, otherwise `implementation-stalled`) and within `implementationLimit` = 3 attempts (`implementation-exhausted`); both ends report the developer's `incomplete` list and summary. The bridge maps that completion (`ok: false`) to `runtimeStatus: 'blocked'`, the run row shows the blocked badge and the loop log renders "Blocked by the host environment" with the localized kind, the failing command and cwd, the required action and a copy button. Older Core packages receive the pre-blocker definition and no `setup` list.
|
|
123
|
+
|
|
122
124
|
Resume responds `202` after admission and continues asynchronously. Legacy phase IDs are `architect`, `developer`, `fixer`, `verify`, `reviewer` and `archive`. For engine v2, use `POST /loop-runs/:runId/resume` instead. `recover` contains exact attempt IDs from `GET /loop-runs/:runId/recovery`; approval and answer controls use pending interrupt IDs. Node paths are display context, not recovery identities. The saved-execution panel and job header expose explicit per-attempt selection. Core's lease and Desktop's execution/mount claim jointly prevent competing writers. Resident human pauses keep their original settlement callback; restart recovery reconnects to the frozen delivery allocation and terminal outbox.
|
|
123
125
|
|
|
124
126
|
Desktop launches `node <Core>/dist/agent-runtime/cli.js` with structured argv (`--approve`, `--recover`, `--invalidate`, `--answer <text>` on resume) and consumes JSON lines for phase events (`workflow-event`), agent output (`agent-event`), verification output, trace spans (`span`: `{ traceId, spanId, name, stepId, attempt, visit, startedAt, endedAt, status, usage?, error? }`) and the terminal `runtime-result`. Spans are stored verbatim as job events for diagnostics and are not narrated in the log. `runtime status --compact` returns `state.traceId`, `pendingApproval`, `pendingQuestion` and per-step `{ status, visits }`. Desktop probes `runtime api` once per executable package content digest; `runtime validate --stdin` runs on every save. The job log shows phase transitions (`[runtime] step_started: developer`), live tool activity per role (`[developer] Read src/app.ts`, `[developer] Bash npm test`) and Core's own phase notes (architecture written, verification passed, review approved or corrections requested); the final JSON of architect and reviewer is not echoed. The narrated view (Relato) derives its milestones from the same events: each runtime phase, the tools used, correction loops and a stopped workflow with Core's structural reason. Accounting uses the invocation's new attempts, so resuming a completed phase does not bill its cumulative history twice. Status queries use `--compact`, are read-only and do not invoke providers; accumulated logs stay in Core's checkpoint instead of overflowing the process status response.
|
|
@@ -5,7 +5,7 @@ architectural dependency declaration. Search a heading or filename and read only
|
|
|
5
5
|
the relevant source and tests. Feature prefixes group the legacy server files;
|
|
6
6
|
new bounded modules live under `server/modules/`.
|
|
7
7
|
|
|
8
|
-
Includes
|
|
8
|
+
Includes 1042 source/build files. Nearby tests are linked where names
|
|
9
9
|
match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
10
10
|
|
|
11
11
|
## cli
|
|
@@ -457,7 +457,9 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
457
457
|
- [AgentRefChip.tsx](../../client/src/features/missions/components/AgentRefChip.tsx)
|
|
458
458
|
- [AgentRunFailureMarker.tsx](../../client/src/features/missions/components/AgentRunFailureMarker.tsx)
|
|
459
459
|
- [AgentRuntimeSelector.tsx](../../client/src/features/missions/components/AgentRuntimeSelector.tsx)
|
|
460
|
+
- [AgentSessionIndicators.tsx](../../client/src/features/missions/components/AgentSessionIndicators.tsx)
|
|
460
461
|
- [AgentSpecDraftCard.tsx](../../client/src/features/missions/components/AgentSpecDraftCard.tsx)
|
|
462
|
+
- [AgentSubagentsCard.tsx](../../client/src/features/missions/components/AgentSubagentsCard.tsx)
|
|
461
463
|
- [AgentThinkingHalo.tsx](../../client/src/features/missions/components/AgentThinkingHalo.tsx) · [test](../../client/src/features/missions/components/__tests__/AgentThinkingHalo.test.tsx)
|
|
462
464
|
- [AgentTierChip.tsx](../../client/src/features/missions/components/AgentTierChip.tsx)
|
|
463
465
|
- [AgentToolbarSelector.tsx](../../client/src/features/missions/components/AgentToolbarSelector.tsx)
|
|
@@ -486,6 +488,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
486
488
|
- [AgentChatContext.tsx](../../client/src/features/missions/context/AgentChatContext.tsx) · [test](../../client/src/features/missions/context/AgentChatContext.test.tsx)
|
|
487
489
|
- [AgentWorkspaceContext.tsx](../../client/src/features/missions/context/AgentWorkspaceContext.tsx) · [test](../../client/src/features/missions/context/__tests__/AgentWorkspaceContext.test.tsx)
|
|
488
490
|
- [MinimizedChatsContext.tsx](../../client/src/features/missions/context/MinimizedChatsContext.tsx) · [test](../../client/src/features/missions/context/__tests__/MinimizedChatsContext.test.tsx)
|
|
491
|
+
- [MissionSessionsContext.tsx](../../client/src/features/missions/context/MissionSessionsContext.tsx) · [test](../../client/src/features/missions/context/__tests__/MissionSessionsContext.test.tsx)
|
|
489
492
|
- [MissionSplitViewsContext.tsx](../../client/src/features/missions/context/MissionSplitViewsContext.tsx)
|
|
490
493
|
- [MissionWindowsContext.tsx](../../client/src/features/missions/context/MissionWindowsContext.tsx) · [test](../../client/src/features/missions/context/__tests__/MissionWindowsContext.test.tsx)
|
|
491
494
|
|
|
@@ -500,10 +503,12 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
500
503
|
- [agent-context-palette.ts](../../client/src/features/missions/lib/agent-context-palette.ts) · [test](../../client/src/features/missions/lib/__tests__/agent-context-palette.test.ts)
|
|
501
504
|
- [agent-refs.ts](../../client/src/features/missions/lib/agent-refs.ts) · [test](../../client/src/features/missions/lib/__tests__/agent-refs.test.ts)
|
|
502
505
|
- [mission-search.ts](../../client/src/features/missions/lib/mission-search.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-search.test.ts)
|
|
506
|
+
- [mission-sessions.ts](../../client/src/features/missions/lib/mission-sessions.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-sessions.test.ts)
|
|
503
507
|
- [mission-split-layout.ts](../../client/src/features/missions/lib/mission-split-layout.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-split-layout.test.ts)
|
|
504
508
|
- [mission-view-state.ts](../../client/src/features/missions/lib/mission-view-state.ts)
|
|
505
509
|
- [mission-window-controller.ts](../../client/src/features/missions/lib/mission-window-controller.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-window-controller.test.ts)
|
|
506
510
|
- [mission-windows.ts](../../client/src/features/missions/lib/mission-windows.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-windows.test.ts)
|
|
511
|
+
- [subagent-result.ts](../../client/src/features/missions/lib/subagent-result.ts) · [test](../../client/src/features/missions/lib/__tests__/subagent-result.test.ts)
|
|
507
512
|
|
|
508
513
|
## client/src/features/plugins/lib
|
|
509
514
|
|
|
@@ -600,6 +605,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
600
605
|
- [RuntimeRetention.tsx](../../client/src/features/settings/components/RuntimeRetention.tsx) · [test](../../client/src/features/settings/components/__tests__/RuntimeRetention.test.tsx)
|
|
601
606
|
- [RuntimeRolePrompts.tsx](../../client/src/features/settings/components/RuntimeRolePrompts.tsx) · [test](../../client/src/features/settings/components/__tests__/RuntimeRolePrompts.test.tsx)
|
|
602
607
|
- [RuntimeSteering.tsx](../../client/src/features/settings/components/RuntimeSteering.tsx) · [test](../../client/src/features/settings/components/__tests__/RuntimeSteering.test.tsx)
|
|
608
|
+
- [SubagentsSettingsSection.tsx](../../client/src/features/settings/components/SubagentsSettingsSection.tsx) · [test](../../client/src/features/settings/components/__tests__/SubagentsSettingsSection.test.tsx)
|
|
603
609
|
- [TerminalSettingsSection.tsx](../../client/src/features/settings/components/TerminalSettingsSection.tsx)
|
|
604
610
|
|
|
605
611
|
## client/src/features/settings/components/pickers
|
|
@@ -1098,6 +1104,35 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
1098
1104
|
- [agent-runtime-verification-log.ts](../../server/modules/agent-runtime/runtime/agent-runtime-verification-log.ts)
|
|
1099
1105
|
- [agent-runtime-verification-suggestions.ts](../../server/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.ts) · [test](../../server/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.test.ts)
|
|
1100
1106
|
|
|
1107
|
+
## server/modules/agent-sessions
|
|
1108
|
+
|
|
1109
|
+
- [index.ts](../../server/modules/agent-sessions/index.ts)
|
|
1110
|
+
- [ports.ts](../../server/modules/agent-sessions/ports.ts)
|
|
1111
|
+
|
|
1112
|
+
## server/modules/agent-sessions/adapters
|
|
1113
|
+
|
|
1114
|
+
- [host-process.ts](../../server/modules/agent-sessions/adapters/host-process.ts)
|
|
1115
|
+
- [http.ts](../../server/modules/agent-sessions/adapters/http.ts)
|
|
1116
|
+
- [rpc-client.ts](../../server/modules/agent-sessions/adapters/rpc-client.ts)
|
|
1117
|
+
|
|
1118
|
+
## server/modules/agent-sessions/application
|
|
1119
|
+
|
|
1120
|
+
- [session-event-pump.ts](../../server/modules/agent-sessions/application/session-event-pump.ts)
|
|
1121
|
+
|
|
1122
|
+
## server/modules/agent-sessions/domain
|
|
1123
|
+
|
|
1124
|
+
- [availability.ts](../../server/modules/agent-sessions/domain/availability.ts)
|
|
1125
|
+
- [errors.ts](../../server/modules/agent-sessions/domain/errors.ts)
|
|
1126
|
+
- [host-state.ts](../../server/modules/agent-sessions/domain/host-state.ts)
|
|
1127
|
+
- [projection.ts](../../server/modules/agent-sessions/domain/projection.ts)
|
|
1128
|
+
- [protocol.ts](../../server/modules/agent-sessions/domain/protocol.ts)
|
|
1129
|
+
- [subagent-policy.ts](../../server/modules/agent-sessions/domain/subagent-policy.ts)
|
|
1130
|
+
|
|
1131
|
+
## server/modules/agent-sessions/runtime
|
|
1132
|
+
|
|
1133
|
+
- [core-sessions-availability.ts](../../server/modules/agent-sessions/runtime/core-sessions-availability.ts)
|
|
1134
|
+
- [session-host-registry.ts](../../server/modules/agent-sessions/runtime/session-host-registry.ts)
|
|
1135
|
+
|
|
1101
1136
|
## server/modules/agents/runtime
|
|
1102
1137
|
|
|
1103
1138
|
- [agent-catalog.ts](../../server/modules/agents/runtime/agent-catalog.ts)
|
|
@@ -1309,10 +1344,14 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
1309
1344
|
- [agent-fence-promotion.ts](../../server/modules/missions/runtime/agent-fence-promotion.ts)
|
|
1310
1345
|
- [agent-input-store.ts](../../server/modules/missions/runtime/agent-input-store.ts) · [test](../../server/modules/missions/runtime/agent-input-store.test.ts)
|
|
1311
1346
|
- [agent-operator-prompt.ts](../../server/modules/missions/runtime/agent-operator-prompt.ts) · [test](../../server/modules/missions/runtime/agent-operator-prompt.test.ts)
|
|
1347
|
+
- [agent-session-store.ts](../../server/modules/missions/runtime/agent-session-store.ts)
|
|
1312
1348
|
- [agent-spec-framing.ts](../../server/modules/missions/runtime/agent-spec-framing.ts) · [test](../../server/modules/missions/runtime/agent-spec-framing.test.ts)
|
|
1313
1349
|
- [agent-steering.ts](../../server/modules/missions/runtime/agent-steering.ts) · [test](../../server/modules/missions/runtime/agent-steering.test.ts)
|
|
1314
1350
|
- [agent-tier.ts](../../server/modules/missions/runtime/agent-tier.ts)
|
|
1351
|
+
- [core-session-runner.ts](../../server/modules/missions/runtime/core-session-runner.ts) · [test](../../server/modules/missions/runtime/core-session-runner.test.ts)
|
|
1352
|
+
- [mission-core-sessions.ts](../../server/modules/missions/runtime/mission-core-sessions.ts) · [test](../../server/modules/missions/runtime/mission-core-sessions.test.ts)
|
|
1315
1353
|
- [mission-run-notify.ts](../../server/modules/missions/runtime/mission-run-notify.ts) · [test](../../server/modules/missions/runtime/mission-run-notify.test.ts)
|
|
1354
|
+
- [mission-session-projector.ts](../../server/modules/missions/runtime/mission-session-projector.ts) · [test](../../server/modules/missions/runtime/mission-session-projector.test.ts)
|
|
1316
1355
|
|
|
1317
1356
|
## server/modules/project-settings
|
|
1318
1357
|
|
|
@@ -7051,7 +7051,7 @@ function connectBridge(clientFacing, appFacing) {
|
|
|
7051
7051
|
});
|
|
7052
7052
|
}
|
|
7053
7053
|
pending.clear();
|
|
7054
|
-
closeBoth();
|
|
7054
|
+
if (!appFacing.recoverable) closeBoth();
|
|
7055
7055
|
};
|
|
7056
7056
|
}
|
|
7057
7057
|
|
|
@@ -7062,7 +7062,7 @@ function authenticatedFetch(agentHeaders, fetchImpl = fetch) {
|
|
|
7062
7062
|
const token = readMcpToken();
|
|
7063
7063
|
const headers = new Headers(init?.headers);
|
|
7064
7064
|
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
7065
|
-
for (const [key, value] of Object.entries(agentHeaders)) headers.set(key, value);
|
|
7065
|
+
for (const [key, value] of Object.entries(typeof agentHeaders === "function" ? agentHeaders() : agentHeaders)) headers.set(key, value);
|
|
7066
7066
|
const response = await fetchImpl(input, { ...init, headers });
|
|
7067
7067
|
const refreshed = response.status === 401 ? readMcpToken() : null;
|
|
7068
7068
|
if (refreshed && refreshed !== token) {
|
|
@@ -7073,12 +7073,25 @@ function authenticatedFetch(agentHeaders, fetchImpl = fetch) {
|
|
|
7073
7073
|
return response;
|
|
7074
7074
|
};
|
|
7075
7075
|
}
|
|
7076
|
+
function rejectsStaleSession(err) {
|
|
7077
|
+
const failure = err;
|
|
7078
|
+
if (failure?.code === 404) return true;
|
|
7079
|
+
return failure?.code === 403 && typeof failure.message === "string" && /reinitiali[sz]e/i.test(failure.message);
|
|
7080
|
+
}
|
|
7081
|
+
function isLostStream(error2) {
|
|
7082
|
+
return /SSE stream disconnected|Maximum reconnection attempts|Failed to reconnect/i.test(error2.message);
|
|
7083
|
+
}
|
|
7076
7084
|
var RecoveringHttpTransport = class {
|
|
7077
|
-
constructor(create) {
|
|
7085
|
+
constructor(create, log = () => {
|
|
7086
|
+
}) {
|
|
7078
7087
|
this.create = create;
|
|
7088
|
+
this.log = log;
|
|
7079
7089
|
this.current = this.attach(create());
|
|
7080
7090
|
}
|
|
7081
7091
|
create;
|
|
7092
|
+
log;
|
|
7093
|
+
/** connectBridge keeps the client side open when the app side can recover. */
|
|
7094
|
+
recoverable = true;
|
|
7082
7095
|
onmessage;
|
|
7083
7096
|
onerror;
|
|
7084
7097
|
onclose;
|
|
@@ -7086,12 +7099,19 @@ var RecoveringHttpTransport = class {
|
|
|
7086
7099
|
initializeMessage;
|
|
7087
7100
|
recovery;
|
|
7088
7101
|
closed = false;
|
|
7102
|
+
/** The last recovery failed; the next request must reinitialize first. */
|
|
7103
|
+
stale = false;
|
|
7089
7104
|
attach(transport) {
|
|
7090
7105
|
transport.onmessage = (message, extra) => {
|
|
7091
7106
|
if (transport === this.current && !this.closed) this.onmessage?.(message, extra);
|
|
7092
7107
|
};
|
|
7093
7108
|
transport.onerror = (error2) => {
|
|
7094
|
-
if (transport
|
|
7109
|
+
if (transport !== this.current || this.closed) return;
|
|
7110
|
+
if (isLostStream(error2)) {
|
|
7111
|
+
this.stale = true;
|
|
7112
|
+
this.log("stream-lost", error2.message);
|
|
7113
|
+
}
|
|
7114
|
+
this.onerror?.(error2);
|
|
7095
7115
|
};
|
|
7096
7116
|
transport.onclose = () => {
|
|
7097
7117
|
if (transport === this.current && !this.closed) this.onclose?.();
|
|
@@ -7104,21 +7124,31 @@ var RecoveringHttpTransport = class {
|
|
|
7104
7124
|
async send(message) {
|
|
7105
7125
|
if (this.closed) throw new Error("MCP bridge is closed");
|
|
7106
7126
|
if ("method" in message && message.method === "initialize") this.initializeMessage = message;
|
|
7127
|
+
if (this.stale && this.initializeMessage && message !== this.initializeMessage) this.recovery ??= this.recover("retry after a failed recovery");
|
|
7107
7128
|
if (this.recovery) await this.recovery;
|
|
7108
7129
|
const attempted = this.current;
|
|
7109
7130
|
try {
|
|
7110
7131
|
await attempted.send(message);
|
|
7111
7132
|
} catch (err) {
|
|
7112
|
-
if (err
|
|
7113
|
-
if (attempted === this.current) {
|
|
7114
|
-
this.recovery ??= this.reinitialize().finally(() => {
|
|
7115
|
-
this.recovery = void 0;
|
|
7116
|
-
});
|
|
7117
|
-
}
|
|
7133
|
+
if (!rejectsStaleSession(err) || !this.initializeMessage || message === this.initializeMessage) throw err;
|
|
7134
|
+
if (attempted === this.current) this.recovery ??= this.recover(`session rejected (${err.code})`);
|
|
7118
7135
|
if (this.recovery) await this.recovery;
|
|
7119
7136
|
await this.current.send(message);
|
|
7120
7137
|
}
|
|
7121
7138
|
}
|
|
7139
|
+
recover(reason) {
|
|
7140
|
+
this.log("recover", reason);
|
|
7141
|
+
return this.reinitialize().then(() => {
|
|
7142
|
+
this.stale = false;
|
|
7143
|
+
this.log("recovered");
|
|
7144
|
+
}).catch((error2) => {
|
|
7145
|
+
this.stale = true;
|
|
7146
|
+
this.log("recover-failed", error2 instanceof Error ? error2.message : String(error2));
|
|
7147
|
+
throw error2;
|
|
7148
|
+
}).finally(() => {
|
|
7149
|
+
this.recovery = void 0;
|
|
7150
|
+
});
|
|
7151
|
+
}
|
|
7122
7152
|
async reinitialize() {
|
|
7123
7153
|
const previous = this.current;
|
|
7124
7154
|
const next = this.attach(this.create());
|
|
@@ -7141,9 +7171,6 @@ var RecoveringHttpTransport = class {
|
|
|
7141
7171
|
void next.send({ ...this.initializeMessage, id }).catch(reject);
|
|
7142
7172
|
});
|
|
7143
7173
|
await next.send({ jsonrpc: "2.0", method: "notifications/initialized" });
|
|
7144
|
-
} catch (err) {
|
|
7145
|
-
await this.close();
|
|
7146
|
-
throw err;
|
|
7147
7174
|
} finally {
|
|
7148
7175
|
if (timer) clearTimeout(timer);
|
|
7149
7176
|
next.onmessage = relay;
|
|
@@ -7171,15 +7198,55 @@ var RecoveringHttpTransport = class {
|
|
|
7171
7198
|
};
|
|
7172
7199
|
|
|
7173
7200
|
// mcp-bridge/src/index.ts
|
|
7201
|
+
var import_node_fs = require("node:fs");
|
|
7202
|
+
var import_node_path = require("node:path");
|
|
7203
|
+
var LOG_LIMIT_BYTES = 256 * 1024;
|
|
7204
|
+
function lifecycleLog() {
|
|
7205
|
+
const capabilityFile = process.env.SPECRAILS_AGENT_CAPABILITY_FILE?.trim();
|
|
7206
|
+
if (!capabilityFile) return () => {
|
|
7207
|
+
};
|
|
7208
|
+
const file = (0, import_node_path.join)((0, import_node_path.dirname)(capabilityFile), "bridge.log");
|
|
7209
|
+
return (event, detail) => {
|
|
7210
|
+
try {
|
|
7211
|
+
try {
|
|
7212
|
+
if ((0, import_node_fs.statSync)(file).size > LOG_LIMIT_BYTES) (0, import_node_fs.truncateSync)(file, 0);
|
|
7213
|
+
} catch {
|
|
7214
|
+
}
|
|
7215
|
+
(0, import_node_fs.appendFileSync)(file, `${(/* @__PURE__ */ new Date()).toISOString()} pid=${process.pid} ${event}${detail ? ` ${detail.replace(/\s+/g, " ").slice(0, 500)}` : ""}
|
|
7216
|
+
`, { mode: 384 });
|
|
7217
|
+
} catch {
|
|
7218
|
+
}
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7174
7221
|
async function main() {
|
|
7175
|
-
const
|
|
7176
|
-
|
|
7222
|
+
const log = lifecycleLog();
|
|
7223
|
+
log("start", `ppid=${process.ppid}`);
|
|
7224
|
+
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) process.once(signal, () => {
|
|
7225
|
+
log("signal", signal);
|
|
7226
|
+
process.exit(0);
|
|
7227
|
+
});
|
|
7228
|
+
process.on("exit", (code) => log("exit", `code=${code}`));
|
|
7229
|
+
process.stdin.once("end", () => log("stdin-end", "the client closed its side"));
|
|
7230
|
+
agentForwardHeaders();
|
|
7231
|
+
const fetchWithCredentials = authenticatedFetch(() => agentForwardHeaders());
|
|
7232
|
+
const appFacing = new RecoveringHttpTransport(() => new StreamableHTTPClientTransport(appUrl(), { fetch: fetchWithCredentials }), log);
|
|
7177
7233
|
const clientFacing = new StdioServerTransport();
|
|
7178
7234
|
connectBridge(clientFacing, appFacing);
|
|
7235
|
+
const relayError = appFacing.onerror;
|
|
7236
|
+
appFacing.onerror = (error2) => {
|
|
7237
|
+
log("app-error", error2.message);
|
|
7238
|
+
relayError?.(error2);
|
|
7239
|
+
};
|
|
7240
|
+
const relayClose = appFacing.onclose;
|
|
7241
|
+
appFacing.onclose = () => {
|
|
7242
|
+
log("close", "app side closed");
|
|
7243
|
+
relayClose?.();
|
|
7244
|
+
};
|
|
7179
7245
|
await appFacing.start();
|
|
7180
7246
|
await clientFacing.start();
|
|
7181
7247
|
}
|
|
7182
7248
|
main().catch((err) => {
|
|
7249
|
+
lifecycleLog()("fatal", err instanceof Error ? err.message : String(err));
|
|
7183
7250
|
process.stderr.write(`[specrails-mcp] fatal: ${err instanceof Error ? err.message : String(err)}
|
|
7184
7251
|
`);
|
|
7185
7252
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -8,6 +8,8 @@ exports.resolveNodeCommand = resolveNodeCommand;
|
|
|
8
8
|
exports.buildSpecrailsMcpEntry = buildSpecrailsMcpEntry;
|
|
9
9
|
exports.mergeSpecrailsIntoWorkspaceMcp = mergeSpecrailsIntoWorkspaceMcp;
|
|
10
10
|
exports.buildAgentMcpArgs = buildAgentMcpArgs;
|
|
11
|
+
exports.prepareAgentMcpSpec = prepareAgentMcpSpec;
|
|
12
|
+
exports.rotateAgentSessionCapability = rotateAgentSessionCapability;
|
|
11
13
|
exports.removeAgentCapabilityFile = removeAgentCapabilityFile;
|
|
12
14
|
exports.prepareAgentMcp = prepareAgentMcp;
|
|
13
15
|
exports.syncExternalServersIntoJsonFile = syncExternalServersIntoJsonFile;
|
|
@@ -235,6 +237,45 @@ function buildAgentTurnEntry(opts) {
|
|
|
235
237
|
}
|
|
236
238
|
return entry;
|
|
237
239
|
}
|
|
240
|
+
function stringRecord(value) {
|
|
241
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
242
|
+
return undefined;
|
|
243
|
+
const entries = Object.entries(value).filter((entry) => typeof entry[1] === 'string');
|
|
244
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* MCP servers for a resident Core agent session. The `specrails` bridge reads
|
|
248
|
+
* the conversation's capability file on every request, and this call
|
|
249
|
+
* atomically (re)writes that file with the CURRENT turn's capability — so a
|
|
250
|
+
* long-lived provider process always acts under the latest turn's tier.
|
|
251
|
+
* External servers are passed through as structured specs.
|
|
252
|
+
*/
|
|
253
|
+
function prepareAgentMcpSpec(opts) {
|
|
254
|
+
const entry = buildAgentTurnEntry({ conversationId: opts.conversationId, port: opts.port, capability: opts.capability });
|
|
255
|
+
if (!entry)
|
|
256
|
+
throw new Error('The bundled Specrails MCP bridge is unavailable.');
|
|
257
|
+
// Specrails authorizes every action against the mission tier; never auto-approve external servers.
|
|
258
|
+
const specs = [{ name: 'specrails', command: entry.command, args: [...entry.args], env: { ...entry.env }, autoApprove: true }];
|
|
259
|
+
for (const server of opts.external ?? []) {
|
|
260
|
+
if (server.name === 'specrails')
|
|
261
|
+
continue;
|
|
262
|
+
const config = server.config;
|
|
263
|
+
if (typeof config.url === 'string') {
|
|
264
|
+
const headers = stringRecord(config.headers);
|
|
265
|
+
specs.push({ name: server.name, url: config.url, ...(headers ? { headers } : {}) });
|
|
266
|
+
}
|
|
267
|
+
else if (typeof config.command === 'string') {
|
|
268
|
+
const env = stringRecord(config.env);
|
|
269
|
+
specs.push({ name: server.name, command: config.command, args: Array.isArray(config.args) ? config.args.filter((arg) => typeof arg === 'string') : [], ...(env ? { env } : {}) });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return specs;
|
|
273
|
+
}
|
|
274
|
+
/** Rotate the resident session's capability for a new turn (atomic rewrite). */
|
|
275
|
+
function rotateAgentSessionCapability(conversationId, port, capability) {
|
|
276
|
+
if (!buildAgentTurnEntry({ conversationId, port, capability }))
|
|
277
|
+
throw new Error('The bundled Specrails MCP bridge is unavailable.');
|
|
278
|
+
}
|
|
238
279
|
/** Delete the on-disk bearer as soon as its owning agent turn settles. */
|
|
239
280
|
function removeAgentCapabilityFile(conversationId, invocationId) {
|
|
240
281
|
if (!AGENT_CONVERSATION_ID_RE.test(conversationId) ||
|
|
@@ -12,6 +12,7 @@ exports.getCLIStatus = getCLIStatus;
|
|
|
12
12
|
exports.checkCoreCompat = checkCoreCompat;
|
|
13
13
|
exports.coreCompatSupportsProvider = coreCompatSupportsProvider;
|
|
14
14
|
const child_process_1 = require("child_process");
|
|
15
|
+
const agent_sessions_1 = require("./modules/agent-sessions");
|
|
15
16
|
const fs_1 = __importDefault(require("fs"));
|
|
16
17
|
const path_1 = __importDefault(require("path"));
|
|
17
18
|
const setup_manager_1 = require("./setup-manager");
|
|
@@ -264,6 +265,7 @@ async function checkCoreCompat() {
|
|
|
264
265
|
contractFound: true,
|
|
265
266
|
contractSchemaVersion: contract.schemaVersion,
|
|
266
267
|
supportedProviders,
|
|
268
|
+
sessions: contract.agentRuntime?.sessions ? (0, agent_sessions_1.checkSessionContract)(contract.agentRuntime.sessions) : null,
|
|
267
269
|
};
|
|
268
270
|
}
|
|
269
271
|
/** True only when the located Core contract explicitly renders this provider. */
|
|
@@ -3,7 +3,7 @@ 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
|
+
exports.GLOBAL_SUBAGENT_RUNTIME_KEY = exports.GLOBAL_ALLOW_SUBAGENTS_KEY = exports.AGENT_SEARCH_INDEX_DDL = void 0;
|
|
7
7
|
exports.setProjectProvidersMirror = setProjectProvidersMirror;
|
|
8
8
|
exports.getDesktopDbPath = getDesktopDbPath;
|
|
9
9
|
exports.rebuildAgentSearchIndex = rebuildAgentSearchIndex;
|
|
@@ -19,6 +19,9 @@ exports.updateProjectRepository = updateProjectRepository;
|
|
|
19
19
|
exports.removeProjectRepository = removeProjectRepository;
|
|
20
20
|
exports.removeProject = removeProject;
|
|
21
21
|
exports.touchProject = touchProject;
|
|
22
|
+
exports.getGlobalAllowSubagents = getGlobalAllowSubagents;
|
|
23
|
+
exports.getGlobalSubagentRuntime = getGlobalSubagentRuntime;
|
|
24
|
+
exports.setGlobalSubagentRuntime = setGlobalSubagentRuntime;
|
|
22
25
|
exports.getDesktopSetting = getDesktopSetting;
|
|
23
26
|
exports.setDesktopSetting = setDesktopSetting;
|
|
24
27
|
exports.setProjectSetupSession = setProjectSetupSession;
|
|
@@ -39,6 +42,7 @@ exports.getWebhook = getWebhook;
|
|
|
39
42
|
exports.addWebhook = addWebhook;
|
|
40
43
|
exports.updateWebhook = updateWebhook;
|
|
41
44
|
exports.removeWebhook = removeWebhook;
|
|
45
|
+
const project_settings_1 = require("./modules/project-settings");
|
|
42
46
|
const fs_1 = __importDefault(require("fs"));
|
|
43
47
|
const path_1 = __importDefault(require("path"));
|
|
44
48
|
const os_1 = __importDefault(require("os"));
|
|
@@ -683,6 +687,75 @@ function applyDesktopMigrations(db) {
|
|
|
683
687
|
if (!columns.includes('workspace_paths'))
|
|
684
688
|
db.exec('ALTER TABLE project_repositories ADD COLUMN workspace_paths TEXT');
|
|
685
689
|
},
|
|
690
|
+
// 33: core-agent-sessions-host — Desktop's rebuildable projection of Core
|
|
691
|
+
// agent sessions. Core's journal (~/.specrails/sessions/<scope>/) is the
|
|
692
|
+
// source of truth; these rows are re-derivable by replaying it. Cursor and
|
|
693
|
+
// projected rows are always written in one transaction. Column-guarded for
|
|
694
|
+
// migration replay fixtures.
|
|
695
|
+
() => {
|
|
696
|
+
db.exec(`
|
|
697
|
+
CREATE TABLE IF NOT EXISTS agent_session_cursors (
|
|
698
|
+
conversation_id TEXT PRIMARY KEY REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
|
699
|
+
core_session_id TEXT NOT NULL,
|
|
700
|
+
scope TEXT NOT NULL,
|
|
701
|
+
last_seq INTEGER NOT NULL DEFAULT 0,
|
|
702
|
+
resident_phase TEXT NOT NULL DEFAULT 'idle' CHECK (resident_phase IN ('idle', 'turn', 'background')),
|
|
703
|
+
process_alive INTEGER NOT NULL DEFAULT 0,
|
|
704
|
+
live_subagents INTEGER NOT NULL DEFAULT 0,
|
|
705
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
706
|
+
);
|
|
707
|
+
CREATE TABLE IF NOT EXISTS agent_subagents (
|
|
708
|
+
conversation_id TEXT NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
|
|
709
|
+
subagent_id TEXT NOT NULL,
|
|
710
|
+
parent_id TEXT,
|
|
711
|
+
kind TEXT NOT NULL CHECK (kind IN ('foreground', 'background')),
|
|
712
|
+
agent_type TEXT,
|
|
713
|
+
description TEXT NOT NULL,
|
|
714
|
+
phase TEXT NOT NULL CHECK (phase IN ('running', 'idle', 'failed', 'stopped', 'killed', 'interrupted')),
|
|
715
|
+
reason TEXT,
|
|
716
|
+
restarts INTEGER NOT NULL DEFAULT 0,
|
|
717
|
+
launched_turn_id TEXT,
|
|
718
|
+
started_at TEXT NOT NULL,
|
|
719
|
+
ended_at TEXT,
|
|
720
|
+
usage_json TEXT,
|
|
721
|
+
tool_uses INTEGER,
|
|
722
|
+
duration_ms INTEGER,
|
|
723
|
+
result_summary TEXT,
|
|
724
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
725
|
+
PRIMARY KEY (conversation_id, subagent_id)
|
|
726
|
+
);
|
|
727
|
+
CREATE INDEX IF NOT EXISTS idx_agent_subagents_turn ON agent_subagents(conversation_id, launched_turn_id);
|
|
728
|
+
CREATE TABLE IF NOT EXISTS agent_subagent_events (
|
|
729
|
+
conversation_id TEXT NOT NULL,
|
|
730
|
+
subagent_id TEXT NOT NULL,
|
|
731
|
+
seq INTEGER NOT NULL,
|
|
732
|
+
channel TEXT NOT NULL CHECK (channel IN ('text', 'tool')),
|
|
733
|
+
delta TEXT,
|
|
734
|
+
tool_json TEXT,
|
|
735
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
736
|
+
PRIMARY KEY (conversation_id, subagent_id, seq),
|
|
737
|
+
FOREIGN KEY (conversation_id, subagent_id) REFERENCES agent_subagents(conversation_id, subagent_id) ON DELETE CASCADE
|
|
738
|
+
);
|
|
739
|
+
`);
|
|
740
|
+
const invocationColumns = db.prepare('PRAGMA table_info(agent_invocations)').all().map((c) => c.name);
|
|
741
|
+
if (!invocationColumns.includes('origin'))
|
|
742
|
+
db.exec(`ALTER TABLE agent_invocations ADD COLUMN origin TEXT NOT NULL DEFAULT 'user' CHECK (origin IN ('user', 'subagent', 'system'));`);
|
|
743
|
+
const messageColumns = db.prepare('PRAGMA table_info(agent_messages)').all().map((c) => c.name);
|
|
744
|
+
if (!messageColumns.includes('core_turn_id'))
|
|
745
|
+
db.exec(`ALTER TABLE agent_messages ADD COLUMN core_turn_id TEXT;`);
|
|
746
|
+
if (!messageColumns.includes('turn_origin'))
|
|
747
|
+
db.exec(`ALTER TABLE agent_messages ADD COLUMN turn_origin TEXT CHECK (turn_origin IS NULL OR turn_origin IN ('user', 'subagent', 'system'));`);
|
|
748
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_agent_messages_core_turn ON agent_messages(conversation_id, core_turn_id) WHERE core_turn_id IS NOT NULL;`);
|
|
749
|
+
},
|
|
750
|
+
// 34: hybrid sub-agent runtime — sub-agents Core launched on another
|
|
751
|
+
// provider record which driver and model ran them. Column-guarded.
|
|
752
|
+
() => {
|
|
753
|
+
const columns = db.prepare('PRAGMA table_info(agent_subagents)').all().map((c) => c.name);
|
|
754
|
+
if (!columns.includes('delegated_driver'))
|
|
755
|
+
db.exec('ALTER TABLE agent_subagents ADD COLUMN delegated_driver TEXT');
|
|
756
|
+
if (!columns.includes('delegated_model'))
|
|
757
|
+
db.exec('ALTER TABLE agent_subagents ADD COLUMN delegated_model TEXT');
|
|
758
|
+
},
|
|
686
759
|
];
|
|
687
760
|
(0, sqlite_migrations_1.applyNumberedMigrations)(db, migrations);
|
|
688
761
|
}
|
|
@@ -805,6 +878,30 @@ function removeProject(db, id) {
|
|
|
805
878
|
function touchProject(db, id) {
|
|
806
879
|
db.prepare("UPDATE projects SET last_seen_at = datetime('now') WHERE id = ?").run(id);
|
|
807
880
|
}
|
|
881
|
+
/** App-global "Allow sub-agents" for missions without a project (default off). */
|
|
882
|
+
exports.GLOBAL_ALLOW_SUBAGENTS_KEY = 'agent_allow_subagents';
|
|
883
|
+
function getGlobalAllowSubagents(db) {
|
|
884
|
+
return getDesktopSetting(db, exports.GLOBAL_ALLOW_SUBAGENTS_KEY) === 'true';
|
|
885
|
+
}
|
|
886
|
+
/** App-wide "Run sub-agents with" for missions without a project; null = the mission agent's provider. */
|
|
887
|
+
exports.GLOBAL_SUBAGENT_RUNTIME_KEY = 'agent_subagent_runtime';
|
|
888
|
+
function getGlobalSubagentRuntime(db) {
|
|
889
|
+
const raw = getDesktopSetting(db, exports.GLOBAL_SUBAGENT_RUNTIME_KEY);
|
|
890
|
+
if (!raw)
|
|
891
|
+
return null;
|
|
892
|
+
try {
|
|
893
|
+
return (0, project_settings_1.parseSubagentRuntimeSetting)(JSON.parse(raw));
|
|
894
|
+
}
|
|
895
|
+
catch {
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function setGlobalSubagentRuntime(db, value) {
|
|
900
|
+
if (value === null)
|
|
901
|
+
db.prepare('DELETE FROM desktop_settings WHERE key = ?').run(exports.GLOBAL_SUBAGENT_RUNTIME_KEY);
|
|
902
|
+
else
|
|
903
|
+
setDesktopSetting(db, exports.GLOBAL_SUBAGENT_RUNTIME_KEY, JSON.stringify(value));
|
|
904
|
+
}
|
|
808
905
|
function getDesktopSetting(db, key) {
|
|
809
906
|
const row = db.prepare('SELECT value FROM desktop_settings WHERE key = ?').get(key);
|
|
810
907
|
return row?.value;
|
|
@@ -828,9 +925,9 @@ function recordAgentInvocation(db, input) {
|
|
|
828
925
|
id, conversation_id, project_id, provider, surface, model, status,
|
|
829
926
|
started_at, finished_at, duration_ms, duration_api_ms,
|
|
830
927
|
tokens_in, tokens_out, tokens_cache_read, tokens_cache_create,
|
|
831
|
-
total_cost_usd, total_cost_usd_estimated, num_turns, session_id
|
|
832
|
-
) VALUES (?, ?, ?, ?, 'agent-chat', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
833
|
-
`).run(input.id, input.conversation_id, input.project_id ?? null, input.provider, input.model ?? null, input.status, input.started_at, input.finished_at ?? null, input.duration_ms ?? null, input.duration_api_ms ?? null, input.tokens_in ?? null, input.tokens_out ?? null, input.tokens_cache_read ?? null, input.tokens_cache_create ?? null, input.total_cost_usd ?? null, input.total_cost_usd_estimated ? 1 : 0, input.num_turns ?? null, input.session_id ?? null);
|
|
928
|
+
total_cost_usd, total_cost_usd_estimated, num_turns, session_id, origin
|
|
929
|
+
) VALUES (?, ?, ?, ?, 'agent-chat', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
930
|
+
`).run(input.id, input.conversation_id, input.project_id ?? null, input.provider, input.model ?? null, input.status, input.started_at, input.finished_at ?? null, input.duration_ms ?? null, input.duration_api_ms ?? null, input.tokens_in ?? null, input.tokens_out ?? null, input.tokens_cache_read ?? null, input.tokens_cache_create ?? null, input.total_cost_usd ?? null, input.total_cost_usd_estimated ? 1 : 0, input.num_turns ?? null, input.session_id ?? null, input.origin ?? 'user');
|
|
834
931
|
}
|
|
835
932
|
/**
|
|
836
933
|
* Sum of all agent-chat turn costs, optionally since an ISO instant (inclusive).
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createDesktopRouter = createDesktopRouter;
|
|
7
|
+
const project_settings_1 = require("./modules/project-settings");
|
|
7
8
|
const http_1 = require("./modules/subscription-usage/adapters/http");
|
|
8
9
|
const runtime_role_prompts_router_1 = require("./runtime-role-prompts-router");
|
|
9
10
|
const express_1 = require("express");
|
|
@@ -816,11 +817,26 @@ function createDesktopRouter(registry, broadcast, usageService) {
|
|
|
816
817
|
'http://localhost:3000';
|
|
817
818
|
const costAlertThresholdRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'cost_alert_threshold_usd');
|
|
818
819
|
const costAlertThresholdUsd = costAlertThresholdRaw != null ? parseFloat(costAlertThresholdRaw) : null;
|
|
819
|
-
|
|
820
|
+
const allowSubagents = (0, desktop_db_1.getGlobalAllowSubagents)(registry.desktopDb);
|
|
821
|
+
res.json({ port: parseInt(port, 10), specrailsTechUrl, costAlertThresholdUsd, allowSubagents, subagentRuntime: (0, desktop_db_1.getGlobalSubagentRuntime)(registry.desktopDb) });
|
|
820
822
|
});
|
|
821
823
|
// PUT /api/settings — update app-level settings
|
|
822
824
|
router.put('/settings', (req, res) => {
|
|
823
|
-
const { port, specrailsTechUrl, costAlertThresholdUsd } = req.body ?? {};
|
|
825
|
+
const { port, specrailsTechUrl, costAlertThresholdUsd, allowSubagents } = req.body ?? {};
|
|
826
|
+
if (allowSubagents !== undefined && typeof allowSubagents !== 'boolean') {
|
|
827
|
+
res.status(400).json({ error: 'allowSubagents must be a boolean' });
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
let subagentRuntime;
|
|
831
|
+
if (req.body?.subagentRuntime !== undefined) {
|
|
832
|
+
try {
|
|
833
|
+
subagentRuntime = (0, project_settings_1.parseSubagentRuntimeSetting)(req.body.subagentRuntime);
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
res.status(400).json({ error: error.message });
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
824
840
|
if (port !== undefined) {
|
|
825
841
|
const n = Number(port);
|
|
826
842
|
if (!Number.isInteger(n) || n < 1 || n > 65535) {
|
|
@@ -848,6 +864,19 @@ function createDesktopRouter(registry, broadcast, usageService) {
|
|
|
848
864
|
(0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'cost_alert_threshold_usd', String(costAlertThresholdUsd));
|
|
849
865
|
}
|
|
850
866
|
}
|
|
867
|
+
if (allowSubagents !== undefined) {
|
|
868
|
+
const previous = (0, desktop_db_1.getGlobalAllowSubagents)(registry.desktopDb);
|
|
869
|
+
(0, desktop_db_1.setDesktopSetting)(registry.desktopDb, desktop_db_1.GLOBAL_ALLOW_SUBAGENTS_KEY, allowSubagents ? 'true' : 'false');
|
|
870
|
+
// Project-less missions re-read their sub-agent policy.
|
|
871
|
+
if (previous !== allowSubagents)
|
|
872
|
+
registry.notifySettingsChanged({ projectId: null });
|
|
873
|
+
}
|
|
874
|
+
if (subagentRuntime !== undefined) {
|
|
875
|
+
const previous = JSON.stringify((0, desktop_db_1.getGlobalSubagentRuntime)(registry.desktopDb));
|
|
876
|
+
(0, desktop_db_1.setGlobalSubagentRuntime)(registry.desktopDb, subagentRuntime);
|
|
877
|
+
if (previous !== JSON.stringify(subagentRuntime))
|
|
878
|
+
registry.notifySettingsChanged({ projectId: null });
|
|
879
|
+
}
|
|
851
880
|
res.json({ ok: true });
|
|
852
881
|
});
|
|
853
882
|
// ─── Budget routes ────────────────────────────────────────────────────────────
|