specrails-desktop 2.59.3 → 2.61.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-BGEayjTY.js → ActivityFeedPage-Wi5XNgoG.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-Qvg4y5Tr.js → AgentBrowserCapture-CkNWDZT3.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-CR7aFeLJ.js → AgentModeAnalyticsPane-CXz2j-Bt.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-gAd88Gng.js → AgentModeCodePane-DnsBPtpS.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-EjCDuTow.js → AgentModeJobsPane-U1J6PaBo.js} +1 -1
- package/client/dist/assets/{AgentsPage-CRHHcp1V.js → AgentsPage-CXrqu5vo.js} +2 -2
- package/client/dist/assets/{AnalyticsPage-a0b1fNEl.js → AnalyticsPage-Cvtt-LOO.js} +1 -1
- package/client/dist/assets/{CodePage-BmV1m7l3.js → CodePage-D35o-MFO.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-B87WmXOQ.js → DesktopAnalyticsPage-CsDFy5rp.js} +1 -1
- package/client/dist/assets/{DocsDialog-Co0O672q.js → DocsDialog-CSlMPemn.js} +1 -1
- package/client/dist/assets/{DocsPage-DxGI6A2T.js → DocsPage-BKlYAvWq.js} +1 -1
- package/client/dist/assets/{ExportDropdown-1Ce031vB.js → ExportDropdown-CweP_1GP.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-CFKvbQeJ.js +19 -0
- package/client/dist/assets/{JobDetailModal-DVLDVgzz.js → JobDetailModal-DYyGq1Vp.js} +1 -1
- package/client/dist/assets/{JobDetailPage-RcP9N-D-.js → JobDetailPage-C6gpdYvj.js} +1 -1
- package/client/dist/assets/{JobsPage-R9M4xQnp.js → JobsPage-CgS0pnKd.js} +1 -1
- package/client/dist/assets/LoopBuilderPage-jUp20DSZ.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-DA2JeKWX.js → LoopPreviewModal-DkLRfsYX.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-BkkeD0hx.js → LoopWindowSurface-CuACkXFV.js} +1 -1
- package/client/dist/assets/{LoopsPage-BHv--iI7.js → LoopsPage-ClSrWzDd.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-Bc9xHByW.js → MinimizedChatsContext-CroAs6Kh.js} +1 -1
- package/client/dist/assets/{PluginsPage-BEy62gxK.js → PluginsPage-nCKQOwRL.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-DDQ_f94T.js → ProjectSettingsDialog-BVDYwllU.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-DIWDFLRM.js → RepositoryDeliveries-DJCm1pZr.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-D3PlHIM-.js → RepositoryScopeSelector-CeCr6Jz-.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-BXdPwmdE.js → ReviewPacketPage-kTErrEjx.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-C7-G6lvI.js → TemplatePreviewModal-B8yd1AGU.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-gGUv7gsQ.js → TicketDetailModalContext-YBJX3RmP.js} +1 -1
- package/client/dist/assets/{Trans-Br_OIUKP.js → Trans-D5eOjfvG.js} +1 -1
- 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/agents-B6ZsnARo.js +1 -0
- package/client/dist/assets/agents-BORdhe0K.js +1 -0
- package/client/dist/assets/agents-C6aq9Oqs.js +1 -0
- package/client/dist/assets/agents-Cbsc4bcX.js +1 -0
- package/client/dist/assets/agents-D0uYR6hq.js +1 -0
- package/client/dist/assets/agents-Doe1sHY5.js +1 -0
- package/client/dist/assets/agents-DyNi_I9m.js +1 -0
- package/client/dist/assets/agents-StKxiTg0.js +1 -0
- package/client/dist/assets/agentstudio-7cYCXn-L.js +1 -0
- package/client/dist/assets/{agentstudio-QdAJIDef.js → agentstudio-BHWQh8G3.js} +1 -1
- package/client/dist/assets/agentstudio-BjnWHyRs.js +1 -0
- package/client/dist/assets/{agentstudio-fLXpRGIi.js → agentstudio-BkV8vcK4.js} +1 -1
- package/client/dist/assets/{agentstudio-BWAKeGgB.js → agentstudio-BzugqB_N.js} +1 -1
- package/client/dist/assets/{agentstudio-Cja8Sjcp.js → agentstudio-CWbp7KvH.js} +1 -1
- package/client/dist/assets/agentstudio-DG6plf5o.js +1 -0
- package/client/dist/assets/agentstudio-DkZVF-Cc.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-Q7QPRFV4.js → formatDistanceToNow-DZ3VWsHq.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-Bq0CXBaJ.js → getTimezoneOffsetInMilliseconds-9JiGlFrx.js} +1 -1
- package/client/dist/assets/{index-DVkWHOR3.js → index-lTGJJTuk.js} +8 -8
- package/client/dist/assets/{jira-api-C2Tzc362.js → jira-api-Bp0zXwjg.js} +1 -1
- 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-DZKmieH3.js → project-repositories-CadLZOnr.js} +1 -1
- package/client/dist/assets/{spending-EXaboRU0.js → spending-DQDNtM8q.js} +1 -1
- package/client/dist/assets/{useDesktop-ChSmmJfc.js → useDesktop-DKNbOF0u.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-w9-5OzZl.js → useRuntimeRuns-D3YMw9qx.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-g0nTHfTT.js → useSharedWebSocket-DaWuiO0S.js} +2 -2
- package/client/dist/index.html +19 -19
- package/docs/getting-started.md +2 -2
- package/docs/guide/de/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/de/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/de/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/en/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/en/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/en/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/es/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/es/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/es/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/fr/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/fr/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/fr/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/it/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/it/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/it/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/ja/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/ja/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/ja/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/pt/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/pt/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/pt/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/zh/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/zh/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/zh/agents/4-custom-agents-catalog.md +3 -3
- package/docs/internals/adding-a-provider.md +7 -6
- package/docs/internals/configuration.md +2 -0
- package/docs/internals/core-runtime-updates.md +22 -3
- package/docs/internals/profiles.md +17 -10
- package/docs/internals/programmatic-agent-runtime.md +2 -0
- package/docs/internals/review-packet.md +1 -1
- package/docs/internals/source-map.md +1 -1
- package/docs/running-pipelines.md +2 -2
- package/package.json +1 -1
- package/server/dist/core-compat.js +7 -3
- package/server/dist/mcp/guide.js +4 -3
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +19 -3
- 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 +42 -7
- package/server/dist/modules/agents/runtime/profiles-router.js +74 -36
- package/server/dist/modules/execution/runtime/queue-manager.js +7 -9
- package/server/dist/modules/loops/runtime/loop-agents.js +2 -5
- package/server/dist/modules/loops/runtime/loop-command-catalog.js +5 -5
- package/server/dist/modules/loops/runtime/loop-core-factory.js +2 -2
- package/server/dist/modules/loops/runtime/loop-definition.js +2 -1
- package/server/dist/modules/loops/runtime/loop-executors.js +0 -2
- 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 +33 -13
- package/server/dist/modules/loops/runtime/loop-templates.js +3 -3
- package/server/dist/modules/loops/runtime/loops-router.js +3 -1
- package/server/dist/providers/gemini-agent-ack.js +13 -10
- package/server/dist/schemas/agent-runtime.schema.json +9 -7
- package/server/dist/setup-manager.js +28 -43
- package/server/dist/workspace-manager.js +13 -71
- package/client/dist/assets/InteractiveJobComposer-DkqR77pO.js +0 -19
- package/client/dist/assets/LoopBuilderPage-C4S-rkWp.js +0 -1
- package/client/dist/assets/agentRuntime-15Pkk6we.js +0 -1
- package/client/dist/assets/agentRuntime-Bpx3yWHK.js +0 -1
- package/client/dist/assets/agentRuntime-C78n13To.js +0 -1
- package/client/dist/assets/agentRuntime-CCI7XF4c.js +0 -1
- package/client/dist/assets/agentRuntime-CKDHe2-a.js +0 -1
- package/client/dist/assets/agentRuntime-Ddqvp2bP.js +0 -1
- package/client/dist/assets/agentRuntime-TXPQowKJ.js +0 -1
- package/client/dist/assets/agentRuntime-hqZRy08E.js +0 -1
- package/client/dist/assets/agents-CA5eT1d0.js +0 -1
- package/client/dist/assets/agents-CAgy-5uS.js +0 -1
- package/client/dist/assets/agents-CNm6Qzlv.js +0 -1
- package/client/dist/assets/agents-CTwbx-fb.js +0 -1
- package/client/dist/assets/agents-DEGV1Vao.js +0 -1
- package/client/dist/assets/agents-HsDF1aQU.js +0 -1
- package/client/dist/assets/agents-IQjdA-z9.js +0 -1
- package/client/dist/assets/agents-RkGoteXh.js +0 -1
- package/client/dist/assets/agentstudio-B2CTtQgZ.js +0 -1
- package/client/dist/assets/agentstudio-DtfPcr99.js +0 -1
- package/client/dist/assets/agentstudio-X70BKf6u.js +0 -1
- package/client/dist/assets/agentstudio-qXKqVnWS.js +0 -1
- package/server/dist/modules/loops/runtime/loop-agent-defaults.json +0 -6
|
@@ -61,7 +61,7 @@ export const exampleAdapter: ProviderAdapter = {
|
|
|
61
61
|
buildArgs: (action: SpawnAction, opts: SpawnOptions): string[] => { /* per-action argv */ },
|
|
62
62
|
parseStreamLine: (line: string): AdapterEvent | null => { /* line → canonical event */ },
|
|
63
63
|
extractResult: (events): NormalisedResult => { /* events → tokens/cost/session */ },
|
|
64
|
-
baselineAgents: () => ['sr-architect', 'sr-developer', 'sr-reviewer'],
|
|
64
|
+
baselineAgents: () => ['sr-architect', 'sr-developer', 'sr-reviewer'], // role identifiers, not installed files
|
|
65
65
|
detectInstalled: async (): Promise<DetectionResult> => { /* `which` + `--version` */ },
|
|
66
66
|
// Optional — see "(Optional) prepareHeadlessSpawn" below. Gemini uses it to
|
|
67
67
|
// pre-acknowledge project subagents so they load in headless `gemini -p` rails.
|
|
@@ -79,11 +79,12 @@ the optional
|
|
|
79
79
|
`prepareHeadlessSpawn` hook, `systemPromptArg: false` system-prompt folding
|
|
80
80
|
(via the `GEMINI_SYSTEM_MD` env), native OTEL with `nativeCostUsd: false`,
|
|
81
81
|
and a per-action `buildArgs` switch that throws defensively on the
|
|
82
|
-
`chat-stream` action it doesn't support. Note the
|
|
83
|
-
three
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
`chat-stream` action it doesn't support. Note the baseline is the
|
|
83
|
+
three role identifiers `['sr-architect', 'sr-developer', 'sr-reviewer']`;
|
|
84
|
+
they name roles the Core runtime defines, not files your scaffold installs,
|
|
85
|
+
and installation detection is never derived from them. `ProfileManager`
|
|
86
|
+
validation requires exactly your `baselineAgents()` to be present in every
|
|
87
|
+
profile chain, so don't add identifiers the runtime won't actually route.
|
|
87
88
|
|
|
88
89
|
The `SpawnAction` union also includes **`chat-stream`** — used by the
|
|
89
90
|
Explore persistent-stdin fast-path and interactive jobs. Only providers
|
|
@@ -74,6 +74,8 @@ Project settings apply to a single project. Open them from the project's **Setti
|
|
|
74
74
|
| **Freestyle pre-prompt** | Text prepended to Freestyle-mode launches (Claude and Kimi advertise this autonomous rail mode). |
|
|
75
75
|
| **Budget** | Per-project daily spend cap (with queue auto-pause) and a per-job cost alert threshold. |
|
|
76
76
|
| **Terminal Settings** | Per-project overrides for the terminal panel defaults (project override → app default → built-in). |
|
|
77
|
+
| **Verification commands** | Repository checks Core runs after the developer finishes; failures go back to the developer (or the fixer). Stored under `verification` in `.specrails/agent-runtime.json`; an empty list lets the architect propose the project's own checks. |
|
|
78
|
+
| **Setup commands** | Idempotent commands Core runs inside the repository checkout before every verification plan, for example `npx playwright install chromium`. Same entry shape as `verification` (`repositoryId`, `command`, `args`, optional `cwd`, `env`, `timeoutMs`, `key`, `label`), stored under `setup`, no auto-detection. Credential-like `env` keys are refused and `key` values must be unique across `setup` and `verification`. A failing setup command ends the run as blocked by the host environment. The list reaches Core only when the installed Core advertises the `setupCommands` capability; older cores receive the configuration without it and the run log records a one-line note. |
|
|
77
79
|
|
|
78
80
|
> The project's **provider(s)** — **Claude, Codex, Gemini, Kimi, or any
|
|
79
81
|
> compatible combination** — are chosen when you add the project and are
|
|
@@ -13,15 +13,15 @@ framework is rejected rather than silently replacing an update.
|
|
|
13
13
|
|
|
14
14
|
## Persistence and publication
|
|
15
15
|
|
|
16
|
-
The release bundle pins Core 6.
|
|
16
|
+
The release bundle pins Core 6.4.0 in `desktop-release.yml` and
|
|
17
17
|
`scripts/assemble-bundled-core.lock.json`. Core 6 is the Desktop-only engine: it
|
|
18
18
|
publishes integration contract 5.1 (no standalone `update`) and ships only the
|
|
19
19
|
implement and retry workflows (Batch was folded into implement; Desktop no
|
|
20
20
|
longer requires `batch-implement` but stays compatible with Cores that still ship it). Update both pins together and check compatibility against
|
|
21
21
|
the staged published package; retained runs still use their original runtime.
|
|
22
22
|
|
|
23
|
-
The paired CI checkout uses the immutable Core 6.
|
|
24
|
-
`
|
|
23
|
+
The paired CI checkout uses the immutable Core 6.4.0 release commit
|
|
24
|
+
`aa00781e962a30c22bbcdecc632bc2abcf3c97d2`. Bundle smoke checks use Node 22.22.3,
|
|
25
25
|
matching Core's minimum supported Node version and the Desktop release runtime.
|
|
26
26
|
|
|
27
27
|
Desktop updates retain the complete npm installation, including dependencies,
|
|
@@ -68,6 +68,25 @@ all workspace refreshes and the final recovery-state write succeed.
|
|
|
68
68
|
Core 6 registry updates and same-version runtime repairs are offered using the
|
|
69
69
|
same supported-major policy as runtime selection.
|
|
70
70
|
|
|
71
|
+
## Role definitions follow the engine
|
|
72
|
+
|
|
73
|
+
Core owns how the implement roles think: `rolePromptDefaults()` returns the
|
|
74
|
+
factory architect, developer, reviewer and fixer definitions (one shared senior
|
|
75
|
+
engineer identity, blast-radius discipline, quality bar and the mandatory
|
|
76
|
+
OpenSpec binding). Desktop owns the workflows that use those roles, and never
|
|
77
|
+
keeps a copy of those definitions.
|
|
78
|
+
|
|
79
|
+
- The Implement rail (engine 1) reads the defaults from the selected Core at
|
|
80
|
+
launch, merged with the user's global overrides from Settings.
|
|
81
|
+
- Loop recipes store `inherit` for the built-in definitions (and
|
|
82
|
+
`inherit:<role>` for declared recipe roles such as `plan`/`build`). The
|
|
83
|
+
launch bridge resolves them to the same Core defaults plus global overrides
|
|
84
|
+
and freezes the result with the run, so every loop follows the installed
|
|
85
|
+
Core automatically. Text the user edits in the loop editor is stored verbatim
|
|
86
|
+
and stays frozen with the loop; "Use the Core definition" returns a role to
|
|
87
|
+
`inherit`. `/api/loop-agent-defaults` returns `rolePromptDefaults` so the
|
|
88
|
+
editor can preview what `inherit` resolves to today.
|
|
89
|
+
|
|
71
90
|
## Version reporting
|
|
72
91
|
|
|
73
92
|
Global status distinguishes the selected runtime and its source, the active
|
|
@@ -21,20 +21,22 @@ Dashboard/Jobs/Analytics/Settings).
|
|
|
21
21
|
- **Profiles** tab — create and edit profiles.
|
|
22
22
|
- **Usage** tab — see which profiles are actually being used (backed by
|
|
23
23
|
the `/analytics` endpoint covered in section 8).
|
|
24
|
-
- **Catalog** tab — read the
|
|
25
|
-
`
|
|
24
|
+
- **Catalog** tab — read the runtime-defined baseline roles (`sr-architect`,
|
|
25
|
+
`sr-developer`, `sr-reviewer`, shown read-only with the definition the Core
|
|
26
|
+
runtime executes) or author custom `custom-*` ones via the Studio.
|
|
26
27
|
|
|
27
28
|
When the project has no profiles yet, the empty state offers two entry
|
|
28
29
|
points:
|
|
29
30
|
|
|
30
|
-
- **Migrate from current agents** —
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
- **Migrate from current agents** — seeds the baseline trio `sr-architect`,
|
|
32
|
+
`sr-developer` and `sr-reviewer` as role identifiers with the provider's
|
|
33
|
+
default model, reads the primary provider's native role catalog
|
|
34
|
+
(`.claude/agents/` or `.kimi-code/skills/`) for custom roles and creates a
|
|
35
|
+
`default` profile mirroring today's behavior. No `sr-*` file has to exist:
|
|
36
|
+
a `model:` is read only from role files an older install still carries.
|
|
37
|
+
Provider-specific model identifiers are retained only when they are in that
|
|
38
|
+
provider's catalog; otherwise migration uses its default and stamps the
|
|
39
|
+
provider into the saved profile.
|
|
38
40
|
- **Blank profile** — start from scratch.
|
|
39
41
|
|
|
40
42
|
## 2. Saved profiles vs selection
|
|
@@ -128,6 +130,11 @@ template/duplicate/edit and rail execution remain available.
|
|
|
128
130
|
`sr-developer`. Fix the listed issues and Save re-enables.
|
|
129
131
|
- **"agent 'xyz' already exists" (409)** — the name collides with an
|
|
130
132
|
existing provider role. Pick a different name.
|
|
133
|
+
- **No `.claude/agents/sr-*.md` in the project** — expected. The baseline
|
|
134
|
+
roles are defined by the Core runtime, not installed as files; read them in
|
|
135
|
+
the Catalog tab and edit their definitions, provider and model in
|
|
136
|
+
**Settings → Agent runtime**. Files left behind by an older Core are
|
|
137
|
+
tolerated until Core prunes them.
|
|
131
138
|
- **The whole Agents section is missing** — it can be disabled server-side
|
|
132
139
|
with `SPECRAILS_AGENTS_SECTION=false`, which 404s the entire
|
|
133
140
|
`/profiles` router. Unset it (or leave it at its default) to restore the
|
|
@@ -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`. 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.
|
|
@@ -140,7 +140,7 @@ one-active-generation-per-rail all come free from the shipped machinery.
|
|
|
140
140
|
|
|
141
141
|
**The loop.** `factory:revision` is the fourth factory loop: `{{cmd:revise}}` (a
|
|
142
142
|
distilled step that applies the one requested change on top of the branch and
|
|
143
|
-
then runs `sr-reviewer` over the resulting diff, so packet v2 keeps its reviewer
|
|
143
|
+
then runs the reviewer role (`sr-reviewer`) over the resulting diff, so packet v2 keeps its reviewer
|
|
144
144
|
tier) followed by the standard `{{cmd:verify}}`. No Architect step — the plan and
|
|
145
145
|
the code already exist. It is resolvable by id but deliberately NOT listed in the
|
|
146
146
|
public catalog: its prompt consumes `{{const:REVISION_REQUEST}}`, which only a
|
|
@@ -79,7 +79,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
79
79
|
|
|
80
80
|
- [AgentRoleFields.tsx](../../client/src/features/agents/components/AgentRoleFields.tsx) · [test](../../client/src/features/agents/components/__tests__/AgentRoleFields.test.tsx)
|
|
81
81
|
- [AgentStudio.tsx](../../client/src/features/agents/components/AgentStudio.tsx)
|
|
82
|
-
- [AgentsCatalogTab.tsx](../../client/src/features/agents/components/AgentsCatalogTab.tsx)
|
|
82
|
+
- [AgentsCatalogTab.tsx](../../client/src/features/agents/components/AgentsCatalogTab.tsx) · [test](../../client/src/features/agents/components/__tests__/AgentsCatalogTab.test.tsx)
|
|
83
83
|
- [AiRefineOverlay.tsx](../../client/src/features/agents/components/AiRefineOverlay.tsx)
|
|
84
84
|
- [ProfileAnalyticsCard.tsx](../../client/src/features/agents/components/ProfileAnalyticsCard.tsx)
|
|
85
85
|
- [ProfileEditor.tsx](../../client/src/features/agents/components/ProfileEditor.tsx) · [test](../../client/src/features/agents/components/__tests__/ProfileEditor.test.tsx)
|
|
@@ -173,9 +173,9 @@ Without profiles, every rail uses the project's frontmatter-baked models. With p
|
|
|
173
173
|
Open **Agents** in the project's right sidebar. Two sub-tabs:
|
|
174
174
|
|
|
175
175
|
- **Profiles** — full CRUD over `.specrails/profiles/*.json`. The live validator enforces the baseline trio and routing ordering — Save is disabled with an "N issues to resolve" hint while the profile is broken.
|
|
176
|
-
- **Agents Catalog** — read-only viewer of
|
|
176
|
+
- **Agents Catalog** — read-only viewer of the runtime-defined baseline roles (`sr-architect`, `sr-developer`, `sr-reviewer`, shown with the definition the Core runtime actually executes) and your `custom-*` agents.
|
|
177
177
|
|
|
178
|
-
The empty state offers **Migrate from current agents**: one click creates a `default` profile
|
|
178
|
+
The empty state offers **Migrate from current agents**: one click creates a `default` profile from the baseline roles and your existing custom agents — no role file has to exist for it to work.
|
|
179
179
|
|
|
180
180
|
Each profile gets a per-profile analytics card showing usage for the last 7 / 30 / 90 days: jobs, success rate, avg tokens, avg duration.
|
|
181
181
|
|
package/package.json
CHANGED
|
@@ -19,8 +19,8 @@ const bundled_core_1 = require("./bundled-core");
|
|
|
19
19
|
const core_runtime_1 = require("./core-runtime");
|
|
20
20
|
// Windows has no `which`; probe PATH via `where` instead.
|
|
21
21
|
const WHICH_CMD = process.platform === 'win32' ? 'where' : 'which';
|
|
22
|
-
/** Paired development target; older supported contracts remain compatible. */
|
|
23
|
-
exports.EXPECTED_CORE_CONTRACT_SCHEMA_VERSION = '5.
|
|
22
|
+
/** Paired development target; older supported contracts (5.1) remain compatible. */
|
|
23
|
+
exports.EXPECTED_CORE_CONTRACT_SCHEMA_VERSION = '5.2';
|
|
24
24
|
// These must mirror KNOWN_VERBS in cli/specrails-desktop.ts
|
|
25
25
|
const DESKTOP_KNOWN_COMMANDS = new Set([
|
|
26
26
|
'implement',
|
|
@@ -29,6 +29,10 @@ const DESKTOP_KNOWN_COMMANDS = new Set([
|
|
|
29
29
|
// Commands older Cores still ship that Desktop no longer invokes. They are
|
|
30
30
|
// tolerated (not drift) but never required: Batch was folded into implement.
|
|
31
31
|
const DESKTOP_TOLERATED_LEGACY_COMMANDS = new Set(['batch-implement']);
|
|
32
|
+
// Checkpoints that contract 5.1 still declares but Desktop retired: role agents
|
|
33
|
+
// are runtime-defined since contract 5.2, so `agent_generation` is tolerated
|
|
34
|
+
// (not drift) and never required.
|
|
35
|
+
const DESKTOP_TOLERATED_LEGACY_CHECKPOINTS = new Set(['agent_generation']);
|
|
32
36
|
function isRecord(value) {
|
|
33
37
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
34
38
|
}
|
|
@@ -233,7 +237,7 @@ async function checkCoreCompat() {
|
|
|
233
237
|
: (contract.checkpoints && typeof contract.checkpoints === 'object'
|
|
234
238
|
? Object.keys(contract.checkpoints)
|
|
235
239
|
: []);
|
|
236
|
-
const missingCheckpoints = contractCheckpoints.filter((c) => !desktopCheckpointKeys.includes(c));
|
|
240
|
+
const missingCheckpoints = contractCheckpoints.filter((c) => !desktopCheckpointKeys.includes(c) && !DESKTOP_TOLERATED_LEGACY_CHECKPOINTS.has(c));
|
|
237
241
|
const deterministic = contract.lifecycle?.mode === 'deterministic' && contract.lifecycle.requiresEnrich === false;
|
|
238
242
|
// Supporting legacy installation checkpoints is compatible with Core 5;
|
|
239
243
|
// demanding them from a deterministic installer would recreate enrich.
|
package/server/dist/mcp/guide.js
CHANGED
|
@@ -385,9 +385,10 @@ can run Contract Refine require AI-spawn, including Explore conversions.
|
|
|
385
385
|
- **Plugins** (\`specrails_plugins\`): per-project MCP integrations; \`preview\`
|
|
386
386
|
the diff before \`install\`; a degraded plugin never blocks rail launches.
|
|
387
387
|
- **Profiles** (\`specrails_agents\`): per-project agent chains; the baseline
|
|
388
|
-
trio \`sr-architect\`/\`sr-developer\`/\`sr-reviewer\`
|
|
389
|
-
|
|
390
|
-
|
|
388
|
+
trio \`sr-architect\`/\`sr-developer\`/\`sr-reviewer\` are role identifiers
|
|
389
|
+
defined by the specrails-core runtime (no installed files) and are required
|
|
390
|
+
in every chain; routing rules end in a single terminal \`default: true\` rule;
|
|
391
|
+
requires specrails-core 4.1.0 or newer.
|
|
391
392
|
- **Setup** (\`specrails_setup\`): \`prerequisites\` → \`add_project\` →
|
|
392
393
|
\`install_config\` per provider → ONE \`install\` (provisions all chosen
|
|
393
394
|
providers; quick-only, offline) → poll \`checkpoints\` until
|
|
@@ -148,20 +148,36 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
148
148
|
if (!Array.isArray(admittedContext.repositories) || !admittedContext.repositories.length || admittedContext.repositories.some(repo => !repo || typeof repo.id !== 'string' || !repo.id))
|
|
149
149
|
throw new Error('Core context is missing its repository scope');
|
|
150
150
|
const source = (0, node_fs_1.existsSync)(options.configPath) ? 'project-role' : 'default';
|
|
151
|
-
|
|
151
|
+
// The engine owns the factory role definitions; the user's global overrides sit on top. The Implement
|
|
152
|
+
// rail merges them under any project text, and loop recipes resolve their `inherit` definitions here.
|
|
153
|
+
const effectiveRolePrompts = { ...(await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).rolePromptDefaults(), ...(0, agent_runtime_settings_1.loadRuntimeRolePrompts)() };
|
|
154
|
+
const loopConfig = options.loopConfig && (0, agent_runtime_settings_1.resolveInheritedRolePrompts)(options.loopConfig, effectiveRolePrompts);
|
|
155
|
+
const { config, origins } = (0, agent_runtime_effective_config_1.resolveEffectiveRuntimeConfig)(loopConfig ? (0, agent_runtime_settings_1.loadLoopRuntimeConfig)(options.configPath, loopConfig, options.providerOverride ?? { provider: options.defaultProvider ?? 'claude' }) : (0, agent_runtime_settings_1.loadRuntimeConfigFile)(options.configPath, options.defaultProvider), {
|
|
152
156
|
repositoryIds: admittedContext.repositories.map(repo => repo.id), source: options.loopConfig ? 'loop-role' : source, providerOverride: options.loopConfig ? undefined : options.providerOverride,
|
|
153
157
|
});
|
|
154
158
|
config.verification = scopedHostChecks(config.verification, admittedContext.repositories);
|
|
155
159
|
if (definitionEngine)
|
|
156
160
|
completeHostChecks(config, admittedContext.repositories);
|
|
157
|
-
if (!
|
|
158
|
-
config.rolePrompts = { ...
|
|
161
|
+
if (!loopConfig)
|
|
162
|
+
config.rolePrompts = { ...effectiveRolePrompts, ...config.rolePrompts };
|
|
159
163
|
// Core rejects unknown connection keys: drop the desktop-only local-engine
|
|
160
164
|
// fields (label/defaultModel/rates/supportsReasoningEffort) before Core sees it.
|
|
161
165
|
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
162
166
|
config.providers = (0, agent_runtime_settings_1.stripDesktopConnectionFields)(config.providers, (0, agent_runtime_settings_1.coreConnectionFieldGates)(runtime.api?.capabilities));
|
|
163
167
|
if (runtime.api?.capabilities?.configurableGuardrails !== 1)
|
|
164
168
|
delete config.guardrails;
|
|
169
|
+
// Setup commands are a `setupCommands` capability: an older Core rejects the unknown key, so strip it and say so once in the run log.
|
|
170
|
+
if (config.setup !== undefined && !(0, agent_runtime_settings_1.coreSupportsSetupCommands)(runtime.api?.capabilities)) {
|
|
171
|
+
if (config.setup.length) {
|
|
172
|
+
try {
|
|
173
|
+
options.onLine?.(`[runtime] setup commands ignored: the installed Core does not advertise setupCommands (${config.setup.length} configured)\n`);
|
|
174
|
+
}
|
|
175
|
+
catch { /* Advisory log observer. */ }
|
|
176
|
+
}
|
|
177
|
+
delete config.setup;
|
|
178
|
+
}
|
|
179
|
+
else if (config.setup)
|
|
180
|
+
config.setup = scopedHostChecks(config.setup, admittedContext.repositories);
|
|
165
181
|
if (definitionEngine && (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1))
|
|
166
182
|
throw new Error('engine_unsupported: Update Core to run workflow definitions');
|
|
167
183
|
const compilationConfig = structuredClone(config);
|
|
@@ -76,6 +76,8 @@ function resolveEffectiveRuntimeConfig(input, options) {
|
|
|
76
76
|
}
|
|
77
77
|
fillDefaultRoleModels(config);
|
|
78
78
|
config.verification = config.verification.filter(check => options.repositoryIds.includes(check.repositoryId));
|
|
79
|
+
if (config.setup)
|
|
80
|
+
config.setup = config.setup.filter(check => options.repositoryIds.includes(check.repositoryId));
|
|
79
81
|
return { config, origins: { architect: override ? 'explicit-launch-override' : options.source, developer: override ? 'explicit-launch-override' : options.source, reviewer: override ? 'explicit-launch-override' : options.source } };
|
|
80
82
|
}
|
|
81
83
|
/**
|
|
@@ -21,6 +21,10 @@ function withDefaultRoleModels(body) {
|
|
|
21
21
|
return { ...body, agents: filled.agents, ...(filled.roles ? { roles: filled.roles } : {}), ...(filled.fixer ? { fixer: filled.fixer } : {}) };
|
|
22
22
|
}
|
|
23
23
|
const loop_role_engines_1 = require("../../loops/runtime/loop-role-engines");
|
|
24
|
+
/** The settings API always answers `setup` as an array; the file keeps it absent until the user saves one. */
|
|
25
|
+
function withSetupList(config) {
|
|
26
|
+
return config ? { ...config, setup: config.setup ?? [] } : config;
|
|
27
|
+
}
|
|
24
28
|
function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
|
|
25
29
|
/** Guardrail catalog of the loaded Core (configurable-guardrails): ids + phase; the UI owns labels. `supported:false` on older cores. */
|
|
26
30
|
router.get('/:projectId/agent-runtime/guardrails', async (_req, res) => {
|
|
@@ -70,7 +74,7 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
|
|
|
70
74
|
openRolesAvailable = capabilities?.openRoles === 1;
|
|
71
75
|
}
|
|
72
76
|
catch { /* Ordinary settings remain readable without capability support. */ }
|
|
73
|
-
res.json({ configured, config, runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null, efficiencyAvailable, openRolesAvailable,
|
|
77
|
+
res.json({ configured, config: withSetupList(config), runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null, efficiencyAvailable, openRolesAvailable,
|
|
74
78
|
workflowRoleDefaults: openRolesAvailable && config ? (0, agent_runtime_effective_config_1.workflowRoleDefaults)(config) : {} });
|
|
75
79
|
}
|
|
76
80
|
catch (err) {
|
|
@@ -147,7 +151,7 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
|
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
const saved = (0, agent_runtime_settings_1.saveAgentRuntimeConfig)(ctx(req).project, config);
|
|
150
|
-
res.json({ configured: true, config: saved, runtimeAvailable, efficiencyAvailable, openRolesAvailable });
|
|
154
|
+
res.json({ configured: true, config: withSetupList(saved), runtimeAvailable, efficiencyAvailable, openRolesAvailable });
|
|
151
155
|
}
|
|
152
156
|
catch (err) {
|
|
153
157
|
const validation = err instanceof agent_runtime_settings_1.AgentRuntimeConfigError;
|
|
@@ -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.AgentRuntimeConfigError = exports.REVIEW_THRESHOLD_FLOORS = exports.MAX_OUTPUT_TOKENS_MAX = exports.MAX_OUTPUT_TOKENS_MIN = exports.CONTEXT_WINDOW_TOKENS_MAX = exports.CONTEXT_WINDOW_TOKENS_MIN = exports.RUNTIME_ROLE_ID = void 0;
|
|
6
|
+
exports.INHERIT_ROLE_PROMPT = exports.AgentRuntimeConfigError = exports.REVIEW_THRESHOLD_FLOORS = exports.MAX_OUTPUT_TOKENS_MAX = exports.MAX_OUTPUT_TOKENS_MIN = exports.CONTEXT_WINDOW_TOKENS_MAX = exports.CONTEXT_WINDOW_TOKENS_MIN = exports.RUNTIME_ROLE_ID = void 0;
|
|
7
7
|
exports.stripDesktopConnectionFields = stripDesktopConnectionFields;
|
|
8
8
|
exports.coreConnectionFieldGates = coreConnectionFieldGates;
|
|
9
9
|
exports.validateLoopRuntimeSettings = validateLoopRuntimeSettings;
|
|
@@ -18,11 +18,14 @@ exports.saveRuntimeProviders = saveRuntimeProviders;
|
|
|
18
18
|
exports.loadRuntimeConfigFile = loadRuntimeConfigFile;
|
|
19
19
|
exports.loadAgentRuntimeConfig = loadAgentRuntimeConfig;
|
|
20
20
|
exports.saveAgentRuntimeConfig = saveAgentRuntimeConfig;
|
|
21
|
+
exports.inheritedRolePrompt = inheritedRolePrompt;
|
|
22
|
+
exports.resolveInheritedRolePrompts = resolveInheritedRolePrompts;
|
|
21
23
|
exports.validateRuntimeRolePrompts = validateRuntimeRolePrompts;
|
|
22
24
|
exports.loadRuntimeRolePrompts = loadRuntimeRolePrompts;
|
|
23
25
|
exports.saveRuntimeRolePrompts = saveRuntimeRolePrompts;
|
|
24
26
|
exports.validateRuntimeProviderOverride = validateRuntimeProviderOverride;
|
|
25
27
|
exports.forCoreRuntime = forCoreRuntime;
|
|
28
|
+
exports.coreSupportsSetupCommands = coreSupportsSetupCommands;
|
|
26
29
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
27
30
|
const node_path_1 = __importDefault(require("node:path"));
|
|
28
31
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -134,17 +137,18 @@ function validateLoopRuntimeSettings(input, providers = loadRuntimeProviders())
|
|
|
134
137
|
const config = validateAgentRuntimeConfig({ ...input, enabled: true, providers: portableProviders, verification: [] });
|
|
135
138
|
if (['architect', 'developer', 'reviewer', 'fixer'].some(role => !config.rolePrompts?.[role]?.trim()))
|
|
136
139
|
throw new AgentRuntimeConfigError('Loop agents require an explicit definition for architect, developer, reviewer and fixer');
|
|
137
|
-
const { enabled: _enabled, providers: _providers, verification: _checks, ...settings } = config;
|
|
140
|
+
const { enabled: _enabled, providers: _providers, verification: _checks, setup: _setup, ...settings } = config;
|
|
138
141
|
return settings;
|
|
139
142
|
}
|
|
140
143
|
/** Read only repository checks from the old project file. Role migration is explicit. */
|
|
141
144
|
function loadLoopRuntimeConfig(file, input, selection = { provider: 'claude' }) {
|
|
142
|
-
let verification = [];
|
|
145
|
+
let verification = [], setup = undefined;
|
|
143
146
|
try {
|
|
144
147
|
const saved = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
|
|
145
148
|
if (!saved || typeof saved !== 'object' || Array.isArray(saved))
|
|
146
149
|
throw new AgentRuntimeConfigError('Saved runtime configuration must be an object');
|
|
147
150
|
verification = saved.verification ?? [];
|
|
151
|
+
setup = saved.setup;
|
|
148
152
|
}
|
|
149
153
|
catch (error) {
|
|
150
154
|
if (error.code !== 'ENOENT')
|
|
@@ -161,7 +165,7 @@ function loadLoopRuntimeConfig(file, input, selection = { provider: 'claude' })
|
|
|
161
165
|
settings.fixer = inherit(settings.fixer);
|
|
162
166
|
for (const [role, agent] of Object.entries(settings.roles ?? {}))
|
|
163
167
|
settings.roles[role] = inherit(agent);
|
|
164
|
-
return validateAgentRuntimeConfig({ ...settings, providers, enabled: true, verification });
|
|
168
|
+
return validateAgentRuntimeConfig({ ...settings, providers, enabled: true, verification, ...(setup !== undefined ? { setup } : {}) });
|
|
165
169
|
}
|
|
166
170
|
const CLI_PROVIDERS = ['claude', 'codex', 'gemini', 'kimi'];
|
|
167
171
|
const ROLES = ['architect', 'developer', 'reviewer'];
|
|
@@ -258,10 +262,12 @@ function validateAgentRuntimeConfig(input) {
|
|
|
258
262
|
if (!Number.isFinite(value) || (key !== 'maxCostUsd' && !Number.isSafeInteger(value)))
|
|
259
263
|
throw new AgentRuntimeConfigError('Workflow limits must be finite, safe numbers');
|
|
260
264
|
}
|
|
261
|
-
|
|
265
|
+
// `setup` entries share the verification entry shape and rules (Core validates both with one validator; keys are unique across the two lists).
|
|
266
|
+
const hostCommands = [...config.verification, ...(config.setup ?? [])];
|
|
267
|
+
const keys = hostCommands.flatMap(command => command.key ? [command.key] : []);
|
|
262
268
|
if (new Set(keys).size !== keys.length)
|
|
263
269
|
throw new AgentRuntimeConfigError('Verification check keys must be unique');
|
|
264
|
-
for (const command of
|
|
270
|
+
for (const command of hostCommands) {
|
|
265
271
|
for (const value of [command.label, ...(command.policy?.inputs ?? []), ...(command.policy?.toolchainInputs ?? []), ...(command.policy?.resources ?? [])])
|
|
266
272
|
if (value !== undefined && (!value.trim() || value.includes('\0')))
|
|
267
273
|
throw new AgentRuntimeConfigError('Invalid verification label or policy input');
|
|
@@ -419,6 +425,27 @@ function saveAgentRuntimeConfig(project, input) {
|
|
|
419
425
|
return config;
|
|
420
426
|
}
|
|
421
427
|
const PROMPT_ROLES = [...ROLES, 'fixer'];
|
|
428
|
+
/** A loop recipe definition that follows the engine: resolved at launch to the selected Core's factory definition plus the user's global override. */
|
|
429
|
+
exports.INHERIT_ROLE_PROMPT = 'inherit';
|
|
430
|
+
const INHERITED_ROLE_PROMPT = /^inherit:(architect|developer|reviewer|fixer)$/;
|
|
431
|
+
/** The prompt a declared recipe role carries when it follows a built-in definition (`inherit:<role>`), or the recipe's own text. */
|
|
432
|
+
function inheritedRolePrompt(prompts, role) {
|
|
433
|
+
const value = prompts?.[role];
|
|
434
|
+
return value === undefined || value === exports.INHERIT_ROLE_PROMPT ? `${exports.INHERIT_ROLE_PROMPT}:${role}` : value;
|
|
435
|
+
}
|
|
436
|
+
/** Replace `inherit` definitions (built-in roles) and `inherit:<role>` prompts (declared roles) with the effective engine definitions. Explicit recipe text is kept verbatim. */
|
|
437
|
+
function resolveInheritedRolePrompts(settings, effective) {
|
|
438
|
+
// A Core predating the fixer stance has no fixer default: the fixer then follows the developer definition.
|
|
439
|
+
const resolved = { ...effective, fixer: effective.fixer ?? effective.developer };
|
|
440
|
+
const lookup = (role) => resolved[role];
|
|
441
|
+
const rolePrompts = settings.rolePrompts && Object.fromEntries(Object.entries(settings.rolePrompts).map(([role, text]) => [role, text === exports.INHERIT_ROLE_PROMPT ? lookup(role) ?? text : text]));
|
|
442
|
+
const roles = settings.roles && Object.fromEntries(Object.entries(settings.roles).map(([id, role]) => {
|
|
443
|
+
const inherited = role.prompt?.match(INHERITED_ROLE_PROMPT);
|
|
444
|
+
const text = inherited ? lookup(inherited[1]) : undefined;
|
|
445
|
+
return [id, text ? { ...role, prompt: text } : role];
|
|
446
|
+
}));
|
|
447
|
+
return { ...settings, ...(rolePrompts ? { rolePrompts } : {}), ...(roles ? { roles } : {}) };
|
|
448
|
+
}
|
|
422
449
|
function validateRuntimeRolePrompts(input, extraRoles = []) {
|
|
423
450
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
424
451
|
throw new AgentRuntimeConfigError('Role prompts must be an object');
|
|
@@ -459,7 +486,7 @@ function validateRuntimeProviderOverride(value) {
|
|
|
459
486
|
throw new AgentRuntimeConfigError('Invalid runtime provider override effort');
|
|
460
487
|
return structuredClone(raw);
|
|
461
488
|
}
|
|
462
|
-
/** Older Core runtimes reject unknown top-level keys: drop `guardrails` unless the loaded Core advertises `configurableGuardrails`. */
|
|
489
|
+
/** Older Core runtimes reject unknown top-level keys: drop `guardrails` unless the loaded Core advertises `configurableGuardrails`, and `setup` unless it advertises `setupCommands`. */
|
|
463
490
|
function forCoreRuntime(config, capabilities) {
|
|
464
491
|
let out = config;
|
|
465
492
|
if (capabilities?.openRoles !== 1 && out.roles !== undefined) {
|
|
@@ -472,6 +499,10 @@ function forCoreRuntime(config, capabilities) {
|
|
|
472
499
|
const { guardrails: _dropped, ...rest } = out;
|
|
473
500
|
out = rest;
|
|
474
501
|
}
|
|
502
|
+
if (!coreSupportsSetupCommands(capabilities) && out.setup !== undefined) {
|
|
503
|
+
const { setup: _dropped, ...rest } = out;
|
|
504
|
+
out = rest;
|
|
505
|
+
}
|
|
475
506
|
// The per-role thinking switch is known only to cores advertising `roleThinkingControl`.
|
|
476
507
|
if (capabilities?.roleThinkingControl !== 1 && ([...Object.values(out.agents ?? {}), ...Object.values(out.roles ?? {})].some(agent => agent?.thinking !== undefined) || out.fixer?.thinking !== undefined)) {
|
|
477
508
|
const strip = (agent) => { const { thinking: _t, ...rest } = agent; return rest; };
|
|
@@ -479,3 +510,7 @@ function forCoreRuntime(config, capabilities) {
|
|
|
479
510
|
}
|
|
480
511
|
return out;
|
|
481
512
|
}
|
|
513
|
+
/** Core advertises `setupCommands: 1` when its verify piece runs `RuntimeConfig.setup` before each verification plan. */
|
|
514
|
+
function coreSupportsSetupCommands(capabilities) {
|
|
515
|
+
return (capabilities?.setupCommands ?? 0) >= 1;
|
|
516
|
+
}
|
|
@@ -18,6 +18,8 @@ const providers_1 = require("../../../providers");
|
|
|
18
18
|
const kimi_skill_prompt_1 = require("../../../providers/kimi-skill-prompt");
|
|
19
19
|
const profile_manager_1 = require("./profile-manager");
|
|
20
20
|
const workspace_resolution_1 = require("../../../workspace-resolution");
|
|
21
|
+
const agent_runtime_loader_1 = require("../../agent-runtime/runtime/agent-runtime-loader");
|
|
22
|
+
const agent_runtime_settings_1 = require("../../agent-runtime/runtime/agent-runtime-settings");
|
|
21
23
|
const project_router_helpers_1 = require("../../../project-router-helpers");
|
|
22
24
|
/**
|
|
23
25
|
* Relocate-artifacts gate: the dir whose `.specrails/{profiles,specrails-version}`
|
|
@@ -70,6 +72,30 @@ function agentsCatalogDir(project, provider) {
|
|
|
70
72
|
function listAgentFiles(project, provider) {
|
|
71
73
|
return (0, agent_catalog_1.listNativeAgentFiles)(specRoot(project), provider ?? project.provider ?? 'claude');
|
|
72
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The baseline roles are identifiers of runtime-defined roles: specrails-core
|
|
77
|
+
* never reads an `sr-*` file for them. Their read-only catalog body is the Core
|
|
78
|
+
* runtime definition (defaults merged with the global Settings overrides).
|
|
79
|
+
*/
|
|
80
|
+
const BASELINE_RUNTIME_ROLES = {
|
|
81
|
+
'sr-architect': 'architect',
|
|
82
|
+
'sr-developer': 'developer',
|
|
83
|
+
'sr-reviewer': 'reviewer',
|
|
84
|
+
};
|
|
85
|
+
const RUNTIME_DEFINED_DESCRIPTION = 'Runtime-defined by specrails-core: the implement pipeline runs this role from the Core runtime definition below, not from an installed file.';
|
|
86
|
+
async function loadRuntimeRoleDefinitions() {
|
|
87
|
+
try {
|
|
88
|
+
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
89
|
+
return { prompts: { ...runtime.rolePromptDefaults(), ...(0, agent_runtime_settings_1.loadRuntimeRolePrompts)() } };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return { prompts: {}, error: error instanceof Error ? error.message : 'Core role prompt catalog is unavailable' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function runtimeRoleBody(definitions, agentId) {
|
|
96
|
+
const role = BASELINE_RUNTIME_ROLES[agentId];
|
|
97
|
+
return role ? definitions.prompts[role] : undefined;
|
|
98
|
+
}
|
|
73
99
|
const AGENTS_SECTION_ENABLED = process.env.SPECRAILS_AGENTS_SECTION !== 'false';
|
|
74
100
|
function handleError(res, err) {
|
|
75
101
|
if (err instanceof profile_manager_1.ProfileValidationError) {
|
|
@@ -175,12 +201,11 @@ function createProfilesRouter() {
|
|
|
175
201
|
? new Map((0, project_router_helpers_1.readAgentModels)({ ...project, provider })
|
|
176
202
|
.map((entry) => [entry.name, entry.model]))
|
|
177
203
|
: null;
|
|
178
|
-
const
|
|
179
|
-
if (!fs_1.default.existsSync(agentsDir)) {
|
|
180
|
-
res.status(400).json({ error: `no ${adapter.projectDirName} roles catalog found` });
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
204
|
+
const baseline = [...adapter.baselineAgents()];
|
|
183
205
|
const agents = [];
|
|
206
|
+
// Role files are optional: Core >= 6.3 defines the baseline roles in its
|
|
207
|
+
// runtime. Read `model:` only from `sr-*` files that still exist (older
|
|
208
|
+
// installs); every other baseline id is seeded with the adapter default.
|
|
184
209
|
for (const entry of listAgentFiles(project, provider)) {
|
|
185
210
|
if (!entry.id.startsWith('sr-'))
|
|
186
211
|
continue;
|
|
@@ -202,13 +227,10 @@ function createProfilesRouter() {
|
|
|
202
227
|
}
|
|
203
228
|
agents.push({ id, model });
|
|
204
229
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
error: `missing baseline agents in this project: ${missing.join(', ')}. Run 'npx specrails-core@latest update' first.`,
|
|
210
|
-
});
|
|
211
|
-
return;
|
|
230
|
+
for (const id of baseline) {
|
|
231
|
+
if (agents.some((a) => a.id === id))
|
|
232
|
+
continue;
|
|
233
|
+
agents.push({ id, model: projectedKimiModels?.get(id) ?? adapter.defaultModel() });
|
|
212
234
|
}
|
|
213
235
|
// Order: baseline trio first (architect, developer, reviewer), optional
|
|
214
236
|
// agents in the middle. sr-merge-resolver is no longer a baseline agent;
|
|
@@ -379,39 +401,44 @@ function createProfilesRouter() {
|
|
|
379
401
|
}
|
|
380
402
|
});
|
|
381
403
|
// GET /api/projects/:projectId/profiles/catalog
|
|
382
|
-
// List
|
|
383
|
-
|
|
404
|
+
// List the selected provider's roles: the runtime-defined baseline (upstream,
|
|
405
|
+
// synthesized from the adapter) plus the native `custom-*` catalog.
|
|
406
|
+
router.get('/catalog', async (req, res) => {
|
|
384
407
|
try {
|
|
385
408
|
const { project } = ctx(req);
|
|
386
409
|
const provider = requestedProvider(req, project);
|
|
387
|
-
const
|
|
388
|
-
if (files.length === 0) {
|
|
389
|
-
res.json({ agents: [] });
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
410
|
+
const adapter = projectAdapter(project, provider);
|
|
392
411
|
const agents = [];
|
|
393
|
-
|
|
412
|
+
// Baseline roles never depend on `sr-*` files; a missing roles directory
|
|
413
|
+
// still yields the three runtime-defined entries.
|
|
414
|
+
const definitions = await loadRuntimeRoleDefinitions();
|
|
415
|
+
for (const id of adapter.baselineAgents()) {
|
|
416
|
+
agents.push({
|
|
417
|
+
id,
|
|
418
|
+
kind: 'upstream',
|
|
419
|
+
description: RUNTIME_DEFINED_DESCRIPTION,
|
|
420
|
+
body: runtimeRoleBody(definitions, id) ?? '',
|
|
421
|
+
roleId: BASELINE_RUNTIME_ROLES[id],
|
|
422
|
+
...(definitions.error ? { runtimeRoleError: definitions.error } : {}),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
for (const entry of listAgentFiles(project, provider)) {
|
|
394
426
|
const id = entry.id;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
: id.startsWith('custom-')
|
|
398
|
-
? 'custom'
|
|
399
|
-
: null;
|
|
400
|
-
if (!kind)
|
|
427
|
+
// Stale `sr-*` files from older Cores are not catalog entries anymore.
|
|
428
|
+
if (!id.startsWith('custom-'))
|
|
401
429
|
continue;
|
|
430
|
+
const kind = 'custom';
|
|
402
431
|
let description;
|
|
403
432
|
let model;
|
|
404
433
|
let projected = {};
|
|
405
434
|
try {
|
|
406
435
|
const body = fs_1.default.readFileSync(entry.file, 'utf8');
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
projected = { runtimeRoleError: error instanceof Error ? error.message : 'Invalid Core role descriptor' };
|
|
414
|
-
}
|
|
436
|
+
try {
|
|
437
|
+
const result = (0, agent_role_descriptor_1.projectCustomAgentRole)({ id, content: body }, { provider });
|
|
438
|
+
projected = { roleId: result.id, runtimeRoleDefaults: result.role };
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
projected = { runtimeRoleError: error instanceof Error ? error.message : 'Invalid Core role descriptor' };
|
|
415
442
|
}
|
|
416
443
|
if (provider === 'kimi') {
|
|
417
444
|
// Use the same js-yaml metadata parser as validation/execution so
|
|
@@ -468,8 +495,9 @@ function createProfilesRouter() {
|
|
|
468
495
|
}
|
|
469
496
|
});
|
|
470
497
|
// GET /api/projects/:projectId/profiles/catalog/:agentId
|
|
471
|
-
// Return the full
|
|
472
|
-
|
|
498
|
+
// Return the full body of a single agent: the Core runtime definition for a
|
|
499
|
+
// baseline role (read-only), the native file for custom-* (editable).
|
|
500
|
+
router.get('/catalog/:agentId', async (req, res) => {
|
|
473
501
|
try {
|
|
474
502
|
const { project } = ctx(req);
|
|
475
503
|
const provider = requestedProvider(req, project);
|
|
@@ -480,6 +508,16 @@ function createProfilesRouter() {
|
|
|
480
508
|
}
|
|
481
509
|
const file = agentFile(project, agentId, provider);
|
|
482
510
|
if (!fs_1.default.existsSync(file)) {
|
|
511
|
+
if (projectAdapter(project, provider).baselineAgents().includes(agentId)) {
|
|
512
|
+
const definitions = await loadRuntimeRoleDefinitions();
|
|
513
|
+
const body = runtimeRoleBody(definitions, agentId);
|
|
514
|
+
if (body !== undefined) {
|
|
515
|
+
res.json({ id: agentId, body, runtimeDefined: true });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
res.status(503).json({ error: definitions.error ?? 'Core role prompt catalog is unavailable' });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
483
521
|
res.status(404).json({ error: 'agent not found' });
|
|
484
522
|
return;
|
|
485
523
|
}
|