specrails-desktop 2.34.0 → 2.35.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-LCBJ5WiD.js +1 -0
- package/client/dist/assets/AgentBrowserCapture-B-xQpyZr.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-BI1Vcf3T.js +2 -0
- package/client/dist/assets/AgentModeCodePane-BTRwCl9h.js +2 -0
- package/client/dist/assets/AgentModeJobsPane-VzeBJLqP.js +1 -0
- package/client/dist/assets/AgentsPage-CJj1K24O.js +87 -0
- package/client/dist/assets/AnalyticsPage-CQpV5_fX.js +1 -0
- package/client/dist/assets/CodePage-9-tFh0jB.js +2 -0
- package/client/dist/assets/DesktopAnalyticsPage-BcZ0_O_C.js +1 -0
- package/client/dist/assets/DocsDialog-BPAzDICW.js +11 -0
- package/client/dist/assets/DocsPage-Dqo4Hn6G.js +11 -0
- package/client/dist/assets/IntegrationsPage-Bhy58lnQ.js +1 -0
- package/client/dist/assets/InteractiveJobComposer-D36fZWai.js +19 -0
- package/client/dist/assets/JobDetailModal-BxsktbkQ.js +1 -0
- package/client/dist/assets/JobDetailPage-WWyCy6Sp.js +1 -0
- package/client/dist/assets/JobsPage-CEbM7uxU.js +1 -0
- package/client/dist/assets/{LoopBuilderPage-WeiofeL6.js → LoopBuilderPage-CYsSN8io.js} +2 -2
- package/client/dist/assets/LoopPreviewModal-BJhgX5wY.js +1 -0
- package/client/dist/assets/LoopsPage-DB4OqMot.js +1 -0
- package/client/dist/assets/MinimizedChatsContext-CcjBHMHd.js +1 -0
- package/client/dist/assets/PluginsPage-CR8K4nM_.js +2 -0
- package/client/dist/assets/{ProjectSettingsDialog-BkCXBQWk.js → ProjectSettingsDialog-C45-qhF4.js} +1 -1
- package/client/dist/assets/ReviewPacketPage-Cz_QQeC3.js +1 -0
- package/client/dist/assets/TemplatePreviewModal-wFBd2vQP.js +1 -0
- package/client/dist/assets/TicketDetailModal-BvZoyDAL.js +14 -0
- package/client/dist/assets/{Trans-CTqw6rr9.js → Trans-DXZ-BLXb.js} +1 -1
- package/client/dist/assets/agent-BQgKv_2F.js +1 -0
- package/client/dist/assets/agent-C1NRDJqL.js +1 -0
- package/client/dist/assets/agent-CLNleiy6.js +1 -0
- package/client/dist/assets/agent-CmceArkQ.js +1 -0
- package/client/dist/assets/agent-D3UxQ3SR.js +1 -0
- package/client/dist/assets/agent-DB1JeE64.js +1 -0
- package/client/dist/assets/agent-DXkHcQ-D.js +1 -0
- package/client/dist/assets/agent-DjJJJ2M8.js +1 -0
- package/client/dist/assets/ban-E4EaZe3a.js +1 -0
- package/client/dist/assets/bot-DzuuuC5R.js +1 -0
- package/client/dist/assets/{browser-DxP-alBK.js → browser-BgqHDMXP.js} +1 -1
- package/client/dist/assets/{browser-CyR1Kp3m.js → browser-CsKJtrpv.js} +1 -1
- package/client/dist/assets/browser-Cu58EpZU.js +1 -0
- package/client/dist/assets/{browser-rRCwi7Y7.js → browser-CvXOJm-j.js} +1 -1
- package/client/dist/assets/{browser-CvgDySd9.js → browser-DOrFh1c7.js} +1 -1
- package/client/dist/assets/{browser-DwZljcRh.js → browser-DQv_Jo7x.js} +1 -1
- package/client/dist/assets/{browser-C25DkQiO.js → browser-LPgxvJu2.js} +1 -1
- package/client/dist/assets/{browser-CiJ7-L32.js → browser-eL-3MIEI.js} +1 -1
- package/client/dist/assets/button-CJqFnUOL.js +1 -0
- package/client/dist/assets/{chunk-KS7C4IRE-Cx5sqopX.js → chunk-KS7C4IRE-DEKu087k.js} +1 -1
- package/client/dist/assets/{commands-DyUZQ1FI.js → commands-6tYQih5m.js} +1 -1
- package/client/dist/assets/{commands-B9RZO4qS.js → commands-BK2QinXN.js} +1 -1
- package/client/dist/assets/{commands-Bbxu8kLU.js → commands-C2Nca76h.js} +1 -1
- package/client/dist/assets/{commands-CDU3qv68.js → commands-CKdLihNX.js} +1 -1
- package/client/dist/assets/{commands-DTQBF_q5.js → commands-CYIBTAlc.js} +1 -1
- package/client/dist/assets/{commands-hOtKuOXc.js → commands-CjUtYJIM.js} +1 -1
- package/client/dist/assets/{commands-BEwbAjtD.js → commands-Cq_u_8y9.js} +1 -1
- package/client/dist/assets/{commands-CzezgtQj.js → commands-D_ZHYQug.js} +1 -1
- package/client/dist/assets/core-B3mKBIh5.js +1 -0
- package/client/dist/assets/dashboard-B9GhC5ku.js +1 -0
- package/client/dist/assets/dashboard-C8WOEiPu.js +1 -0
- package/client/dist/assets/dashboard-CBnv6zOI.js +1 -0
- package/client/dist/assets/dashboard-CaP0fU87.js +1 -0
- package/client/dist/assets/dashboard-D4ZZ4x84.js +1 -0
- package/client/dist/assets/dashboard-DmuTQBX8.js +1 -0
- package/client/dist/assets/dashboard-DynLYj3d.js +1 -0
- package/client/dist/assets/dashboard-zj4gGFJ1.js +1 -0
- package/client/dist/assets/dialog-CdWCb02j.js +45 -0
- package/client/dist/assets/dist-js-CWeUdf5Z.js +1 -0
- package/client/dist/assets/dist-js-Rlh4FLH5.js +1 -0
- package/client/dist/assets/event-Bt7f3wXe.js +1 -0
- package/client/dist/assets/file-text-BcLCfcWZ.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-BOuXgebD.js → formatDistanceToNow-C0ImhVKp.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-EjGOpn4b.js → getTimezoneOffsetInMilliseconds-owhDdTXs.js} +1 -1
- package/client/dist/assets/git-refresh-wTW0rO0Z.js +1 -0
- package/client/dist/assets/i18n-DwgvwTnx.js +2 -0
- package/client/dist/assets/index-DhjjFyU5.js +66 -0
- package/client/dist/assets/index-xBFsOEiJ.css +2 -0
- package/client/dist/assets/jira-api-CcZMcEN5.js +1 -0
- package/client/dist/assets/loops-B7YpGizO.js +1 -0
- package/client/dist/assets/{loops-CMMQOw2P.js → loops-BomD2J-h.js} +1 -1
- package/client/dist/assets/{loops-B2_97D26.js → loops-BrQHSQw3.js} +1 -1
- package/client/dist/assets/{loops-BwxCndZk.js → loops-BvBwvTPA.js} +1 -1
- package/client/dist/assets/{loops-BISOkuCu.js → loops-CODk6Slz.js} +1 -1
- package/client/dist/assets/{loops-CD19kEzq.js → loops-DC6k-Ym-.js} +1 -1
- package/client/dist/assets/{loops-aEb4RxN2.js → loops-DSEg8-uY.js} +1 -1
- package/client/dist/assets/{loops-Bo6w_peZ.js → loops-DhCoXD_v.js} +1 -1
- package/client/dist/assets/narration-4Z2VUhZw.js +1 -0
- package/client/dist/assets/narration-BGuiEDj3.js +1 -0
- package/client/dist/assets/narration-BuSoqRmv.js +1 -0
- package/client/dist/assets/narration-CB9t29fy.js +1 -0
- package/client/dist/assets/narration-CU3x8FHD.js +1 -0
- package/client/dist/assets/narration-CWeXhfP5.js +1 -0
- package/client/dist/assets/narration-DRaDnhT5.js +1 -0
- package/client/dist/assets/narration-DtEbQgCw.js +1 -0
- package/client/dist/assets/packet-B1IxaL5f.js +1 -0
- package/client/dist/assets/packet-Bh5kkbiB.js +1 -0
- package/client/dist/assets/packet-By8fqYxW.js +1 -0
- package/client/dist/assets/packet-CZdvA44E.js +1 -0
- package/client/dist/assets/packet-CrjHRQCP.js +1 -0
- package/client/dist/assets/packet-Dwi2pTtx.js +1 -0
- package/client/dist/assets/packet-_-ZIa0IR.js +1 -0
- package/client/dist/assets/packet-n0W6E1Hn.js +1 -0
- package/client/dist/assets/save-B6wGWwBR.js +1 -0
- package/client/dist/assets/settings-Bd0x4TBa.js +1 -0
- package/client/dist/assets/settings-C1-7cXzC.js +1 -0
- package/client/dist/assets/settings-CAFaT38b.js +1 -0
- package/client/dist/assets/settings-Ct3v1nW2.js +1 -0
- package/client/dist/assets/settings-DHh7_IF5.js +1 -0
- package/client/dist/assets/settings-DQXAmdcv.js +1 -0
- package/client/dist/assets/settings-guzlhpup.js +1 -0
- package/client/dist/assets/settings-sZfTPMEH.js +1 -0
- package/client/dist/assets/{spending-BpYPsevQ.js → spending-BQX3P7Cq.js} +1 -1
- package/client/dist/assets/useDesktop-BzX769yH.js +1 -0
- package/client/dist/index.html +29 -31
- package/docs/internals/embedded-browser-native-webview-evaluation.md +161 -0
- package/docs/internals/review-packet.md +212 -0
- package/package.json +1 -1
- package/server/dist/accept-ladder.js +61 -0
- package/server/dist/agent-chat-manager.js +22 -1
- package/server/dist/agent-defaults.js +359 -0
- package/server/dist/agent-operator-prompt.js +13 -5
- package/server/dist/browser-playwright.js +39 -2
- package/server/dist/db.js +38 -0
- package/server/dist/delivery-evidence.js +242 -0
- package/server/dist/desktop-router.js +23 -0
- package/server/dist/feature-flags.js +23 -0
- package/server/dist/loop-command-catalog.js +33 -0
- package/server/dist/loop-executors.js +12 -2
- package/server/dist/loop-factory.js +16 -1
- package/server/dist/loops-router.js +2 -0
- package/server/dist/mcp/tools/rails.js +16 -0
- package/server/dist/project-registry.js +33 -1
- package/server/dist/project-router-loop-runs.js +27 -2
- package/server/dist/providers/claude-adapter.js +60 -3
- package/server/dist/queue-manager.js +47 -4
- package/server/dist/rail-isolated-launch.js +119 -5
- package/server/dist/rail-pr-store.js +40 -3
- package/server/dist/rail-worktree-release.js +17 -1
- package/server/dist/rails-router.js +124 -3
- package/server/dist/review-packet.js +327 -0
- package/server/dist/revision-seed.js +73 -0
- package/server/dist/run-duration-stats.js +71 -0
- package/server/dist/stuck-run-detector.js +153 -0
- package/server/dist/telemetry-receiver.js +22 -0
- package/server/dist/worktree-node-modules.js +138 -16
- package/client/dist/assets/ActivityFeedPage-DIDxNsDj.js +0 -1
- package/client/dist/assets/AgentBrowserCapture-7pJ2G5VY.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-BSSyTnTM.js +0 -2
- package/client/dist/assets/AgentModeCodePane-DzW6gvDw.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-9wJff15f.js +0 -1
- package/client/dist/assets/AgentsPage-CIA67psb.js +0 -87
- package/client/dist/assets/AnalyticsPage-C0LwYELz.js +0 -1
- package/client/dist/assets/CodePage-RtKvHmAO.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-BU8Y_Ts1.js +0 -1
- package/client/dist/assets/DocsDialog-DIpoaGlQ.js +0 -11
- package/client/dist/assets/DocsPage-ME47KZpK.js +0 -11
- package/client/dist/assets/IntegrationsPage-Bs00_tl4.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-BGjIe9R6.js +0 -19
- package/client/dist/assets/JobDetailModal-1UZkqH4I.js +0 -1
- package/client/dist/assets/JobDetailPage-CN65sAf1.js +0 -1
- package/client/dist/assets/JobsPage-f-4dqys7.js +0 -1
- package/client/dist/assets/LoopPreviewModal-DDTnTyH0.js +0 -1
- package/client/dist/assets/LoopsPage-BVNchIex.js +0 -1
- package/client/dist/assets/MinimizedChatsContext-CHsu3NCX.js +0 -1
- package/client/dist/assets/PluginsPage-CdFzGEfQ.js +0 -2
- package/client/dist/assets/TemplatePreviewModal-RDi0khFG.js +0 -1
- package/client/dist/assets/TicketDetailModal-MhJzrqbi.js +0 -14
- package/client/dist/assets/agent-Bd-q3Wqp.js +0 -1
- package/client/dist/assets/agent-BlvdW4Hv.js +0 -1
- package/client/dist/assets/agent-C-KQhxeM.js +0 -1
- package/client/dist/assets/agent-Ca15w5eu.js +0 -1
- package/client/dist/assets/agent-D6fKaoMd.js +0 -1
- package/client/dist/assets/agent-HFMYMTbB.js +0 -1
- package/client/dist/assets/agent-QIEbkBjg.js +0 -1
- package/client/dist/assets/agent-mTCsAbCl.js +0 -1
- package/client/dist/assets/api-B2M7CtDw.js +0 -1
- package/client/dist/assets/arrow-left-CpIR4j7G.js +0 -1
- package/client/dist/assets/arrow-up-right-9DoMn7fD.js +0 -1
- package/client/dist/assets/browser-CXUcnTMI.js +0 -1
- package/client/dist/assets/button-BUIIBQPT.js +0 -1
- package/client/dist/assets/chevron-right-CViMxucY.js +0 -1
- package/client/dist/assets/dashboard-BHRqsGCV.js +0 -1
- package/client/dist/assets/dashboard-Bfr90vOl.js +0 -1
- package/client/dist/assets/dashboard-CTsM8x3m.js +0 -1
- package/client/dist/assets/dashboard-CqC6wX0h.js +0 -1
- package/client/dist/assets/dashboard-CuZtAszc.js +0 -1
- package/client/dist/assets/dashboard-DAcqIc-n.js +0 -1
- package/client/dist/assets/dashboard-DekiV-86.js +0 -1
- package/client/dist/assets/dashboard-DoDczc5S.js +0 -1
- package/client/dist/assets/dialog-CzIekCw0.js +0 -45
- package/client/dist/assets/dist-Cez5CRkX.js +0 -1
- package/client/dist/assets/dist-js-BmsFWRly.js +0 -1
- package/client/dist/assets/dist-js-J5W_N5ng.js +0 -1
- package/client/dist/assets/external-link-BcU5EWZp.js +0 -1
- package/client/dist/assets/file-text-CFT-xXKN.js +0 -1
- package/client/dist/assets/i18n-nJQvD5zJ.js +0 -2
- package/client/dist/assets/index-C_sdsSZt.js +0 -63
- package/client/dist/assets/index-DCN5QbDm.css +0 -2
- package/client/dist/assets/jira-api-Bhixwn-d.js +0 -1
- package/client/dist/assets/loops-CnxqL8IL.js +0 -1
- package/client/dist/assets/save-28sIlCdr.js +0 -1
- package/client/dist/assets/settings-BveOJoaI.js +0 -1
- package/client/dist/assets/settings-Cj1C6F5k.js +0 -1
- package/client/dist/assets/settings-CuVwOpe0.js +0 -1
- package/client/dist/assets/settings-D5X4DfRc.js +0 -1
- package/client/dist/assets/settings-DXwxe-BW.js +0 -1
- package/client/dist/assets/settings-Di81Ba_V.js +0 -1
- package/client/dist/assets/settings-jbLjCYC_.js +0 -1
- package/client/dist/assets/settings-wIZNPFnn.js +0 -1
- package/client/dist/assets/useBrowserCaptureSession-BBXBN0Na.js +0 -1
- package/client/dist/assets/useDesktop-B6Du2N83.js +0 -1
- package/client/dist/assets/useSharedWebSocket-DK96ctb7.js +0 -1
|
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.claudeAdapter = void 0;
|
|
14
14
|
exports._normaliseClaudeModel = normaliseModel;
|
|
15
|
+
exports._resolveClaudeSpawnModel = resolveSpawnModel;
|
|
15
16
|
const child_process_1 = require("child_process");
|
|
16
17
|
const path_1 = __importDefault(require("path"));
|
|
17
18
|
const WHICH_CMD = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -33,6 +34,7 @@ function normaliseModel(model) {
|
|
|
33
34
|
return 'sonnet';
|
|
34
35
|
case 'claude-fable-5':
|
|
35
36
|
return 'fable';
|
|
37
|
+
case 'claude-opus-5':
|
|
36
38
|
case 'claude-opus-4-8':
|
|
37
39
|
case 'claude-opus-4-5':
|
|
38
40
|
case 'claude-opus-4-1-20250805':
|
|
@@ -46,6 +48,24 @@ function normaliseModel(model) {
|
|
|
46
48
|
return model || 'sonnet';
|
|
47
49
|
}
|
|
48
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Catalog aliases whose GENERATION Specrails pins explicitly.
|
|
53
|
+
*
|
|
54
|
+
* The catalog value (`opus`) stays the stored, validated and displayed identity
|
|
55
|
+
* — profiles, project settings, conversation rows and analytics all keep using
|
|
56
|
+
* it, and `normaliseModel` collapses the pinned id back to it. Only the spawn
|
|
57
|
+
* argument is expanded, so picking "Claude Opus" is a product decision about
|
|
58
|
+
* WHICH Opus runs instead of delegating that to whatever generation the CLI's
|
|
59
|
+
* bare `opus` alias currently points at.
|
|
60
|
+
*/
|
|
61
|
+
const PINNED_ALIAS_MODEL_IDS = {
|
|
62
|
+
opus: 'claude-opus-5',
|
|
63
|
+
};
|
|
64
|
+
/** Catalog value (or concrete id) in, spawn model id out. */
|
|
65
|
+
function resolveSpawnModel(model) {
|
|
66
|
+
const alias = normaliseModel(model);
|
|
67
|
+
return PINNED_ALIAS_MODEL_IDS[alias] ?? alias;
|
|
68
|
+
}
|
|
49
69
|
/**
|
|
50
70
|
* Common flags + the effective tool boundary for this spawn.
|
|
51
71
|
*
|
|
@@ -95,7 +115,7 @@ function commonFlagsFor(opts) {
|
|
|
95
115
|
}
|
|
96
116
|
function buildClaudeArgs(action, opts) {
|
|
97
117
|
const args = [];
|
|
98
|
-
const model =
|
|
118
|
+
const model = resolveSpawnModel(opts.model);
|
|
99
119
|
// Titles are unconditionally pure-output. Centralising the policy here keeps
|
|
100
120
|
// both ChatManager and AgentChatManager from accidentally regaining tools.
|
|
101
121
|
const commonFlags = commonFlagsFor(action === 'auto-title' ? { ...opts, toolPolicy: 'none' } : opts);
|
|
@@ -272,14 +292,51 @@ function parseClaudeStreamLine(line) {
|
|
|
272
292
|
const tool = blocks.find((b) => b.type === 'tool_use');
|
|
273
293
|
if (tool?.name) {
|
|
274
294
|
const input = JSON.stringify(parsed.message?.content?.[0]?.input ?? {});
|
|
275
|
-
return withAssistantUsage({
|
|
295
|
+
return withAssistantUsage({
|
|
296
|
+
kind: 'tool-use',
|
|
297
|
+
name: tool.name,
|
|
298
|
+
inputPreview: input.slice(0, 200),
|
|
299
|
+
...(typeof tool.id === 'string'
|
|
300
|
+
? { toolUseId: tool.id }
|
|
301
|
+
: {}),
|
|
302
|
+
}, msg);
|
|
276
303
|
}
|
|
277
304
|
return withAssistantUsage({ kind: 'other', type, raw: parsed }, msg);
|
|
278
305
|
}
|
|
279
306
|
if (type === 'tool_use') {
|
|
280
307
|
const name = parsed.name ?? '<unnamed>';
|
|
281
308
|
const input = JSON.stringify(parsed.input ?? {});
|
|
282
|
-
return {
|
|
309
|
+
return {
|
|
310
|
+
kind: 'tool-use',
|
|
311
|
+
name,
|
|
312
|
+
inputPreview: input.slice(0, 200),
|
|
313
|
+
...(typeof parsed.id === 'string' ? { toolUseId: parsed.id } : {}),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// `user`-role frames carry tool RESULTS back to the model. Surface a bounded
|
|
317
|
+
// text projection so activity surfaces (agent chat's execution log) can show
|
|
318
|
+
// what a tool returned; frames without a tool_result block stay 'other'.
|
|
319
|
+
if (type === 'user') {
|
|
320
|
+
const msg = parsed.message;
|
|
321
|
+
const blocks = Array.isArray(msg?.content) ? msg.content : [];
|
|
322
|
+
const resultBlock = blocks.find((b) => b.type === 'tool_result');
|
|
323
|
+
if (resultBlock) {
|
|
324
|
+
const content = resultBlock.content;
|
|
325
|
+
const text = typeof content === 'string'
|
|
326
|
+
? content
|
|
327
|
+
: Array.isArray(content)
|
|
328
|
+
? content
|
|
329
|
+
.filter((c) => c.type === 'text' && typeof c.text === 'string')
|
|
330
|
+
.map((c) => c.text)
|
|
331
|
+
.join('\n')
|
|
332
|
+
: '';
|
|
333
|
+
return {
|
|
334
|
+
kind: 'tool-result',
|
|
335
|
+
outputPreview: text.slice(0, 600),
|
|
336
|
+
...(typeof resultBlock.tool_use_id === 'string' ? { toolUseId: resultBlock.tool_use_id } : {}),
|
|
337
|
+
...(resultBlock.is_error === true ? { isError: true } : {}),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
283
340
|
}
|
|
284
341
|
return { kind: 'other', type, raw: parsed };
|
|
285
342
|
}
|
|
@@ -26,6 +26,7 @@ const result_event_1 = require("./result-event");
|
|
|
26
26
|
const crypto_1 = require("crypto");
|
|
27
27
|
const providers_1 = require("./providers");
|
|
28
28
|
const runtime_1 = require("./providers/runtime");
|
|
29
|
+
const agent_defaults_1 = require("./agent-defaults");
|
|
29
30
|
const codex_otel_bridge_1 = require("./codex-otel-bridge");
|
|
30
31
|
const db_1 = require("./db");
|
|
31
32
|
const interactive_job_session_1 = require("./interactive-job-session");
|
|
@@ -250,6 +251,7 @@ class QueueManager {
|
|
|
250
251
|
* For codex it controls the catalog model used at spawn time and as the
|
|
251
252
|
* fallback model name stamped onto the ai_invocations row. */
|
|
252
253
|
_resolvedModel;
|
|
254
|
+
_agentDefaults;
|
|
253
255
|
_onJobFinished;
|
|
254
256
|
/** Project-owned durable effects that are part of every queue admission
|
|
255
257
|
* (for example, recovery/rail ownership). Invoked after queued_jobs has
|
|
@@ -356,6 +358,7 @@ class QueueManager {
|
|
|
356
358
|
this._getDesktopDailyBudget = options?.getDesktopDailyBudget ?? null;
|
|
357
359
|
this._adapter = (0, providers_1.getAdapter)(options?.provider ?? 'claude');
|
|
358
360
|
this._resolvedModel = options?.resolvedModel ?? null;
|
|
361
|
+
this._agentDefaults = options?.agentDefaults ?? null;
|
|
359
362
|
this._onJobFinished = options?.onJobFinished ?? null;
|
|
360
363
|
this._onJobAdmission = options?.onJobAdmission ?? null;
|
|
361
364
|
this._onBudgetExceeded = options?.onBudgetExceeded ?? null;
|
|
@@ -2272,9 +2275,22 @@ class QueueManager {
|
|
|
2272
2275
|
// Per-job model override (consumed once) takes precedence — used by the
|
|
2273
2276
|
// freestyle model picker so the user can choose haiku/sonnet/opus per launch.
|
|
2274
2277
|
const modelOverride = this._jobModelSelection.get(jobId);
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
+
// Global Specrails Agents defaults (app Settings ▸ Specrails Agents) —
|
|
2279
|
+
// read AT SPAWN TIME (no restart) and layered BELOW every project-level
|
|
2280
|
+
// choice, ABOVE the built-in adapter default.
|
|
2281
|
+
let globalAgentDefaults = null;
|
|
2282
|
+
try {
|
|
2283
|
+
globalAgentDefaults = this._agentDefaults?.(adapter.id) ?? null;
|
|
2284
|
+
}
|
|
2285
|
+
catch {
|
|
2286
|
+
globalAgentDefaults = null;
|
|
2287
|
+
}
|
|
2288
|
+
const claudeProjectSettings = adapter.id === 'claude' && this._db ? (0, db_1.getProjectSettings)(this._db) : null;
|
|
2289
|
+
const fallbackRailModel = claudeProjectSettings
|
|
2290
|
+
? (claudeProjectSettings.orchestratorModelExplicit
|
|
2291
|
+
? claudeProjectSettings.orchestratorModel
|
|
2292
|
+
: globalAgentDefaults?.pipelineModel ?? claudeProjectSettings.orchestratorModel)
|
|
2293
|
+
: (this._resolvedModel ?? globalAgentDefaults?.pipelineModel ?? adapter.defaultModel());
|
|
2278
2294
|
// Relocate-artifacts: when relocated, claude is spawned from the workspace
|
|
2279
2295
|
// so add `--add-dir <repoDir>` so its tools can still reach repo files by
|
|
2280
2296
|
// absolute path. (gemini/codex get env-only tweaks at spawn time below.)
|
|
@@ -2301,7 +2317,24 @@ class QueueManager {
|
|
|
2301
2317
|
if (selection !== null && coreSupports) {
|
|
2302
2318
|
// selection is string (explicit) or undefined (default resolution)
|
|
2303
2319
|
const { resolveProfile, snapshotForJob, persistJobProfile, } = require('./profile-manager');
|
|
2304
|
-
|
|
2320
|
+
let resolved = resolveProfile(execution.cwd, selection ?? undefined, adapter.id);
|
|
2321
|
+
// Global agent defaults: fill per-agent model gaps in the resolved
|
|
2322
|
+
// profile, or synthesize a baseline profile when the project has
|
|
2323
|
+
// none. Explicit profile choices always win; inert when the global
|
|
2324
|
+
// layer is off (byte-identical legacy behaviour).
|
|
2325
|
+
if (globalAgentDefaults && adapter.capabilities.profiles === true) {
|
|
2326
|
+
if (resolved) {
|
|
2327
|
+
const merged = (0, agent_defaults_1.mergeProfileWithAgentDefaults)(resolved.profile, globalAgentDefaults);
|
|
2328
|
+
if (merged.changed)
|
|
2329
|
+
resolved = { name: resolved.name, profile: merged.profile };
|
|
2330
|
+
}
|
|
2331
|
+
else if (Object.keys(globalAgentDefaults.agentModels).length > 0) {
|
|
2332
|
+
resolved = {
|
|
2333
|
+
name: agent_defaults_1.GLOBAL_DEFAULTS_PROFILE_NAME,
|
|
2334
|
+
profile: (0, agent_defaults_1.synthesizeProfileFromDefaults)(adapter, globalAgentDefaults),
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2305
2338
|
if (resolved) {
|
|
2306
2339
|
profileSnapshotPath = snapshotForJob(this._projectSlug, jobId, resolved);
|
|
2307
2340
|
profileName = resolved.name;
|
|
@@ -2322,10 +2355,19 @@ class QueueManager {
|
|
|
2322
2355
|
// explicit per-job override > resolved profile orchestrator > project/default.
|
|
2323
2356
|
// resolveProfile validates the profile model against this adapter's catalog.
|
|
2324
2357
|
const railModel = modelOverride ?? profileOrchestratorModel ?? fallbackRailModel;
|
|
2358
|
+
// Pipeline-level reasoning effort from the global layer (rails never had a
|
|
2359
|
+
// per-job effort knob, so this is purely additive). Validated against the
|
|
2360
|
+
// FINAL model — claude/codex sub-agents inherit the process-level effort,
|
|
2361
|
+
// kimi rides its env knob via buildProviderEnv, gemini has no effort.
|
|
2362
|
+
const globalRailEffort = globalAgentDefaults?.pipelineEffort
|
|
2363
|
+
&& (0, runtime_1.isReasoningEffortValidForModel)(adapter, railModel, globalAgentDefaults.pipelineEffort)
|
|
2364
|
+
? globalAgentDefaults.pipelineEffort
|
|
2365
|
+
: undefined;
|
|
2325
2366
|
const railSpawnOptions = {
|
|
2326
2367
|
prompt: railPrompt,
|
|
2327
2368
|
systemPrompt: systemAppend || undefined,
|
|
2328
2369
|
model: railModel,
|
|
2370
|
+
...(globalRailEffort ? { reasoning_effort: globalRailEffort } : {}),
|
|
2329
2371
|
extraArgs: railExtraArgs.length > 0 ? railExtraArgs : undefined,
|
|
2330
2372
|
};
|
|
2331
2373
|
const args = adapter.buildArgs('rail-job', railSpawnOptions);
|
|
@@ -2544,6 +2586,7 @@ class QueueManager {
|
|
|
2544
2586
|
// spawn and APPEND on top of the CLI default instead of replacing it.
|
|
2545
2587
|
systemPrompt: isFreestyle ? (systemAppend || undefined) : undefined,
|
|
2546
2588
|
model: railModel,
|
|
2589
|
+
...(globalRailEffort ? { reasoning_effort: globalRailEffort } : {}),
|
|
2547
2590
|
extraArgs: !isFreestyle && systemAppend
|
|
2548
2591
|
? ['--append-system-prompt', systemAppend, ...(railExtraArgs ?? [])]
|
|
2549
2592
|
: (railExtraArgs.length > 0 ? railExtraArgs : undefined),
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.PrContinuationIsolationError = void 0;
|
|
37
|
+
exports.buildSpecSnapshot = buildSpecSnapshot;
|
|
37
38
|
exports.launchIsolatedRail = launchIsolatedRail;
|
|
38
39
|
exports.reconcileRailWorktrees = reconcileRailWorktrees;
|
|
39
40
|
/**
|
|
@@ -70,6 +71,7 @@ const rail_worktrees_store_1 = require("./rail-worktrees-store");
|
|
|
70
71
|
const pr_naming_1 = require("./pr-naming");
|
|
71
72
|
const jira_db_1 = require("./jira/jira-db");
|
|
72
73
|
const db_1 = require("./db");
|
|
74
|
+
const delivery_evidence_1 = require("./delivery-evidence");
|
|
73
75
|
const integration_branch_1 = require("./integration-branch");
|
|
74
76
|
const repo_lock_1 = require("./repo-lock");
|
|
75
77
|
const rail_isolation_1 = require("./rail-isolation");
|
|
@@ -79,6 +81,7 @@ const rail_merge_orchestrator_1 = require("./rail-merge-orchestrator");
|
|
|
79
81
|
const loop_executors_1 = require("./loop-executors");
|
|
80
82
|
const worktree_overlay_1 = require("./worktree-overlay");
|
|
81
83
|
const worktree_node_modules_1 = require("./worktree-node-modules");
|
|
84
|
+
const revision_seed_1 = require("./revision-seed");
|
|
82
85
|
const workspace_resolution_1 = require("./workspace-resolution");
|
|
83
86
|
const feature_flags_1 = require("./feature-flags");
|
|
84
87
|
const file_provenance_1 = require("./file-provenance");
|
|
@@ -125,6 +128,66 @@ function unitNamingInput(ctx, ticketId) {
|
|
|
125
128
|
}
|
|
126
129
|
return { ticketId, title: spec?.title ?? null, labels: spec?.labels ?? null, jiraKey };
|
|
127
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Assemble the revision briefing from the generation being revised. Reads only
|
|
133
|
+
* durable rows, tolerates every one of them being absent (a legacy row carries
|
|
134
|
+
* no snapshot or evidence), and never throws — a thinner briefing is far better
|
|
135
|
+
* than a failed revision launch.
|
|
136
|
+
*/
|
|
137
|
+
function buildRevisionSeedForLaunch(db, revision, ticketIds) {
|
|
138
|
+
try {
|
|
139
|
+
const previous = (0, rail_pr_store_1.getPrDelivery)(db, revision.ofDeliveryId);
|
|
140
|
+
const branches = previous
|
|
141
|
+
? [...new Set(JSON.parse(previous.branches || '[]').map((b) => b.branch).filter(Boolean))]
|
|
142
|
+
: [];
|
|
143
|
+
// Revision depth walks the supersession chain, bounded so a corrupted
|
|
144
|
+
// lineage can never spin here.
|
|
145
|
+
let depth = 1;
|
|
146
|
+
let cursor = previous;
|
|
147
|
+
for (let hops = 0; hops < 20 && cursor?.revision_of; hops++) {
|
|
148
|
+
depth += 1;
|
|
149
|
+
cursor = (0, rail_pr_store_1.getPrDelivery)(db, cursor.revision_of) ?? undefined;
|
|
150
|
+
}
|
|
151
|
+
return (0, revision_seed_1.buildRevisionSeed)({
|
|
152
|
+
note: revision.note,
|
|
153
|
+
specSnapshot: previous ? (0, rail_pr_store_1.readSpecSnapshot)(previous.spec_snapshot) : null,
|
|
154
|
+
ticketIds: [...ticketIds],
|
|
155
|
+
branches,
|
|
156
|
+
baseBranch: previous?.base_branch ?? 'the integration branch',
|
|
157
|
+
branchDiffSummary: null,
|
|
158
|
+
evidence: previous ? (0, delivery_evidence_1.readSettleEvidence)(previous.settle_evidence) : null,
|
|
159
|
+
revisionNumber: depth,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Absolute floor: the user's instruction still reaches the run.
|
|
164
|
+
return revision.note;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Launch-time freeze of every covered ticket for the delivery row's
|
|
169
|
+
* spec_snapshot column (migration 56). The review packet's "what you asked"
|
|
170
|
+
* section renders this snapshot, never the live store — a spec edited mid-run
|
|
171
|
+
* must not rewrite what the delivery was asked to do. Failure-tolerant: an
|
|
172
|
+
* unreadable ticket degrades to a bare-id entry.
|
|
173
|
+
*/
|
|
174
|
+
function buildSpecSnapshot(ctx, ticketIds) {
|
|
175
|
+
return ticketIds.map((ticketId) => {
|
|
176
|
+
let spec;
|
|
177
|
+
try {
|
|
178
|
+
spec = ctx.getTicketSpec(ticketId);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
/* tolerated — snapshot stays bare */
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
ticketId,
|
|
185
|
+
title: spec?.title ?? null,
|
|
186
|
+
description: spec?.description ?? null,
|
|
187
|
+
labels: Array.isArray(spec?.labels) ? spec.labels : [],
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
}
|
|
128
191
|
/**
|
|
129
192
|
* Commit messages are consumed by GitHub↔Jira development panels. Branch names
|
|
130
193
|
* already carry the Jira key for new work; commits must too, especially when
|
|
@@ -301,7 +364,7 @@ function branchRecords(results) {
|
|
|
301
364
|
branchOwnership: result.run.branchOwnership,
|
|
302
365
|
worktreePath: result.run.handle.worktreePath,
|
|
303
366
|
overlayExcludes: result.run.overlayExcludes,
|
|
304
|
-
overlayCleanupEvidence: result.run.overlayCleanupEvidence,
|
|
367
|
+
overlayCleanupEvidence: [...result.run.overlayCleanupEvidence, ...result.run.warmLinkEvidence],
|
|
305
368
|
settlementIgnoredPaths: result.run.settlementIgnoredPaths ?? null,
|
|
306
369
|
})));
|
|
307
370
|
}
|
|
@@ -323,7 +386,18 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
323
386
|
const baseRepo = ctx.project.path;
|
|
324
387
|
const slug = ctx.project.slug;
|
|
325
388
|
const worktreesRoot = path.join((0, artifact_registry_1.resolveHome)(), '.specrails', 'projects', slug, 'worktrees');
|
|
326
|
-
|
|
389
|
+
// A revision run is seeded with a full briefing (the instruction PLUS the
|
|
390
|
+
// frozen spec, the branch that carries the work and what the previous run
|
|
391
|
+
// actually reported) rather than the bare sentence — the run starts a FRESH
|
|
392
|
+
// provider session by contract, so durable context is the only thing it has.
|
|
393
|
+
// Per-RUN data, so it is layered over the project's constant map for this
|
|
394
|
+
// launch only and never persisted as a constant.
|
|
395
|
+
const constants = input.revision
|
|
396
|
+
? {
|
|
397
|
+
...(0, loop_constants_1.loadConstantMap)(ctx.desktopDb),
|
|
398
|
+
REVISION_REQUEST: buildRevisionSeedForLaunch(ctx.db, input.revision, ticketIds),
|
|
399
|
+
}
|
|
400
|
+
: (0, loop_constants_1.loadConstantMap)(ctx.desktopDb);
|
|
327
401
|
// Capture the PR-delivery mode ONCE at launch entry so a mid-flight env flip
|
|
328
402
|
// can never split one launch across the two delivery paths.
|
|
329
403
|
const prMode = (0, rail_isolation_1.isRailPrDeliveryEnabled)();
|
|
@@ -582,9 +656,16 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
582
656
|
supersedesDeliveryId: launchContinuation?.source === 'rail-pr-delivery'
|
|
583
657
|
? launchContinuation.deliveryId
|
|
584
658
|
: null,
|
|
659
|
+
specSnapshot: buildSpecSnapshot(ctx, ticketIds),
|
|
660
|
+
...(input.revision ? { revisionNote: input.revision.note, revisionOf: input.revision.ofDeliveryId } : {}),
|
|
585
661
|
}, input.requiredPrContinuation
|
|
586
662
|
? { id: input.requiredPrContinuation.deliveryId, decision: input.requiredPrContinuation.decision }
|
|
587
|
-
|
|
663
|
+
// A revision replaces the generation it revises, atomically, so the rail
|
|
664
|
+
// never shows two active deliveries and a failed revision restores the
|
|
665
|
+
// predecessor through the shipped rollback path.
|
|
666
|
+
: input.revision
|
|
667
|
+
? { id: input.revision.ofDeliveryId, decision: input.revision.decision }
|
|
668
|
+
: null);
|
|
588
669
|
prDeliveryId = generation.delivery.id;
|
|
589
670
|
supersededDelivery = generation.superseded;
|
|
590
671
|
input.onPrDeliveryCreated?.(prDeliveryId);
|
|
@@ -695,10 +776,17 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
695
776
|
// into the fresh worktree (best-effort; a failure just leaves the run
|
|
696
777
|
// on the legacy cold start). Linked paths join the overlay exclusion
|
|
697
778
|
// list so they inherit the commit-time guarantees.
|
|
779
|
+
let warmLinkEvidence = [];
|
|
698
780
|
try {
|
|
699
781
|
const nm = (io.linkNodeModules ?? worktree_node_modules_1.linkNodeModulesIntoWorktree)(baseRepo, handle.worktreePath);
|
|
700
|
-
|
|
701
|
-
|
|
782
|
+
// `authenticated` (not `linked`) is the authorized set: it also covers
|
|
783
|
+
// links a previous pass created, which a resumed launch would
|
|
784
|
+
// otherwise leave both un-excluded from the commit and unauthorized
|
|
785
|
+
// for release.
|
|
786
|
+
const authorized = nm.authenticated ?? nm.linked ?? [];
|
|
787
|
+
if (authorized.length > 0)
|
|
788
|
+
overlayExcludes = overlayExcludes.concat(authorized);
|
|
789
|
+
warmLinkEvidence = nm.evidence ?? [];
|
|
702
790
|
if (nm.warnings.length > 0)
|
|
703
791
|
notifyOverlayDegraded(unit.ticketId, nm.warnings);
|
|
704
792
|
}
|
|
@@ -732,6 +820,7 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
732
820
|
handle,
|
|
733
821
|
overlayExcludes,
|
|
734
822
|
overlayCleanupEvidence,
|
|
823
|
+
warmLinkEvidence,
|
|
735
824
|
provenanceSnapshot,
|
|
736
825
|
continuationTarget,
|
|
737
826
|
baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
|
|
@@ -876,6 +965,13 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
876
965
|
instructionsFilename: overlayInstructions,
|
|
877
966
|
}, a.overlayCleanupEvidence);
|
|
878
967
|
}
|
|
968
|
+
// Warm-dependency links are re-proven against the base repo (their source),
|
|
969
|
+
// not the framework overlay source. A link the engine replaced with a real
|
|
970
|
+
// install loses its authorization here and correctly preserves the worktree.
|
|
971
|
+
if (a.warmLinkEvidence.length > 0) {
|
|
972
|
+
a.warmLinkEvidence = (0, worktree_node_modules_1.authenticateWarmNodeModulesLinks)(baseRepo, a.handle.worktreePath)
|
|
973
|
+
.filter((live) => a.warmLinkEvidence.some((prior) => prior.path === live.path && prior.kind === live.kind && prior.digest === live.digest));
|
|
974
|
+
}
|
|
879
975
|
let commit;
|
|
880
976
|
try {
|
|
881
977
|
commit = await (0, worktree_manager_1.commitWorktreeAndVerify)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId, implementationOutcome === 'failed'), a.overlayExcludes);
|
|
@@ -1347,7 +1443,25 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
1347
1443
|
const expectedHeadByBranch = (0, rail_worktree_release_1.durableBranchHeads)(settledBranchRecords);
|
|
1348
1444
|
const overlayEvidenceByBranch = (0, rail_worktree_release_1.durableOverlayCleanupEvidence)(settledBranchRecords);
|
|
1349
1445
|
const settlementIgnoredByBranch = (0, rail_worktree_release_1.durableSettlementIgnoredPaths)(settledBranchRecords);
|
|
1446
|
+
// Review-packet evidence harvest (deterministic, no model calls) — MUST
|
|
1447
|
+
// run while the reviewable worktrees are still mounted, i.e. before the
|
|
1448
|
+
// releaseRailWorktrees calls below. harvestDeliveryEvidence never throws
|
|
1449
|
+
// by contract; the belt-and-braces catch keeps settle unconditional.
|
|
1450
|
+
const harvest = io.harvestEvidence ?? delivery_evidence_1.harvestDeliveryEvidence;
|
|
1451
|
+
let settleEvidence;
|
|
1452
|
+
try {
|
|
1453
|
+
settleEvidence = harvest({ readEvents: (runId) => (0, db_1.getJobEvents)(ctx.db, runId) }, results.map((result) => ({
|
|
1454
|
+
ticketId: result.run.ticketId,
|
|
1455
|
+
runId: result.run.runId,
|
|
1456
|
+
worktreePath: result.run.handle.worktreePath,
|
|
1457
|
+
})));
|
|
1458
|
+
}
|
|
1459
|
+
catch (e) {
|
|
1460
|
+
console.warn(`[rail-isolated] evidence harvest failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1461
|
+
settleEvidence = undefined;
|
|
1462
|
+
}
|
|
1350
1463
|
const patch = {
|
|
1464
|
+
settleEvidence,
|
|
1351
1465
|
branches: settledBranchRecords,
|
|
1352
1466
|
worktreeIds,
|
|
1353
1467
|
implementationOutcome,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PrDeliveryGenerationConflict = exports.ACTIVE_PR_DECISIONS = exports.TERMINAL_PR_DECISIONS = void 0;
|
|
3
|
+
exports.PrDeliveryGenerationConflict = exports.ACTIVE_PR_DECISIONS = exports.TERMINAL_PR_DECISIONS = exports.REVISION_NOTE_CAP = void 0;
|
|
4
|
+
exports.boundSpecSnapshot = boundSpecSnapshot;
|
|
5
|
+
exports.boundRevisionNote = boundRevisionNote;
|
|
6
|
+
exports.readSpecSnapshot = readSpecSnapshot;
|
|
4
7
|
exports.isTerminalPrDecision = isTerminalPrDecision;
|
|
5
8
|
exports.createPrDelivery = createPrDelivery;
|
|
6
9
|
exports.createPrDeliveryGeneration = createPrDeliveryGeneration;
|
|
@@ -26,6 +29,34 @@ exports.toPrDeliverySnapshot = toPrDeliverySnapshot;
|
|
|
26
29
|
exports.toRailPrStateMessage = toRailPrStateMessage;
|
|
27
30
|
exports.toPrDecisionCardEnvelope = toPrDecisionCardEnvelope;
|
|
28
31
|
const ids_1 = require("./ids");
|
|
32
|
+
/** Defensive per-ticket cap; contract-layer specs run long but bounded. */
|
|
33
|
+
const SPEC_SNAPSHOT_DESCRIPTION_CAP = 32_768;
|
|
34
|
+
function boundSpecSnapshot(entries) {
|
|
35
|
+
return entries.map((entry) => ({
|
|
36
|
+
ticketId: entry.ticketId,
|
|
37
|
+
title: entry.title,
|
|
38
|
+
description: entry.description == null ? null : entry.description.slice(0, SPEC_SNAPSHOT_DESCRIPTION_CAP),
|
|
39
|
+
labels: entry.labels.slice(0, 32),
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/** A revision instruction is one sentence of intent, not a document. Bound it so
|
|
43
|
+
* a pasted essay cannot bloat every snapshot that carries the row. */
|
|
44
|
+
exports.REVISION_NOTE_CAP = 2000;
|
|
45
|
+
function boundRevisionNote(note) {
|
|
46
|
+
return note.replace(/[\r\n\t\0]+/g, ' ').replace(/\s{2,}/g, ' ').trim().slice(0, exports.REVISION_NOTE_CAP);
|
|
47
|
+
}
|
|
48
|
+
/** Parse a persisted spec_snapshot column value (null-tolerant). */
|
|
49
|
+
function readSpecSnapshot(raw) {
|
|
50
|
+
if (!raw)
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
29
60
|
/** States after which the delivery is closed (no further decision possible). */
|
|
30
61
|
exports.TERMINAL_PR_DECISIONS = new Set(['completed', 'merged', 'discarded', 'superseded']);
|
|
31
62
|
/** States in which the delivery still awaits (or is executing) a user decision. */
|
|
@@ -53,9 +84,12 @@ function createPrDelivery(db, input) {
|
|
|
53
84
|
id, rail_index, loop_id, rail_key, ticket_ids, base_branch,
|
|
54
85
|
loop_name, origin_surface, origin_conversation_id,
|
|
55
86
|
implementation_outcome, delivery_outcome, status_code,
|
|
56
|
-
is_continuation, supersedes_delivery_id
|
|
87
|
+
is_continuation, supersedes_delivery_id, spec_snapshot,
|
|
88
|
+
revision_note, revision_of
|
|
57
89
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', 'pending',
|
|
58
|
-
'implementation_running', ?, ?)`).run(id, input.railIndex, input.loopId ?? null, input.railKey, JSON.stringify(input.ticketIds), input.baseBranch, input.loopName, input.originSurface, input.originConversationId ?? null, input.isContinuation ? 1 : 0, input.supersedesDeliveryId ?? null
|
|
90
|
+
'implementation_running', ?, ?, ?, ?, ?)`).run(id, input.railIndex, input.loopId ?? null, input.railKey, JSON.stringify(input.ticketIds), input.baseBranch, input.loopName, input.originSurface, input.originConversationId ?? null, input.isContinuation ? 1 : 0, input.supersedesDeliveryId ?? null, input.specSnapshot && input.specSnapshot.length > 0
|
|
91
|
+
? JSON.stringify(boundSpecSnapshot(input.specSnapshot))
|
|
92
|
+
: null, input.revisionNote ? boundRevisionNote(input.revisionNote) : null, input.revisionOf ?? null);
|
|
59
93
|
return getPrDelivery(db, id);
|
|
60
94
|
}
|
|
61
95
|
class PrDeliveryGenerationConflict extends Error {
|
|
@@ -345,6 +379,7 @@ const PATCH_COLUMNS = {
|
|
|
345
379
|
operationStartedAtMs: 'operation_started_at_ms',
|
|
346
380
|
cleanupWarnings: 'cleanup_warnings',
|
|
347
381
|
safetyArchives: 'safety_archives',
|
|
382
|
+
settleEvidence: 'settle_evidence',
|
|
348
383
|
};
|
|
349
384
|
function patchValue(key, patch) {
|
|
350
385
|
const raw = patch[key];
|
|
@@ -358,6 +393,8 @@ function patchValue(key, patch) {
|
|
|
358
393
|
return raw == null ? null : boundPrDiagnostic(String(raw));
|
|
359
394
|
if (key === 'isContinuation')
|
|
360
395
|
return raw ? 1 : 0;
|
|
396
|
+
if (key === 'settleEvidence')
|
|
397
|
+
return raw == null ? null : JSON.stringify(raw);
|
|
361
398
|
return raw;
|
|
362
399
|
}
|
|
363
400
|
/** Bounded, single-line diagnostics are safe to persist/broadcast as secondary
|
|
@@ -42,6 +42,7 @@ const worktree_manager_1 = require("./worktree-manager");
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
44
|
const worktree_overlay_1 = require("./worktree-overlay");
|
|
45
|
+
const worktree_node_modules_1 = require("./worktree-node-modules");
|
|
45
46
|
const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
46
47
|
/** Collapse per-unit settlement evidence into one unambiguous immutable HEAD per
|
|
47
48
|
* branch. Conflicting or malformed records intentionally yield no authorization. */
|
|
@@ -157,8 +158,23 @@ async function verifyReleaseEvidence(input, worktree) {
|
|
|
157
158
|
if (!expected || !COMMIT_SHA_RE.test(expected)) {
|
|
158
159
|
return { failure: 'no durable settled HEAD is recorded for its branch', overlayEntries: [] };
|
|
159
160
|
}
|
|
160
|
-
const
|
|
161
|
+
const persistedEntries = (input.overlayEvidenceByBranch.get(worktree.branch) ?? [])
|
|
161
162
|
.filter((entry) => (0, worktree_overlay_1.matchesOverlayCleanupEvidence)(worktree.worktree_path, entry));
|
|
163
|
+
// Warm-dependency links are authenticated LIVE against the base repo, on top
|
|
164
|
+
// of whatever was persisted. Two reasons this is derived and not persisted-
|
|
165
|
+
// only: a repo's ordinary `node_modules/` DIRECTORY ignore pattern does not
|
|
166
|
+
// ignore a SYMLINK of that name, so the link reads as untracked dirt and
|
|
167
|
+
// permanently blocked release (and therefore branch checkout); and worktrees
|
|
168
|
+
// that settled before the links carried evidence are stuck at `needs-review`
|
|
169
|
+
// where no persisted fix could ever reach them. The proof is identical to the
|
|
170
|
+
// persisted one — a symlink resolving to the base checkout's identically-
|
|
171
|
+
// named dependency directory — so nothing weaker is being accepted here.
|
|
172
|
+
const warmEntries = (input.authenticateWarmLinks ?? worktree_node_modules_1.authenticateWarmNodeModulesLinks)(input.repoDir, worktree.worktree_path);
|
|
173
|
+
const seenPaths = new Set(persistedEntries.map((entry) => entry.path));
|
|
174
|
+
const overlayEntries = [
|
|
175
|
+
...persistedEntries,
|
|
176
|
+
...warmEntries.filter((entry) => !seenPaths.has(entry.path)),
|
|
177
|
+
];
|
|
162
178
|
const overlayExcludes = overlayEntries.map((entry) => entry.path);
|
|
163
179
|
try {
|
|
164
180
|
const status = await input.git.run([
|