@atolis-hq/wake 0.2.50 → 0.2.52
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/src/adapters/fake/fake-ticketing-system.js +2 -1
- package/dist/src/adapters/github/github-issues-work-source.js +3 -2
- package/dist/src/adapters/http/ui-server.js +2 -1
- package/dist/src/core/event-builders.js +3 -2
- package/dist/src/core/outbox.js +8 -10
- package/dist/src/core/policy-engine.js +5 -5
- package/dist/src/core/projection-updater.js +17 -6
- package/dist/src/core/sink-router.js +4 -3
- package/dist/src/core/stale-run-reconciler.js +43 -2
- package/dist/src/core/tick-runner.js +86 -10
- package/dist/src/core/workspace-cleanup.js +3 -2
- package/dist/src/domain/event-types.js +130 -0
- package/dist/src/domain/schema.js +22 -6
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { access } from 'node:fs/promises';
|
|
2
|
+
import { LABELS_REQUESTED_EVENT } from '../../domain/event-types.js';
|
|
2
3
|
import { buildResourceUri } from '../../domain/resource-uri.js';
|
|
3
4
|
import { createEventEnvelope, createUnkeyedEventEnvelope } from '../../lib/event-log.js';
|
|
4
5
|
import { readJsonFile } from '../../lib/json-file.js';
|
|
@@ -81,7 +82,7 @@ export function createFakeTicketingSystem(options) {
|
|
|
81
82
|
},
|
|
82
83
|
async deliverIntent(input) {
|
|
83
84
|
const publishedAt = (options.now ?? (() => new Date()))().toISOString();
|
|
84
|
-
if (input.event.sourceEventType ===
|
|
85
|
+
if (input.event.sourceEventType === LABELS_REQUESTED_EVENT) {
|
|
85
86
|
const ticket = options.tickets.find((issue) => issue.repo === input.event.sourceRefs.repo &&
|
|
86
87
|
issue.number === input.event.sourceRefs.issueNumber);
|
|
87
88
|
const currentLabels = ticket?.labels ?? [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { autoApprovalLabel, resolveAutoApprovalIntent } from '../../core/approval-intents.js';
|
|
3
|
+
import { LABELS_REQUESTED_EVENT } from '../../domain/event-types.js';
|
|
3
4
|
import { defaultAgentIdentity } from '../../domain/schema.js';
|
|
4
5
|
import { buildResourceUri } from '../../domain/resource-uri.js';
|
|
5
6
|
import { wakeStageLabelPrefix } from '../../domain/stages.js';
|
|
@@ -509,7 +510,7 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
509
510
|
throw new Error(`cannot deliver intent ${input.event.eventId}: malformed repo "${repo}"`);
|
|
510
511
|
}
|
|
511
512
|
const publishedAt = deps.now().toISOString();
|
|
512
|
-
if (input.event.sourceEventType ===
|
|
513
|
+
if (input.event.sourceEventType === LABELS_REQUESTED_EVENT) {
|
|
513
514
|
// Outbound intents are keyed envelopes Wake itself minted, so the work
|
|
514
515
|
// item is already named on the event — a direct read, not a lookup.
|
|
515
516
|
const projection = await deps.stateStore.readIssueState(input.event.workItemKey);
|
|
@@ -594,7 +595,7 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
594
595
|
return [];
|
|
595
596
|
}
|
|
596
597
|
const publishedAt = deps.now().toISOString();
|
|
597
|
-
if (input.event.sourceEventType ===
|
|
598
|
+
if (input.event.sourceEventType === LABELS_REQUESTED_EVENT) {
|
|
598
599
|
const projection = await deps.stateStore.readIssueState(input.event.workItemKey);
|
|
599
600
|
const currentLabels = projection?.issue.labels ?? [];
|
|
600
601
|
const expected = [
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname } from 'node:path';
|
|
5
5
|
import { createProjectionUpdater } from '../../core/projection-updater.js';
|
|
6
|
+
import { RETRY_REQUESTED_EVENT } from '../../domain/event-types.js';
|
|
6
7
|
import { configuredTicketSource } from '../../domain/sources.js';
|
|
7
8
|
import { createEventEnvelope } from '../../lib/event-log.js';
|
|
8
9
|
import { writeJsonFile } from '../../lib/json-file.js';
|
|
@@ -118,7 +119,7 @@ async function handleRequest(req, res, options, now, projectionUpdater) {
|
|
|
118
119
|
streamScope: 'work-item',
|
|
119
120
|
direction: 'internal',
|
|
120
121
|
sourceSystem: 'wake',
|
|
121
|
-
sourceEventType:
|
|
122
|
+
sourceEventType: RETRY_REQUESTED_EVENT,
|
|
122
123
|
sourceRefs: { repo: item.issue.repo, issueNumber: item.issue.number },
|
|
123
124
|
occurredAt,
|
|
124
125
|
ingestedAt: occurredAt,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LABELS_REQUESTED_EVENT, PUBLISH_INTENT_REQUESTED_EVENT } from '../domain/event-types.js';
|
|
1
2
|
import { createEventEnvelope } from '../lib/event-log.js';
|
|
2
3
|
import { extractTokenCount, formatCostUsd, formatDuration, formatTokenCount, } from '../lib/format.js';
|
|
3
4
|
function isReviewThreadResourceUri(resourceUri) {
|
|
@@ -37,7 +38,7 @@ export function createPublishIntentEvent(input) {
|
|
|
37
38
|
streamScope: 'work-item',
|
|
38
39
|
direction: 'outbound',
|
|
39
40
|
sourceSystem: 'wake',
|
|
40
|
-
sourceEventType:
|
|
41
|
+
sourceEventType: PUBLISH_INTENT_REQUESTED_EVENT,
|
|
41
42
|
sourceRefs: {
|
|
42
43
|
repo: input.projection.issue.repo,
|
|
43
44
|
issueNumber: input.projection.issue.number,
|
|
@@ -129,7 +130,7 @@ export function createLabelsEvent(input) {
|
|
|
129
130
|
streamScope: 'work-item',
|
|
130
131
|
direction: 'outbound',
|
|
131
132
|
sourceSystem: 'wake',
|
|
132
|
-
sourceEventType:
|
|
133
|
+
sourceEventType: LABELS_REQUESTED_EVENT,
|
|
133
134
|
sourceRefs: {
|
|
134
135
|
repo: input.projection.issue.repo,
|
|
135
136
|
issueNumber: input.projection.issue.number,
|
package/dist/src/core/outbox.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { LABELS_REQUESTED_EVENT, PUBLISH_CONFIRMED_EVENT, PUBLISH_FAILED_EVENT, PUBLISH_INTENT_REQUESTED_EVENT, PUBLISH_SENT_UNCONFIRMED_EVENT, } from '../domain/event-types.js';
|
|
2
3
|
import { createEventEnvelope } from '../lib/event-log.js';
|
|
3
4
|
const outboxMaxAttempts = 3;
|
|
4
|
-
const outboundIntentEventTypes = new Set([
|
|
5
|
-
'wake.publish.intent.requested',
|
|
6
|
-
'wake.labels.requested',
|
|
7
|
-
]);
|
|
5
|
+
const outboundIntentEventTypes = new Set([PUBLISH_INTENT_REQUESTED_EVENT, LABELS_REQUESTED_EVENT]);
|
|
8
6
|
const outboundConfirmationEventTypes = new Set([
|
|
9
7
|
'ticket.reply.published',
|
|
10
8
|
'ticket.labels.updated',
|
|
11
|
-
|
|
9
|
+
PUBLISH_CONFIRMED_EVENT,
|
|
12
10
|
'pr.comment.reply.published',
|
|
13
11
|
'pr.review-comment.reply.published',
|
|
14
12
|
]);
|
|
@@ -29,7 +27,7 @@ export function createOutbox(deps) {
|
|
|
29
27
|
streamScope: 'work-item',
|
|
30
28
|
direction: 'internal',
|
|
31
29
|
sourceSystem: 'wake',
|
|
32
|
-
sourceEventType:
|
|
30
|
+
sourceEventType: PUBLISH_FAILED_EVENT,
|
|
33
31
|
sourceRefs: intentEvent.sourceRefs,
|
|
34
32
|
occurredAt,
|
|
35
33
|
ingestedAt: occurredAt,
|
|
@@ -53,7 +51,7 @@ export function createOutbox(deps) {
|
|
|
53
51
|
streamScope: 'work-item',
|
|
54
52
|
direction: 'internal',
|
|
55
53
|
sourceSystem: 'wake',
|
|
56
|
-
sourceEventType:
|
|
54
|
+
sourceEventType: PUBLISH_SENT_UNCONFIRMED_EVENT,
|
|
57
55
|
sourceRefs: intentEvent.sourceRefs,
|
|
58
56
|
occurredAt,
|
|
59
57
|
ingestedAt: occurredAt,
|
|
@@ -76,7 +74,7 @@ export function createOutbox(deps) {
|
|
|
76
74
|
streamScope: 'work-item',
|
|
77
75
|
direction: 'internal',
|
|
78
76
|
sourceSystem: 'wake',
|
|
79
|
-
sourceEventType:
|
|
77
|
+
sourceEventType: PUBLISH_CONFIRMED_EVENT,
|
|
80
78
|
sourceRefs: intentEvent.sourceRefs,
|
|
81
79
|
occurredAt: confirmedAt,
|
|
82
80
|
ingestedAt: confirmedAt,
|
|
@@ -154,10 +152,10 @@ export function createOutbox(deps) {
|
|
|
154
152
|
if (outboundConfirmationEventTypes.has(event.sourceEventType)) {
|
|
155
153
|
confirmedIntentIds.add(intentEventId);
|
|
156
154
|
}
|
|
157
|
-
if (event.sourceEventType ===
|
|
155
|
+
if (event.sourceEventType === PUBLISH_FAILED_EVENT) {
|
|
158
156
|
failureAttempts.set(intentEventId, (failureAttempts.get(intentEventId) ?? 0) + 1);
|
|
159
157
|
}
|
|
160
|
-
if (event.sourceEventType ===
|
|
158
|
+
if (event.sourceEventType === PUBLISH_SENT_UNCONFIRMED_EVENT) {
|
|
161
159
|
uncertainIntentIds.add(intentEventId);
|
|
162
160
|
}
|
|
163
161
|
}
|
|
@@ -110,7 +110,7 @@ export function createPolicyEngine() {
|
|
|
110
110
|
const handledCommentId = typeof context.lastHandledCommentId === 'string' ? context.lastHandledCommentId : undefined;
|
|
111
111
|
const lastCompletedAction = typeof context.lastCompletedAction === 'string' ? context.lastCompletedAction : undefined;
|
|
112
112
|
const lastRunSentinel = typeof context.lastRunSentinel === 'string' ? context.lastRunSentinel : undefined;
|
|
113
|
-
const
|
|
113
|
+
const lastRetrySafety = typeof context.lastRetrySafety === 'string' ? context.lastRetrySafety : undefined;
|
|
114
114
|
if (issue.wake.lastRunId === undefined) {
|
|
115
115
|
return true;
|
|
116
116
|
}
|
|
@@ -122,15 +122,15 @@ export function createPolicyEngine() {
|
|
|
122
122
|
if (isAwaitingApproval(issue)) {
|
|
123
123
|
return false;
|
|
124
124
|
}
|
|
125
|
-
if (lastRunSentinel === failedRunnerSentinel
|
|
125
|
+
if (lastRunSentinel === failedRunnerSentinel) {
|
|
126
|
+
if (lastRetrySafety === 'SAFE_TO_RETRY' || lastRetrySafety === 'SAFE_TO_RESUME') {
|
|
127
|
+
return belowFailureRetryLimit(issue, config);
|
|
128
|
+
}
|
|
126
129
|
return false;
|
|
127
130
|
}
|
|
128
131
|
if (lastRunSentinel === 'BLOCKED') {
|
|
129
132
|
return false;
|
|
130
133
|
}
|
|
131
|
-
if (lastFailureClass === 'quota') {
|
|
132
|
-
return belowFailureRetryLimit(issue, config);
|
|
133
|
-
}
|
|
134
134
|
const workflowAction = chooseWorkflowAction(issue, workflow);
|
|
135
135
|
return workflowAction !== null && lastCompletedAction !== workflowAction.action;
|
|
136
136
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { CORRELATION_PRIMARY_CONFLICT_EVENT, CORRELATION_REGISTERED_EVENT, CORRELATION_RETRACTED_EVENT,
|
|
1
|
+
import { CORRELATION_PRIMARY_CONFLICT_EVENT, CORRELATION_REGISTERED_EVENT, CORRELATION_RETRACTED_EVENT, RETRY_REQUESTED_EVENT, RUN_CLAIMED_EVENT, RUN_COMPLETED_EVENT, WORKFLOW_SELECTED_EVENT, WORKSPACE_CLEANED_EVENT, } from '../domain/event-types.js';
|
|
2
|
+
import { UNRESOLVED_WORK_ITEM_KEY, parseIssueStateRecord } from '../domain/schema.js';
|
|
2
3
|
import { doneRunnerSentinel, stageFromLabels } from '../domain/stages.js';
|
|
3
4
|
import { builtInDefaultWorkflowDefinition, defaultWorkflowName, selectWorkflowForEvent, workflowStageVocabulary, } from '../domain/workflows.js';
|
|
4
5
|
import { isCustomCommandAction } from '../domain/custom-commands.js';
|
|
5
6
|
import { createEventEnvelope } from '../lib/event-log.js';
|
|
6
|
-
const WORKFLOW_SELECTED_EVENT = 'wake.workflow.selected';
|
|
7
7
|
function stringArrayFromPayload(value) {
|
|
8
8
|
return Array.isArray(value)
|
|
9
9
|
? value.filter((entry) => typeof entry === 'string')
|
|
@@ -159,7 +159,7 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
159
159
|
},
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
|
-
if (event.sourceEventType ===
|
|
162
|
+
if (event.sourceEventType === RUN_CLAIMED_EVENT) {
|
|
163
163
|
const payload = event.payload;
|
|
164
164
|
if (payload.claimedStage === undefined) {
|
|
165
165
|
return parseIssueStateRecord({
|
|
@@ -190,7 +190,7 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
190
190
|
},
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
|
-
if (event.sourceEventType ===
|
|
193
|
+
if (event.sourceEventType === RUN_COMPLETED_EVENT) {
|
|
194
194
|
const payload = event.payload;
|
|
195
195
|
if (payload.watcherRun === true) {
|
|
196
196
|
return parseIssueStateRecord({
|
|
@@ -253,6 +253,17 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
253
253
|
...(payload.workflowOutcome !== undefined
|
|
254
254
|
? { lastWorkflowOutcome: payload.workflowOutcome }
|
|
255
255
|
: {}),
|
|
256
|
+
...(payload.failurePhase !== undefined ? { lastFailurePhase: payload.failurePhase } : {}),
|
|
257
|
+
...(payload.processStarted !== undefined
|
|
258
|
+
? { lastProcessStarted: payload.processStarted }
|
|
259
|
+
: {}),
|
|
260
|
+
...(payload.workspaceChanged !== undefined
|
|
261
|
+
? { lastWorkspaceChanged: payload.workspaceChanged }
|
|
262
|
+
: {}),
|
|
263
|
+
...(payload.externalSideEffects !== undefined
|
|
264
|
+
? { lastExternalSideEffects: payload.externalSideEffects }
|
|
265
|
+
: {}),
|
|
266
|
+
...(payload.retrySafety !== undefined ? { lastRetrySafety: payload.retrySafety } : {}),
|
|
256
267
|
};
|
|
257
268
|
if (payload.sentinel === 'BLOCKED' || payload.sentinel === 'FAILED') {
|
|
258
269
|
nextContext.blockedFromStage = current.wake.stage;
|
|
@@ -317,7 +328,7 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
317
328
|
},
|
|
318
329
|
});
|
|
319
330
|
}
|
|
320
|
-
if (event.sourceEventType ===
|
|
331
|
+
if (event.sourceEventType === RETRY_REQUESTED_EVENT) {
|
|
321
332
|
const nextContext = { ...current.context };
|
|
322
333
|
delete nextContext.lastRunSentinel;
|
|
323
334
|
delete nextContext.lastFailureClass;
|
|
@@ -332,7 +343,7 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
332
343
|
},
|
|
333
344
|
});
|
|
334
345
|
}
|
|
335
|
-
if (event.sourceEventType ===
|
|
346
|
+
if (event.sourceEventType === WORKSPACE_CLEANED_EVENT) {
|
|
336
347
|
return parseIssueStateRecord({
|
|
337
348
|
...current,
|
|
338
349
|
wake: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LABELS_REQUESTED_EVENT, PUBLISH_INTENT_REQUESTED_EVENT } from '../domain/event-types.js';
|
|
1
2
|
export function createWorkSourceFanIn(sources) {
|
|
2
3
|
return {
|
|
3
4
|
async pollEvents(input) {
|
|
@@ -19,7 +20,7 @@ export function createWorkSourceFanIn(sources) {
|
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
function intentKind(event) {
|
|
22
|
-
if (event.sourceEventType !==
|
|
23
|
+
if (event.sourceEventType !== PUBLISH_INTENT_REQUESTED_EVENT) {
|
|
23
24
|
return null;
|
|
24
25
|
}
|
|
25
26
|
return typeof event.payload.kind === 'string' ? event.payload.kind : null;
|
|
@@ -54,14 +55,14 @@ function targetSinksForEvent(input) {
|
|
|
54
55
|
const targetSinks = new Set();
|
|
55
56
|
const origin = typeof input.event.payload.origin === 'string' ? input.event.payload.origin : undefined;
|
|
56
57
|
const sourceOrigin = input.event.sourceRefs.sink ?? origin;
|
|
57
|
-
if (input.event.sourceEventType ===
|
|
58
|
+
if (input.event.sourceEventType === LABELS_REQUESTED_EVENT) {
|
|
58
59
|
const projectionOrigin = typeof input.event.payload.origin === 'string' ? input.event.payload.origin : undefined;
|
|
59
60
|
if (projectionOrigin !== undefined) {
|
|
60
61
|
targetSinks.add(projectionOrigin);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
const resourceUri = input.event.sourceRefs.resourceUri;
|
|
64
|
-
if (input.event.sourceEventType ===
|
|
65
|
+
if (input.event.sourceEventType === PUBLISH_INTENT_REQUESTED_EVENT &&
|
|
65
66
|
sourceOrigin !== undefined) {
|
|
66
67
|
const resourceSink = resourceUri === undefined ? sourceOrigin : sinkNameForResourceUri(resourceUri, sourceOrigin);
|
|
67
68
|
targetSinks.add(input.sinksByName.has(resourceSink) ? resourceSink : sourceOrigin);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLabelsEvent } from './event-builders.js';
|
|
2
|
+
import { RUN_COMPLETED_EVENT } from '../domain/event-types.js';
|
|
2
3
|
import { stageLabelForStage } from '../domain/stages.js';
|
|
3
4
|
import { workflowLabelForWorkflowName, workflowNameForProjection } from '../domain/workflows.js';
|
|
4
5
|
import { createEventEnvelope } from '../lib/event-log.js';
|
|
@@ -25,6 +26,37 @@ export function createStaleRunReconciler(deps) {
|
|
|
25
26
|
return 'SUPERSEDED';
|
|
26
27
|
}
|
|
27
28
|
}
|
|
29
|
+
function failurePhaseForLifecycle(lifecycle) {
|
|
30
|
+
switch (lifecycle) {
|
|
31
|
+
case 'CREATED':
|
|
32
|
+
case 'CLAIMED':
|
|
33
|
+
case 'PREPARING':
|
|
34
|
+
return 'workspace-prep';
|
|
35
|
+
case 'PROCESS_STARTING':
|
|
36
|
+
return 'process-starting';
|
|
37
|
+
case 'RUNNING':
|
|
38
|
+
case 'FINALISING':
|
|
39
|
+
return 'running';
|
|
40
|
+
case 'TERMINAL':
|
|
41
|
+
return 'unknown';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function classifyReconciledFailure(record) {
|
|
45
|
+
const processStarted = record.agentPid !== undefined ||
|
|
46
|
+
record.agentProcessStartedAt !== undefined ||
|
|
47
|
+
record.lifecycle === 'RUNNING' ||
|
|
48
|
+
record.lifecycle === 'FINALISING';
|
|
49
|
+
const metadata = record.metadata;
|
|
50
|
+
const workspaceChanged = typeof metadata?.workspacePath === 'string';
|
|
51
|
+
const retrySafety = processStarted || workspaceChanged ? 'REQUIRES_RECONCILIATION' : 'SAFE_TO_RETRY';
|
|
52
|
+
return {
|
|
53
|
+
failurePhase: failurePhaseForLifecycle(record.lifecycle),
|
|
54
|
+
processStarted,
|
|
55
|
+
workspaceChanged,
|
|
56
|
+
externalSideEffects: processStarted ? 'unknown' : 'none',
|
|
57
|
+
retrySafety,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
28
60
|
async function staleReason(record, now) {
|
|
29
61
|
if (record.status !== 'running') {
|
|
30
62
|
return null;
|
|
@@ -80,7 +112,7 @@ export function createStaleRunReconciler(deps) {
|
|
|
80
112
|
streamScope: 'work-item',
|
|
81
113
|
direction: 'internal',
|
|
82
114
|
sourceSystem: 'wake',
|
|
83
|
-
sourceEventType:
|
|
115
|
+
sourceEventType: RUN_COMPLETED_EVENT,
|
|
84
116
|
sourceRefs: {
|
|
85
117
|
repo: projection.issue.repo,
|
|
86
118
|
issueNumber: projection.issue.number,
|
|
@@ -93,6 +125,11 @@ export function createStaleRunReconciler(deps) {
|
|
|
93
125
|
action: projection.context.lastRunAction,
|
|
94
126
|
sentinel: 'FAILED',
|
|
95
127
|
executionOutcome: 'CANCELED_BY_RECONCILIATION',
|
|
128
|
+
failurePhase: 'unknown',
|
|
129
|
+
processStarted: false,
|
|
130
|
+
workspaceChanged: false,
|
|
131
|
+
externalSideEffects: 'unknown',
|
|
132
|
+
retrySafety: 'MANUAL_REVIEW_REQUIRED',
|
|
96
133
|
runId,
|
|
97
134
|
reason: 'runner:missing-run-record',
|
|
98
135
|
},
|
|
@@ -145,6 +182,7 @@ export function createStaleRunReconciler(deps) {
|
|
|
145
182
|
continue;
|
|
146
183
|
}
|
|
147
184
|
const staleExecutionOutcome = reason === 'timeout' ? 'TIMED_OUT' : recoveryOutcomeForLifecycle(record.lifecycle);
|
|
185
|
+
const failureContext = classifyReconciledFailure(record);
|
|
148
186
|
await deps.stateStore.writeRunRecord({
|
|
149
187
|
...record,
|
|
150
188
|
lifecycle: 'TERMINAL',
|
|
@@ -152,6 +190,7 @@ export function createStaleRunReconciler(deps) {
|
|
|
152
190
|
finishedAt,
|
|
153
191
|
sentinel: 'FAILED',
|
|
154
192
|
executionOutcome: staleExecutionOutcome,
|
|
193
|
+
...failureContext,
|
|
155
194
|
summary: `Run exceeded timeout while marked running and was reconciled by a later tick.`,
|
|
156
195
|
metadata: {
|
|
157
196
|
...record.metadata,
|
|
@@ -159,6 +198,7 @@ export function createStaleRunReconciler(deps) {
|
|
|
159
198
|
recoveryLifecycle: record.lifecycle,
|
|
160
199
|
staleReason: reason,
|
|
161
200
|
timeoutMs: deps.runnerTimeoutMs(),
|
|
201
|
+
...failureContext,
|
|
162
202
|
},
|
|
163
203
|
});
|
|
164
204
|
const runCompletedEvent = createEventEnvelope({
|
|
@@ -167,7 +207,7 @@ export function createStaleRunReconciler(deps) {
|
|
|
167
207
|
streamScope: 'work-item',
|
|
168
208
|
direction: 'internal',
|
|
169
209
|
sourceSystem: 'wake',
|
|
170
|
-
sourceEventType:
|
|
210
|
+
sourceEventType: RUN_COMPLETED_EVENT,
|
|
171
211
|
sourceRefs: {
|
|
172
212
|
repo: record.repo,
|
|
173
213
|
issueNumber: record.issueNumber,
|
|
@@ -180,6 +220,7 @@ export function createStaleRunReconciler(deps) {
|
|
|
180
220
|
action: record.action,
|
|
181
221
|
sentinel: 'FAILED',
|
|
182
222
|
executionOutcome: staleExecutionOutcome,
|
|
223
|
+
...failureContext,
|
|
183
224
|
runId: record.runId,
|
|
184
225
|
reason: reason === 'timeout'
|
|
185
226
|
? 'runner:stale-timeout'
|
|
@@ -3,7 +3,8 @@ import { createLifecycleService } from './lifecycle-service.js';
|
|
|
3
3
|
import { createPolicyEngine } from './policy-engine.js';
|
|
4
4
|
import { createProjectionUpdater } from './projection-updater.js';
|
|
5
5
|
import { acquireFileLock } from '../lib/lock.js';
|
|
6
|
-
import { CORRELATION_REGISTERED_EVENT, CORRELATION_PRIMARY_CONFLICT_EVENT,
|
|
6
|
+
import { CORRELATION_REGISTERED_EVENT, CORRELATION_PRIMARY_CONFLICT_EVENT, PR_AUTO_MERGE_ENABLED_EVENT, PR_REVIEW_APPROVED_EVENT, PUBLISH_INTENT_REQUESTED_EVENT, RUN_CLAIMED_EVENT, RUN_COMPLETED_EVENT, } from '../domain/event-types.js';
|
|
7
|
+
import { parseRunnerArtifacts, parseRunnerResult } from '../domain/schema.js';
|
|
7
8
|
import { maxConfiguredRunnerTimeoutMs, resolveRunnerRouting } from '../domain/runner-routing.js';
|
|
8
9
|
import { awaitingApprovalRunnerSentinel, stageLabelForStage } from '../domain/stages.js';
|
|
9
10
|
import { isMeaningfulRuntimeEvent } from '../domain/runtime-events.js';
|
|
@@ -51,6 +52,54 @@ function shouldPublishRunResult(input) {
|
|
|
51
52
|
}
|
|
52
53
|
return input.failureClass !== input.previousFailureClass;
|
|
53
54
|
}
|
|
55
|
+
function hasConfirmedExternalSideEffect(projection) {
|
|
56
|
+
return projection.correlatedResources.some((resource) => /^[a-z0-9-]+:pr:/.test(resource.resourceUri));
|
|
57
|
+
}
|
|
58
|
+
function failurePhaseForRecord(record) {
|
|
59
|
+
if (record.agentPid !== undefined || record.agentProcessStartedAt !== undefined) {
|
|
60
|
+
return 'running';
|
|
61
|
+
}
|
|
62
|
+
if (record.lifecycle === 'PROCESS_STARTING' || record.lifecycle === 'RUNNING') {
|
|
63
|
+
return 'process-starting';
|
|
64
|
+
}
|
|
65
|
+
if (record.lifecycle === 'PREPARING') {
|
|
66
|
+
return 'workspace-prep';
|
|
67
|
+
}
|
|
68
|
+
return 'unknown';
|
|
69
|
+
}
|
|
70
|
+
function classifyFailedRun(input) {
|
|
71
|
+
const processStarted = input.record.agentPid !== undefined || input.record.agentProcessStartedAt !== undefined;
|
|
72
|
+
const workspaceChanged = input.workspacePath !== undefined;
|
|
73
|
+
const externalSideEffects = hasConfirmedExternalSideEffect(input.projection)
|
|
74
|
+
? 'confirmed'
|
|
75
|
+
: processStarted
|
|
76
|
+
? 'unknown'
|
|
77
|
+
: 'none';
|
|
78
|
+
const failurePhase = input.failurePhase ??
|
|
79
|
+
(input.envelope === 'degraded' && input.sentinel === 'FAILED'
|
|
80
|
+
? 'result-parsing'
|
|
81
|
+
: failurePhaseForRecord(input.record));
|
|
82
|
+
let retrySafety;
|
|
83
|
+
if (input.failureClass === 'task') {
|
|
84
|
+
retrySafety = 'NOT_RETRYABLE';
|
|
85
|
+
}
|
|
86
|
+
else if (externalSideEffects === 'confirmed' || workspaceChanged) {
|
|
87
|
+
retrySafety = 'REQUIRES_RECONCILIATION';
|
|
88
|
+
}
|
|
89
|
+
else if (processStarted) {
|
|
90
|
+
retrySafety = 'SAFE_TO_RESUME';
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
retrySafety = 'SAFE_TO_RETRY';
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
failurePhase,
|
|
97
|
+
processStarted,
|
|
98
|
+
workspaceChanged,
|
|
99
|
+
externalSideEffects,
|
|
100
|
+
retrySafety,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
54
103
|
export function createTickRunner(deps) {
|
|
55
104
|
const policy = createPolicyEngine();
|
|
56
105
|
const lifecycle = createLifecycleService();
|
|
@@ -184,7 +233,7 @@ export function createTickRunner(deps) {
|
|
|
184
233
|
streamScope: 'work-item',
|
|
185
234
|
direction: 'internal',
|
|
186
235
|
sourceSystem: 'wake',
|
|
187
|
-
sourceEventType:
|
|
236
|
+
sourceEventType: RUN_COMPLETED_EVENT,
|
|
188
237
|
sourceRefs: {
|
|
189
238
|
repo: input.projection.issue.repo,
|
|
190
239
|
issueNumber: input.projection.issue.number,
|
|
@@ -214,7 +263,7 @@ export function createTickRunner(deps) {
|
|
|
214
263
|
streamScope: 'work-item',
|
|
215
264
|
direction: 'outbound',
|
|
216
265
|
sourceSystem: 'wake',
|
|
217
|
-
sourceEventType:
|
|
266
|
+
sourceEventType: PUBLISH_INTENT_REQUESTED_EVENT,
|
|
218
267
|
sourceRefs: {
|
|
219
268
|
repo: input.projection.issue.repo,
|
|
220
269
|
issueNumber: input.projection.issue.number,
|
|
@@ -271,7 +320,7 @@ export function createTickRunner(deps) {
|
|
|
271
320
|
streamScope: 'work-item',
|
|
272
321
|
direction: 'internal',
|
|
273
322
|
sourceSystem: 'wake',
|
|
274
|
-
sourceEventType:
|
|
323
|
+
sourceEventType: PR_REVIEW_APPROVED_EVENT,
|
|
275
324
|
sourceRefs: {
|
|
276
325
|
resourceUri: targetResourceUri,
|
|
277
326
|
commentId: input.approvalResolution.triggeringCommentId,
|
|
@@ -295,7 +344,7 @@ export function createTickRunner(deps) {
|
|
|
295
344
|
streamScope: 'work-item',
|
|
296
345
|
direction: 'internal',
|
|
297
346
|
sourceSystem: 'wake',
|
|
298
|
-
sourceEventType:
|
|
347
|
+
sourceEventType: PR_AUTO_MERGE_ENABLED_EVENT,
|
|
299
348
|
sourceRefs: {
|
|
300
349
|
resourceUri: targetResourceUri,
|
|
301
350
|
commentId: input.approvalResolution.triggeringCommentId,
|
|
@@ -493,7 +542,7 @@ export function createTickRunner(deps) {
|
|
|
493
542
|
streamScope: 'work-item',
|
|
494
543
|
direction: 'internal',
|
|
495
544
|
sourceSystem: 'wake',
|
|
496
|
-
sourceEventType:
|
|
545
|
+
sourceEventType: RUN_COMPLETED_EVENT,
|
|
497
546
|
sourceRefs: {
|
|
498
547
|
repo: projection.issue.repo,
|
|
499
548
|
issueNumber: projection.issue.number,
|
|
@@ -914,7 +963,7 @@ export function createTickRunner(deps) {
|
|
|
914
963
|
streamScope: 'work-item',
|
|
915
964
|
direction: 'internal',
|
|
916
965
|
sourceSystem: 'wake',
|
|
917
|
-
sourceEventType:
|
|
966
|
+
sourceEventType: RUN_COMPLETED_EVENT,
|
|
918
967
|
sourceRefs: {
|
|
919
968
|
repo: candidate.issue.repo,
|
|
920
969
|
issueNumber: candidate.issue.number,
|
|
@@ -1111,7 +1160,7 @@ export function createTickRunner(deps) {
|
|
|
1111
1160
|
streamScope: 'work-item',
|
|
1112
1161
|
direction: 'internal',
|
|
1113
1162
|
sourceSystem: 'wake',
|
|
1114
|
-
sourceEventType:
|
|
1163
|
+
sourceEventType: RUN_CLAIMED_EVENT,
|
|
1115
1164
|
sourceRefs: {
|
|
1116
1165
|
repo: candidate.issue.repo,
|
|
1117
1166
|
issueNumber: candidate.issue.number,
|
|
@@ -1463,6 +1512,16 @@ export function createTickRunner(deps) {
|
|
|
1463
1512
|
: undefined;
|
|
1464
1513
|
await transitionRunLifecycle('FINALISING');
|
|
1465
1514
|
const finalisingRecord = (await deps.stateStore.readRunRecord(runId));
|
|
1515
|
+
const failureContext = sentinel === 'FAILED'
|
|
1516
|
+
? classifyFailedRun({
|
|
1517
|
+
projection: candidate,
|
|
1518
|
+
record: finalisingRecord,
|
|
1519
|
+
failureClass: runnerResult.failureClass ?? 'task',
|
|
1520
|
+
...(workspacePath === undefined ? {} : { workspacePath }),
|
|
1521
|
+
envelope: parsedRunnerResult.envelope,
|
|
1522
|
+
sentinel,
|
|
1523
|
+
})
|
|
1524
|
+
: undefined;
|
|
1466
1525
|
await deps.stateStore.writeRunRecord({
|
|
1467
1526
|
...finalisingRecord,
|
|
1468
1527
|
lifecycle: 'TERMINAL',
|
|
@@ -1478,6 +1537,7 @@ export function createTickRunner(deps) {
|
|
|
1478
1537
|
sentinel,
|
|
1479
1538
|
executionOutcome,
|
|
1480
1539
|
...(workflowOutcome !== undefined ? { workflowOutcome } : {}),
|
|
1540
|
+
...(failureContext === undefined ? {} : failureContext),
|
|
1481
1541
|
summary: parsedRunnerResult.body,
|
|
1482
1542
|
...(runnerResult.routing === undefined ? {} : { routing: runnerResult.routing }),
|
|
1483
1543
|
...(runnerResult.tokenUsage === undefined ? {} : { tokenUsage: runnerResult.tokenUsage }),
|
|
@@ -1492,7 +1552,7 @@ export function createTickRunner(deps) {
|
|
|
1492
1552
|
streamScope: 'work-item',
|
|
1493
1553
|
direction: 'internal',
|
|
1494
1554
|
sourceSystem: 'wake',
|
|
1495
|
-
sourceEventType:
|
|
1555
|
+
sourceEventType: RUN_COMPLETED_EVENT,
|
|
1496
1556
|
sourceRefs: {
|
|
1497
1557
|
repo: candidate.issue.repo,
|
|
1498
1558
|
issueNumber: candidate.issue.number,
|
|
@@ -1518,6 +1578,7 @@ export function createTickRunner(deps) {
|
|
|
1518
1578
|
...(runnerResult.failureClass === undefined
|
|
1519
1579
|
? {}
|
|
1520
1580
|
: { failureClass: runnerResult.failureClass }),
|
|
1581
|
+
...(failureContext === undefined ? {} : failureContext),
|
|
1521
1582
|
// Only mark the triggering comment handled when the run reached the
|
|
1522
1583
|
// agent and produced a real outcome. Quota/infra failures are transient
|
|
1523
1584
|
// blips, not an answer to the human's comment — leaving handledCommentId
|
|
@@ -1611,6 +1672,18 @@ export function createTickRunner(deps) {
|
|
|
1611
1672
|
const finishedAt = deps.clock.now().toISOString();
|
|
1612
1673
|
const sentinel = 'FAILED';
|
|
1613
1674
|
const failedRecord = (await deps.stateStore.readRunRecord(runId)) ?? runningRecord;
|
|
1675
|
+
const failedRecordMetadata = failedRecord.metadata;
|
|
1676
|
+
const failedRecordWorkspacePath = typeof failedRecordMetadata?.workspacePath === 'string'
|
|
1677
|
+
? failedRecordMetadata.workspacePath
|
|
1678
|
+
: undefined;
|
|
1679
|
+
const failureContext = classifyFailedRun({
|
|
1680
|
+
projection: candidate,
|
|
1681
|
+
record: failedRecord,
|
|
1682
|
+
failureClass: 'infra',
|
|
1683
|
+
...(failedRecordWorkspacePath === undefined
|
|
1684
|
+
? {}
|
|
1685
|
+
: { workspacePath: failedRecordWorkspacePath }),
|
|
1686
|
+
});
|
|
1614
1687
|
await deps.stateStore.writeRunRecord({
|
|
1615
1688
|
...failedRecord,
|
|
1616
1689
|
lifecycle: 'TERMINAL',
|
|
@@ -1618,10 +1691,12 @@ export function createTickRunner(deps) {
|
|
|
1618
1691
|
finishedAt,
|
|
1619
1692
|
sentinel,
|
|
1620
1693
|
executionOutcome: 'PROCESS_FAILED',
|
|
1694
|
+
...failureContext,
|
|
1621
1695
|
summary: err instanceof Error ? err.message : String(err),
|
|
1622
1696
|
metadata: {
|
|
1623
1697
|
...failedRecord.metadata,
|
|
1624
1698
|
failureClass: 'infra',
|
|
1699
|
+
...failureContext,
|
|
1625
1700
|
},
|
|
1626
1701
|
});
|
|
1627
1702
|
const runCompletedEvent = createEventEnvelope({
|
|
@@ -1630,7 +1705,7 @@ export function createTickRunner(deps) {
|
|
|
1630
1705
|
streamScope: 'work-item',
|
|
1631
1706
|
direction: 'internal',
|
|
1632
1707
|
sourceSystem: 'wake',
|
|
1633
|
-
sourceEventType:
|
|
1708
|
+
sourceEventType: RUN_COMPLETED_EVENT,
|
|
1634
1709
|
sourceRefs: {
|
|
1635
1710
|
repo: candidate.issue.repo,
|
|
1636
1711
|
issueNumber: candidate.issue.number,
|
|
@@ -1645,6 +1720,7 @@ export function createTickRunner(deps) {
|
|
|
1645
1720
|
runId,
|
|
1646
1721
|
reason: 'runner:infrastructure-error',
|
|
1647
1722
|
failureClass: 'infra',
|
|
1723
|
+
...failureContext,
|
|
1648
1724
|
executionOutcome: 'PROCESS_FAILED',
|
|
1649
1725
|
// Deliberately omit handledCommentId: an infra blip (CLI crash, timeout,
|
|
1650
1726
|
// network error) never reached the agent, so it isn't an answer to the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rm } from 'node:fs/promises';
|
|
2
2
|
import { isAbsolute, join, relative } from 'node:path';
|
|
3
|
+
import { WORKSPACE_CLEANED_EVENT, WORKSPACE_CLEANUP_FAILED_EVENT } from '../domain/event-types.js';
|
|
3
4
|
import { createEventEnvelope } from '../lib/event-log.js';
|
|
4
5
|
// Cleans up per-issue workspaces (and, unless retained, transcripts) once the
|
|
5
6
|
// originating issue is closed. A cleanup failure is recorded as an event and
|
|
@@ -36,7 +37,7 @@ export function createWorkspaceCleanup(deps) {
|
|
|
36
37
|
streamScope: 'work-item',
|
|
37
38
|
direction: 'internal',
|
|
38
39
|
sourceSystem: 'wake',
|
|
39
|
-
sourceEventType:
|
|
40
|
+
sourceEventType: WORKSPACE_CLEANUP_FAILED_EVENT,
|
|
40
41
|
sourceRefs: {
|
|
41
42
|
repo: projection.issue.repo,
|
|
42
43
|
issueNumber: projection.issue.number,
|
|
@@ -58,7 +59,7 @@ export function createWorkspaceCleanup(deps) {
|
|
|
58
59
|
streamScope: 'work-item',
|
|
59
60
|
direction: 'internal',
|
|
60
61
|
sourceSystem: 'wake',
|
|
61
|
-
sourceEventType:
|
|
62
|
+
sourceEventType: WORKSPACE_CLEANED_EVENT,
|
|
62
63
|
sourceRefs: {
|
|
63
64
|
repo: projection.issue.repo,
|
|
64
65
|
issueNumber: projection.issue.number,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export const AUTONOMOUS_DECISION_AUDIT_EVENT = 'wake.audit.autonomous-decision';
|
|
2
|
+
export const CORRELATION_PRIMARY_CONFLICT_EVENT = 'wake.correlation.primary-conflict';
|
|
3
|
+
export const CORRELATION_REGISTERED_EVENT = 'wake.correlation.registered';
|
|
4
|
+
export const CORRELATION_RETRACTED_EVENT = 'wake.correlation.retracted';
|
|
5
|
+
export const LABELS_REQUESTED_EVENT = 'wake.labels.requested';
|
|
6
|
+
export const PR_AUTO_MERGE_ENABLED_EVENT = 'wake.pr-auto-merge.enabled';
|
|
7
|
+
export const PR_REVIEW_APPROVED_EVENT = 'wake.pr-review.approved';
|
|
8
|
+
export const PUBLISH_CONFIRMED_EVENT = 'wake.publish.confirmed';
|
|
9
|
+
export const PUBLISH_FAILED_EVENT = 'wake.publish.failed';
|
|
10
|
+
export const PUBLISH_INTENT_REQUESTED_EVENT = 'wake.publish.intent.requested';
|
|
11
|
+
export const PUBLISH_SENT_UNCONFIRMED_EVENT = 'wake.publish.sent-unconfirmed';
|
|
12
|
+
export const RETRY_REQUESTED_EVENT = 'wake.retry.requested';
|
|
13
|
+
export const RUN_CLAIMED_EVENT = 'wake.run.claimed';
|
|
14
|
+
export const RUN_COMPLETED_EVENT = 'wake.run.completed';
|
|
15
|
+
export const WORKFLOW_SELECTED_EVENT = 'wake.workflow.selected';
|
|
16
|
+
export const WORK_ITEM_CREATED_EVENT = 'wake.workitem.created';
|
|
17
|
+
export const WORKSPACE_CLEANED_EVENT = 'wake.workspace.cleaned';
|
|
18
|
+
export const WORKSPACE_CLEANUP_FAILED_EVENT = 'wake.workspace.cleanup-failed';
|
|
19
|
+
export const wakeEventTypeValues = [
|
|
20
|
+
AUTONOMOUS_DECISION_AUDIT_EVENT,
|
|
21
|
+
CORRELATION_PRIMARY_CONFLICT_EVENT,
|
|
22
|
+
CORRELATION_REGISTERED_EVENT,
|
|
23
|
+
CORRELATION_RETRACTED_EVENT,
|
|
24
|
+
LABELS_REQUESTED_EVENT,
|
|
25
|
+
PR_AUTO_MERGE_ENABLED_EVENT,
|
|
26
|
+
PR_REVIEW_APPROVED_EVENT,
|
|
27
|
+
PUBLISH_CONFIRMED_EVENT,
|
|
28
|
+
PUBLISH_FAILED_EVENT,
|
|
29
|
+
PUBLISH_INTENT_REQUESTED_EVENT,
|
|
30
|
+
PUBLISH_SENT_UNCONFIRMED_EVENT,
|
|
31
|
+
RETRY_REQUESTED_EVENT,
|
|
32
|
+
RUN_CLAIMED_EVENT,
|
|
33
|
+
RUN_COMPLETED_EVENT,
|
|
34
|
+
WORKFLOW_SELECTED_EVENT,
|
|
35
|
+
WORK_ITEM_CREATED_EVENT,
|
|
36
|
+
WORKSPACE_CLEANED_EVENT,
|
|
37
|
+
WORKSPACE_CLEANUP_FAILED_EVENT,
|
|
38
|
+
];
|
|
39
|
+
export const wakeEventTypeDefinitions = [
|
|
40
|
+
{
|
|
41
|
+
type: AUTONOMOUS_DECISION_AUDIT_EVENT,
|
|
42
|
+
description: 'Records an autonomous workflow decision and the inputs used to make it.',
|
|
43
|
+
payloadShape: '{ decisionType, workItemId, runId, workflowRevision, inputsConsidered, outcome, timestamp }',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: CORRELATION_PRIMARY_CONFLICT_EVENT,
|
|
47
|
+
description: 'Warns that a requested primary resource correlation was downgraded because another work item already owns the resource.',
|
|
48
|
+
payloadShape: '{ resourceUri, incumbentWorkItemKey }',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: CORRELATION_REGISTERED_EVENT,
|
|
52
|
+
description: 'Registers a resource URI as correlated to a work item.',
|
|
53
|
+
payloadShape: '{ resourceUri, role, relation, provenance, registeredBy? }',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: CORRELATION_RETRACTED_EVENT,
|
|
57
|
+
description: 'Removes a resource URI correlation from a work item.',
|
|
58
|
+
payloadShape: '{ resourceUri }',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: LABELS_REQUESTED_EVENT,
|
|
62
|
+
description: 'Requests outbound synchronization of Wake status, stage, and workflow labels.',
|
|
63
|
+
payloadShape: "{ statusLabel, stageLabel, workflowLabel, origin, idempotencyKey, deliveryState: 'PENDING' }",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: PR_AUTO_MERGE_ENABLED_EVENT,
|
|
67
|
+
description: 'Records that Wake enabled auto-merge for an approved pull request.',
|
|
68
|
+
payloadShape: '{ idempotencyKey }',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: PR_REVIEW_APPROVED_EVENT,
|
|
72
|
+
description: 'Records that Wake approved a pull request review after approval policy passed.',
|
|
73
|
+
payloadShape: '{ idempotencyKey }',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: PUBLISH_CONFIRMED_EVENT,
|
|
77
|
+
description: 'Confirms that an outbound publish intent was delivered or intentionally suppressed.',
|
|
78
|
+
payloadShape: "{ intentEventId, idempotencyKey, deliveryState: 'CONFIRMED', ...sinkDetails }",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: PUBLISH_FAILED_EVENT,
|
|
82
|
+
description: 'Records a failed outbound publish attempt for bounded retry.',
|
|
83
|
+
payloadShape: "{ intentEventId, intentEventType, idempotencyKey, deliveryState: 'PENDING', error }",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: PUBLISH_INTENT_REQUESTED_EVENT,
|
|
87
|
+
description: 'Requests an outbound comment, reply, status update, question, or approval card.',
|
|
88
|
+
payloadShape: "{ kind, origin, body, action, sentinel, runId, idempotencyKey, deliveryState: 'PENDING', sessionId?, model?, cli?, duration?, tokens?, cost?, workspacePath?, failureRepeated?, previousFailureClass? }",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: PUBLISH_SENT_UNCONFIRMED_EVENT,
|
|
92
|
+
description: 'Marks an outbound publish intent as sent before Wake has observed confirmation.',
|
|
93
|
+
payloadShape: "{ intentEventId, intentEventType, idempotencyKey, deliveryState: 'SENT_UNCONFIRMED' }",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: RETRY_REQUESTED_EVENT,
|
|
97
|
+
description: 'Requests that a failed work item be retried.',
|
|
98
|
+
payloadShape: '{ requestedBy }',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: RUN_CLAIMED_EVENT,
|
|
102
|
+
description: 'Records that Wake claimed a work item for an agent run.',
|
|
103
|
+
payloadShape: '{ action, priorStage, claimedStage?, sourceRevision, watcherRun?, watcherTrigger? }',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: RUN_COMPLETED_EVENT,
|
|
107
|
+
description: 'Records the terminal result of an agent run or internal lifecycle transition.',
|
|
108
|
+
payloadShape: '{ action?, sentinel, nextStage?, runId, sessionId?, sessionCli?, workspacePath?, reason?, handledCommentId?, failureClass?, blockReason?, executionOutcome?, workflowOutcome?, watcherRun?, allowAutoApproval?, body? }',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: WORKFLOW_SELECTED_EVENT,
|
|
112
|
+
description: 'Pins the workflow selected for a newly minted work item.',
|
|
113
|
+
payloadShape: '{ workflow, selectedFromEventId }',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: WORK_ITEM_CREATED_EVENT,
|
|
117
|
+
description: 'Mints a Wake work item identity before resource correlation is registered.',
|
|
118
|
+
payloadShape: '{}',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: WORKSPACE_CLEANED_EVENT,
|
|
122
|
+
description: 'Records successful cleanup of a closed issue workspace.',
|
|
123
|
+
payloadShape: '{ workspacePath }',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: WORKSPACE_CLEANUP_FAILED_EVENT,
|
|
127
|
+
description: 'Records a workspace cleanup failure without aborting the cleanup sweep.',
|
|
128
|
+
payloadShape: '{ workspacePath, error }',
|
|
129
|
+
},
|
|
130
|
+
];
|
|
@@ -7,6 +7,7 @@ import { runnerSentinelValues } from './stages.js';
|
|
|
7
7
|
import { correlationProvenanceSchema, correlationRelationSchema, correlationRoleSchema, resourceUriSchema, } from './resource-uri.js';
|
|
8
8
|
import { runtimeEventTypeValues } from './runtime-events.js';
|
|
9
9
|
import { alwaysManualIgnoredLabels } from './manual-labels.js';
|
|
10
|
+
export { AUTONOMOUS_DECISION_AUDIT_EVENT, CORRELATION_PRIMARY_CONFLICT_EVENT, CORRELATION_REGISTERED_EVENT, CORRELATION_RETRACTED_EVENT, WORK_ITEM_CREATED_EVENT, } from './event-types.js';
|
|
10
11
|
const isoTimestampSchema = z.string().datetime({ offset: true });
|
|
11
12
|
const identifierSchema = z.string().min(1);
|
|
12
13
|
export const reportedArtifactSchema = z.object({
|
|
@@ -225,12 +226,6 @@ export const eventEnvelopeSourceRefsSchema = z.object({
|
|
|
225
226
|
// own locator grammar; this keeps that knowledge out of core/.
|
|
226
227
|
parentResourceUri: resourceUriSchema.optional(),
|
|
227
228
|
});
|
|
228
|
-
/** Event type constants for the correlation registry (ADR 0001 §5). */
|
|
229
|
-
export const WORK_ITEM_CREATED_EVENT = 'wake.workitem.created';
|
|
230
|
-
export const CORRELATION_REGISTERED_EVENT = 'wake.correlation.registered';
|
|
231
|
-
export const CORRELATION_RETRACTED_EVENT = 'wake.correlation.retracted';
|
|
232
|
-
export const CORRELATION_PRIMARY_CONFLICT_EVENT = 'wake.correlation.primary-conflict';
|
|
233
|
-
export const AUTONOMOUS_DECISION_AUDIT_EVENT = 'wake.audit.autonomous-decision';
|
|
234
229
|
/**
|
|
235
230
|
* Shared key for events whose resource failed mint qualification (spec D1').
|
|
236
231
|
* Never a real work item: `readIssueState(UNRESOLVED_WORK_ITEM_KEY)` always
|
|
@@ -331,6 +326,22 @@ const runLeaseSchema = z.object({
|
|
|
331
326
|
lastRenewedAt: isoTimestampSchema,
|
|
332
327
|
expiresAt: isoTimestampSchema,
|
|
333
328
|
});
|
|
329
|
+
export const failurePhaseSchema = z.enum([
|
|
330
|
+
'workspace-prep',
|
|
331
|
+
'process-starting',
|
|
332
|
+
'running',
|
|
333
|
+
'result-parsing',
|
|
334
|
+
'publishing',
|
|
335
|
+
'unknown',
|
|
336
|
+
]);
|
|
337
|
+
export const externalSideEffectsSchema = z.enum(['none', 'confirmed', 'unknown']);
|
|
338
|
+
export const retrySafetySchema = z.enum([
|
|
339
|
+
'SAFE_TO_RETRY',
|
|
340
|
+
'SAFE_TO_RESUME',
|
|
341
|
+
'REQUIRES_RECONCILIATION',
|
|
342
|
+
'MANUAL_REVIEW_REQUIRED',
|
|
343
|
+
'NOT_RETRYABLE',
|
|
344
|
+
]);
|
|
334
345
|
function legacyRunLifecycle(input) {
|
|
335
346
|
if (input.lifecycle !== undefined) {
|
|
336
347
|
return input;
|
|
@@ -374,6 +385,11 @@ export const runRecordSchema = z.preprocess((input) => {
|
|
|
374
385
|
sentinel: runnerSentinelSchema.optional(),
|
|
375
386
|
executionOutcome: executionOutcomeSchema.optional(),
|
|
376
387
|
workflowOutcome: workflowOutcomeSchema.optional(),
|
|
388
|
+
failurePhase: failurePhaseSchema.optional(),
|
|
389
|
+
processStarted: z.boolean().optional(),
|
|
390
|
+
workspaceChanged: z.boolean().optional(),
|
|
391
|
+
externalSideEffects: externalSideEffectsSchema.optional(),
|
|
392
|
+
retrySafety: retrySafetySchema.optional(),
|
|
377
393
|
summary: z.string().optional(),
|
|
378
394
|
routing: runnerRoutingSchema.optional(),
|
|
379
395
|
lease: runLeaseSchema.optional(),
|
package/dist/src/version.js
CHANGED