specrails-desktop 2.59.0 → 2.59.1

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.
Files changed (87) hide show
  1. package/client/dist/assets/{ActivityFeedPage-DBFt8ZHP.js → ActivityFeedPage-vAY9C71W.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-DiQx0W67.js → AgentBrowserCapture-CpwjGXQm.js} +1 -1
  3. package/client/dist/assets/{AgentModeAnalyticsPane-BmDSXp_J.js → AgentModeAnalyticsPane-BHeoJVxQ.js} +2 -2
  4. package/client/dist/assets/{AgentModeCodePane-BLQLPSwr.js → AgentModeCodePane-B0Nf3YkN.js} +2 -2
  5. package/client/dist/assets/{AgentModeJobsPane-BbTXdKUa.js → AgentModeJobsPane-CpbIItY_.js} +1 -1
  6. package/client/dist/assets/{AgentsPage-Dei1jGR8.js → AgentsPage-BVJAFXn3.js} +1 -1
  7. package/client/dist/assets/{AnalyticsPage-BhkeGIGn.js → AnalyticsPage-DDWees-K.js} +1 -1
  8. package/client/dist/assets/{CodePage-B_9yYWUx.js → CodePage-CzV5tkvS.js} +1 -1
  9. package/client/dist/assets/{DesktopAnalyticsPage-DAcfEJac.js → DesktopAnalyticsPage-DfRHNK7i.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-WCuiYgsc.js → DocsDialog-BxduBYbh.js} +1 -1
  11. package/client/dist/assets/{DocsPage-bGVYTMTU.js → DocsPage-DOVeRJn_.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-CsodZ-yb.js → ExportDropdown-BnKVV8UK.js} +1 -1
  13. package/client/dist/assets/{InteractiveJobComposer-D5fh_eVL.js → InteractiveJobComposer-Da9dvM-0.js} +1 -1
  14. package/client/dist/assets/{JobDetailModal-DlpXF61O.js → JobDetailModal-DhwKbZGe.js} +1 -1
  15. package/client/dist/assets/{JobDetailPage-2yR19_Lb.js → JobDetailPage-MtPE-E_1.js} +1 -1
  16. package/client/dist/assets/{JobsPage-CS9IR6Dq.js → JobsPage-0vdlbNik.js} +1 -1
  17. package/client/dist/assets/{LoopBuilderPage-BwiAtmOr.js → LoopBuilderPage-DCUHozXh.js} +1 -1
  18. package/client/dist/assets/{LoopPreviewModal--upwvgTv.js → LoopPreviewModal-Df-S8NGf.js} +1 -1
  19. package/client/dist/assets/{LoopWindowSurface-DRnUixtl.js → LoopWindowSurface-sdb7QbSo.js} +1 -1
  20. package/client/dist/assets/{LoopsPage-Xm3nRToQ.js → LoopsPage-CDfCC9Px.js} +1 -1
  21. package/client/dist/assets/{MinimizedChatsContext-D_8L4EWZ.js → MinimizedChatsContext-DKlPf54Y.js} +1 -1
  22. package/client/dist/assets/{PluginsPage-CAFQ1B4Q.js → PluginsPage-iL0G3rQQ.js} +1 -1
  23. package/client/dist/assets/{ProjectSettingsDialog-DYSO4v90.js → ProjectSettingsDialog-DOkjT7xv.js} +1 -1
  24. package/client/dist/assets/{RepositoryDeliveries-CbQmW7Qx.js → RepositoryDeliveries-CJVpOUpZ.js} +1 -1
  25. package/client/dist/assets/RepositoryScopeSelector-BeM-2gIZ.js +2 -0
  26. package/client/dist/assets/{ReviewPacketPage-SDBhYdQB.js → ReviewPacketPage-EBCim2w_.js} +1 -1
  27. package/client/dist/assets/{TemplatePreviewModal-DHBUds51.js → TemplatePreviewModal-1-92UGwN.js} +1 -1
  28. package/client/dist/assets/{TicketDetailModalContext-UAeNm7BB.js → TicketDetailModalContext-CkVXuHxU.js} +1 -1
  29. package/client/dist/assets/{Trans-DExd3Z00.js → Trans-CzhPjnjB.js} +1 -1
  30. package/client/dist/assets/{agent-DrM4qned.js → agent--8QAfoXL.js} +1 -1
  31. package/client/dist/assets/{agent-CW74ODDP.js → agent-3HxXkIWX.js} +1 -1
  32. package/client/dist/assets/{agent-BElLtdKP.js → agent-BjttZLIl.js} +1 -1
  33. package/client/dist/assets/{agent-C-m9VugX.js → agent-CpFCz32T.js} +1 -1
  34. package/client/dist/assets/{agent-KdIXq3w5.js → agent-DVnw6ybO.js} +1 -1
  35. package/client/dist/assets/{agent-CGSKxfZk.js → agent-Z5s_GTP0.js} +1 -1
  36. package/client/dist/assets/{agent-DeCAZDhL.js → agent-hv57-zyG.js} +1 -1
  37. package/client/dist/assets/{agent-jK8mCisM.js → agent-xGrZ_aN1.js} +1 -1
  38. package/client/dist/assets/{formatDistanceToNow-aw7H5ORC.js → formatDistanceToNow-DlQaXc7c.js} +1 -1
  39. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-NyC7gZJX.js → getTimezoneOffsetInMilliseconds-DxUFGcVq.js} +1 -1
  40. package/client/dist/assets/index-Bfunvi6w.css +2 -0
  41. package/client/dist/assets/{index-JSTZVJfe.js → index-Czov72g7.js} +43 -43
  42. package/client/dist/assets/{jira-api-DlXaRwA3.js → jira-api-CbJvbRYx.js} +1 -1
  43. package/client/dist/assets/{project-repositories-DXhb9FP4.js → project-repositories-BkBD3rxx.js} +1 -1
  44. package/client/dist/assets/{spending-BdcyVkcd.js → spending-CYotH-9N.js} +1 -1
  45. package/client/dist/assets/{subscriptionUsage-DoWQv7l1.js → subscriptionUsage-BH73RO86.js} +1 -1
  46. package/client/dist/assets/{subscriptionUsage-DQ02J25O.js → subscriptionUsage-BWTeWlHx.js} +1 -1
  47. package/client/dist/assets/{subscriptionUsage-N9hB-ARf.js → subscriptionUsage-CBh82zVa.js} +1 -1
  48. package/client/dist/assets/{subscriptionUsage-DWF3zKp4.js → subscriptionUsage-CHcQ7lLe.js} +1 -1
  49. package/client/dist/assets/subscriptionUsage-CdlE5Mg1.js +1 -0
  50. package/client/dist/assets/{subscriptionUsage-Cf87DeS_.js → subscriptionUsage-DUPBmSQi.js} +1 -1
  51. package/client/dist/assets/{subscriptionUsage-DkIf6Zuh.js → subscriptionUsage-DrlwrJ17.js} +1 -1
  52. package/client/dist/assets/{subscriptionUsage-D6u7VBdz.js → subscriptionUsage-EFu0mIK3.js} +1 -1
  53. package/client/dist/assets/{useDesktop-D07TvesS.js → useDesktop-DpxVGfUC.js} +1 -1
  54. package/client/dist/assets/{useRuntimeRuns-pGLmtAC6.js → useRuntimeRuns-CfGRbhr9.js} +1 -1
  55. package/client/dist/assets/{useSharedWebSocket-TzKtPRp9.js → useSharedWebSocket-C17TGqT8.js} +2 -2
  56. package/client/dist/index.html +17 -17
  57. package/docs/internals/api-reference.md +1 -0
  58. package/docs/internals/core-runtime-updates.md +9 -2
  59. package/docs/internals/desktop-owned-workflows.md +23 -0
  60. package/docs/internals/mission-rail-cards.md +15 -0
  61. package/docs/internals/operations-runbook.md +6 -0
  62. package/docs/internals/safe-pr-review-flow.md +25 -7
  63. package/docs/internals/source-map.md +3 -1
  64. package/docs/internals/visual-design-audit.md +5 -0
  65. package/package.json +1 -1
  66. package/server/dist/core-update-manager.js +1 -1
  67. package/server/dist/db/migrations.js +9 -0
  68. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +114 -5
  69. package/server/dist/modules/delivery/runtime/multi-repo-execution.js +7 -5
  70. package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +65 -30
  71. package/server/dist/modules/delivery/runtime/rail-pr-store.js +3 -3
  72. package/server/dist/modules/delivery/runtime/rail-relaunch.js +154 -0
  73. package/server/dist/modules/delivery/runtime/rails-router.js +109 -10
  74. package/server/dist/modules/execution/runtime/revision-seed.js +6 -2
  75. package/server/dist/modules/loops/runtime/loop-agent-defaults.json +1 -1
  76. package/server/dist/modules/loops/runtime/loop-definition-events.js +1 -1
  77. package/server/dist/modules/loops/runtime/loop-implement-recipe.js +8 -4
  78. package/server/dist/modules/loops/runtime/loop-run-manager.js +27 -17
  79. package/server/dist/modules/subscription-usage/adapters/claude.js +30 -20
  80. package/server/dist/modules/subscription-usage/application.js +2 -1
  81. package/server/dist/modules/subscription-usage/domain.js +35 -1
  82. package/server/dist/modules/subscription-usage/runtime/usage-service.js +1 -1
  83. package/server/dist/worktree-manager.js +114 -6
  84. package/server/dist/worktree-overlay.js +4 -4
  85. package/client/dist/assets/RepositoryScopeSelector-DKzvIGSZ.js +0 -2
  86. package/client/dist/assets/index-CahchzS5.css +0 -2
  87. package/client/dist/assets/subscriptionUsage-zRcRPHkP.js +0 -1
