@exaudeus/workrail 3.73.1 → 3.74.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/dist/cli-worktrain.js +126 -1
- package/dist/console-ui/assets/{index-txIYXGHx.js → index-CfU3va8H.js} +1 -1
- package/dist/console-ui/index.html +1 -1
- package/dist/coordinators/pr-review.d.ts +11 -1
- package/dist/coordinators/types.d.ts +15 -0
- package/dist/coordinators/types.js +2 -0
- package/dist/manifest.json +81 -57
- package/dist/mcp/handlers/v2-advance-core/index.d.ts +1 -0
- package/dist/mcp/handlers/v2-advance-core/index.js +3 -3
- package/dist/mcp/handlers/v2-advance-core/outcome-success.js +4 -18
- package/dist/mcp/handlers/v2-advance-events.d.ts +1 -1
- package/dist/mcp/handlers/v2-advance-events.js +1 -1
- package/dist/mcp/handlers/v2-execution/advance.d.ts +1 -0
- package/dist/mcp/handlers/v2-execution/advance.js +3 -3
- package/dist/mcp/handlers/v2-execution/continue-advance.d.ts +1 -0
- package/dist/mcp/handlers/v2-execution/continue-advance.js +2 -1
- package/dist/mcp/handlers/v2-execution/index.js +3 -1
- package/dist/mcp/server.js +6 -4
- package/dist/mcp/types.d.ts +2 -0
- package/dist/trigger/coordinator-deps.js +203 -36
- package/dist/trigger/delivery-action.d.ts +1 -0
- package/dist/trigger/delivery-action.js +1 -1
- package/dist/trigger/delivery-pipeline.d.ts +13 -2
- package/dist/trigger/delivery-pipeline.js +58 -3
- package/dist/trigger/trigger-router.js +6 -3
- package/dist/v2/durable-core/constants.d.ts +1 -0
- package/dist/v2/durable-core/constants.js +1 -0
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +202 -0
- package/dist/v2/durable-core/schemas/session/events.d.ts +56 -0
- package/dist/v2/durable-core/schemas/session/events.js +8 -0
- package/dist/v2/infra/local/git-snapshot/index.d.ts +6 -0
- package/dist/v2/infra/local/git-snapshot/index.js +39 -0
- package/dist/v2/ports/git-snapshot.port.d.ts +10 -0
- package/dist/v2/ports/git-snapshot.port.js +9 -0
- package/dist/v2/projections/session-metrics.js +17 -2
- package/docs/authoring.md +23 -0
- package/docs/design/engine-boundary-discovery.md +123 -0
- package/docs/design/engine-boundary-review-findings.md +72 -0
- package/docs/ideas/backlog.md +129 -48
- package/package.json +1 -1
- package/spec/authoring-spec.json +36 -1
|
@@ -45,6 +45,102 @@ const infra_js_1 = require("../context-assembly/infra.js");
|
|
|
45
45
|
function createCoordinatorDeps(deps) {
|
|
46
46
|
const { ctx, execFileAsync, consoleService } = deps;
|
|
47
47
|
let dispatch = null;
|
|
48
|
+
async function fetchAgentResult(sessionHandle) {
|
|
49
|
+
const emptyResult = { recapMarkdown: null, artifacts: [] };
|
|
50
|
+
if (consoleService === null) {
|
|
51
|
+
return emptyResult;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const detailResult = await consoleService.getSessionDetail(sessionHandle);
|
|
55
|
+
if (detailResult.isErr())
|
|
56
|
+
return emptyResult;
|
|
57
|
+
const run = detailResult.value.runs[0];
|
|
58
|
+
if (!run)
|
|
59
|
+
return emptyResult;
|
|
60
|
+
const tipNodeId = run.preferredTipNodeId;
|
|
61
|
+
if (!tipNodeId)
|
|
62
|
+
return emptyResult;
|
|
63
|
+
const allNodeIds = run.nodes
|
|
64
|
+
.map((n) => n.nodeId)
|
|
65
|
+
.filter((id) => typeof id === 'string' && id !== '');
|
|
66
|
+
const nodeIdsToFetch = allNodeIds.length > 0 ? allNodeIds : [tipNodeId];
|
|
67
|
+
let recap = null;
|
|
68
|
+
const collectedArtifacts = [];
|
|
69
|
+
for (const nodeId of nodeIdsToFetch) {
|
|
70
|
+
try {
|
|
71
|
+
const nodeResult = await consoleService.getNodeDetail(sessionHandle, nodeId);
|
|
72
|
+
if (nodeResult.isErr())
|
|
73
|
+
continue;
|
|
74
|
+
if (nodeId === tipNodeId)
|
|
75
|
+
recap = nodeResult.value.recapMarkdown;
|
|
76
|
+
if (nodeResult.value.artifacts.length > 0)
|
|
77
|
+
collectedArtifacts.push(...nodeResult.value.artifacts);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { recapMarkdown: recap, artifacts: collectedArtifacts };
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
87
|
+
process.stderr.write(`[WARN coord:reason=exception handle=${sessionHandle.slice(0, 16)}] fetchAgentResult: ${msg}\n`);
|
|
88
|
+
return emptyResult;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function fetchChildSessionResult(handle, coordinatorSessionId) {
|
|
92
|
+
if (consoleService === null) {
|
|
93
|
+
process.stderr.write(`[WARN coord:reason=await_degraded handle=${handle.slice(0, 16)}${coordinatorSessionId ? ' parent=' + coordinatorSessionId.slice(0, 16) : ''}] fetchChildSessionResult: ConsoleService unavailable\n`);
|
|
94
|
+
return {
|
|
95
|
+
kind: 'await_degraded',
|
|
96
|
+
message: 'ConsoleService unavailable -- cannot read child session outcome',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
let runStatus = null;
|
|
100
|
+
try {
|
|
101
|
+
const detailResult = await consoleService.getSessionDetail(handle);
|
|
102
|
+
if (detailResult.isErr()) {
|
|
103
|
+
process.stderr.write(`[WARN coord:reason=getSessionDetail_failed handle=${handle.slice(0, 16)}] fetchChildSessionResult: ${String(detailResult.error)}\n`);
|
|
104
|
+
return {
|
|
105
|
+
kind: 'failed',
|
|
106
|
+
reason: 'error',
|
|
107
|
+
message: `Could not read session detail: ${String(detailResult.error)}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const run = detailResult.value.runs[0];
|
|
111
|
+
runStatus = run?.status ?? null;
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
115
|
+
process.stderr.write(`[WARN coord:reason=exception handle=${handle.slice(0, 16)}] fetchChildSessionResult getSessionDetail: ${msg}\n`);
|
|
116
|
+
return { kind: 'failed', reason: 'error', message: `Exception reading session detail: ${msg}` };
|
|
117
|
+
}
|
|
118
|
+
if (runStatus === 'complete' || runStatus === 'complete_with_gaps') {
|
|
119
|
+
const agentResult = await fetchAgentResult(handle);
|
|
120
|
+
return {
|
|
121
|
+
kind: 'success',
|
|
122
|
+
notes: agentResult.recapMarkdown,
|
|
123
|
+
artifacts: agentResult.artifacts,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (runStatus === 'blocked') {
|
|
127
|
+
return {
|
|
128
|
+
kind: 'failed',
|
|
129
|
+
reason: 'stuck',
|
|
130
|
+
message: `Child session ${handle.slice(0, 16)} reached blocked state`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (runStatus === null) {
|
|
134
|
+
return {
|
|
135
|
+
kind: 'timed_out',
|
|
136
|
+
message: `Child session ${handle.slice(0, 16)} has no terminal run status (likely timed out)`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
kind: 'timed_out',
|
|
141
|
+
message: `Child session ${handle.slice(0, 16)} is still in state '${runStatus}' -- awaitSessions may not have been called`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
48
144
|
return {
|
|
49
145
|
setDispatch(fn) {
|
|
50
146
|
if (dispatch !== null) {
|
|
@@ -53,11 +149,16 @@ function createCoordinatorDeps(deps) {
|
|
|
53
149
|
}
|
|
54
150
|
dispatch = fn;
|
|
55
151
|
},
|
|
56
|
-
spawnSession: async (workflowId, goal, workspace, context, agentConfig) => {
|
|
152
|
+
spawnSession: async (workflowId, goal, workspace, context, agentConfig, parentSessionId) => {
|
|
57
153
|
if (dispatch === null) {
|
|
58
154
|
return { kind: 'err', error: 'in-process router not initialized -- coordinator deps not ready' };
|
|
59
155
|
}
|
|
60
|
-
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId, workspacePath: workspace, goal }, ctx, {
|
|
156
|
+
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId, workspacePath: workspace, goal }, ctx, {
|
|
157
|
+
is_autonomous: 'true',
|
|
158
|
+
workspacePath: workspace,
|
|
159
|
+
triggerSource: 'daemon',
|
|
160
|
+
...(parentSessionId !== undefined ? { parentSessionId } : {}),
|
|
161
|
+
});
|
|
61
162
|
if (startResult.isErr()) {
|
|
62
163
|
const detail = `${startResult.error.kind}${'message' in startResult.error ? ': ' + startResult.error.message : ''}`;
|
|
63
164
|
return { kind: 'err', error: `Session creation failed: ${detail}` };
|
|
@@ -173,47 +274,113 @@ function createCoordinatorDeps(deps) {
|
|
|
173
274
|
};
|
|
174
275
|
},
|
|
175
276
|
getAgentResult: async (sessionHandle) => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
277
|
+
return fetchAgentResult(sessionHandle);
|
|
278
|
+
},
|
|
279
|
+
getChildSessionResult: async (handle, coordinatorSessionId) => {
|
|
280
|
+
return fetchChildSessionResult(handle, coordinatorSessionId);
|
|
281
|
+
},
|
|
282
|
+
spawnAndAwait: async (workflowId, goal, workspace, opts) => {
|
|
283
|
+
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
284
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
285
|
+
const coordinatorSessionId = opts?.coordinatorSessionId;
|
|
286
|
+
const agentConfig = opts?.agentConfig;
|
|
287
|
+
if (dispatch === null) {
|
|
288
|
+
return {
|
|
289
|
+
kind: 'failed',
|
|
290
|
+
reason: 'error',
|
|
291
|
+
message: 'spawnAndAwait: in-process router not initialized (setDispatch not called)',
|
|
292
|
+
};
|
|
179
293
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
294
|
+
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId, workspacePath: workspace, goal }, ctx, {
|
|
295
|
+
is_autonomous: 'true',
|
|
296
|
+
workspacePath: workspace,
|
|
297
|
+
triggerSource: 'daemon',
|
|
298
|
+
...(coordinatorSessionId !== undefined ? { parentSessionId: coordinatorSessionId } : {}),
|
|
299
|
+
});
|
|
300
|
+
if (startResult.isErr()) {
|
|
301
|
+
const detail = `${startResult.error.kind}${'message' in startResult.error ? ': ' + startResult.error.message : ''}`;
|
|
302
|
+
return { kind: 'failed', reason: 'error', message: `Session creation failed: ${detail}` };
|
|
303
|
+
}
|
|
304
|
+
const startContinueToken = startResult.value.response.continueToken;
|
|
305
|
+
let handle;
|
|
306
|
+
if (!startContinueToken) {
|
|
307
|
+
handle = workflowId;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const tokenResult = await (0, v2_token_ops_js_1.parseContinueTokenOrFail)(startContinueToken, ctx.v2.tokenCodecPorts, ctx.v2.tokenAliasStore);
|
|
311
|
+
if (tokenResult.isErr()) {
|
|
312
|
+
return {
|
|
313
|
+
kind: 'failed',
|
|
314
|
+
reason: 'error',
|
|
315
|
+
message: `Internal error: could not extract session handle from new session: ${tokenResult.error.message}`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
handle = tokenResult.value.sessionId;
|
|
319
|
+
const trigger = {
|
|
320
|
+
workflowId,
|
|
321
|
+
goal,
|
|
322
|
+
workspacePath: workspace,
|
|
323
|
+
...(agentConfig !== undefined ? { agentConfig } : {}),
|
|
324
|
+
};
|
|
325
|
+
const r = startResult.value.response;
|
|
326
|
+
const allocatedSession = {
|
|
327
|
+
continueToken: r.continueToken ?? '',
|
|
328
|
+
checkpointToken: r.checkpointToken,
|
|
329
|
+
firstStepPrompt: r.pending?.prompt ?? '',
|
|
330
|
+
isComplete: r.isComplete,
|
|
331
|
+
triggerSource: 'daemon',
|
|
332
|
+
};
|
|
333
|
+
const source = {
|
|
334
|
+
kind: 'pre_allocated',
|
|
335
|
+
trigger,
|
|
336
|
+
session: allocatedSession,
|
|
337
|
+
};
|
|
338
|
+
dispatch(trigger, source);
|
|
339
|
+
}
|
|
340
|
+
const awaitResult = await (async () => {
|
|
341
|
+
const POLL_INTERVAL_MS = 3000;
|
|
342
|
+
if (consoleService === null) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const startMs = Date.now();
|
|
346
|
+
const pending = new Set([handle]);
|
|
347
|
+
while (pending.size > 0) {
|
|
348
|
+
const elapsed = Date.now() - startMs;
|
|
349
|
+
if (elapsed >= timeoutMs)
|
|
350
|
+
break;
|
|
351
|
+
for (const h of [...pending]) {
|
|
352
|
+
try {
|
|
353
|
+
const detail = await consoleService.getSessionDetail(h);
|
|
354
|
+
if (detail.isErr())
|
|
355
|
+
continue;
|
|
356
|
+
const run = detail.value.runs[0];
|
|
357
|
+
if (!run)
|
|
358
|
+
continue;
|
|
359
|
+
const status = run.status;
|
|
360
|
+
if (status === 'complete' || status === 'complete_with_gaps') {
|
|
361
|
+
pending.delete(h);
|
|
362
|
+
}
|
|
363
|
+
else if (status === 'blocked') {
|
|
364
|
+
pending.delete(h);
|
|
365
|
+
}
|
|
201
366
|
}
|
|
202
|
-
|
|
203
|
-
|
|
367
|
+
catch {
|
|
368
|
+
pending.delete(h);
|
|
204
369
|
}
|
|
205
370
|
}
|
|
206
|
-
|
|
207
|
-
|
|
371
|
+
if (pending.size > 0) {
|
|
372
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
208
373
|
}
|
|
209
374
|
}
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
375
|
+
return handle;
|
|
376
|
+
})();
|
|
377
|
+
if (awaitResult === null) {
|
|
378
|
+
return {
|
|
379
|
+
kind: 'await_degraded',
|
|
380
|
+
message: 'ConsoleService unavailable -- cannot await child session outcome',
|
|
381
|
+
};
|
|
216
382
|
}
|
|
383
|
+
return fetchChildSessionResult(handle, coordinatorSessionId);
|
|
217
384
|
},
|
|
218
385
|
listOpenPRs: async (workspace) => {
|
|
219
386
|
try {
|
|
@@ -342,7 +342,7 @@ async function runDelivery(artifact, workspacePath, flags, execFn) {
|
|
|
342
342
|
});
|
|
343
343
|
}
|
|
344
344
|
const prUrl = prStdout.trim().split('\n').at(-1)?.trim() ?? '';
|
|
345
|
-
return { _tag: 'pr_opened', url: prUrl };
|
|
345
|
+
return { _tag: 'pr_opened', url: prUrl, sha };
|
|
346
346
|
}
|
|
347
347
|
function formatExecError(e) {
|
|
348
348
|
if (e instanceof Error) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { WorkflowRunSuccess } from '../daemon/workflow-runner.js';
|
|
2
2
|
import type { TriggerDefinition } from './types.js';
|
|
3
3
|
import type { ExecFn, HandoffArtifact } from './delivery-action.js';
|
|
4
|
+
import type { ExecutionSessionGateV2 } from '../v2/usecases/execution-session-gate.js';
|
|
5
|
+
import type { SessionEventLogAppendStorePortV2 } from '../v2/ports/session-event-log-store.port.js';
|
|
4
6
|
export type DeliveryStageOutcome = {
|
|
5
7
|
readonly kind: 'continue';
|
|
6
8
|
} | {
|
|
@@ -9,10 +11,19 @@ export type DeliveryStageOutcome = {
|
|
|
9
11
|
};
|
|
10
12
|
export interface DeliveryStage {
|
|
11
13
|
readonly name: string;
|
|
12
|
-
run(result: WorkflowRunSuccess, trigger: TriggerDefinition, execFn: ExecFn, ctx: PipelineContext): Promise<DeliveryStageOutcome>;
|
|
14
|
+
run(result: WorkflowRunSuccess, trigger: TriggerDefinition, execFn: ExecFn, ctx: PipelineContext, deps?: DeliveryPipelineDeps): Promise<DeliveryStageOutcome>;
|
|
13
15
|
}
|
|
14
16
|
export interface PipelineContext {
|
|
15
17
|
handoffArtifact?: HandoffArtifact;
|
|
18
|
+
commitSha?: string;
|
|
19
|
+
prUrl?: string;
|
|
16
20
|
}
|
|
17
|
-
export
|
|
21
|
+
export interface DeliveryPipelineDeps {
|
|
22
|
+
readonly gate: ExecutionSessionGateV2;
|
|
23
|
+
readonly sessionStore: SessionEventLogAppendStorePortV2 & import('../v2/ports/session-event-log-store.port.js').SessionEventLogReadonlyStorePortV2;
|
|
24
|
+
readonly idFactory: {
|
|
25
|
+
readonly mintEventId: () => string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare function runDeliveryPipeline(stages: readonly DeliveryStage[], result: WorkflowRunSuccess, trigger: TriggerDefinition, execFn: ExecFn, triggerId: string, deps?: DeliveryPipelineDeps): Promise<void>;
|
|
18
29
|
export declare const DEFAULT_DELIVERY_PIPELINE: readonly DeliveryStage[];
|
|
@@ -39,11 +39,16 @@ const fs = __importStar(require("node:fs/promises"));
|
|
|
39
39
|
const path = __importStar(require("node:path"));
|
|
40
40
|
const workflow_runner_js_1 = require("../daemon/workflow-runner.js");
|
|
41
41
|
const delivery_action_js_1 = require("./delivery-action.js");
|
|
42
|
-
|
|
42
|
+
const constants_js_1 = require("../v2/durable-core/constants.js");
|
|
43
|
+
const index_js_1 = require("../v2/durable-core/ids/index.js");
|
|
44
|
+
const session_index_js_1 = require("../v2/durable-core/session-index.js");
|
|
45
|
+
const sorted_event_log_js_1 = require("../v2/durable-core/sorted-event-log.js");
|
|
46
|
+
const neverthrow_1 = require("neverthrow");
|
|
47
|
+
async function runDeliveryPipeline(stages, result, trigger, execFn, triggerId, deps) {
|
|
43
48
|
const ctx = {};
|
|
44
49
|
try {
|
|
45
50
|
for (const stage of stages) {
|
|
46
|
-
const outcome = await stage.run(result, trigger, execFn, ctx);
|
|
51
|
+
const outcome = await stage.run(result, trigger, execFn, ctx, deps);
|
|
47
52
|
if (outcome.kind === 'stop') {
|
|
48
53
|
console.log(`[DeliveryPipeline] Stage "${stage.name}" stopped pipeline: triggerId=${triggerId} reason=${outcome.reason}`);
|
|
49
54
|
return;
|
|
@@ -96,9 +101,12 @@ const gitDeliveryStage = {
|
|
|
96
101
|
switch (deliveryResult._tag) {
|
|
97
102
|
case 'committed':
|
|
98
103
|
console.log(`[DeliveryPipeline] Delivery committed: triggerId=${trigger.id} sha=${deliveryResult.sha}`);
|
|
104
|
+
ctx.commitSha = deliveryResult.sha;
|
|
99
105
|
break;
|
|
100
106
|
case 'pr_opened':
|
|
101
|
-
console.log(`[DeliveryPipeline] Delivery PR opened: triggerId=${trigger.id} url=${deliveryResult.url}`);
|
|
107
|
+
console.log(`[DeliveryPipeline] Delivery PR opened: triggerId=${trigger.id} url=${deliveryResult.url} sha=${deliveryResult.sha}`);
|
|
108
|
+
ctx.commitSha = deliveryResult.sha;
|
|
109
|
+
ctx.prUrl = deliveryResult.url;
|
|
102
110
|
break;
|
|
103
111
|
case 'skipped':
|
|
104
112
|
console.log(`[DeliveryPipeline] Delivery skipped: triggerId=${trigger.id} reason=${deliveryResult.reason}`);
|
|
@@ -111,6 +119,52 @@ const gitDeliveryStage = {
|
|
|
111
119
|
return { kind: 'continue' };
|
|
112
120
|
},
|
|
113
121
|
};
|
|
122
|
+
const recordCommitShasStage = {
|
|
123
|
+
name: 'recordCommitShas',
|
|
124
|
+
async run(result, _trigger, _execFn, ctx, deps) {
|
|
125
|
+
const { sessionId } = result;
|
|
126
|
+
if (!sessionId || !ctx.commitSha || !deps) {
|
|
127
|
+
return { kind: 'continue' };
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const sid = (0, index_js_1.asSessionId)(sessionId);
|
|
131
|
+
const shas = [ctx.commitSha];
|
|
132
|
+
const prUrl = ctx.prUrl;
|
|
133
|
+
await deps.gate.withHealthySessionLock(sid, (lock) => deps.sessionStore.load(sid).andThen((truth) => {
|
|
134
|
+
const sortedResult = (0, sorted_event_log_js_1.asSortedEventLog)(truth.events);
|
|
135
|
+
if (sortedResult.isErr()) {
|
|
136
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
137
|
+
}
|
|
138
|
+
const index = (0, session_index_js_1.buildSessionIndex)(sortedResult.value);
|
|
139
|
+
const runCompleted = truth.events.find(e => e.kind === 'run_completed');
|
|
140
|
+
const runId = runCompleted?.scope.runId;
|
|
141
|
+
if (!runId) {
|
|
142
|
+
return (0, neverthrow_1.okAsync)(undefined);
|
|
143
|
+
}
|
|
144
|
+
const event = {
|
|
145
|
+
v: 1,
|
|
146
|
+
eventId: deps.idFactory.mintEventId(),
|
|
147
|
+
eventIndex: index.nextEventIndex,
|
|
148
|
+
sessionId,
|
|
149
|
+
kind: constants_js_1.EVENT_KIND.DELIVERY_RECORDED,
|
|
150
|
+
dedupeKey: `delivery-recorded:${sessionId}:${runId}`,
|
|
151
|
+
scope: { runId },
|
|
152
|
+
data: { shas, ...(prUrl ? { prUrl } : {}) },
|
|
153
|
+
timestampMs: Date.now(),
|
|
154
|
+
};
|
|
155
|
+
return deps.sessionStore.append(lock, { events: [event], snapshotPins: [] });
|
|
156
|
+
})).match(() => {
|
|
157
|
+
console.log(`[DeliveryPipeline] Commit SHAs recorded: sessionId=${sessionId} shas=${shas.join(',')}`);
|
|
158
|
+
}, (err) => {
|
|
159
|
+
console.warn(`[DeliveryPipeline] Could not record commit SHAs: sessionId=${sessionId} err=${JSON.stringify(err)}`);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.warn(`[DeliveryPipeline] Unexpected error in recordCommitShasStage: ${err instanceof Error ? err.message : String(err)}`);
|
|
164
|
+
}
|
|
165
|
+
return { kind: 'continue' };
|
|
166
|
+
},
|
|
167
|
+
};
|
|
114
168
|
const cleanupWorktreeStage = {
|
|
115
169
|
name: 'cleanupWorktree',
|
|
116
170
|
async run(result, trigger, execFn, _ctx) {
|
|
@@ -143,6 +197,7 @@ const deleteSidecarStage = {
|
|
|
143
197
|
exports.DEFAULT_DELIVERY_PIPELINE = [
|
|
144
198
|
parseHandoffStage,
|
|
145
199
|
gitDeliveryStage,
|
|
200
|
+
recordCommitShasStage,
|
|
146
201
|
cleanupWorktreeStage,
|
|
147
202
|
deleteSidecarStage,
|
|
148
203
|
];
|
|
@@ -118,7 +118,7 @@ function validateHmac(rawBody, secret, headerValue) {
|
|
|
118
118
|
}
|
|
119
119
|
return crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), Buffer.from(received, 'utf8'));
|
|
120
120
|
}
|
|
121
|
-
async function maybeRunDelivery(triggerId, trigger, result, execFn) {
|
|
121
|
+
async function maybeRunDelivery(triggerId, trigger, result, execFn, deps) {
|
|
122
122
|
if (result._tag !== 'success')
|
|
123
123
|
return;
|
|
124
124
|
if (result.lastStepNotes === undefined) {
|
|
@@ -131,7 +131,7 @@ async function maybeRunDelivery(triggerId, trigger, result, execFn) {
|
|
|
131
131
|
}
|
|
132
132
|
if (trigger.autoCommit !== true)
|
|
133
133
|
return;
|
|
134
|
-
await (0, delivery_pipeline_js_1.runDeliveryPipeline)(delivery_pipeline_js_1.DEFAULT_DELIVERY_PIPELINE, result, trigger, execFn, triggerId);
|
|
134
|
+
await (0, delivery_pipeline_js_1.runDeliveryPipeline)(delivery_pipeline_js_1.DEFAULT_DELIVERY_PIPELINE, result, trigger, execFn, triggerId, deps);
|
|
135
135
|
}
|
|
136
136
|
class Semaphore {
|
|
137
137
|
constructor(max) {
|
|
@@ -340,7 +340,10 @@ class TriggerRouter {
|
|
|
340
340
|
(0, assert_never_js_1.assertNever)(result);
|
|
341
341
|
}
|
|
342
342
|
this.notificationService?.notify(result, workflowTrigger.goal);
|
|
343
|
-
|
|
343
|
+
const deliveryDeps = this.ctx.v2
|
|
344
|
+
? { gate: this.ctx.v2.gate, sessionStore: this.ctx.v2.sessionStore, idFactory: this.ctx.v2.idFactory }
|
|
345
|
+
: undefined;
|
|
346
|
+
await maybeRunDelivery(trigger.id, trigger, originalResult, this.execFn, deliveryDeps);
|
|
344
347
|
});
|
|
345
348
|
return { _tag: 'enqueued', triggerId: trigger.id };
|
|
346
349
|
}
|
|
@@ -38,6 +38,7 @@ export declare const EVENT_KIND: {
|
|
|
38
38
|
readonly DIVERGENCE_RECORDED: "divergence_recorded";
|
|
39
39
|
readonly DECISION_TRACE_APPENDED: "decision_trace_appended";
|
|
40
40
|
readonly RUN_COMPLETED: "run_completed";
|
|
41
|
+
readonly DELIVERY_RECORDED: "delivery_recorded";
|
|
41
42
|
};
|
|
42
43
|
export type EventKindV1 = typeof EVENT_KIND[keyof typeof EVENT_KIND];
|
|
43
44
|
export declare const OUTPUT_CHANNEL: {
|