specrails-desktop 2.59.2 → 2.59.3
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-vAY9C71W.js → ActivityFeedPage-BGEayjTY.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-CpwjGXQm.js → AgentBrowserCapture-Qvg4y5Tr.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-BHeoJVxQ.js → AgentModeAnalyticsPane-CR7aFeLJ.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-B0Nf3YkN.js → AgentModeCodePane-gAd88Gng.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-CpbIItY_.js → AgentModeJobsPane-EjCDuTow.js} +1 -1
- package/client/dist/assets/{AgentsPage-BVJAFXn3.js → AgentsPage-CRHHcp1V.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-DDWees-K.js → AnalyticsPage-a0b1fNEl.js} +1 -1
- package/client/dist/assets/{CodePage-CzV5tkvS.js → CodePage-BmV1m7l3.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DfRHNK7i.js → DesktopAnalyticsPage-B87WmXOQ.js} +1 -1
- package/client/dist/assets/{DocsDialog-BxduBYbh.js → DocsDialog-Co0O672q.js} +1 -1
- package/client/dist/assets/{DocsPage-DOVeRJn_.js → DocsPage-DxGI6A2T.js} +1 -1
- package/client/dist/assets/{ExportDropdown-BnKVV8UK.js → ExportDropdown-1Ce031vB.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-Da9dvM-0.js → InteractiveJobComposer-DkqR77pO.js} +2 -2
- package/client/dist/assets/JobDetailModal-DVLDVgzz.js +1 -0
- package/client/dist/assets/JobDetailPage-RcP9N-D-.js +1 -0
- package/client/dist/assets/{JobsPage-0vdlbNik.js → JobsPage-R9M4xQnp.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-DCUHozXh.js → LoopBuilderPage-C4S-rkWp.js} +1 -1
- package/client/dist/assets/{LoopPreviewModal-Df-S8NGf.js → LoopPreviewModal-DA2JeKWX.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-sdb7QbSo.js → LoopWindowSurface-BkkeD0hx.js} +1 -1
- package/client/dist/assets/{LoopsPage-CDfCC9Px.js → LoopsPage-BHv--iI7.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-DKlPf54Y.js → MinimizedChatsContext-Bc9xHByW.js} +1 -1
- package/client/dist/assets/PluginsPage-BEy62gxK.js +2 -0
- package/client/dist/assets/{ProjectSettingsDialog-DOkjT7xv.js → ProjectSettingsDialog-DDQ_f94T.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-CJVpOUpZ.js → RepositoryDeliveries-DIWDFLRM.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-BeM-2gIZ.js → RepositoryScopeSelector-D3PlHIM-.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-EBCim2w_.js → ReviewPacketPage-BXdPwmdE.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-1-92UGwN.js → TemplatePreviewModal-C7-G6lvI.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-CkVXuHxU.js → TicketDetailModalContext-gGUv7gsQ.js} +1 -1
- package/client/dist/assets/{Trans-CzhPjnjB.js → Trans-Br_OIUKP.js} +1 -1
- package/client/dist/assets/{formatDistanceToNow-DlQaXc7c.js → formatDistanceToNow-Q7QPRFV4.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxUFGcVq.js → getTimezoneOffsetInMilliseconds-Bq0CXBaJ.js} +1 -1
- package/client/dist/assets/{index-Czov72g7.js → index-DVkWHOR3.js} +7 -7
- package/client/dist/assets/jira-B6OYw4CJ.js +1 -0
- package/client/dist/assets/jira-BTF6RaOe.js +1 -0
- package/client/dist/assets/jira-CEetIB2q.js +1 -0
- package/client/dist/assets/jira-CIzsZLw5.js +1 -0
- package/client/dist/assets/{jira-BOoaUXbb.js → jira-Cq6yvN4q.js} +1 -1
- package/client/dist/assets/jira-Daw9tmpI.js +1 -0
- package/client/dist/assets/jira-FXr7u2lh.js +1 -0
- package/client/dist/assets/jira-LaNQn7yl.js +1 -0
- package/client/dist/assets/{jira-api-CbJvbRYx.js → jira-api-C2Tzc362.js} +1 -1
- package/client/dist/assets/{jobs-DZAbh1m2.js → jobs-ARwD85B9.js} +1 -1
- package/client/dist/assets/{jobs-BD47Ov11.js → jobs-BMF1w4WT.js} +1 -1
- package/client/dist/assets/{jobs-tWjcDF_6.js → jobs-CBfzGLwm.js} +1 -1
- package/client/dist/assets/{jobs-CSrRx5lD.js → jobs-CPjiluBY.js} +1 -1
- package/client/dist/assets/{jobs-epP7gt8W.js → jobs-CQQd58pY.js} +1 -1
- package/client/dist/assets/{jobs-1G3wodhl.js → jobs-De3EqaO0.js} +1 -1
- package/client/dist/assets/{jobs-BLOoVRRl.js → jobs-Du9-Ei9a.js} +1 -1
- package/client/dist/assets/{jobs-B4pu8TdF.js → jobs-qMHbnxwC.js} +1 -1
- package/client/dist/assets/{project-repositories-BkBD3rxx.js → project-repositories-DZKmieH3.js} +1 -1
- package/client/dist/assets/{spending-CYotH-9N.js → spending-EXaboRU0.js} +1 -1
- package/client/dist/assets/{useDesktop-DpxVGfUC.js → useDesktop-ChSmmJfc.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-CfGRbhr9.js → useRuntimeRuns-w9-5OzZl.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-C17TGqT8.js → useSharedWebSocket-g0nTHfTT.js} +2 -2
- package/client/dist/index.html +16 -16
- package/docs/internals/configuration.md +45 -0
- package/docs/internals/loop-step-log-explorer.md +27 -0
- package/docs/internals/mission-rail-cards.md +7 -0
- package/docs/internals/safe-pr-review-flow.md +9 -0
- package/docs/internals/source-map.md +5 -1
- package/docs/jira-integration-plan.md +7 -4
- package/package.json +1 -1
- package/server/dist/core-execution.js +5 -18
- package/server/dist/jira/jira-db.js +33 -6
- package/server/dist/jira/jira-materializer.js +11 -9
- package/server/dist/jira/jira-status-resolver.js +64 -22
- package/server/dist/jira/jira-sync-manager.js +40 -1
- package/server/dist/jira-router.js +8 -41
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +15 -67
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +7 -4
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-log.js +252 -0
- package/server/dist/modules/delivery/runtime/rail-relaunch.js +10 -2
- package/server/dist/modules/loops/runtime/loop-run-manager.js +5 -3
- package/server/dist/path-resolver.js +41 -20
- package/server/dist/project-env.js +22 -6
- package/server/dist/util/checkout-path.js +22 -0
- package/server/dist/worktree-node-modules.js +70 -34
- package/client/dist/assets/JobDetailModal-DhwKbZGe.js +0 -1
- package/client/dist/assets/JobDetailPage-MtPE-E_1.js +0 -1
- package/client/dist/assets/PluginsPage-iL0G3rQQ.js +0 -2
- package/client/dist/assets/jira-BNZu8f9O.js +0 -1
- package/client/dist/assets/jira-BYVN4L2u.js +0 -1
- package/client/dist/assets/jira-CKJg3HZd.js +0 -1
- package/client/dist/assets/jira-DDDsZ4ub.js +0 -1
- package/client/dist/assets/jira-DXwYjVE4.js +0 -1
- package/client/dist/assets/jira-Dn6U2nRW.js +0 -1
- package/client/dist/assets/jira-j7hVolR5.js +0 -1
|
@@ -999,6 +999,11 @@ class JiraSyncManager {
|
|
|
999
999
|
return;
|
|
1000
1000
|
let changed = false;
|
|
1001
1001
|
while (!this.authPaused) {
|
|
1002
|
+
// A failed transition may have acquired a newer intention while its HTTP
|
|
1003
|
+
// request was in flight. Reconcile before each claim, including legacy
|
|
1004
|
+
// manual retries whose newer transition has already completed.
|
|
1005
|
+
if ((0, jira_db_1.reconcileSupersededTransitions)(this.db) > 0)
|
|
1006
|
+
changed = true;
|
|
1002
1007
|
const batch = (0, jira_db_1.claimDrainable)(this.db, MAX_DRAIN_BATCH);
|
|
1003
1008
|
if (batch.length === 0)
|
|
1004
1009
|
break;
|
|
@@ -1099,6 +1104,7 @@ class JiraSyncManager {
|
|
|
1099
1104
|
// the SAME category (e.g. In Progress → In Review, both indeterminate)
|
|
1100
1105
|
// while still noop-ing when the issue already sits on the target.
|
|
1101
1106
|
currentStatusName: issue.data.fields.status?.name,
|
|
1107
|
+
currentStatusId: issue.data.fields.status?.id,
|
|
1102
1108
|
explicitTarget,
|
|
1103
1109
|
getTransitions: async () => {
|
|
1104
1110
|
const res = await client.getTransitions(payload.jiraIssueId);
|
|
@@ -1231,6 +1237,7 @@ class JiraSyncManager {
|
|
|
1231
1237
|
}
|
|
1232
1238
|
}
|
|
1233
1239
|
broadcastOutboxState() {
|
|
1240
|
+
(0, jira_db_1.reconcileSupersededTransitions)(this.db);
|
|
1234
1241
|
const counts = (0, jira_db_1.countOutboxByState)(this.db);
|
|
1235
1242
|
this.broadcast({ type: 'jira.outbox_changed', projectId: this.projectId, pending: counts.pending + counts.inflight, dead: counts.dead });
|
|
1236
1243
|
}
|
|
@@ -1309,11 +1316,43 @@ class JiraSyncManager {
|
|
|
1309
1316
|
return (0, jira_db_1.listLinks)(this.db);
|
|
1310
1317
|
}
|
|
1311
1318
|
listOutbox(state) {
|
|
1312
|
-
|
|
1319
|
+
if ((0, jira_db_1.reconcileSupersededTransitions)(this.db) > 0)
|
|
1320
|
+
this.broadcastOutboxState();
|
|
1321
|
+
const links = new Map((0, jira_db_1.listLinks)(this.db).map((link) => [link.jiraIssueId, link.jiraKey]));
|
|
1322
|
+
const statusMap = (0, jira_db_1.getConnection)(this.db, this.projectId)?.statusMap;
|
|
1323
|
+
const logicalStates = ['todo', 'in_progress', 'on_review', 'done', 'cancelled'];
|
|
1324
|
+
return (0, jira_db_1.listOutbox)(this.db, state ? { state } : {}).map((row) => {
|
|
1325
|
+
let logicalState = null;
|
|
1326
|
+
let targetStatus = null;
|
|
1327
|
+
if (row.opType === 'transition') {
|
|
1328
|
+
try {
|
|
1329
|
+
const payload = JSON.parse(row.payload);
|
|
1330
|
+
if (payload && typeof payload === 'object') {
|
|
1331
|
+
if (logicalStates.includes(payload.logicalState))
|
|
1332
|
+
logicalState = payload.logicalState;
|
|
1333
|
+
if (typeof payload.targetStatus === 'string' && payload.targetStatus.trim())
|
|
1334
|
+
targetStatus = payload.targetStatus;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
catch {
|
|
1338
|
+
// Historical/corrupt payloads remain visible for diagnosis.
|
|
1339
|
+
}
|
|
1340
|
+
targetStatus ??= logicalState ? statusMap?.[logicalState] ?? null : null;
|
|
1341
|
+
}
|
|
1342
|
+
return { ...row, jiraKey: links.get(row.jiraIssueId) ?? null, logicalState, targetStatus };
|
|
1343
|
+
});
|
|
1313
1344
|
}
|
|
1314
1345
|
outboxCounts() {
|
|
1346
|
+
if ((0, jira_db_1.reconcileSupersededTransitions)(this.db) > 0)
|
|
1347
|
+
this.broadcastOutboxState();
|
|
1315
1348
|
return (0, jira_db_1.countOutboxByState)(this.db);
|
|
1316
1349
|
}
|
|
1350
|
+
retryOutbox(id) {
|
|
1351
|
+
const disposition = (0, jira_db_1.retryDeadOutbox)(this.db, id);
|
|
1352
|
+
if (disposition)
|
|
1353
|
+
this.broadcastOutboxState();
|
|
1354
|
+
return disposition;
|
|
1355
|
+
}
|
|
1317
1356
|
}
|
|
1318
1357
|
exports.JiraSyncManager = JiraSyncManager;
|
|
1319
1358
|
// ─── Error envelope for thrown client failures inside the walk ─────────────────
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.createJiraRouter = createJiraRouter;
|
|
37
4
|
const express_1 = require("express");
|
|
@@ -201,26 +168,26 @@ function createJiraRouter() {
|
|
|
201
168
|
router.get('/outbox', (req, res) => {
|
|
202
169
|
const c = ctx(req);
|
|
203
170
|
const state = req.query.state;
|
|
204
|
-
const valid = ['pending', 'inflight', 'done', 'dead'];
|
|
171
|
+
const valid = ['pending', 'inflight', 'done', 'dead', 'superseded'];
|
|
205
172
|
const filter = valid.includes(state) ? state : undefined;
|
|
206
173
|
res.json({ ops: c.jiraSyncManager.listOutbox(filter), counts: c.jiraSyncManager.outboxCounts() });
|
|
207
174
|
});
|
|
208
175
|
// POST /outbox/:id/retry — re-queue a dead-lettered op for a manual retry.
|
|
209
176
|
router.post('/outbox/:id/retry', async (req, res) => {
|
|
210
177
|
const c = ctx(req);
|
|
211
|
-
const id =
|
|
212
|
-
if (Number.
|
|
178
|
+
const id = Number(req.params.id);
|
|
179
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
213
180
|
res.status(400).json({ error: 'Invalid op id' });
|
|
214
181
|
return;
|
|
215
182
|
}
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
if (!ok) {
|
|
183
|
+
const disposition = c.jiraSyncManager.retryOutbox(id);
|
|
184
|
+
if (!disposition) {
|
|
219
185
|
res.status(404).json({ error: 'Op not found or not in dead state' });
|
|
220
186
|
return;
|
|
221
187
|
}
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
if (disposition === 'pending')
|
|
189
|
+
void c.jiraSyncManager.drainOnce().catch(() => undefined);
|
|
190
|
+
res.json({ ok: true, disposition });
|
|
224
191
|
});
|
|
225
192
|
// POST /specs — Add Spec when the project source is Jira: create the issue in
|
|
226
193
|
// Jira and materialize it locally. Keeps the generic local ticket-create path
|
|
@@ -10,7 +10,7 @@ exports.runAgentRuntimeControl = runAgentRuntimeControl;
|
|
|
10
10
|
const agent_runtime_verification_suggestions_1 = require("./agent-runtime-verification-suggestions");
|
|
11
11
|
const agent_runtime_history_1 = require("./agent-runtime-history");
|
|
12
12
|
const agent_runtime_repositories_1 = require("./agent-runtime-repositories");
|
|
13
|
-
const
|
|
13
|
+
const agent_runtime_verification_log_1 = require("./agent-runtime-verification-log");
|
|
14
14
|
const agent_runtime_effective_config_1 = require("./agent-runtime-effective-config");
|
|
15
15
|
const node_child_process_1 = require("node:child_process");
|
|
16
16
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -93,66 +93,6 @@ function requiresConfiguredChecks(value) {
|
|
|
93
93
|
return Object.values(body.nodes ?? {}).some(node => node.kind === 'verify' && node.params?.commands === 'configured' && !node.params.additionalCommandsFrom && node.params.unverified !== true)
|
|
94
94
|
|| Object.values(body.components ?? {}).some(requiresConfiguredChecks);
|
|
95
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
|
-
}
|
|
156
96
|
exports.RUNTIME_HOST_ENV_KEYS = [
|
|
157
97
|
'SPECRAILS_REPO_DIR', 'SPECRAILS_GIT_AUTO', 'SPECRAILS_REPO_MAP_PATH',
|
|
158
98
|
'SPECRAILS_EXECUTION_CONTEXT', 'SPECRAILS_EXECUTION_MANIFEST', 'SPECRAILS_REPOSITORIES',
|
|
@@ -356,7 +296,13 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
356
296
|
callback?.();
|
|
357
297
|
}
|
|
358
298
|
catch { /* Logging cannot replay a workflow. */ } };
|
|
359
|
-
const readableVerification =
|
|
299
|
+
const readableVerification = (0, agent_runtime_verification_log_1.createVerificationLogProjection)();
|
|
300
|
+
const readableLines = (lines) => {
|
|
301
|
+
for (const line of lines)
|
|
302
|
+
observe(() => line.attemptId
|
|
303
|
+
? options.onLine?.(line.text, 'stdout', { attemptId: line.attemptId })
|
|
304
|
+
: options.onLine?.(line.text));
|
|
305
|
+
};
|
|
360
306
|
const lines = (0, node_readline_1.createInterface)({ input: child.stdout });
|
|
361
307
|
lines.on('line', line => {
|
|
362
308
|
if (line.length > 2_000_000) {
|
|
@@ -388,6 +334,10 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
388
334
|
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL');
|
|
389
335
|
return;
|
|
390
336
|
}
|
|
337
|
+
const boundary = event.type === 'workflow-event' || event.type === 'runtime-result';
|
|
338
|
+
// Flush buffered output before the projection advances the current step.
|
|
339
|
+
if (boundary)
|
|
340
|
+
readableLines(readableVerification.project(event));
|
|
391
341
|
try {
|
|
392
342
|
options.onRuntimeEvent?.(event);
|
|
393
343
|
}
|
|
@@ -400,6 +350,8 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
400
350
|
if (event.type === 'runtime-graph')
|
|
401
351
|
graph = event;
|
|
402
352
|
observe(() => options.onRawLine?.(event.repositories ? JSON.stringify(event) : line));
|
|
353
|
+
if (!boundary)
|
|
354
|
+
readableLines(readableVerification.project(event));
|
|
403
355
|
if (event.type === 'runtime-result') {
|
|
404
356
|
if (result)
|
|
405
357
|
invalidProtocol = true;
|
|
@@ -435,11 +387,6 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
435
387
|
observe(() => options.onLine?.(`[${role}]${scope} ${payload.tool}${payload.detail ? ' ' + payload.detail : ''}\n`));
|
|
436
388
|
}
|
|
437
389
|
}
|
|
438
|
-
else if (event.type === 'verification-output' && typeof event.text === 'string') {
|
|
439
|
-
const readable = readableVerification(String(event.text));
|
|
440
|
-
if (readable)
|
|
441
|
-
observe(() => options.onLine?.(readable));
|
|
442
|
-
}
|
|
443
390
|
else if (event.type === 'span') {
|
|
444
391
|
// Trace spans are telemetry; the raw line is already recorded for diagnostics.
|
|
445
392
|
}
|
|
@@ -461,6 +408,7 @@ async function runAgentRuntimeInvocation(options) {
|
|
|
461
408
|
if (timer)
|
|
462
409
|
clearTimeout(timer);
|
|
463
410
|
lines.close();
|
|
411
|
+
readableLines(readableVerification.flush());
|
|
464
412
|
const usage = result?.invocationUsage;
|
|
465
413
|
const known = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
466
414
|
const cost = known(usage?.costUsd), tokensIn = known(usage?.inputTokens), tokensOut = known(usage?.outputTokens);
|
|
@@ -26,6 +26,7 @@ const agent_runtime_bridge_1 = require("./agent-runtime-bridge");
|
|
|
26
26
|
const core_node_runtime_1 = require("../../../core-node-runtime");
|
|
27
27
|
const win_spawn_1 = require("../../../util/win-spawn");
|
|
28
28
|
const workspace_resolution_1 = require("../../../workspace-resolution");
|
|
29
|
+
const project_env_1 = require("../../../project-env");
|
|
29
30
|
const loop_runs_store_1 = require("../../loops/runtime/loop-runs-store");
|
|
30
31
|
const multi_repo_execution_store_1 = require("../../delivery/runtime/multi-repo-execution-store");
|
|
31
32
|
const db_1 = require("../../../db");
|
|
@@ -309,7 +310,7 @@ class AgentRuntimeControls {
|
|
|
309
310
|
const host = JSON.parse(node_fs_1.default.readFileSync(hostFile, 'utf8'));
|
|
310
311
|
if (host.schemaVersion !== 1 || typeof host.cwd !== 'string' || ![frozen.backlogRoot, ...frozen.repositories.map((repository) => repository.path)].includes(host.cwd) || !host.env || typeof host.env !== 'object' || Array.isArray(host.env) || Object.entries(host.env).some(([key, value]) => !agent_runtime_bridge_1.RUNTIME_HOST_ENV_KEYS.includes(key) || typeof value !== 'string'))
|
|
311
312
|
throw new RuntimeControlError(409, 'runtime_host_invalid', 'The saved runtime host settings are invalid');
|
|
312
|
-
const env = { ...(0, workspace_resolution_1.resolveLoopBaseEnv)(this.ctx.project) };
|
|
313
|
+
const env = { ...(0, workspace_resolution_1.resolveLoopBaseEnv)(this.ctx.project, undefined, (0, project_env_1.applyWorktreeEnvPassthrough)(this.ctx.db, process.env)) };
|
|
313
314
|
for (const key of agent_runtime_bridge_1.RUNTIME_HOST_ENV_KEYS)
|
|
314
315
|
delete env[key];
|
|
315
316
|
Object.assign(env, host.env, { SPECRAILS_EXECUTION_CONTEXT: file });
|
|
@@ -541,15 +542,17 @@ class AgentRuntimeControls {
|
|
|
541
542
|
loopDurationBaseline: previous.total_duration_ms, completedDurationMs: 0,
|
|
542
543
|
iterationCount: previous.iteration_count, activeTurnStartedAtMs: startedAtMs, lastActivityAtMs: startedAtMs,
|
|
543
544
|
});
|
|
544
|
-
const logLine = (line, source = 'stdout') => {
|
|
545
|
+
const logLine = (line, source = 'stdout', metadata) => {
|
|
545
546
|
if (this.disposed)
|
|
546
547
|
return;
|
|
548
|
+
const attemptId = metadata?.attemptId;
|
|
549
|
+
const attribution = typeof attemptId === 'string' && attemptId.trim() && attemptId.length <= 256 ? { attemptId } : {};
|
|
547
550
|
try {
|
|
548
|
-
(0, db_1.appendEvent)(this.ctx.db, runId, ++sequence, { event_type: 'log', source, payload: JSON.stringify({ line: line.replace(/\n$/, '') }) });
|
|
551
|
+
(0, db_1.appendEvent)(this.ctx.db, runId, ++sequence, { event_type: 'log', source, payload: JSON.stringify({ line: line.replace(/\n$/, ''), ...attribution }) });
|
|
549
552
|
}
|
|
550
553
|
catch { /* events table best-effort */ }
|
|
551
554
|
try {
|
|
552
|
-
this.ctx.broadcast?.({ type: 'log', source, line, timestamp: new Date().toISOString(), processId: runId });
|
|
555
|
+
this.ctx.broadcast?.({ type: 'log', source, line, timestamp: new Date().toISOString(), processId: runId, ...attribution });
|
|
553
556
|
}
|
|
554
557
|
catch { /* persisted log remains authoritative */ }
|
|
555
558
|
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createVerificationLogProjection = createVerificationLogProjection;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
6
|
+
const text = (value) => typeof value === 'string' ? value : '';
|
|
7
|
+
const display = (value, limit = 512) => (0, node_util_1.stripVTControlCharacters)(value).replace(/[\r\n]/g, ' ').slice(0, limit);
|
|
8
|
+
const attemptId = (event) => typeof event.attemptId === 'string' && event.attemptId.length <= 256 && event.attemptId.trim() ? event.attemptId : undefined;
|
|
9
|
+
const output = (value, id) => ({ text: value, ...(id ? { attemptId: id } : {}) });
|
|
10
|
+
const scope = (event) => JSON.stringify([event.runId, event.scopeId, event.nodePath, event.attemptId, event.attempt, event.visit]
|
|
11
|
+
.map(value => typeof value === 'string' && value.length <= 512 || typeof value === 'number' && Number.isSafeInteger(value) ? value : null));
|
|
12
|
+
const reference = 'Details → Verification evidence (stdout/stderr; retained evidence may be truncated)';
|
|
13
|
+
const shortPrefix = (value) => value.length > 96 ? value.slice(0, 92) + '…] ' : value;
|
|
14
|
+
/** This is presentation only. Neither its heuristics nor check progress can
|
|
15
|
+
* certify a candidate; original runtime events and command evidence are untouched. */
|
|
16
|
+
function createVerificationLogProjection() {
|
|
17
|
+
const streams = new Map();
|
|
18
|
+
const active = new Map();
|
|
19
|
+
let overflow = false;
|
|
20
|
+
const binding = (event, repository) => JSON.stringify([scope(event), repository]);
|
|
21
|
+
function get(key, event, prefix) {
|
|
22
|
+
let state = streams.get(key);
|
|
23
|
+
if (!state && streams.size < 64) {
|
|
24
|
+
state = { scope: scope(event), attemptId: attemptId(event), prefix: shortPrefix(prefix), tap: false, failed: false, yaml: false, inputLines: null, inputShortened: false,
|
|
25
|
+
headLines: 0, headChars: 0, shown: new Set(), diagnostics: [[], [], [], []], summaries: new Map(), tail: [], previous: [], context: 0, omitted: 0, announced: false };
|
|
26
|
+
streams.set(key, state);
|
|
27
|
+
}
|
|
28
|
+
return state;
|
|
29
|
+
}
|
|
30
|
+
function omitted(state) {
|
|
31
|
+
state.omitted++;
|
|
32
|
+
if (state.announced)
|
|
33
|
+
return '';
|
|
34
|
+
state.announced = true;
|
|
35
|
+
return state.prefix + '[Verbose output omitted; ' + reference + ']\n';
|
|
36
|
+
}
|
|
37
|
+
function diagnostic(state, line, priority) {
|
|
38
|
+
const bounded = line.slice(0, priority === 3 ? 256 : 512), bucket = state.diagnostics[priority];
|
|
39
|
+
if (state.shown.has(line) || bucket.includes(bounded))
|
|
40
|
+
return;
|
|
41
|
+
// First fact plus three recent facts in each class; warnings cannot consume
|
|
42
|
+
// the compiler/assertion budget, and later failures remain represented.
|
|
43
|
+
if (bucket.length === (priority === 3 ? 2 : 4))
|
|
44
|
+
bucket.splice(1, 1);
|
|
45
|
+
bucket.push(bounded);
|
|
46
|
+
}
|
|
47
|
+
function priority(body) {
|
|
48
|
+
if (/^(?!['"`]|(?:actual|expected|source):).+(?:\(\d+,\d+\)|:\d+(?::\d+)?)(?::|\s+-)?\s+(?:fatal\s+)?error\b/i.test(body)
|
|
49
|
+
|| /^(?:fatal\s+)?error\s+[A-Z]+\d+\s*:/.test(body)
|
|
50
|
+
|| /^(?:AssertionError|TypeError|ReferenceError|SyntaxError|RangeError)\b/.test(body))
|
|
51
|
+
return 0;
|
|
52
|
+
if (/^(?:FAIL\s|not ok \d+\b|[✖✗]\s|(?:fatal\s+)?error(?:\s*\[[^\]]+\])?\s*:)/i.test(body)
|
|
53
|
+
&& !/\b0 errors?\b/i.test(body))
|
|
54
|
+
return 1;
|
|
55
|
+
if (/^(?:expected|actual|operator|test at|location)\s*[: ]/i.test(body))
|
|
56
|
+
return 2;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
function summary(body) {
|
|
60
|
+
if (/^Ran all test suites[.\s]*$/.test(body))
|
|
61
|
+
return 'ran';
|
|
62
|
+
const match = body.match(/^(Test Suites|Test Files|Tests|Snapshots|Time|Duration|Ran all test suites)\s*:?\s/i)
|
|
63
|
+
?? body.match(/^(?:#|ℹ)\s*(tests|suites|pass|fail|cancelled|skipped|todo|duration_ms|duration)\b/i);
|
|
64
|
+
return match?.[1].toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
function line(state, original, body) {
|
|
67
|
+
let value = original;
|
|
68
|
+
if (/AssertionError\b.*\bInput:\s*$/.test(body))
|
|
69
|
+
state.inputLines = 0;
|
|
70
|
+
else if (state.inputLines !== null) {
|
|
71
|
+
if (/^["'`]/.test(body)) {
|
|
72
|
+
state.inputLines++;
|
|
73
|
+
if (state.inputLines > 2) {
|
|
74
|
+
if (state.inputShortened)
|
|
75
|
+
return '';
|
|
76
|
+
state.inputShortened = true;
|
|
77
|
+
return state.prefix + '[Assertion input shortened; inspect raw evidence]\n';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (!body)
|
|
81
|
+
return '';
|
|
82
|
+
else
|
|
83
|
+
state.inputLines = null;
|
|
84
|
+
}
|
|
85
|
+
if (/^[✔✓]\s/.test(body))
|
|
86
|
+
return '';
|
|
87
|
+
if (/^PASS\s/.test(body))
|
|
88
|
+
return omitted(state);
|
|
89
|
+
if (/^[✖✗]\s/.test(body))
|
|
90
|
+
state.failed = true;
|
|
91
|
+
if (/^TAP version |^# Subtest:/.test(body)) {
|
|
92
|
+
state.tap = true;
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
if (/^(?:not )?ok \d+\b/.test(body)) {
|
|
96
|
+
state.tap = true;
|
|
97
|
+
state.failed = body.startsWith('not ok');
|
|
98
|
+
state.yaml = false;
|
|
99
|
+
if (!state.failed)
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
if (state.tap) {
|
|
103
|
+
if (body === '---') {
|
|
104
|
+
state.yaml = true;
|
|
105
|
+
if (!state.failed)
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
if (body === '...') {
|
|
109
|
+
state.yaml = false;
|
|
110
|
+
if (!state.failed)
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
if (state.yaml && !state.failed)
|
|
114
|
+
return '';
|
|
115
|
+
if (/^1\.\.\d+$/.test(body))
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
if (!body)
|
|
119
|
+
return '';
|
|
120
|
+
if (value.length > 2_000)
|
|
121
|
+
value = value.slice(0, 1_900) + '\n[Long diagnostic line shortened; inspect raw evidence]';
|
|
122
|
+
const total = summary(body);
|
|
123
|
+
if (total) {
|
|
124
|
+
// Known totals have a fixed key set, so repeated progress updates do not
|
|
125
|
+
// become another unbounded stream or evict diagnostics.
|
|
126
|
+
state.summaries.set(total, state.prefix + body.slice(0, 128));
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
const rank = priority(body.slice(0, 2_000));
|
|
130
|
+
if (state.headLines < 24 && state.headChars + value.length <= 3_500) {
|
|
131
|
+
state.headLines++;
|
|
132
|
+
state.headChars += value.length;
|
|
133
|
+
state.shown.add(value);
|
|
134
|
+
state.previous.push(value);
|
|
135
|
+
if (state.previous.length > 2)
|
|
136
|
+
state.previous.shift();
|
|
137
|
+
return value + '\n';
|
|
138
|
+
}
|
|
139
|
+
if (rank !== undefined) {
|
|
140
|
+
if (rank < 2) {
|
|
141
|
+
for (const preceding of state.previous)
|
|
142
|
+
diagnostic(state, preceding, 3);
|
|
143
|
+
state.context = 3;
|
|
144
|
+
}
|
|
145
|
+
diagnostic(state, value, rank);
|
|
146
|
+
}
|
|
147
|
+
else if (state.context > 0) {
|
|
148
|
+
diagnostic(state, value, 3);
|
|
149
|
+
state.context--;
|
|
150
|
+
}
|
|
151
|
+
state.previous.push(value);
|
|
152
|
+
if (state.previous.length > 2)
|
|
153
|
+
state.previous.shift();
|
|
154
|
+
state.tail.push(value.slice(0, 256));
|
|
155
|
+
if (state.tail.length > 6)
|
|
156
|
+
state.tail.shift();
|
|
157
|
+
return omitted(state);
|
|
158
|
+
}
|
|
159
|
+
function flush(key) {
|
|
160
|
+
const result = [];
|
|
161
|
+
for (const [id, state] of streams) {
|
|
162
|
+
if (key !== undefined && id !== key)
|
|
163
|
+
continue;
|
|
164
|
+
let content = '';
|
|
165
|
+
const pending = [...state.diagnostics.flat(), ...state.tail, ...state.summaries.values()];
|
|
166
|
+
const seen = new Set(state.shown);
|
|
167
|
+
for (const value of pending)
|
|
168
|
+
if (!seen.has(value)) {
|
|
169
|
+
seen.add(value);
|
|
170
|
+
content += value + '\n';
|
|
171
|
+
}
|
|
172
|
+
if (state.omitted)
|
|
173
|
+
content += state.prefix + `[${state.omitted} output lines compacted; ${reference}]\n`;
|
|
174
|
+
if (content)
|
|
175
|
+
result.push(output(content, state.attemptId));
|
|
176
|
+
streams.delete(id);
|
|
177
|
+
for (const [name, activeId] of active)
|
|
178
|
+
if (activeId === id)
|
|
179
|
+
active.delete(name);
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
function project(event) {
|
|
184
|
+
if (event.type === 'verification-output' && typeof event.text === 'string') {
|
|
185
|
+
const result = [];
|
|
186
|
+
for (const value of (0, node_util_1.stripVTControlCharacters)(event.text).split('\n')) {
|
|
187
|
+
if (!value)
|
|
188
|
+
continue;
|
|
189
|
+
const match = value.match(/^(\[verification ([^/\]]+)\/[^\]]+\] )?(.*)$/);
|
|
190
|
+
const prefix = match?.[1] ?? '', repository = match?.[2] ?? '', body = (match?.[3] ?? value).trim();
|
|
191
|
+
const key = active.get(binding(event, repository)) ?? JSON.stringify([scope(event), prefix.slice(0, 1024)]);
|
|
192
|
+
const state = get(key, event, prefix);
|
|
193
|
+
if (state) {
|
|
194
|
+
const content = line(state, shortPrefix(prefix) + (match?.[3] ?? value), body);
|
|
195
|
+
if (content)
|
|
196
|
+
result.push(output(content, state.attemptId));
|
|
197
|
+
}
|
|
198
|
+
else if (!overflow) {
|
|
199
|
+
overflow = true;
|
|
200
|
+
result.push(output(`[Additional verification output omitted; ${reference}]\n`, attemptId(event)));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
if (event.type === 'runtime-efficiency-event') {
|
|
206
|
+
const data = object(event.payload) ? { ...event, ...event.payload } : event;
|
|
207
|
+
const kind = text(data.kind), id = text(data.executionId), repository = text(data.repositoryId), label = text(data.label);
|
|
208
|
+
if (!['check-started', 'check-finished', 'check-reused', 'check-invalidated'].includes(kind) || !id || id.length > 256 || !repository || repository.length > 256 || !label || label.length > 4096)
|
|
209
|
+
return [];
|
|
210
|
+
const prefix = `[verification ${display(repository)}/${display(label)}] `;
|
|
211
|
+
const key = JSON.stringify([scope(data), id]), name = binding(data, repository);
|
|
212
|
+
if (kind === 'check-started') {
|
|
213
|
+
if (active.get(name) === key)
|
|
214
|
+
return [];
|
|
215
|
+
const result = active.has(name) ? flush(active.get(name)) : [];
|
|
216
|
+
if (get(key, data, prefix))
|
|
217
|
+
active.set(name, key);
|
|
218
|
+
result.push(output(prefix + 'started\n', attemptId(data)));
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
if (kind === 'check-invalidated')
|
|
222
|
+
return [output(prefix + 'evidence invalidated' + (typeof data.reason === 'string' ? ': ' + display(data.reason) : '') + '\n', attemptId(data))];
|
|
223
|
+
if (!Number.isSafeInteger(data.exitCode) || typeof data.durationMs !== 'number' || !Number.isFinite(data.durationMs) || data.durationMs < 0)
|
|
224
|
+
return [];
|
|
225
|
+
const result = flush(key);
|
|
226
|
+
return [...result, output(prefix + `${kind === 'check-reused' ? 'reused' : data.exitCode === 0 ? 'command passed' : 'command failed'} (exit ${data.exitCode}, ${(data.durationMs / 1000).toFixed(1)} s)\n`, attemptId(data))];
|
|
227
|
+
}
|
|
228
|
+
if (event.type === 'runtime-result')
|
|
229
|
+
return flush();
|
|
230
|
+
if (event.type === 'workflow-event' && object(event.event)) {
|
|
231
|
+
const data = event.event, kind = text(data.type);
|
|
232
|
+
if (/^workflow_(?:succeeded|failed|cancelled|paused)$/.test(kind))
|
|
233
|
+
return flush();
|
|
234
|
+
if (!/^step_(?:started|succeeded|failed|cancelled|interrupted)$/.test(kind))
|
|
235
|
+
return [];
|
|
236
|
+
const result = [];
|
|
237
|
+
for (const [key, state] of streams) {
|
|
238
|
+
const identity = JSON.parse(state.scope);
|
|
239
|
+
// Older transports lack attempt identity. Flush them before a phase
|
|
240
|
+
// boundary; v2 flushes only the ending attempt, leaving siblings intact.
|
|
241
|
+
const legacy = identity[2] === null && identity[3] === null;
|
|
242
|
+
const sameAttempt = typeof data.attemptId === 'string' && identity[3] === data.attemptId;
|
|
243
|
+
const sameNode = !data.attemptId && (data.nodePath ?? data.stepId) === identity[2] && (!data.scopeId || data.scopeId === identity[1]);
|
|
244
|
+
if (legacy || (kind !== 'step_started' && (sameAttempt || sameNode)))
|
|
245
|
+
result.push(...flush(key));
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
return { project, flush };
|
|
252
|
+
}
|
|
@@ -104,8 +104,16 @@ function resolveRailRelaunch(c, railIndex, sourceId) {
|
|
|
104
104
|
config.baseBranch = row.base_branch;
|
|
105
105
|
if (delivery.executionManifest) {
|
|
106
106
|
config.repositoryIds = delivery.executionManifest.selectedRepositoryIds;
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
if (savedConfig?.workspaceSelection === undefined) {
|
|
108
|
+
const entries = delivery.executionManifest.repositories
|
|
109
|
+
.filter(repo => repo.selectedWorkspacePaths !== undefined).map(repo => [repo.repositoryId, repo.selectedWorkspacePaths]);
|
|
110
|
+
// No recorded narrowing means registered defaults, represented by
|
|
111
|
+
// absence. Explicit values remain subject to ordinary launch validation.
|
|
112
|
+
if (entries.length)
|
|
113
|
+
config.workspaceSelection = Object.fromEntries(entries);
|
|
114
|
+
else
|
|
115
|
+
delete config.workspaceSelection;
|
|
116
|
+
}
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
if (!Array.isArray(ticketIds) || !ticketIds.length || ticketIds.some(id => !Number.isSafeInteger(id) || id <= 0))
|
|
@@ -950,13 +950,15 @@ class LoopRunManager {
|
|
|
950
950
|
openStep = null;
|
|
951
951
|
emitRunEvent('loop_step_end', payload);
|
|
952
952
|
};
|
|
953
|
-
const logLine = (line, source = 'stdout') => {
|
|
953
|
+
const logLine = (line, source = 'stdout', metadata) => {
|
|
954
|
+
const attemptId = metadata?.attemptId;
|
|
955
|
+
const attribution = typeof attemptId === 'string' && attemptId.trim() && attemptId.length <= 256 ? { attemptId } : {};
|
|
954
956
|
const s = takeSeq();
|
|
955
957
|
try {
|
|
956
|
-
(0, db_1.appendEvent)(this.db, runId, s, { event_type: 'log', source, payload: JSON.stringify({ line }) });
|
|
958
|
+
(0, db_1.appendEvent)(this.db, runId, s, { event_type: 'log', source, payload: JSON.stringify({ line, ...attribution }) });
|
|
957
959
|
}
|
|
958
960
|
catch { /* events table best-effort */ }
|
|
959
|
-
this._emit({ type: 'log', source, line, timestamp: new Date(this.now()).toISOString(), processId: runId });
|
|
961
|
+
this._emit({ type: 'log', source, line, timestamp: new Date(this.now()).toISOString(), processId: runId, ...attribution });
|
|
960
962
|
};
|
|
961
963
|
// Forward a RAW provider stdout line (claude/codex JSONL) as an `event` —
|
|
962
964
|
// identical to QueueManager — so JobStatusPanel parses real activity
|