@@ -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-JSTZVJfe.js"></script>
207
+ <script type="module" crossorigin src="/assets/index-Czov72g7.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">
@@ -213,7 +213,7 @@
213
213
  <link rel="modulepreload" crossorigin href="/assets/tauri-shell-DE6LfGPw.js">
214
214
  <link rel="modulepreload" crossorigin href="/assets/activity-DiyjsgQV.js">
215
215
  <link rel="modulepreload" crossorigin href="/assets/addspec-BOvgmcgk.js">
216
- <link rel="modulepreload" crossorigin href="/assets/agent-CGSKxfZk.js">
216
+ <link rel="modulepreload" crossorigin href="/assets/agent-Z5s_GTP0.js">
217
217
  <link rel="modulepreload" crossorigin href="/assets/agentRuntime-Bpx3yWHK.js">
218
218
  <link rel="modulepreload" crossorigin href="/assets/agents-CTwbx-fb.js">
219
219
  <link rel="modulepreload" crossorigin href="/assets/agentstudio-BWAKeGgB.js">
@@ -239,19 +239,19 @@
239
239
  <link rel="modulepreload" crossorigin href="/assets/settings-Dbyd6gmj.js">
240
240
  <link rel="modulepreload" crossorigin href="/assets/setup-CKGUxKr-.js">
241
241
  <link rel="modulepreload" crossorigin href="/assets/specs-DfIdoU5L.js">
242
- <link rel="modulepreload" crossorigin href="/assets/subscriptionUsage-zRcRPHkP.js">
242
+ <link rel="modulepreload" crossorigin href="/assets/subscriptionUsage-CdlE5Mg1.js">
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-TzKtPRp9.js">
247
- <link rel="modulepreload" crossorigin href="/assets/Trans-DExd3Z00.js">
248
- <link rel="modulepreload" crossorigin href="/assets/useDesktop-D07TvesS.js">
246
+ <link rel="modulepreload" crossorigin href="/assets/useSharedWebSocket-C17TGqT8.js">
247
+ <link rel="modulepreload" crossorigin href="/assets/Trans-CzhPjnjB.js">
248
+ <link rel="modulepreload" crossorigin href="/assets/useDesktop-DpxVGfUC.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-BdcyVkcd.js">
251
+ <link rel="modulepreload" crossorigin href="/assets/spending-CYotH-9N.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-D_8L4EWZ.js">
254
+ <link rel="modulepreload" crossorigin href="/assets/MinimizedChatsContext-DKlPf54Y.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,22 +266,22 @@
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-DlXaRwA3.js">
269
+ <link rel="modulepreload" crossorigin href="/assets/jira-api-CbJvbRYx.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-NyC7gZJX.js">
275
- <link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-aw7H5ORC.js">
274
+ <link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-DxUFGcVq.js">
275
+ <link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-DlQaXc7c.js">
276
276
  <link rel="modulepreload" crossorigin href="/assets/button-D1xExSTF.js">
277
- <link rel="modulepreload" crossorigin href="/assets/project-repositories-DXhb9FP4.js">
278
- <link rel="modulepreload" crossorigin href="/assets/RepositoryScopeSelector-DKzvIGSZ.js">
279
- <link rel="modulepreload" crossorigin href="/assets/TicketDetailModalContext-UAeNm7BB.js">
277
+ <link rel="modulepreload" crossorigin href="/assets/project-repositories-BkBD3rxx.js">
278
+ <link rel="modulepreload" crossorigin href="/assets/RepositoryScopeSelector-BeM-2gIZ.js">
279
+ <link rel="modulepreload" crossorigin href="/assets/TicketDetailModalContext-CkVXuHxU.js">
280
280
  <link rel="modulepreload" crossorigin href="/assets/core-B3mKBIh5.js">
