specrails-desktop 2.60.0 → 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-3J7fR8yB.js → ActivityFeedPage-Wi5XNgoG.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-D4a3wYM1.js → AgentBrowserCapture-CkNWDZT3.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-Dps30tk9.js → AgentModeAnalyticsPane-CXz2j-Bt.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-CvLimey8.js → AgentModeCodePane-DnsBPtpS.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-25tQlLpF.js → AgentModeJobsPane-U1J6PaBo.js} +1 -1
- package/client/dist/assets/{AgentsPage-CUDp_sfw.js → AgentsPage-CXrqu5vo.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-DXWMGn8b.js → AnalyticsPage-Cvtt-LOO.js} +1 -1
- package/client/dist/assets/{CodePage-D981zRHy.js → CodePage-D35o-MFO.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-BkFBq0E6.js → DesktopAnalyticsPage-CsDFy5rp.js} +1 -1
- package/client/dist/assets/{DocsDialog-BL7uSbIJ.js → DocsDialog-CSlMPemn.js} +1 -1
- package/client/dist/assets/{DocsPage-CsDLbmZX.js → DocsPage-BKlYAvWq.js} +1 -1
- package/client/dist/assets/{ExportDropdown-B4Olg1Fk.js → ExportDropdown-CweP_1GP.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-CFKvbQeJ.js +19 -0
- package/client/dist/assets/{JobDetailModal-DS_Owxs6.js → JobDetailModal-DYyGq1Vp.js} +1 -1
- package/client/dist/assets/{JobDetailPage-1TUQ9vlE.js → JobDetailPage-C6gpdYvj.js} +1 -1
- package/client/dist/assets/{JobsPage-BSrl8P7u.js → JobsPage-CgS0pnKd.js} +1 -1
- package/client/dist/assets/LoopBuilderPage-jUp20DSZ.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-CH1r-KhU.js → LoopPreviewModal-DkLRfsYX.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-CiZdI6aw.js → LoopWindowSurface-CuACkXFV.js} +1 -1
- package/client/dist/assets/{LoopsPage-BwFujdGi.js → LoopsPage-ClSrWzDd.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-BD2bsRBe.js → MinimizedChatsContext-CroAs6Kh.js} +1 -1
- package/client/dist/assets/{PluginsPage-B8f49Mvn.js → PluginsPage-nCKQOwRL.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-D7q-O6h3.js → ProjectSettingsDialog-BVDYwllU.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-NNorcGzT.js → RepositoryDeliveries-DJCm1pZr.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-D-jb4bI_.js → RepositoryScopeSelector-CeCr6Jz-.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-DpZSzF-Q.js → ReviewPacketPage-kTErrEjx.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-5CZdAZ0G.js → TemplatePreviewModal-B8yd1AGU.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-BHmtj1As.js → TicketDetailModalContext-YBJX3RmP.js} +1 -1
- package/client/dist/assets/{Trans-wmTUDVyC.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/{formatDistanceToNow-Dax4zqPF.js → formatDistanceToNow-DZ3VWsHq.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-dpIo42JZ.js → getTimezoneOffsetInMilliseconds-9JiGlFrx.js} +1 -1
- package/client/dist/assets/{index-g8S62tpS.js → index-lTGJJTuk.js} +8 -8
- package/client/dist/assets/{jira-api-CLrIvXFm.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-BkyT8AjU.js → project-repositories-CadLZOnr.js} +1 -1
- package/client/dist/assets/{spending-Oov7p52A.js → spending-DQDNtM8q.js} +1 -1
- package/client/dist/assets/{useDesktop-DpiS8Uov.js → useDesktop-DKNbOF0u.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-DBm1mul0.js → useRuntimeRuns-D3YMw9qx.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-BZxKaVNT.js → useSharedWebSocket-DaWuiO0S.js} +2 -2
- package/client/dist/index.html +17 -17
- package/docs/internals/configuration.md +2 -0
- package/docs/internals/core-runtime-updates.md +3 -3
- package/docs/internals/programmatic-agent-runtime.md +2 -0
- package/package.json +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +12 -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/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 +28 -9
- package/server/dist/modules/loops/runtime/loop-templates.js +3 -3
- package/server/dist/schemas/agent-runtime.schema.json +9 -7
- package/client/dist/assets/InteractiveJobComposer-Z6fl4791.js +0 -19
- package/client/dist/assets/LoopBuilderPage-Dh5uk2Qy.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/index.html
CHANGED
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
.specrails-splash__mark .pill { opacity: 1; }
|
|
205
205
|
}
|
|
206
206
|
</style>
|
|
207
|
-
<script type="module" crossorigin src="/assets/index-
|
|
207
|
+
<script type="module" crossorigin src="/assets/index-lTGJJTuk.js"></script>
|
|
208
208
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CNC7AqOf.js">
|
|
209
209
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-zu2_FqZY.js">
|
|
210
210
|
<link rel="modulepreload" crossorigin href="/assets/react-dom-DiWzBDP9.js">
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
<link rel="modulepreload" crossorigin href="/assets/activity-DiyjsgQV.js">
|
|
215
215
|
<link rel="modulepreload" crossorigin href="/assets/addspec-BOvgmcgk.js">
|
|
216
216
|
<link rel="modulepreload" crossorigin href="/assets/agent-Z5s_GTP0.js">
|
|
217
|
-
<link rel="modulepreload" crossorigin href="/assets/agentRuntime-
|
|
217
|
+
<link rel="modulepreload" crossorigin href="/assets/agentRuntime-C_yLPKDy.js">
|
|
218
218
|
<link rel="modulepreload" crossorigin href="/assets/agents-Doe1sHY5.js">
|
|
219
219
|
<link rel="modulepreload" crossorigin href="/assets/agentstudio-BzugqB_N.js">
|
|
220
220
|
<link rel="modulepreload" crossorigin href="/assets/aiedit-iIjz2_Qk.js">
|
|
@@ -231,9 +231,9 @@
|
|
|
231
231
|
<link rel="modulepreload" crossorigin href="/assets/integrations-Dwl9KQp0.js">
|
|
232
232
|
<link rel="modulepreload" crossorigin href="/assets/jira-FXr7u2lh.js">
|
|
233
233
|
<link rel="modulepreload" crossorigin href="/assets/jobs-De3EqaO0.js">
|
|
234
|
-
<link rel="modulepreload" crossorigin href="/assets/loops
|
|
234
|
+
<link rel="modulepreload" crossorigin href="/assets/loops-WyWDkhok.js">
|
|
235
235
|
<link rel="modulepreload" crossorigin href="/assets/mcp-CUAN6ORK.js">
|
|
236
|
-
<link rel="modulepreload" crossorigin href="/assets/narration-
|
|
236
|
+
<link rel="modulepreload" crossorigin href="/assets/narration-DbHnz0qV.js">
|
|
237
237
|
<link rel="modulepreload" crossorigin href="/assets/nav--u7iPYlS.js">
|
|
238
238
|
<link rel="modulepreload" crossorigin href="/assets/packet-B0H15elv.js">
|
|
239
239
|
<link rel="modulepreload" crossorigin href="/assets/settings-Dbyd6gmj.js">
|
|
@@ -243,15 +243,15 @@
|
|
|
243
243
|
<link rel="modulepreload" crossorigin href="/assets/terminal-BAzpHtpY.js">
|
|
244
244
|
<link rel="modulepreload" crossorigin href="/assets/tickets-D41ZMQpf.js">
|
|
245
245
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-Bjbbp_KS.js">
|
|
246
|
-
<link rel="modulepreload" crossorigin href="/assets/useSharedWebSocket-
|
|
247
|
-
<link rel="modulepreload" crossorigin href="/assets/Trans-
|
|
248
|
-
<link rel="modulepreload" crossorigin href="/assets/useDesktop-
|
|
246
|
+
<link rel="modulepreload" crossorigin href="/assets/useSharedWebSocket-DaWuiO0S.js">
|
|
247
|
+
<link rel="modulepreload" crossorigin href="/assets/Trans-D5eOjfvG.js">
|
|
248
|
+
<link rel="modulepreload" crossorigin href="/assets/useDesktop-DKNbOF0u.js">
|
|
249
249
|
<link rel="modulepreload" crossorigin href="/assets/provider-capabilities-lePfeT_Y.js">
|
|
250
250
|
<link rel="modulepreload" crossorigin href="/assets/save-OsUqVn99.js">
|
|
251
|
-
<link rel="modulepreload" crossorigin href="/assets/spending-
|
|
251
|
+
<link rel="modulepreload" crossorigin href="/assets/spending-DQDNtM8q.js">
|
|
252
252
|
<link rel="modulepreload" crossorigin href="/assets/sparkles-DrY8hb0X.js">
|
|
253
253
|
<link rel="modulepreload" crossorigin href="/assets/x-CBMI9ejn.js">
|
|
254
|
-
<link rel="modulepreload" crossorigin href="/assets/MinimizedChatsContext-
|
|
254
|
+
<link rel="modulepreload" crossorigin href="/assets/MinimizedChatsContext-CroAs6Kh.js">
|
|
255
255
|
<link rel="modulepreload" crossorigin href="/assets/zap-CUOlvtT1.js">
|
|
256
256
|
<link rel="modulepreload" crossorigin href="/assets/file-text-BcLCfcWZ.js">
|
|
257
257
|
<link rel="modulepreload" crossorigin href="/assets/circle-x-CMYsN4ZH.js">
|
|
@@ -266,20 +266,20 @@
|
|
|
266
266
|
<link rel="modulepreload" crossorigin href="/assets/search-Cq_gE3Ik.js">
|
|
267
267
|
<link rel="modulepreload" crossorigin href="/assets/trash-2-BOU3WdZZ.js">
|
|
268
268
|
<link rel="modulepreload" crossorigin href="/assets/triangle-alert-H9T4jAmh.js">
|
|
269
|
-
<link rel="modulepreload" crossorigin href="/assets/jira-api-
|
|
269
|
+
<link rel="modulepreload" crossorigin href="/assets/jira-api-Bp0zXwjg.js">
|
|
270
270
|
<link rel="modulepreload" crossorigin href="/assets/clsx-CjueKrWZ.js">
|
|
271
271
|
<link rel="modulepreload" crossorigin href="/assets/utils-BC4DHlz2.js">
|
|
272
272
|
<link rel="modulepreload" crossorigin href="/assets/dialog-CMgps2MY.js">
|
|
273
273
|
<link rel="modulepreload" crossorigin href="/assets/lib-CGdyxIWg.js">
|
|
274
|
-
<link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-
|
|
275
|
-
<link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-
|
|
274
|
+
<link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-9JiGlFrx.js">
|
|
275
|
+
<link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-DZ3VWsHq.js">
|
|
276
276
|
<link rel="modulepreload" crossorigin href="/assets/button-D1xExSTF.js">
|
|
277
|
-
<link rel="modulepreload" crossorigin href="/assets/project-repositories-
|
|
278
|
-
<link rel="modulepreload" crossorigin href="/assets/RepositoryScopeSelector-
|
|
279
|
-
<link rel="modulepreload" crossorigin href="/assets/TicketDetailModalContext-
|
|
277
|
+
<link rel="modulepreload" crossorigin href="/assets/project-repositories-CadLZOnr.js">
|
|
278
|
+
<link rel="modulepreload" crossorigin href="/assets/RepositoryScopeSelector-CeCr6Jz-.js">
|
|
279
|
+
<link rel="modulepreload" crossorigin href="/assets/TicketDetailModalContext-YBJX3RmP.js">
|
|
280
280
|
<link rel="modulepreload" crossorigin href="/assets/core-B3mKBIh5.js">
|
|
281
|
-
<link rel="modulepreload" crossorigin href="/assets/RepositoryDeliveries-
|
|
282
|
-
<link rel="modulepreload" crossorigin href="/assets/useRuntimeRuns-
|
|
281
|
+
<link rel="modulepreload" crossorigin href="/assets/RepositoryDeliveries-DJCm1pZr.js">
|
|
282
|
+
<link rel="modulepreload" crossorigin href="/assets/useRuntimeRuns-D3YMw9qx.js">
|
|
283
283
|
<link rel="modulepreload" crossorigin href="/assets/event-Bt7f3wXe.js">
|
|
284
284
|
<link rel="stylesheet" crossorigin href="/assets/index-Bfunvi6w.css">
|
|
285
285
|
</head>
|
|
@@ -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,
|
|
@@ -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.
|
package/package.json
CHANGED
|
@@ -166,6 +166,18 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
166
166
|
config.providers = (0, agent_runtime_settings_1.stripDesktopConnectionFields)(config.providers, (0, agent_runtime_settings_1.coreConnectionFieldGates)(runtime.api?.capabilities));
|
|
167
167
|
if (runtime.api?.capabilities?.configurableGuardrails !== 1)
|
|
168
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);
|
|
169
181
|
if (definitionEngine && (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1))
|
|
170
182
|
throw new Error('engine_unsupported: Update Core to run workflow definitions');
|
|
171
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;
|
|
@@ -25,6 +25,7 @@ exports.loadRuntimeRolePrompts = loadRuntimeRolePrompts;
|
|
|
25
25
|
exports.saveRuntimeRolePrompts = saveRuntimeRolePrompts;
|
|
26
26
|
exports.validateRuntimeProviderOverride = validateRuntimeProviderOverride;
|
|
27
27
|
exports.forCoreRuntime = forCoreRuntime;
|
|
28
|
+
exports.coreSupportsSetupCommands = coreSupportsSetupCommands;
|
|
28
29
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
29
30
|
const node_path_1 = __importDefault(require("node:path"));
|
|
30
31
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -136,17 +137,18 @@ function validateLoopRuntimeSettings(input, providers = loadRuntimeProviders())
|
|
|
136
137
|
const config = validateAgentRuntimeConfig({ ...input, enabled: true, providers: portableProviders, verification: [] });
|
|
137
138
|
if (['architect', 'developer', 'reviewer', 'fixer'].some(role => !config.rolePrompts?.[role]?.trim()))
|
|
138
139
|
throw new AgentRuntimeConfigError('Loop agents require an explicit definition for architect, developer, reviewer and fixer');
|
|
139
|
-
const { enabled: _enabled, providers: _providers, verification: _checks, ...settings } = config;
|
|
140
|
+
const { enabled: _enabled, providers: _providers, verification: _checks, setup: _setup, ...settings } = config;
|
|
140
141
|
return settings;
|
|
141
142
|
}
|
|
142
143
|
/** Read only repository checks from the old project file. Role migration is explicit. */
|
|
143
144
|
function loadLoopRuntimeConfig(file, input, selection = { provider: 'claude' }) {
|
|
144
|
-
let verification = [];
|
|
145
|
+
let verification = [], setup = undefined;
|
|
145
146
|
try {
|
|
146
147
|
const saved = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
|
|
147
148
|
if (!saved || typeof saved !== 'object' || Array.isArray(saved))
|
|
148
149
|
throw new AgentRuntimeConfigError('Saved runtime configuration must be an object');
|
|
149
150
|
verification = saved.verification ?? [];
|
|
151
|
+
setup = saved.setup;
|
|
150
152
|
}
|
|
151
153
|
catch (error) {
|
|
152
154
|
if (error.code !== 'ENOENT')
|
|
@@ -163,7 +165,7 @@ function loadLoopRuntimeConfig(file, input, selection = { provider: 'claude' })
|
|
|
163
165
|
settings.fixer = inherit(settings.fixer);
|
|
164
166
|
for (const [role, agent] of Object.entries(settings.roles ?? {}))
|
|
165
167
|
settings.roles[role] = inherit(agent);
|
|
166
|
-
return validateAgentRuntimeConfig({ ...settings, providers, enabled: true, verification });
|
|
168
|
+
return validateAgentRuntimeConfig({ ...settings, providers, enabled: true, verification, ...(setup !== undefined ? { setup } : {}) });
|
|
167
169
|
}
|
|
168
170
|
const CLI_PROVIDERS = ['claude', 'codex', 'gemini', 'kimi'];
|
|
169
171
|
const ROLES = ['architect', 'developer', 'reviewer'];
|
|
@@ -260,10 +262,12 @@ function validateAgentRuntimeConfig(input) {
|
|
|
260
262
|
if (!Number.isFinite(value) || (key !== 'maxCostUsd' && !Number.isSafeInteger(value)))
|
|
261
263
|
throw new AgentRuntimeConfigError('Workflow limits must be finite, safe numbers');
|
|
262
264
|
}
|
|
263
|
-
|
|
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] : []);
|
|
264
268
|
if (new Set(keys).size !== keys.length)
|
|
265
269
|
throw new AgentRuntimeConfigError('Verification check keys must be unique');
|
|
266
|
-
for (const command of
|
|
270
|
+
for (const command of hostCommands) {
|
|
267
271
|
for (const value of [command.label, ...(command.policy?.inputs ?? []), ...(command.policy?.toolchainInputs ?? []), ...(command.policy?.resources ?? [])])
|
|
268
272
|
if (value !== undefined && (!value.trim() || value.includes('\0')))
|
|
269
273
|
throw new AgentRuntimeConfigError('Invalid verification label or policy input');
|
|
@@ -482,7 +486,7 @@ function validateRuntimeProviderOverride(value) {
|
|
|
482
486
|
throw new AgentRuntimeConfigError('Invalid runtime provider override effort');
|
|
483
487
|
return structuredClone(raw);
|
|
484
488
|
}
|
|
485
|
-
/** 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`. */
|
|
486
490
|
function forCoreRuntime(config, capabilities) {
|
|
487
491
|
let out = config;
|
|
488
492
|
if (capabilities?.openRoles !== 1 && out.roles !== undefined) {
|
|
@@ -495,6 +499,10 @@ function forCoreRuntime(config, capabilities) {
|
|
|
495
499
|
const { guardrails: _dropped, ...rest } = out;
|
|
496
500
|
out = rest;
|
|
497
501
|
}
|
|
502
|
+
if (!coreSupportsSetupCommands(capabilities) && out.setup !== undefined) {
|
|
503
|
+
const { setup: _dropped, ...rest } = out;
|
|
504
|
+
out = rest;
|
|
505
|
+
}
|
|
498
506
|
// The per-role thinking switch is known only to cores advertising `roleThinkingControl`.
|
|
499
507
|
if (capabilities?.roleThinkingControl !== 1 && ([...Object.values(out.agents ?? {}), ...Object.values(out.roles ?? {})].some(agent => agent?.thinking !== undefined) || out.fixer?.thinking !== undefined)) {
|
|
500
508
|
const strip = (agent) => { const { thinking: _t, ...rest } = agent; return rest; };
|
|
@@ -502,3 +510,7 @@ function forCoreRuntime(config, capabilities) {
|
|
|
502
510
|
}
|
|
503
511
|
return out;
|
|
504
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
|
+
}
|
|
@@ -4,9 +4,9 @@ exports.coreFactoryGraph = coreFactoryGraph;
|
|
|
4
4
|
const loop_implement_recipe_1 = require("./loop-implement-recipe");
|
|
5
5
|
const loop_agents_1 = require("./loop-agents");
|
|
6
6
|
/** App-owned definition graphs. Core supplies schemas, execution and canonical versions. */
|
|
7
|
-
function coreFactoryGraph(mode, independent = false, configurable = false) {
|
|
7
|
+
function coreFactoryGraph(mode, independent = false, configurable = false, capabilities) {
|
|
8
8
|
if (mode === 'implement' && configurable)
|
|
9
|
-
return (0, loop_implement_recipe_1.configurableImplementGraph)();
|
|
9
|
+
return (0, loop_implement_recipe_1.configurableImplementGraph)(capabilities);
|
|
10
10
|
const nodes = [{ id: 'start', type: 'start', position: { x: 0, y: 0 } }];
|
|
11
11
|
const edges = [];
|
|
12
12
|
const config = { maxIterations: 12, maxTransitions: 120, timeoutMinutes: 0, aiStepTimeoutMinutes: 0,
|
|
@@ -41,6 +41,7 @@ function compileLoopToDefinition(graph, launch) {
|
|
|
41
41
|
: {}),
|
|
42
42
|
...(typeof node.data?.exit === 'string' ? { exit: node.data.exit } : {}),
|
|
43
43
|
...(typeof node.data?.reason === 'string' ? { reason: node.data.reason } : {}),
|
|
44
|
+
...(typeof node.data?.blockerFrom === 'string' ? { blockerFrom: node.data.blockerFrom } : {}),
|
|
44
45
|
}
|
|
45
46
|
: node.data.params;
|
|
46
47
|
const promptRole = node.id === 'fix' ? 'fixer' : node.id === 'prepare' ? 'architect' : rawParams.access === 'read' ? 'reviewer' : 'developer';
|
|
@@ -94,7 +94,7 @@ const FACTORY_BY_ID = new Map([
|
|
|
94
94
|
function factoryLoopsForCapabilities(capabilities) {
|
|
95
95
|
if (capabilities?.engineV2 !== 1 || capabilities.workflowDefinitions !== 1)
|
|
96
96
|
return exports.FACTORY_LOOPS;
|
|
97
|
-
return exports.FACTORY_LOOPS.map(factory => ({ ...factory, graph: (0, loop_core_factory_1.coreFactoryGraph)(factory.id === 'factory:implement' ? 'implement' : factory.id === 'factory:freestyle' ? 'freestyle' : 'quick-sdd', capabilities.implementationSteps === 1, capabilities.workflowAgentSteps === 1) }));
|
|
97
|
+
return exports.FACTORY_LOOPS.map(factory => ({ ...factory, graph: (0, loop_core_factory_1.coreFactoryGraph)(factory.id === 'factory:implement' ? 'implement' : factory.id === 'factory:freestyle' ? 'freestyle' : 'quick-sdd', capabilities.implementationSteps === 1, capabilities.workflowAgentSteps === 1, capabilities) }));
|
|
98
98
|
}
|
|
99
99
|
function getFactoryLoop(id, capabilities) {
|
|
100
100
|
const legacy = FACTORY_BY_ID.get(id);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CORE_NODE_KINDS = void 0;
|
|
4
|
+
exports.effectivePieceOutcomes = effectivePieceOutcomes;
|
|
4
5
|
exports.isDefinitionReviewerPath = isDefinitionReviewerPath;
|
|
5
6
|
exports.isDefinitionGraph = isDefinitionGraph;
|
|
6
7
|
exports.assertDefinitionGraph = assertDefinitionGraph;
|
|
@@ -16,6 +17,12 @@ exports.interpolateSpec = interpolateSpec;
|
|
|
16
17
|
exports.CORE_NODE_KINDS = ['prompt', 'role-turn', 'decider', 'condition', 'assign', 'verify', 'shell',
|
|
17
18
|
'artifact-contract', 'openspec-validate', 'openspec-archive', 'approval', 'question', 'gate', 'map', 'join',
|
|
18
19
|
'component', 'implementation', 'implementation-step', 'end'];
|
|
20
|
+
/** Mirror of Core's `getOutcomes(params)`: `verify` advertises `blocked` only when the node opts into host blockers. */
|
|
21
|
+
function effectivePieceOutcomes(descriptor, params) {
|
|
22
|
+
if (descriptor.kind === 'verify' && params?.hostBlockers !== true)
|
|
23
|
+
return descriptor.outcomes.filter(outcome => outcome !== 'blocked');
|
|
24
|
+
return descriptor.outcomes;
|
|
25
|
+
}
|
|
19
26
|
function isDefinitionReviewerPath(graph, value) {
|
|
20
27
|
if (typeof value !== 'string' || !value || value.length > 2048)
|
|
21
28
|
return false;
|
|
@@ -201,7 +208,7 @@ function validateLoopGraph(graph, catalog) {
|
|
|
201
208
|
const labels = new Set();
|
|
202
209
|
for (const edge of out) {
|
|
203
210
|
if (typeof edge.label !== 'string' || !/^[a-z][a-z0-9-]{0,31}$/.test(edge.label) || labels.has(edge.label) ||
|
|
204
|
-
(descriptor && !descriptor.
|
|
211
|
+
(descriptor && !effectivePieceOutcomes(descriptor, node.data?.params).includes(edge.label))) {
|
|
205
212
|
errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, edgeId: edge.id, message: 'Core outcome edges need unique labels advertised by the piece.' });
|
|
206
213
|
}
|
|
207
214
|
if (edge.label)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.supportsHostBlockers = exports.HOST_BLOCKER_KINDS = void 0;
|
|
3
4
|
exports.configurableImplementGraph = configurableImplementGraph;
|
|
4
5
|
const agent_runtime_settings_1 = require("../../agent-runtime/runtime/agent-runtime-settings");
|
|
5
6
|
const loop_agents_1 = require("./loop-agents");
|
|
@@ -9,8 +10,17 @@ const strings = { type: 'array', items: string, maxItems: 100 };
|
|
|
9
10
|
const score = { type: 'number', minimum: 0, maximum: 100 };
|
|
10
11
|
const aspects = { type_correctness: 60, pattern_adherence: 60, test_coverage: 60, security: 75, architectural_alignment: 60 };
|
|
11
12
|
const reviewPolicy = 'approved == true; issues must be empty; score >= 70; ' + Object.entries(aspects).map(([aspect, threshold]) => `${aspect} >= ${threshold}`).join('; ');
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
exports.HOST_BLOCKER_KINDS = ['network', 'credential', 'environment-variable', 'toolchain', 'setup', 'environment', 'scope'];
|
|
14
|
+
/** Fixer/developer structured `blocker`: the host ends the run on it instead of counting a correction round. */
|
|
15
|
+
const blocker = objectSchema({ kind: { enum: [...exports.HOST_BLOCKER_KINDS] }, command: string, cwd: string, evidence: string, requiredAction: string }, ['kind', 'requiredAction']);
|
|
16
|
+
/** Host-owned environment repair and structured blockers need the paired Core generation. */
|
|
17
|
+
const supportsHostBlockers = (capabilities) => capabilities?.hostBlockers === 1;
|
|
18
|
+
exports.supportsHostBlockers = supportsHostBlockers;
|
|
19
|
+
/** The recipe, roles, schemas and decisions are Desktop data, never Core phases.
|
|
20
|
+
* `capabilities` are the installed Core's advertised runtime capabilities: without `hostBlockers`
|
|
21
|
+
* the verify nodes compile exactly as before (no `setup`, no `blocked` edge, no `blockerFrom`). */
|
|
22
|
+
function configurableImplementGraph(capabilities) {
|
|
23
|
+
const hostBlockers = (0, exports.supportsHostBlockers)(capabilities);
|
|
14
24
|
const agents = (0, loop_agents_1.defaultLoopAgents)();
|
|
15
25
|
agents.roles = {
|
|
16
26
|
plan: { ...agents.agents.architect, access: 'read', artifacts: 'all', openspecSkill: 'openspec-ff-change', prompt: (0, agent_runtime_settings_1.inheritedRolePrompt)(agents.rolePrompts, 'architect') },
|
|
@@ -24,7 +34,11 @@ function configurableImplementGraph() {
|
|
|
24
34
|
for (const [outcome, target] of Object.entries(outcomes))
|
|
25
35
|
edges.push({ id: `e-${id}-${outcome}`, source: id, target, label: outcome });
|
|
26
36
|
};
|
|
27
|
-
const turn = (id, label, roleId, prompt, schema, next) => node(id, label, 'role-turn', { roleId, prompt, structuredOutput: schema, sessionContinuity: 'none' }, { next, invalid: 'failed', failed: 'failed' });
|
|
37
|
+
const turn = (id, label, roleId, prompt, schema, next, extra = {}) => node(id, label, 'role-turn', { roleId, prompt, structuredOutput: schema, sessionContinuity: 'none', ...extra }, { next, invalid: 'failed', failed: 'failed' });
|
|
38
|
+
const verifyParams = { commands: 'configured', additionalCommandsFrom: 'architect', ...(hostBlockers ? { setup: 'configured', hostBlockers: true } : {}) };
|
|
39
|
+
const verifyEnds = (passed, failed, blockedEnd) => ({ pass: passed, fail: failed, failed: 'failed', ...(hostBlockers ? { blocked: blockedEnd } : {}) });
|
|
40
|
+
// Same Core generation as host blockers: older role-turn schemas reject unknown params.
|
|
41
|
+
const writes = hostBlockers ? { verificationProposalsFrom: 'architect' } : {};
|
|
28
42
|
const change = '{{run.changeId}}';
|
|
29
43
|
turn('architect', 'Architect', 'plan', `Plan the frozen ticket requirements for OpenSpec change ${change}. Use the bound official fast-forward skill. Author complete proposal, design, delta specs and actionable tasks. Propose existing repository verification commands for repositories with no configured checks. Return confidence, question (empty when none), and verification [{repositoryId,command,args,cwd?}]. When revisiting low confidence, investigate the missing repository evidence before asking again. Context and human answers: {{history.text}}`, objectSchema({ confidence: { enum: ['high', 'medium', 'low'] }, question: string, verification: { type: 'array', maxItems: 20, items: objectSchema({ repositoryId: string, command: string, args: strings, cwd: string }, ['repositoryId', 'command', 'args']) } }), 'confidence');
|
|
30
44
|
node('confidence', 'Planning confidence', 'condition', { expr: '$outputs.architect.structured.confidence != "low"' }, { true: 'validate', false: 'investigate' });
|
|
@@ -35,10 +49,10 @@ function configurableImplementGraph() {
|
|
|
35
49
|
node('validate', 'Validate OpenSpec', 'openspec-validate', { change }, { pass: 'freeze', fail: 'architect', failed: 'failed' });
|
|
36
50
|
node('freeze', 'Freeze approved artifacts', 'artifact-contract', { change, contractId: 'plan', action: 'freeze' }, { pass: 'initialize-corrections', fail: 'architect', failed: 'failed' });
|
|
37
51
|
node('initialize-corrections', 'Initialize automatic correction budget', 'assign', { set: { correctionAttempts: 0, correctionLimit: 3 } }, { next: 'developer', failed: 'failed' });
|
|
38
|
-
const implementation = objectSchema({ summary: string, incomplete: strings });
|
|
39
|
-
turn('developer', 'Developer', 'build', `Implement every approved task for change ${change} using the bound official apply skill. Preserve proposal, design and specs; only tick tasks you completed. Report summary and incomplete task descriptions. Prior execution evidence and human answers: {{history.text}}`, implementation, 'tasks');
|
|
52
|
+
const implementation = objectSchema({ summary: string, incomplete: strings, blocker }, ['summary', 'incomplete']);
|
|
53
|
+
turn('developer', 'Developer', 'build', `Implement every approved task for change ${change} using the bound official apply skill. Preserve proposal, design and specs; only tick tasks you completed. Report summary and incomplete task descriptions. Prior execution evidence and human answers: {{history.text}}`, implementation, 'tasks', writes);
|
|
40
54
|
node('tasks', 'Check artifacts and completed tasks', 'artifact-contract', { change, contractId: 'plan', action: 'check', requireCompletedTasks: true }, { pass: 'verify', fail: 'developer', failed: 'failed' });
|
|
41
|
-
node('verify', 'Verify candidate', 'verify',
|
|
55
|
+
node('verify', 'Verify candidate', 'verify', verifyParams, verifyEnds('reviewer', 'correction-context', 'host-blocked'));
|
|
42
56
|
turn('reviewer', 'Reviewer', 'assess', `Review change ${change} read-only against all approved artifacts and frozen ticket acceptance criteria. Inspect real code and the host verification evidence: {{outputs.verify}}. Return approved, summary, issues, score (0-100), and aspects. Acceptance policy: ${reviewPolicy}. For any rejection or below-threshold score, explain the concrete unmet requirement and actionable repair in summary and issues; passing commands do not prove every acceptance obligation. Prior execution: {{history.text}}`, objectSchema({ approved: { type: 'boolean' }, summary: string, issues: strings, score, aspects: objectSchema(Object.fromEntries(Object.keys(aspects).map(aspect => [aspect, score]))) }), 'review-policy');
|
|
43
57
|
node('review-policy', 'Review acceptance policy', 'condition', { expr: ['$outputs.reviewer.structured.approved == true', '$outputs.reviewer.structured.issues.length == 0', '$outputs.reviewer.structured.score >= 70', ...Object.entries(aspects).map(([aspect, threshold]) => `$outputs.reviewer.structured.aspects.${aspect} >= ${threshold}`)].join(' && ') }, { true: 'approve', false: 'correction-context' });
|
|
44
58
|
node('correction-context', 'Select current review findings', 'condition', { expr: '$outputs.verify.valid == true && exists($outputs.reviewer.candidateHash) && $outputs.reviewer.candidateHash == $outputs.verify.candidateHash' }, { true: 'capture-review', false: 'clear-review' });
|
|
@@ -46,13 +60,18 @@ function configurableImplementGraph() {
|
|
|
46
60
|
node('clear-review', 'Clear outdated review findings', 'assign', { set: { latestReview: 'No reviewer findings for the current verified candidate. Diagnose the current host verification failure first.' } }, { next: 'correction-budget', failed: 'failed' });
|
|
47
61
|
node('correction-budget', 'Check automatic correction budget', 'condition', { expr: '$vars.correctionAttempts < $vars.correctionLimit' }, { true: 'begin-correction', false: 'correction-exhausted' });
|
|
48
62
|
node('begin-correction', 'Count automatic correction attempt', 'assign', { increment: { correctionAttempts: 1 } }, { next: 'fixer', failed: 'failed' });
|
|
49
|
-
turn('fixer', 'Fixer', 'correct', `Correct the reported implementation, test or review failure for change ${change} using the bound apply skill and preserving approved artifacts. Read failureSummary, command, cwd, exitCode and evidenceId first; use read_verification_evidence for truncated evidence. If Host verification is valid, repair the concrete rejected review obligation or below-threshold aspect instead of only rerunning green commands. Acceptance policy: ${reviewPolicy}. Reproduce the exact focused failure, inspect the assertion and tested code, then apply the smallest repair that preserves the required behavior. An unchanged filename does not prove an unrelated failure. A mandatory test inside the admitted workspace may receive a proven formatting/compatibility repair while retaining every required guard. Report diagnosis, focused commands with original exit codes, repair and unresolved failures in summary and incomplete. If the cause
|
|
63
|
+
turn('fixer', 'Fixer', 'correct', `Correct the reported implementation, test or review failure for change ${change} using the bound apply skill and preserving approved artifacts. Read failureSummary, command, cwd, exitCode and evidenceId first; use read_verification_evidence for truncated evidence. If Host verification is valid, repair the concrete rejected review obligation or below-threshold aspect instead of only rerunning green commands. Acceptance policy: ${reviewPolicy}. Reproduce the exact focused failure, inspect the assertion and tested code, then apply the smallest repair that preserves the required behavior. An unchanged filename does not prove an unrelated failure. A mandatory test inside the admitted workspace may receive a proven formatting/compatibility repair while retaining every required guard. Report diagnosis, focused commands with original exit codes, repair and unresolved failures in summary and incomplete. If the cause is a host precondition or environment blocker outside this change, make no edits and return the structured blocker (kind, command, cwd, evidence, requiredAction): the host ends the run with your required action. Any other unchanged correction stops this run even when host checks pass. Do not make unrelated edits merely to change the candidate hash. Host verification: {{outputs.verify}}. Latest review findings for this candidate: {{run.latestReview}}. Prior execution: {{history.text}}`, implementation, 'correction-blocker', writes);
|
|
64
|
+
node('correction-blocker', 'Fixer reported a host blocker', 'condition', { expr: 'exists($outputs.fixer.structured.blocker) && $outputs.fixer.structured.blocker != null' }, { true: 'fixer-blocked', false: 'correction-progress' });
|
|
50
65
|
node('correction-progress', 'Check correction progress', 'condition', { expr: 'exists($outputs.fixer.candidateHash) && $outputs.fixer.candidateHash != null && $outputs.fixer.candidateHash != $outputs.verify.candidateHash' }, { true: 'tasks', false: 'correction-stalled' });
|
|
51
66
|
node('approve', 'Approve archive', 'approval', { reason: `Approve archiving the reviewed and verified candidate for change ${change}.`, bindCandidate: true, enabled: agents.approvalBeforeArchive === true }, { next: 'approval-policy' });
|
|
52
67
|
node('approval-policy', 'Approval decision', 'condition', { expr: '$outputs.approve.response.approved == true' }, { true: 'archive', false: 'failed' });
|
|
53
68
|
node('archive', 'Archive OpenSpec', 'openspec-archive', { change, allowArchived: true, requiresVerified: true, reviewedCandidate: '{{outputs.reviewer.candidateHash}}', approvedCandidate: '{{outputs.approve.candidateHash}}' }, { next: 'verify-archive', failed: 'verify' });
|
|
54
|
-
node('verify-archive', 'Verify archived candidate', 'verify',
|
|
55
|
-
nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction made no candidate changes. Host verification is valid: {{outputs.verify.valid}}. Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}` } }, { id: 'correction-exhausted', type: 'end', position: { x: 1080, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction limit reached ({{run.correctionAttempts}}/{{run.correctionLimit}}). Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}. Host verification: {{outputs.verify}}` } });
|
|
69
|
+
node('verify-archive', 'Verify archived candidate', 'verify', verifyParams, verifyEnds('done', 'failed', 'host-blocked-archive'));
|
|
70
|
+
nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction made no candidate changes. Host verification is valid: {{outputs.verify.valid}}. Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}` } }, { id: 'correction-exhausted', type: 'end', position: { x: 1080, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction limit reached ({{run.correctionAttempts}}/{{run.correctionLimit}}). Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}. Host verification: {{outputs.verify}}` } }, { id: 'fixer-blocked', type: 'end', position: { x: 1440, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'Fixer reported a host blocker ({{outputs.fixer.structured.blocker.kind}}): {{outputs.fixer.structured.blocker.requiredAction}} Evidence: {{outputs.fixer.structured.blocker.evidence}}', ...(hostBlockers ? { blockerFrom: 'fixer' } : {}) } });
|
|
71
|
+
if (hostBlockers) {
|
|
72
|
+
const hostBlocked = (id, source) => ({ id, type: 'end', position: { x: 1800, y: nodes.length * 140 }, data: { outcome: 'failure', blockerFrom: source, reason: `Host blocker ({{outputs.${source}.blocker.kind}}): {{outputs.${source}.blocker.reason}} Required action: {{outputs.${source}.blocker.requiredAction}}` } });
|
|
73
|
+
nodes.push(hostBlocked('host-blocked', 'verify'), hostBlocked('host-blocked-archive', 'verify-archive'));
|
|
74
|
+
}
|
|
56
75
|
edges.unshift({ id: 'e-start', source: 'start', target: 'architect' });
|
|
57
76
|
return { nodes, edges, config: { maxIterations: 12, maxTransitions: 200, timeoutMinutes: 0, aiStepTimeoutMinutes: 0, change: 'new', journal: 'ledger-only', ticketScope: 'all', agents, reviewerStepId: 'reviewer' } };
|
|
58
77
|
}
|
|
@@ -341,10 +341,10 @@ const CORE_STARTER_SHAPES = {
|
|
|
341
341
|
};
|
|
342
342
|
/** Core definition graph for one of the eight starters. Deterministic: same id ⇒
|
|
343
343
|
* structurally identical graph (positions included). */
|
|
344
|
-
function coreStarterGraph(id, independent = false, configurable = false) {
|
|
344
|
+
function coreStarterGraph(id, independent = false, configurable = false, capabilities) {
|
|
345
345
|
const shape = CORE_STARTER_SHAPES[id];
|
|
346
346
|
if (shape === 'ship' && configurable)
|
|
347
|
-
return (0, loop_core_factory_1.coreFactoryGraph)('implement', true, true);
|
|
347
|
+
return (0, loop_core_factory_1.coreFactoryGraph)('implement', true, true, capabilities);
|
|
348
348
|
const { prompt, goal, maxIterations } = STARTER_TEXT[id];
|
|
349
349
|
const nodes = [{ id: 'start', type: 'start', position: { x: COL_X, y: 0 } }];
|
|
350
350
|
const edges = [];
|
|
@@ -438,7 +438,7 @@ function loopTemplatesForCapabilities(capabilities) {
|
|
|
438
438
|
...template,
|
|
439
439
|
description: CORE_STARTER_DESCRIPTIONS[template.id],
|
|
440
440
|
...(readOnly ? { readOnly: true } : {}),
|
|
441
|
-
graph: coreStarterGraph(template.id, capabilities?.implementationSteps === 1, capabilities?.workflowAgentSteps === 1),
|
|
441
|
+
graph: coreStarterGraph(template.id, capabilities?.implementationSteps === 1, capabilities?.workflowAgentSteps === 1, capabilities),
|
|
442
442
|
};
|
|
443
443
|
});
|
|
444
444
|
}
|
|
@@ -134,13 +134,8 @@
|
|
|
134
134
|
"maxAttempts": { "$ref": "#/definitions/positiveInteger" }, "maxTokens": { "$ref": "#/definitions/positiveInteger" },
|
|
135
135
|
"maxCostUsd": { "type": "number", "exclusiveMinimum": 0 }, "timeoutMs": { "$ref": "#/definitions/positiveInteger" }
|
|
136
136
|
} },
|
|
137
|
-
"verification": { "type": "array", "items": { "
|
|
138
|
-
|
|
139
|
-
"repositoryId": { "$ref": "#/definitions/id" }, "command": { "type": "string", "minLength": 1 },
|
|
140
|
-
"args": { "type": "array", "items": { "type": "string" } }, "cwd": { "type": "string", "minLength": 1 },
|
|
141
|
-
"env": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "additionalProperties": { "type": "string" } },
|
|
142
|
-
"timeoutMs": { "$ref": "#/definitions/positiveInteger" }
|
|
143
|
-
} } },
|
|
137
|
+
"verification": { "type": "array", "items": { "$ref": "#/definitions/checkCommand" } },
|
|
138
|
+
"setup": { "type": "array", "description": "Idempotent commands the verify piece runs sequentially in the admitted workspace before each verification plan; a failing setup command blocks the run instead of starting a correction round.", "items": { "$ref": "#/definitions/checkCommand" } },
|
|
144
139
|
"approvalBeforeArchive": { "type": "boolean" },
|
|
145
140
|
"review": { "type": "object", "additionalProperties": false, "description": "Review gate thresholds; unset fields keep Core's defaults (score 70, security 75, other aspects 60).", "properties": {
|
|
146
141
|
"minScore": { "$ref": "#/definitions/score" },
|
|
@@ -154,6 +149,13 @@
|
|
|
154
149
|
} }
|
|
155
150
|
},
|
|
156
151
|
"definitions": {
|
|
152
|
+
"checkCommand": { "type": "object", "additionalProperties": false, "required": ["repositoryId", "command", "args"], "properties": {
|
|
153
|
+
"key": { "$ref": "#/definitions/id" }, "label": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "\\S" }, "policy": { "$ref": "#/definitions/checkPolicy" },
|
|
154
|
+
"repositoryId": { "$ref": "#/definitions/id" }, "command": { "type": "string", "minLength": 1 },
|
|
155
|
+
"args": { "type": "array", "items": { "type": "string" } }, "cwd": { "type": "string", "minLength": 1 },
|
|
156
|
+
"env": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "additionalProperties": { "type": "string" } },
|
|
157
|
+
"timeoutMs": { "$ref": "#/definitions/positiveInteger" }
|
|
158
|
+
} },
|
|
157
159
|
"effort": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{0,31}$" },
|
|
158
160
|
"inputList": { "type": "array", "maxItems": 256, "items": { "type": "string", "minLength": 1, "maxLength": 4096, "pattern": "\\S" } },
|
|
159
161
|
"checkPolicy": { "type": "object", "additionalProperties": false, "properties": {
|