@atolis-hq/wake 0.2.31 → 0.2.32
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.
|
@@ -66,6 +66,14 @@ export function createGitHubClient(token) {
|
|
|
66
66
|
}),
|
|
67
67
|
});
|
|
68
68
|
},
|
|
69
|
+
async getIssue(owner, repo, issueNumber) {
|
|
70
|
+
const { data } = await octokit.rest.issues.get({
|
|
71
|
+
owner,
|
|
72
|
+
repo,
|
|
73
|
+
issue_number: issueNumber,
|
|
74
|
+
});
|
|
75
|
+
return data;
|
|
76
|
+
},
|
|
69
77
|
async createComment(owner, repo, issueNumber, body) {
|
|
70
78
|
return octokit.rest.issues.createComment({
|
|
71
79
|
owner,
|
|
@@ -185,6 +185,9 @@ export async function readControlPlaneUiUrl(wakeRoot) {
|
|
|
185
185
|
return undefined;
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
+
function isGitHubNotFound(error) {
|
|
189
|
+
return error instanceof Error && error.status === 404;
|
|
190
|
+
}
|
|
188
191
|
export function formatGitHubError(error) {
|
|
189
192
|
if (error instanceof Error) {
|
|
190
193
|
const octokit = error;
|
|
@@ -301,6 +304,71 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
301
304
|
return deps.stateStore.readIssueState(workItemKey);
|
|
302
305
|
}
|
|
303
306
|
return {
|
|
307
|
+
async refreshForDispatch(input) {
|
|
308
|
+
const repoRef = input.projection.issue.repo;
|
|
309
|
+
if (deps.client.getIssue === undefined) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
if (!deps.config.sources.github.repos.includes(repoRef)) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
const [owner, repo] = repoRef.split('/');
|
|
316
|
+
if (owner === undefined || repo === undefined) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const ingestedAt = deps.now().toISOString();
|
|
320
|
+
let issue;
|
|
321
|
+
try {
|
|
322
|
+
issue = await deps.client.getIssue(owner, repo, input.projection.issue.number);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (isGitHubNotFound(error)) {
|
|
326
|
+
return {
|
|
327
|
+
events: [],
|
|
328
|
+
sourceRevision: `github:issue:${repoRef}#${input.projection.issue.number}@missing`,
|
|
329
|
+
sourceExists: false,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
const events = [];
|
|
335
|
+
if (input.projection.issue.updatedAt !== issue.updated_at) {
|
|
336
|
+
events.push(normalizeTicketUpsert({
|
|
337
|
+
repo: repoRef,
|
|
338
|
+
issue,
|
|
339
|
+
ingestedAt,
|
|
340
|
+
expectedEcho: isExpectedLabelEcho(issue, input.projection),
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
const comments = await deps.client.listComments(owner, repo, input.projection.issue.number, deps.config.sources.github.polling.commentPageSize);
|
|
344
|
+
let latestCommentRevision = '';
|
|
345
|
+
for (const comment of comments) {
|
|
346
|
+
if (comment.updated_at > latestCommentRevision) {
|
|
347
|
+
latestCommentRevision = comment.updated_at;
|
|
348
|
+
}
|
|
349
|
+
const known = input.projection.comments.find((entry) => entry.id === String(comment.id));
|
|
350
|
+
if (known?.updatedAt === comment.updated_at) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (input.projection.wake.expectedEcho.commentIds.includes(String(comment.id))) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
events.push(normalizeTicketCommentEvent({
|
|
357
|
+
repo: repoRef,
|
|
358
|
+
issueNumber: input.projection.issue.number,
|
|
359
|
+
comment,
|
|
360
|
+
ingestedAt,
|
|
361
|
+
...(deps.selfLogin === undefined ? {} : { selfLogin: deps.selfLogin }),
|
|
362
|
+
...(known?.updatedAt === undefined ? {} : { existingUpdatedAt: known.updatedAt }),
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
events,
|
|
367
|
+
sourceRevision: latestCommentRevision.length > 0
|
|
368
|
+
? `github:issue:${repoRef}#${issue.number}@${issue.updated_at};comments@${latestCommentRevision}`
|
|
369
|
+
: `github:issue:${repoRef}#${issue.number}@${issue.updated_at}`,
|
|
370
|
+
};
|
|
371
|
+
},
|
|
304
372
|
async pollEvents(_input) {
|
|
305
373
|
const ingestedAt = deps.now().toISOString();
|
|
306
374
|
const events = [];
|
|
@@ -4,6 +4,18 @@ export function createWorkSourceFanIn(sources) {
|
|
|
4
4
|
const batches = await Promise.all(sources.map((source) => source.pollEvents(input)));
|
|
5
5
|
return batches.flat();
|
|
6
6
|
},
|
|
7
|
+
async refreshForDispatch(input) {
|
|
8
|
+
for (const source of sources) {
|
|
9
|
+
if (source.refreshForDispatch === undefined) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const result = await source.refreshForDispatch(input);
|
|
13
|
+
if (result !== null) {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
},
|
|
7
19
|
};
|
|
8
20
|
}
|
|
9
21
|
function intentKind(event) {
|
|
@@ -21,6 +21,15 @@ function latestHumanCommentId(candidate) {
|
|
|
21
21
|
const human = candidate.comments.filter((c) => !c.isBotAuthored);
|
|
22
22
|
return human.at(-1)?.id;
|
|
23
23
|
}
|
|
24
|
+
function projectedSourceRevision(projection) {
|
|
25
|
+
const latestCommentUpdatedAt = projection.comments
|
|
26
|
+
.map((comment) => comment.updatedAt)
|
|
27
|
+
.sort()
|
|
28
|
+
.at(-1);
|
|
29
|
+
return latestCommentUpdatedAt === undefined
|
|
30
|
+
? `${projection.issue.repo}#${projection.issue.number}@${projection.issue.updatedAt}`
|
|
31
|
+
: `${projection.issue.repo}#${projection.issue.number}@${projection.issue.updatedAt};comments@${latestCommentUpdatedAt}`;
|
|
32
|
+
}
|
|
24
33
|
function isLateralReadOnlyAction(action, config) {
|
|
25
34
|
return isCustomCommandAction(action, config);
|
|
26
35
|
}
|
|
@@ -221,6 +230,18 @@ export function createTickRunner(deps) {
|
|
|
221
230
|
processStartedAt: record.workerProcessStartedAt,
|
|
222
231
|
}));
|
|
223
232
|
}
|
|
233
|
+
async function hasSchedulerCapacity(now) {
|
|
234
|
+
const runRecords = await deps.stateStore.listRunRecords();
|
|
235
|
+
for (const record of runRecords) {
|
|
236
|
+
if (record.status !== 'running') {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (await isRunningRecordActive(record, now)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
224
245
|
async function parkConfigDriftedProjections(projections) {
|
|
225
246
|
let parked = false;
|
|
226
247
|
for (const projection of projections) {
|
|
@@ -314,10 +335,31 @@ export function createTickRunner(deps) {
|
|
|
314
335
|
if (await parkConfigDriftedProjections(projections)) {
|
|
315
336
|
return { status: 'processed' };
|
|
316
337
|
}
|
|
317
|
-
|
|
338
|
+
let candidate = projections.find((issue) => policy.resolveNextEligibleAction(issue, deps.config) !== null);
|
|
318
339
|
if (candidate === undefined) {
|
|
319
340
|
return { status: 'idle' };
|
|
320
341
|
}
|
|
342
|
+
let sourceRevision = projectedSourceRevision(candidate);
|
|
343
|
+
let refresh;
|
|
344
|
+
try {
|
|
345
|
+
refresh = await deps.workSource.refreshForDispatch?.({ projection: candidate });
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return { status: 'idle' };
|
|
349
|
+
}
|
|
350
|
+
if (refresh !== undefined && refresh !== null) {
|
|
351
|
+
if (refresh.sourceExists === false) {
|
|
352
|
+
return { status: 'idle' };
|
|
353
|
+
}
|
|
354
|
+
sourceRevision = refresh.sourceRevision;
|
|
355
|
+
if (refresh.events.length > 0) {
|
|
356
|
+
await ingestInboundEvents(refresh.events);
|
|
357
|
+
candidate = (await deps.stateStore.readIssueState(candidate.workItemKey)) ?? candidate;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (policy.resolveNextEligibleAction(candidate, deps.config) === null) {
|
|
361
|
+
return { status: 'idle' };
|
|
362
|
+
}
|
|
321
363
|
const workflow = workflowForProjection(candidate, deps.config);
|
|
322
364
|
if (workflow === null) {
|
|
323
365
|
return { status: 'idle' };
|
|
@@ -444,6 +486,9 @@ export function createTickRunner(deps) {
|
|
|
444
486
|
if (routing === null) {
|
|
445
487
|
return { status: 'idle' };
|
|
446
488
|
}
|
|
489
|
+
if (!(await hasSchedulerCapacity(deps.clock.now()))) {
|
|
490
|
+
return { status: 'idle' };
|
|
491
|
+
}
|
|
447
492
|
const runId = `run-${candidate.issue.number}-${deps.clock.now().getTime()}`;
|
|
448
493
|
const lease = createRunLease({ clock: deps.clock, ownerInstanceId });
|
|
449
494
|
const workerIdentity = currentProcessIdentity();
|
|
@@ -461,6 +506,9 @@ export function createTickRunner(deps) {
|
|
|
461
506
|
lease,
|
|
462
507
|
workerPid: workerIdentity.pid,
|
|
463
508
|
workerProcessStartedAt: workerIdentity.processStartedAt,
|
|
509
|
+
metadata: {
|
|
510
|
+
sourceRevision,
|
|
511
|
+
},
|
|
464
512
|
};
|
|
465
513
|
await deps.stateStore.writeRunRecord(runningRecord);
|
|
466
514
|
async function transitionRunLifecycle(lifecycle) {
|
|
@@ -489,6 +537,7 @@ export function createTickRunner(deps) {
|
|
|
489
537
|
action,
|
|
490
538
|
priorStage: candidate.wake.stage,
|
|
491
539
|
claimedStage,
|
|
540
|
+
sourceRevision,
|
|
492
541
|
},
|
|
493
542
|
});
|
|
494
543
|
await deps.stateStore.appendEventEnvelope(claimedEvent);
|
|
@@ -768,6 +817,7 @@ export function createTickRunner(deps) {
|
|
|
768
817
|
executionOutcome: 'PROCESS_FAILED',
|
|
769
818
|
summary: err instanceof Error ? err.message : String(err),
|
|
770
819
|
metadata: {
|
|
820
|
+
...failedRecord.metadata,
|
|
771
821
|
failureClass: 'infra',
|
|
772
822
|
},
|
|
773
823
|
});
|
package/dist/src/version.js
CHANGED