281
- <link rel="modulepreload" crossorigin href="/assets/RepositoryDeliveries-CbQmW7Qx.js">
282
- <link rel="modulepreload" crossorigin href="/assets/useRuntimeRuns-pGLmtAC6.js">
281
+ <link rel="modulepreload" crossorigin href="/assets/RepositoryDeliveries-CJVpOUpZ.js">
282
+ <link rel="modulepreload" crossorigin href="/assets/useRuntimeRuns-CfGRbhr9.js">
283
283
  <link rel="modulepreload" crossorigin href="/assets/event-Bt7f3wXe.js">
284
- <link rel="stylesheet" crossorigin href="/assets/index-CahchzS5.css">
284
+ <link rel="stylesheet" crossorigin href="/assets/index-Bfunvi6w.css">
285
285
  </head>
286
286
  <body>
287
287
  <div id="specrails-splash" aria-hidden="false" role="status" aria-label="Loading Specrails">
@@ -446,6 +446,7 @@ A non-developer-friendly file tree + Monaco viewer with plain-language AI summar
446
446
  | `PUT` | `/:railIndex/name` | Set the rail's display name. Body `{ name }` (string or `null` to clear back to the default label); 400 if longer than 60 characters |
447
447
  | `POST` | `/:railIndex/launch` | Launch the rail. Body `{ mode?, loopId?, profileName?, aiEngine?, model?, reasoning_effort?, originConversationId?, originSurface?, interactive? }`. `mode` is `implement` / `freestyle` / `loop` (the removed Batch mode's `batch-implement` / `batch` are accepted as input aliases and normalized to `implement`); a factory `loopId` maps to its canonical rail mode (`factory:batch` resolves to `factory:implement`). `model` and `reasoning_effort` are validated against the effective adapter/model (Kimi effort is K3-only). Kimi + a loop containing a Decider returns a capability error before the first step. Claude alone advertises persistent in-job stdin; `interactive` remains wire-compatible. Returns `202 { jobId, railIndex, mode }`; a missing provider CLI fails before a job starts. |
448
448
  | `POST` | `/:railIndex/stop` | Stop the rail's running job (cancels every job the rail registered) |
449
+ | `POST` | `/:railIndex/relaunch` | Relaunch a settled source. Body `{ sourceId: deliveryId or 'run:<runId>', originConversationId?, originSurface? }`. Uses its recorded specs and admitted options, restores empty assignments after preflight and atomically replaces an undelivered failed generation. Returns the same `202` launch response. Reused/removed rails, active/stale sources, missing specs/workflow and delivered work needing recovery return actionable errors. Request-side workflow overrides are ignored. |
449
450
 
450
451
  ---
451
452
 
@@ -13,13 +13,17 @@ 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.1.0 in `desktop-release.yml` and
16
+ The release bundle pins Core 6.2.1 in `desktop-release.yml` and
17
17
  `scripts/assemble-bundled-core.lock.json`. Core 6 is the Desktop-only engine: it
18
- publishes integration contract 5.0 (no standalone `update`) and ships only the
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.2.1 release commit
24
+ `ae52a5ff0ff9dce024008adf16115e7f04d1ef35`. Bundle smoke checks use Node 22.22.3,
25
+ matching Core's minimum supported Node version and the Desktop release runtime.
26
+
23
27
  Desktop updates retain the complete npm installation, including dependencies,
24
28
  under `~/.specrails/core/<version>/`. The registry home override applies to this
25
29
  directory. Only the managed package corresponding to the active framework is
@@ -61,6 +65,9 @@ rollback restored already updated copies. **Finish updating** retries with the
61
65
  retained package and works offline. Successful completion is emitted only after
62
66
  all workspace refreshes and the final recovery-state write succeed.
63
67
 
68
+ Core 6 registry updates and same-version runtime repairs are offered using the
69
+ same supported-major policy as runtime selection.
70
+
64
71
  ## Version reporting
65
72
 
66
73
  Global status distinguishes the selected runtime and its source, the active
@@ -54,6 +54,29 @@ real committed full verification and the reviewed/approved candidate before
54
54
  publishing the recoverable write set. The final host check certifies the archived
55
55
  candidate. Reviewer assertions remain AI evidence and cannot create verification.
56
56
 
57
+ Implement checks the host candidate hash after each correction. When verification
58
+ failed and the correction leaves that candidate unchanged, it ends unsuccessfully
59
+ with the corrector's explanation instead of rerunning the same failed checks.
60
+ Corrections after a passing verification may still refresh review evidence without
61
+ source changes. Failed completion reasons are surfaced in Desktop's job error.
62
+
63
+ The corrector receives the exact verification command, cwd, original exit code,
64
+ failure summary, full-evidence identifier and current candidate's review output
65
+ directly. The recipe explicitly clears review feedback when verification fails
66
+ or its candidate no longer matches, including before the first reviewer visit.
67
+ It reproduces the failure, distinguishes implementation defects from test
68
+ compatibility assumptions and external blockers, and may make a minimal test
69
+ compatibility repair inside the admitted workspace while preserving the tested
70
+ behavior and every required safety guard. An unchanged filename alone cannot
71
+ justify an unrelated-failure diagnosis. Focused checks retain their original exit
72
+ codes; repaired assertions need negative cases. Saved/customized definitions keep
73
+ their frozen prompts; future unedited recipes use these updated defaults.
74
+
75
+ The paired Core also supplies the official verify skill and real status/apply
76
+ context before a read-only reviewer turn, including custom `assess` roles. Missing
77
+ planning context still blocks invocation; host preparation does not approve the
78
+ candidate or replace the review's inspection and structured findings.
79
+
57
80
  Admission freezes the graph, agents, Core package and repository checks. Resume
58
81
  uses this snapshot. Generic forks copy an active change into a separate change,
59
82
  carry scope-owned contracts forward, rebind `run.changeId`, and clear verification.
@@ -34,6 +34,20 @@ Mission mode (Agent Mode) replaces the routed dashboard, so `RailsBoard`, `Specs
34
34
 
35
35
  ## Protocol contract
36
36
 
37
+ Relaunch identifies the card's delivery or shared run with
38
+ `POST /rails/:railIndex/relaunch { sourceId, originConversationId?, originSurface? }`.
39
+ It uses the source's saved specs and launch options, even after terminal cleanup
40
+ emptied the rail. New attempts freeze these options in migration 71; historical
41
+ ones reconstruct only recorded loop/runtime/manifest fields. A missing workflow
42
+ requires a configured fresh launch. A rail reused for different specs is left
43
+ untouched; stale cards and delivered work that needs recovery are rejected.
44
+ Independent per-ticket siblings do not invalidate each other; newer overlapping
45
+ spec ownership does. A successful unit reporting no changes is not delivered work.
46
+ Undelivered failed generations are atomically superseded in fresh worktrees,
47
+ with allocation rollback and the original failed checkout retained. A synchronous
48
+ click guard prevents duplicate submissions; accepted relaunches stay disabled
49
+ until the new card appears, and rejected admission details remain inline.
50
+
37
51
  ```rail-launch
38
52
  {
39
53
  "version": 1,
@@ -85,6 +99,7 @@ WS messages: `agent_pr_decision` (existing, now carries the new fields), `agent_
85
99
 
86
100
  - `client/src/features/missions/components/AgentMessage.tsx` — extraction step after spec-draft (flag `FEATURE_MISSION_RAIL_CARDS`); renders `AgentRailLaunchCard` per proposal, `AgentRailLaunchPending`, `AgentRailLaunchUnreadable`.
87
101
  - `AgentRailLaunchCard.tsx` — live reconciliation (`/rails` availability, `/tickets`, `/profiles`, `useProviderDetection`, model/effort catalogs, loops), New rail, Play flow (`POST /rails` → `PUT /tickets` → `POST /launch` with `originConversationId` + `originSurface:'agent-chat'` → `notifyGitChanged` → intent PATCH), inline 400/409, launched/dismissed stubs, `FOCUS_PR_CARD_EVENT`.
102
+ - Repository context (`/repositories`) is required before Play. The card shows launch targets and each spec's saved scope, including primary-only historical specs. **Edit → Save repositories** persists only `repositoryIds` through the pinned project's existing ticket PATCH; Cancel leaves it untouched. Saved scope and spec-list changes reconcile required targets while retaining extra launch targets and dropping workspace entries for removed repositories. Optional launch targets do not edit specs. Omitted required targets and missing/offline/context-only implementation targets block Play visibly. **Refresh** reloads assignments; failed loads expose **Retry loading**. The resolved launch scope and filtered workspace selection are frozen in the launched intent, and server admission remains authoritative.
88
103
  - `client/src/features/rails/lib/rail-launch-intents.ts` — session overlay (`recordLocalIntent`, `intentFor`); `useRailLaunchProposals.ts` — undecided proposals for the dock; `AgentPrPinnedDock.tsx` — proposals pinned above PR cards.
89
104
  - `AgentPrDecisionCard.tsx` — run phase header (`deriveMissionRunStatus`, live `useRuntimeRuns` first), run-only rendering for `hasDelivery === false`, `RunFailureBlock`, Resume / Approve / Recover / Relaunch, focus-bus scroll + flash.
90
105
  - `agent-pr-pinning.ts` `isPrEnvelopePinned`; `agent-run-failure.ts` (`parseRunFailureRow`, `systemBriefingRunId`, `FOCUS_PR_CARD_EVENT`, `MISSION_OPEN_RUN_EVENT`, `requestMissionOpenRun`); `AgentRunFailureMarker.tsx`.
@@ -217,6 +217,12 @@ thresholds and publish separate reports. Native macOS, Windows Core assembly and
217
217
  runtime portability jobs remain required. The final `test` job preserves the check
218
218
  name used by branch protection and rejects any failed, skipped or cancelled lane.
219
219
 
220
+ The paired Core lane uses an immutable commit in `.github/workflows/ci.yml`.
221
+ When Desktop tests require new Core behavior, push that Core commit first and
222
+ update the pin in the same Desktop change. Validate the real bridge suites with
223
+ `SPECRAILS_CORE_SOURCE_DIR` selecting the built Core checkout; a missing checkout
224
+ skips these optional local tests and does not prove paired compatibility.
225
+
220
226
  Push CI still runs on every branch: native release validation requires a trusted
221
227
  push run for the exact branch and commit. PR and merge-queue runs remain enabled.
222
228
  Concurrency cancels superseded runs within the same event/ref. No path filters
@@ -113,7 +113,12 @@ Both temporary checkouts use non-force cleanup, including after a Git runner exc
113
113
  ## Launch and recovery wiring (`server/modules/delivery/runtime/rail-isolated-launch.ts`)
114
114
 
115
115
  - `prMode` is captured once. Admission is rechecked inside the per-repository allocation lock;
116
- two requests cannot both create a generation or reuse the ticket-keyed worktree.
116
+ two requests cannot both create a generation or claim the same continuation branch.
117
+ - Fresh launches allocate `<worktreesRoot>/<runId>/ticket-<id>` and a new collision-free
118
+ branch from the selected base. Failed attempts retain their dirty mounts and branches
119
+ for explicit recovery; pressing Launch never silently adopts their edits. Same-run
120
+ recovery uses the frozen mount. Recorded delivery/PR continuations find the exact
121
+ mounted branch and verify its recorded commit before execution.
117
122
  - A continuation supersedes its prior generation and creates the new `building` row in one DB
118
123
  transaction. Allocation failure closes the new row and restores the prior generation atomically,
119
124
  recording the failed replacement id so every client can safely undo its tombstone for that pair.
@@ -121,6 +126,16 @@ Both temporary checkouts use non-force cleanup, including after a Git runner exc
121
126
  verification distinguish changed, resumed, and no-change results.
122
127
  - Per-unit settlement returns structured execution + delivery results. `onLoopRunFinished` receives
123
128
  the engine outcome only; commit/status/ref/provenance/push failures cannot rewrite it.
129
+ - Before staging, settlement blocks active OpenSpec change directories introduced relative to
130
+ the frozen integration/base commit, including the run's own unarchived change. Base changes
131
+ and archives are allowed. It reports the paths as a blocked `commit_failed` delivery and
132
+ preserves the checkout; Core never archives unrelated changes on Desktop's behalf.
133
+ - Revision semantics require actual delivered branch/commit evidence. Open addenda on an
134
+ undelivered failed generation replace it atomically with a fresh full-spec launch, without
135
+ a revision briefing or branch adoption. Equivalent delta retries use readable, stable,
136
+ Core-compatible names derived from durable lineage/addendum bodies rather than run IDs.
137
+ All frozen addenda must be claimed before any agent starts; claim failure reopens earlier
138
+ sibling claims and rolls allocation back through the existing ownership-aware path.
124
139
  - Clean, committed worktrees may be released only after a final live preflight proves tracked and
125
140
  untracked status (apart from durably recorded Specrails overlay paths), plus exact worktree HEAD
126
141
  and branch ref. Gitignored paths are release-safe only when covered by the IMMUTABLE settlement
@@ -219,9 +234,12 @@ at launch:
219
234
  from the first root containing them when checkout lacks them. Windows: junction →
220
235
  dereferencing-copy fallback.
221
236
  - **Never on the PR**: the resume-safe manifest records authenticated overlay leaves against
222
- any configured root; settlement persists immutable fingerprints. Commit delivery deliberately
237
+ any configured root across supported provider namespaces, so switching provider retains
238
+ prior authenticated exclusions; settlement persists immutable fingerprints. Commit delivery deliberately
223
239
  runs plain `git add -A`, audits the index, resets forbidden literal top-level paths, re-audits,
224
- and makes those exclusions authoritative with `git commit --only`. The writable manifest alone
240
+ and makes those exclusions authoritative with `git commit --only`. Unexcluded staged symlinks
241
+ with absolute destinations outside the checkout block commit rather than leaking machine paths.
242
+ The writable manifest alone
225
243
  never grants cleanup authority. Cleanup atomically renames each authenticated root into a unique
226
244
  sibling quarantine, revalidates it after the move, discloses raced content there, and never
227
245
  deletes or restores quarantined paths automatically over a possibly recreated source. A modified
@@ -371,9 +389,10 @@ diff collection), fully unit-tested.
371
389
  nothing (`<type>/<ref>`). Every generated name passes `isValidBranchName`.
372
390
  - **Batch branch** (N>1 assembled delivery) — `<type>/<primary-ref>-batch-<n>-tickets`.
373
391
  - **Collisions** — bounded suffixing `-2`, `-3`… (20 attempts). At worktree allocation
374
- (`rail-isolated-launch`) a branch that a PRIOR rail run allocated for the SAME ticket (per the
375
- `rail_worktrees` ledger) is **resumed**, not suffixed — preserving the stop/relaunch resume
376
- semantics; exhaustion falls back to the legacy `sr/<slug>/ticket-<id>`. The **integration
392
+ (`rail-isolated-launch`) a fresh run collision-suffixes even a prior branch for the same ticket;
393
+ only exact recorded delivery/PR continuations reuse a branch. Same-run recovery retains its
394
+ frozen checkout. Exhaustion falls back to the legacy `sr/<slug>/ticket-<id>` with fresh-branch
395
+ validation. The **integration
377
396
  branch is never used** (reserved in the resolver + asserted in tests). `createWorktree` stays
378
397
  generic: an optional preferred `branch` input with the legacy fallback when absent.
379
398
  - **PR title** — `[<ref>]<type> - <change>` (e.g. `[SKILLS-101]feat - darkmode added`); the
@@ -781,4 +800,3 @@ typed, frozen delta that travels with the DELIVERY, never with the spec.
781
800
  scope-creep detection, PR head-drift revalidation policy, partial-commit
782
801
  recovery UX (the preparation-failure Discard fix above covers the stuck-rail
783
802
  half of the incident).
784
-
@@ -5,7 +5,7 @@ architectural dependency declaration. Search a heading or filename and read only
5
5
  the relevant source and tests. Feature prefixes group the legacy server files;
6
6
  new bounded modules live under `server/modules/`.
7
7
 
8
- Includes 1012 source/build files. Nearby tests are linked where names
8
+ Includes 1014 source/build files. Nearby tests are linked where names
9
9
  match; integration suites may live elsewhere. Use `rg` to find other consumers.
10
10
 
11
11
  ## cli
@@ -717,6 +717,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
717
717
 
718
718
  ## client/src/features/subscription-usage/components
719
719
 
720
+ - [EnterpriseSpendMeter.tsx](../../client/src/features/subscription-usage/components/EnterpriseSpendMeter.tsx)
720
721
  - [SubscriptionUsageFooter.tsx](../../client/src/features/subscription-usage/components/SubscriptionUsageFooter.tsx)
721
722
  - [SubscriptionUsagePanel.tsx](../../client/src/features/subscription-usage/components/SubscriptionUsagePanel.tsx)
722
723
  - [SubscriptionUsageSection.tsx](../../client/src/features/subscription-usage/components/SubscriptionUsageSection.tsx)
@@ -1219,6 +1220,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
1219
1220
  - [rail-pr-recovery-git.ts](../../server/modules/delivery/runtime/rail-pr-recovery-git.ts) · [test](../../server/modules/delivery/runtime/rail-pr-recovery-git.test.ts)
1220
1221
  - [rail-pr-store.ts](../../server/modules/delivery/runtime/rail-pr-store.ts) · [test](../../server/modules/delivery/runtime/rail-pr-store.test.ts)
1221
1222
  - [rail-pr-ticket-effects.ts](../../server/modules/delivery/runtime/rail-pr-ticket-effects.ts) · [test](../../server/modules/delivery/runtime/rail-pr-ticket-effects.test.ts)
1223
+ - [rail-relaunch.ts](../../server/modules/delivery/runtime/rail-relaunch.ts) · [test](../../server/modules/delivery/runtime/rail-relaunch.test.ts)
1222
1224
  - [rail-worktree-release.ts](../../server/modules/delivery/runtime/rail-worktree-release.ts) · [test](../../server/modules/delivery/runtime/rail-worktree-release.test.ts)
1223
1225
  - [rail-worktrees-store.ts](../../server/modules/delivery/runtime/rail-worktrees-store.ts) · [test](../../server/modules/delivery/runtime/rail-worktrees-store.test.ts)
1224
1226
  - [rails-router.ts](../../server/modules/delivery/runtime/rails-router.ts) · [test](../../server/modules/delivery/runtime/rails-router.test.ts)
@@ -16,6 +16,11 @@ The canon below is the **majority-existing code**, promoted to law. Reference im
16
16
  - **Priority canon** (the ProposeSpecModal set — the only fully tokenized one): critical=`destructive`, high=`accent-warning`, medium=`accent-info`, low=`muted`.
17
17
  - **Left sidebar title = accent-primary, right sidebars = accent-secondary** (existing rule; document, keep).
18
18
 
19
+ The left sidebar footer groups desktop tools behind a Settings gear and right
20
+ chevron disclosure, closed initially. Expanded tools appear vertically in this
21
+ order: Companion (when available), Analytics, Docs. The Settings icon and label
22
+ open the app settings modal; a separate chevron button toggles the group.
23
+
19
24
  ## 1.2 Icon scale (lucide, `w-N h-N` pair idiom — never `size={}` props)
20
25
  | Context | Size | Margin |
21
26
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.59.0",
3
+ "version": "2.59.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -73,7 +73,7 @@ class CoreUpdateManager {
73
73
  this.latestVersion != null &&
74
74
  currentVersion != null &&
75
75
  ((0, semver_lite_1.isNewer)(this.latestVersion, currentVersion) || selected.error !== null && this.latestVersion === currentVersion) &&
76
- Number(this.latestVersion.split('.')[0]) <= 5;
76
+ (0, core_package_1.isSupportedCoreVersion)(this.latestVersion);
77
77
  return {
78
78
  available: this.isAvailable(),
79
79
  currentVersion,
@@ -1608,6 +1608,15 @@ const MIGRATIONS = [
1608
1608
  (db) => {
1609
1609
  db.exec(`UPDATE rails SET mode='implement' WHERE mode IN ('batch-implement','batch')`);
1610
1610
  },
1611
+ // Migration 71: source-bound rail relaunch. NULL preserves historical rows;
1612
+ // new attempts freeze their admitted launch options before agent execution.
1613
+ (db) => {
1614
+ for (const table of ['rail_pr_deliveries', 'loop_runs']) {
1615
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all();
1616
+ if (!columns.some(column => column.name === 'launch_config_json'))
1617
+ db.exec(`ALTER TABLE ${table} ADD COLUMN launch_config_json TEXT`);
1618
+ }
1619
+ },
1611
1620
  ];
1612
1621
  function applyMigrations(db) {
1613
1622
  // Ensure the migrations table exists (migration 1 creates it, but we need
@@ -7,6 +7,7 @@ exports.readFrozenRuntimeHost = readFrozenRuntimeHost;
7
7
  exports.runAgentRuntimeInvocation = runAgentRuntimeInvocation;
8
8
  exports.assertRetainedForkSupport = assertRetainedForkSupport;
9
9
  exports.runAgentRuntimeControl = runAgentRuntimeControl;
10
+ const agent_runtime_verification_suggestions_1 = require("./agent-runtime-verification-suggestions");
10
11
  const agent_runtime_history_1 = require("./agent-runtime-history");
11
12
  const agent_runtime_repositories_1 = require("./agent-runtime-repositories");
12
13
  const node_util_1 = require("node:util");
@@ -67,6 +68,91 @@ function scopedHostChecks(checks, repositories = []) {
67
68
  return [{ ...check, cwd }];
68
69
  });
69
70
  }
71
+ /** Discover only checks belonging to the admitted worktrees. Explicit host
72
+ * checks win; inferred commands are frozen per run, never saved to project settings. */
73
+ function completeHostChecks(config, repositories) {
74
+ for (const repository of repositories) {
75
+ if (config.verification.some(check => check.repositoryId === repository.id) || !repository.path)
76
+ continue;
77
+ const workspacePaths = (repository.scope?.length ? repository.scope.map(scope => (0, node_path_1.resolve)(repository.path, scope)) : [repository.path]).filter(workspace => (0, node_fs_1.existsSync)(workspace));
78
+ const root = (0, node_fs_1.realpathSync)(repository.path);
79
+ for (const workspace of workspacePaths) {
80
+ const inside = (0, node_path_1.relative)(root, (0, node_fs_1.realpathSync)(workspace));
81
+ if ((0, node_path_1.isAbsolute)(inside) || inside === '..' || inside.startsWith('..' + node_path_1.sep))
82
+ throw new Error('Verification workspace escapes its admitted repository');
83
+ }
84
+ const suggestions = (0, agent_runtime_verification_suggestions_1.suggestVerificationCommands)([{ id: repository.id, path: repository.path, workspacePaths }]);
85
+ config.verification.push(...suggestions.map(({ reason: _reason, ...check }) => check));
86
+ }
87
+ }
88
+ /** Configured-only gates cannot obtain missing checks from an agent later. */
89
+ function requiresConfiguredChecks(value) {
90
+ if (!value || typeof value !== 'object')
91
+ return false;
92
+ const body = value;
93
+ return Object.values(body.nodes ?? {}).some(node => node.kind === 'verify' && node.params?.commands === 'configured' && !node.params.additionalCommandsFrom && node.params.unverified !== true)
94
+ || Object.values(body.components ?? {}).some(requiresConfiguredChecks);
95
+ }
96
+ /** Keep TAP failure diagnostics and totals in the readable log. The unfiltered
97
+ * event stream remains available as runtime evidence. State is invocation-local. */
98
+ function verificationLogFilter() {
99
+ const streams = new Map();
100
+ return text => (0, node_util_1.stripVTControlCharacters)(text).split(/(?<=\n)/).flatMap(line => {
101
+ const match = line.match(/^(\[verification [^\]]+\] )?(.*?)(?:\n)?$/);
102
+ if (!match)
103
+ return [line];
104
+ const key = match[1] ?? '', body = match[2].trim();
105
+ const state = streams.get(key) ?? { tap: false, failed: false, yaml: false, inputLines: null };
106
+ streams.set(key, state);
107
+ if (/AssertionError\b.*\bInput:\s*$/.test(body))
108
+ state.inputLines = 0;
109
+ else if (state.inputLines !== null) {
110
+ // Node prints assert.match's entire source input as quoted concatenation
111
+ // lines. Bound only that block; stacks, expected values and other streams
112
+ // remain visible and original events remain intact.
113
+ if (/^["'`]/.test(body)) {
114
+ state.inputLines++;
115
+ if (state.inputLines > 2)
116
+ return state.inputLines === 3 ? [key + '[Assertion input shortened; inspect raw evidence]\n'] : [];
117
+ }
118
+ else if (!body)
119
+ return [];
120
+ else
121
+ state.inputLines = null;
122
+ }
123
+ if (/^[✔✓]\s/.test(body))
124
+ return [];
125
+ if (/^[✖✗]\s/.test(body)) {
126
+ state.failed = true;
127
+ return [line];
128
+ }
129
+ if (/^TAP version |^# Subtest:/.test(body)) {
130
+ state.tap = true;
131
+ return [];
132
+ }
133
+ if (/^(?:not )?ok \d+\b/.test(body)) {
134
+ state.tap = true;
135
+ state.failed = body.startsWith('not ok');
136
+ state.yaml = false;
137
+ return state.failed ? [line] : [];
138
+ }
139
+ if (!state.tap)
140
+ return [line];
141
+ if (body === '---') {
142
+ state.yaml = true;
143
+ return state.failed ? [line] : [];
144
+ }
145
+ if (body === '...') {
146
+ state.yaml = false;
147
+ return state.failed ? [line] : [];
148
+ }
149
+ if (state.yaml)
150
+ return state.failed ? [line] : [];
151
+ if (/^1\.\.\d+$/.test(body) || !body)
152
+ return [];
153
+ return [line];
154
+ }).map(line => line.length > 2_000 ? line.slice(0, 1_900) + '\n[Long diagnostic line shortened; inspect raw evidence]\n' : line).join('');
155
+ }
70
156
  exports.RUNTIME_HOST_ENV_KEYS = [
71
157
  'SPECRAILS_REPO_DIR', 'SPECRAILS_GIT_AUTO', 'SPECRAILS_REPO_MAP_PATH',
72
158
  'SPECRAILS_EXECUTION_CONTEXT', 'SPECRAILS_EXECUTION_MANIFEST', 'SPECRAILS_REPOSITORIES',
@@ -126,6 +212,8 @@ async function runAgentRuntimeInvocation(options) {
126
212
  repositoryIds: admittedContext.repositories.map(repo => repo.id), source: options.loopConfig ? 'loop-role' : source, providerOverride: options.loopConfig ? undefined : options.providerOverride,
127
213
  });
128
214
  config.verification = scopedHostChecks(config.verification, admittedContext.repositories);
215
+ if (definitionEngine)
216
+ completeHostChecks(config, admittedContext.repositories);
129
217
  if (!options.loopConfig)
130
218
  config.rolePrompts = { ...(await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).rolePromptDefaults(), ...(0, agent_runtime_settings_1.loadRuntimeRolePrompts)(), ...config.rolePrompts };
131
219
  // Core rejects unknown connection keys: drop the desktop-only local-engine
@@ -146,6 +234,13 @@ async function runAgentRuntimeInvocation(options) {
146
234
  : options.definitionPath ? JSON.parse((0, node_fs_1.readFileSync)(options.definitionPath, 'utf8')) : undefined : undefined;
147
235
  if (definitionEngine && !draft)
148
236
  throw new Error('A new Core workflow requires a definition');
237
+ if (requiresConfiguredChecks(draft)) {
238
+ const missing = admittedContext.repositories.filter(repository => !config.verification.some(check => check.repositoryId === repository.id));
239
+ if (missing.length)
240
+ throw new Error(`verification_checks_missing: No configured or detected verification commands for ${missing.map(repository => repository.name ?? repository.id).join(', ')}. Configure checks in project agent-runtime settings before starting this workflow.`);
241
+ if (config.verification.length > 100)
242
+ throw new Error('verification_checks_limit: This workflow supports at most 100 verification commands. Reduce the configured checks before starting it.');
243
+ }
149
244
  const selectedOrigins = (0, agent_runtime_effective_config_1.bindWorkflowRoleSelections)(config, draft, options.workflowRoleBindings);
150
245
  if (options.loopConfig && draft?.roles?.includes('loop-decider') && !config.roles?.['loop-decider'])
151
246
  throw new Error('The loop must define its loop-decider agent');
@@ -241,6 +336,7 @@ async function runAgentRuntimeInvocation(options) {
241
336
  let result;
242
337
  let graph;
243
338
  let stderr = '', summary = '', invalidProtocol = false, timedOut = false;
339
+ const stepFailures = new Map();
244
340
  let timer;
245
341
  const child = (0, node_child_process_1.spawn)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), args, {
246
342
  cwd: options.cwd, env: (0, win_spawn_1.windowsSpawnEnv)(options.env), shell: false, windowsHide: true,
@@ -260,6 +356,7 @@ async function runAgentRuntimeInvocation(options) {
260
356
  callback?.();
261
357
  }
262
358
  catch { /* Logging cannot replay a workflow. */ } };
359
+ const readableVerification = verificationLogFilter();
263
360
  const lines = (0, node_readline_1.createInterface)({ input: child.stdout });
264
361
  lines.on('line', line => {
265
362
  if (line.length > 2_000_000) {
@@ -310,8 +407,17 @@ async function runAgentRuntimeInvocation(options) {
310
407
  }
311
408
  else if (event.type === 'workflow-event') {
312
409
  const payload = event.event;
313
- if (payload?.type)
314
- observe(() => options.onLine?.(`[runtime] ${payload.type}${payload.stepId ? ': ' + payload.stepId : ''}${payload.message ? ' — ' + payload.message : ''}\n`));
410
+ const kind = payload?.type === 'step_succeeded' && payload.outcome === 'fail' ? 'verification_failed' : payload?.type;
411
+ const detail = payload?.error?.message ?? payload?.message;
412
+ const stepKey = JSON.stringify([payload?.scopeId ?? null, payload?.nodePath ?? payload?.stepId ?? null]);
413
+ if (payload?.type === 'step_failed' && typeof detail === 'string' && detail.trim()) {
414
+ stepFailures.delete(stepKey);
415
+ stepFailures.set(stepKey, detail.trim().slice(0, 4_000));
416
+ }
417
+ else if (payload?.type === 'step_succeeded')
418
+ stepFailures.delete(stepKey);
419
+ if (kind)
420
+ observe(() => options.onLine?.(`[runtime] ${kind}${payload.stepId ? ': ' + payload.stepId : ''}${detail ? ' — ' + detail : ''}\n`));
315
421
  }
316
422
  else if (event.type === 'agent-event') {
317
423
  const payload = event.event;
@@ -328,7 +434,9 @@ async function runAgentRuntimeInvocation(options) {
328
434
  }
329
435
  }
330
436
  else if (event.type === 'verification-output' && typeof event.text === 'string') {
331
- observe(() => options.onLine?.((0, node_util_1.stripVTControlCharacters)(String(event.text))));
437
+ const readable = readableVerification(String(event.text));
438
+ if (readable)
439
+ observe(() => options.onLine?.(readable));
332
440
  }
333
441
  else if (event.type === 'span') {
334
442
  // Trace spans are telemetry; the raw line is already recorded for diagnostics.
@@ -364,6 +472,7 @@ async function runAgentRuntimeInvocation(options) {
364
472
  const validPause = definitionEngine && code === 2 && result?.status === 'paused';
365
473
  const failed = (!validPause && (code !== 0 || result?.status !== 'succeeded')) || invalidProtocol || timedOut || Boolean(observerError) || Boolean(acceptanceBlocked);
366
474
  const runtimeError = typeof result?.error === 'string' ? result.error : result?.error?.message;
475
+ const completionError = Array.isArray(result?.completion?.reasons) ? result.completion.reasons.filter(reason => typeof reason === 'string' && reason.trim()).join('; ').slice(0, 4_000) : '';
367
476
  const runtimeStatus = invalidProtocol || timedOut || observerError || (result?.status === 'paused' && !validPause) || (result?.status === 'succeeded' && code !== 0) ? 'failed' : acceptanceBlocked ? 'blocked' : ['succeeded', 'paused', 'failed', 'blocked', 'cancelled'].includes(result?.status ?? '') ? result.status : 'failed';
368
477
  const errorText = observerError ?? (timedOut ? 'Programmatic workflow timed out; inspect its checkpoint before recovery'
369
478
  : invalidProtocol ? 'Core returned an invalid runtime event stream'
@@ -371,9 +480,9 @@ async function runAgentRuntimeInvocation(options) {
371
480
  : runtimeError ?? (result?.status === 'paused' ? (typeof result.pendingQuestion?.question === 'string' && result.pendingQuestion.question.trim()
372
481
  ? `Workflow awaits an answer in Agent Runtime settings: ${result.pendingQuestion.question.trim().slice(0, 500)}`
373
482
  : 'Workflow awaits approval in Agent Runtime settings')
374
- : failed ? stderr || 'Core exited without a successful programmatic workflow result' : undefined));
483
+ : failed ? completionError || [...stepFailures.values()].at(-1) || stderr || 'Core exited without a successful programmatic workflow result' : undefined));
375
484
  try {
376
- (0, agent_runtime_history_1.writeRuntimeHistory)(options.contextPath, invalidProtocol || timedOut || observerError || !result ? { status: 'failed', error: errorText } : { ...result });
485
+ (0, agent_runtime_history_1.writeRuntimeHistory)(options.contextPath, invalidProtocol || timedOut || observerError || !result ? { status: 'failed', error: errorText } : { ...result, ...(failed && errorText ? { error: errorText } : {}) });
377
486
  }
378
487
  catch { /* Projection failure cannot replay a completed execution. */ }
379
488
  resolve({
@@ -231,7 +231,7 @@ async function launchMultiRepositoryRail(input, io = {}) {
231
231
  integrationBranches.set(repo.id, target.branch);
232
232
  }
233
233
  }
234
- const previousParentId = input.revision?.ofDeliveryId ?? input.repositoryContinuation?.deliveryId;
234
+ const previousParentId = input.revision?.ofDeliveryId ?? input.repositoryContinuation?.deliveryId ?? input.retryOfDelivery?.deliveryId;
235
235
  const previousParent = previousParentId ? (0, rail_pr_store_1.getPrDelivery)(ctx.db, previousParentId) : undefined;
236
236
  const previousManifest = (0, multi_repo_execution_store_1.readExecutionManifest)(previousParent?.execution_manifest);
237
237
  const previousChildren = previousParentId ? listRepositoryDeliveries(ctx.db, previousParentId) : [];
@@ -268,12 +268,13 @@ async function launchMultiRepositoryRail(input, io = {}) {
268
268
  artifactRepositoryId, selectedRepositoryIds: selected.map((repo) => repo.id), repositories: [],
269
269
  };
270
270
  const generation = (0, rail_pr_store_1.createPrDeliveryGeneration)(ctx.db, {
271
+ launchConfig: input.launchConfig,
271
272
  id: parentId, railIndex: input.railIndex, loopId: input.loopId, railKey: `${input.railIndex}-${input.loopId}`,
272
273
  ticketIds: input.ticketIds, baseBranch: input.baseBranch ?? '', loopName: input.loopName,
273
274
  originSurface: input.originSurface ?? 'dashboard', originConversationId: input.originConversationId,
274
275
  specSnapshot: (0, rail_isolated_launch_1.buildSpecSnapshot)(ctx, input.ticketIds),
275
276
  ...(input.revision ? { revisionNote: input.revision.note, revisionOf: input.revision.ofDeliveryId } : {}),
276
- }, input.revision ? { id: input.revision.ofDeliveryId, decision: input.revision.decision } : input.repositoryContinuation ? { id: input.repositoryContinuation.deliveryId, decision: input.repositoryContinuation.decision } : null);
277
+ }, input.revision ? { id: input.revision.ofDeliveryId, decision: input.revision.decision } : input.repositoryContinuation ? { id: input.repositoryContinuation.deliveryId, decision: input.repositoryContinuation.decision } : input.retryOfDelivery ? { id: input.retryOfDelivery.deliveryId, decision: input.retryOfDelivery.decision } : null);
277
278
  for (const child of previousChildren) {
278
279
  if (!['merged', 'completed', 'discarded', 'superseded'].includes(child.decision))
279
280
  (0, rail_pr_store_1.transitionDecision)(ctx.db, child.id, child.decision, 'superseded');
@@ -315,14 +316,14 @@ async function launchMultiRepositoryRail(input, io = {}) {
315
316
  const childSettlement = deferred();
316
317
  const previous = previousChildren.find((row) => row.repository_id === repository.id);
317
318
  const previousRepository = previousManifest?.repositories.find((row) => row.repositoryId === repository.id);
318
- const previousBaseBranch = previous && ['merged', 'completed'].includes(previous.decision)
319
+ const previousBaseBranch = input.retryOfDelivery ? undefined : previous && ['merged', 'completed'].includes(previous.decision)
319
320
  ? previousRepository?.integrationBranch ?? previous.base_branch
320
321
  : previous?.base_branch;
321
322
  const oldSnapshot = previous ? (0, rail_pr_store_1.toPrDeliverySnapshot)(previous) : null;
322
323
  const revisionBranches = new Map();
323
- if (oldSnapshot && !['merged', 'completed', 'discarded'].includes(oldSnapshot.decision) && !previous?.pr_url) {
324
+ if (!input.retryOfDelivery && oldSnapshot && !['merged', 'completed', 'discarded'].includes(oldSnapshot.decision) && !previous?.pr_url) {
324
325
  for (const unit of oldSnapshot.branches) {
325
- if (unit.finalSha)
326
+ if (unit.succeeded && unit.finalSha && unit.implementationOutcome !== 'failed' && unit.deliveryOutcome !== 'blocked')
326
327
  revisionBranches.set(unit.ticketId, { branch: unit.branch, sha: unit.finalSha });
327
328
  }
328
329
  }
@@ -355,6 +356,7 @@ async function launchMultiRepositoryRail(input, io = {}) {
355
356
  ...input, ctx: childCtx, scope: effectiveScope, loopGraph: graph,
356
357
  runtimeStateProject: ctx.project,
357
358
  revision: input.revision && previous ? { ...input.revision, ofDeliveryId: previous.id, decision: previous.decision } : undefined,
359
+ retryOfDelivery: input.retryOfDelivery && previous ? { deliveryId: previous.id, decision: previous.decision } : undefined,
358
360
  requiredPrContinuation: continuation,
359
361
  explicitPrTarget: continuation?.prNumber ? { prNumber: continuation.prNumber } : input.explicitPrTarget,
360
362
  originConversationId: undefined,