@atolis-hq/wake 0.1.4 → 0.1.5
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.
|
@@ -59,6 +59,37 @@ export function createGitHubClient(token) {
|
|
|
59
59
|
});
|
|
60
60
|
return data;
|
|
61
61
|
},
|
|
62
|
+
async getRequiredStatusChecks(owner, repo, branch) {
|
|
63
|
+
const { data } = await octokit.rest.repos.getBranch({
|
|
64
|
+
owner,
|
|
65
|
+
repo,
|
|
66
|
+
branch,
|
|
67
|
+
});
|
|
68
|
+
const requiredStatusChecks = data.protection?.required_status_checks;
|
|
69
|
+
return {
|
|
70
|
+
contexts: requiredStatusChecks?.contexts ?? [],
|
|
71
|
+
checks: (requiredStatusChecks?.checks ?? [])
|
|
72
|
+
.map((check) => check.context)
|
|
73
|
+
.filter((context) => typeof context === 'string'),
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
async listCheckRunsForRef(owner, repo, ref) {
|
|
77
|
+
const { data } = await octokit.rest.checks.listForRef({
|
|
78
|
+
owner,
|
|
79
|
+
repo,
|
|
80
|
+
ref,
|
|
81
|
+
per_page: 100,
|
|
82
|
+
});
|
|
83
|
+
return data.check_runs;
|
|
84
|
+
},
|
|
85
|
+
async getCombinedStatusForRef(owner, repo, ref) {
|
|
86
|
+
const { data } = await octokit.rest.repos.getCombinedStatusForRef({
|
|
87
|
+
owner,
|
|
88
|
+
repo,
|
|
89
|
+
ref,
|
|
90
|
+
});
|
|
91
|
+
return data.statuses;
|
|
92
|
+
},
|
|
62
93
|
async listPullRequests(owner, repo, maxResults) {
|
|
63
94
|
const perPage = Math.min(maxResults, 100);
|
|
64
95
|
const results = [];
|
|
@@ -22,6 +22,19 @@ function reviewThreadRootId(comment, byId) {
|
|
|
22
22
|
function reviewThreadResourceUri(repo, prNumber, rootId) {
|
|
23
23
|
return buildResourceUri('github', 'pr-review-thread', `${repo}#${prNumber}/rt_${rootId}`);
|
|
24
24
|
}
|
|
25
|
+
function safeEventToken(value) {
|
|
26
|
+
return value.replace(/[^a-z0-9]+/gi, '-');
|
|
27
|
+
}
|
|
28
|
+
function isFailingCheckRun(run) {
|
|
29
|
+
return (run.status === 'completed' &&
|
|
30
|
+
(run.conclusion === 'failure' ||
|
|
31
|
+
run.conclusion === 'timed_out' ||
|
|
32
|
+
run.conclusion === 'cancelled' ||
|
|
33
|
+
run.conclusion === 'action_required'));
|
|
34
|
+
}
|
|
35
|
+
function isFailingStatus(status) {
|
|
36
|
+
return status.state === 'failure' || status.state === 'error';
|
|
37
|
+
}
|
|
25
38
|
// selfLogin is the only reliable signal for an agent-authored comment posted
|
|
26
39
|
// by direct API/CLI call (e.g. a `revise` run replying via `gh api
|
|
27
40
|
// .../replies`), which never carries the marker and whose account `type` is
|
|
@@ -206,13 +219,118 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
206
219
|
}
|
|
207
220
|
return events;
|
|
208
221
|
}
|
|
222
|
+
async function pollRequiredCheckFailures(ref, resourceUri, ingestedAt) {
|
|
223
|
+
if (!deps.config.sources.github.pullRequests.enabled ||
|
|
224
|
+
!deps.config.sources.github.pullRequests.checks.enabled ||
|
|
225
|
+
deps.client.getRequiredStatusChecks === undefined ||
|
|
226
|
+
deps.client.listCheckRunsForRef === undefined ||
|
|
227
|
+
deps.client.getCombinedStatusForRef === undefined) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
const pr = await deps.client.getPullRequest(ref.owner, ref.repo, ref.number);
|
|
231
|
+
const headSha = pr.head.sha;
|
|
232
|
+
const baseRef = pr.base?.ref;
|
|
233
|
+
if (headSha === undefined || baseRef === undefined) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
const required = await deps.client.getRequiredStatusChecks(ref.owner, ref.repo, baseRef);
|
|
237
|
+
const requiredContexts = new Set([...required.contexts, ...required.checks]);
|
|
238
|
+
if (requiredContexts.size === 0) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const [checkRuns, statuses] = await Promise.all([
|
|
242
|
+
deps.client.listCheckRunsForRef(ref.owner, ref.repo, headSha),
|
|
243
|
+
deps.client.getCombinedStatusForRef(ref.owner, ref.repo, headSha),
|
|
244
|
+
]);
|
|
245
|
+
const events = [];
|
|
246
|
+
for (const run of checkRuns) {
|
|
247
|
+
if (!requiredContexts.has(run.name) || !isFailingCheckRun(run)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const occurredAt = run.completed_at ?? run.started_at ?? ingestedAt;
|
|
251
|
+
const sourceUrl = run.html_url ?? run.details_url ?? pr.html_url;
|
|
252
|
+
events.push(createUnkeyedEventEnvelope({
|
|
253
|
+
eventId: `pr-check-failed-${safeEventToken(ref.repoRef)}-${ref.number}-${safeEventToken(run.name)}-${headSha}-${safeEventToken(occurredAt)}-${run.conclusion}`,
|
|
254
|
+
streamScope: 'work-item',
|
|
255
|
+
direction: 'inbound',
|
|
256
|
+
sourceSystem: githubPrSource,
|
|
257
|
+
sourceEventType: 'pr.checks.failed',
|
|
258
|
+
sourceRefs: { repo: ref.repoRef, sourceUrl, resourceUri },
|
|
259
|
+
occurredAt,
|
|
260
|
+
ingestedAt,
|
|
261
|
+
trigger: 'context-only',
|
|
262
|
+
payload: {
|
|
263
|
+
comment: {
|
|
264
|
+
id: `pr-check-failed-${headSha}-${run.id}-${run.conclusion}`,
|
|
265
|
+
body: `Required check failed: ${run.name} (${run.conclusion}).`,
|
|
266
|
+
author: { login: 'github-checks' },
|
|
267
|
+
createdAt: occurredAt,
|
|
268
|
+
updatedAt: occurredAt,
|
|
269
|
+
resourceUri,
|
|
270
|
+
},
|
|
271
|
+
checkFailure: {
|
|
272
|
+
kind: 'check_run',
|
|
273
|
+
name: run.name,
|
|
274
|
+
conclusion: run.conclusion,
|
|
275
|
+
headSha,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
for (const status of statuses) {
|
|
281
|
+
if (!requiredContexts.has(status.context) || !isFailingStatus(status)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
events.push(createUnkeyedEventEnvelope({
|
|
285
|
+
eventId: `pr-status-failed-${safeEventToken(ref.repoRef)}-${ref.number}-${safeEventToken(status.context)}-${headSha}-${safeEventToken(status.updated_at)}-${status.state}`,
|
|
286
|
+
streamScope: 'work-item',
|
|
287
|
+
direction: 'inbound',
|
|
288
|
+
sourceSystem: githubPrSource,
|
|
289
|
+
sourceEventType: 'pr.checks.failed',
|
|
290
|
+
sourceRefs: { repo: ref.repoRef, sourceUrl: status.target_url ?? pr.html_url, resourceUri },
|
|
291
|
+
occurredAt: status.updated_at,
|
|
292
|
+
ingestedAt,
|
|
293
|
+
trigger: 'context-only',
|
|
294
|
+
payload: {
|
|
295
|
+
comment: {
|
|
296
|
+
id: `pr-status-failed-${headSha}-${status.context}-${status.state}`,
|
|
297
|
+
body: `Required status failed: ${status.context} (${status.state})${status.description === undefined || status.description === null ? '.' : `: ${status.description}`}`,
|
|
298
|
+
author: { login: 'github-status' },
|
|
299
|
+
createdAt: status.created_at,
|
|
300
|
+
updatedAt: status.updated_at,
|
|
301
|
+
resourceUri,
|
|
302
|
+
},
|
|
303
|
+
checkFailure: {
|
|
304
|
+
kind: 'status',
|
|
305
|
+
name: status.context,
|
|
306
|
+
conclusion: status.state,
|
|
307
|
+
headSha,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
return events;
|
|
313
|
+
}
|
|
209
314
|
return {
|
|
210
315
|
async pollEvents(input) {
|
|
211
316
|
const ingestedAt = deps.now().toISOString();
|
|
212
317
|
const watched = (input?.watch ?? []).filter((ref) => ref.resourceUri.startsWith('github:pr:'));
|
|
213
318
|
const discovered = await discoverPullRequests(ingestedAt);
|
|
214
319
|
const activityBatches = await Promise.all(watched.map((ref) => pollWatchedPr(ref.resourceUri, ingestedAt)));
|
|
215
|
-
|
|
320
|
+
const checkBatches = await Promise.all(watched.map(async (watchRef) => {
|
|
321
|
+
const ref = repoAndNumberFromPrUri(watchRef.resourceUri);
|
|
322
|
+
if (ref === null) {
|
|
323
|
+
return [];
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
return await pollRequiredCheckFailures(ref, watchRef.resourceUri, ingestedAt);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
console.error(`[github-pr-activity-source] check poll failed for ${watchRef.resourceUri}, skipping this tick: ${error instanceof Error ? error.message : String(error)}`);
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
}));
|
|
333
|
+
return [...discovered, ...activityBatches.flat(), ...checkBatches.flat()];
|
|
216
334
|
},
|
|
217
335
|
async deliverIntent(input) {
|
|
218
336
|
const resourceUri = input.event.sourceRefs.resourceUri;
|
|
@@ -91,7 +91,8 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
91
91
|
event.sourceEventType === 'ticket.comment.updated' ||
|
|
92
92
|
event.sourceEventType === 'pr.comment.created' ||
|
|
93
93
|
event.sourceEventType === 'pr.review.created' ||
|
|
94
|
-
event.sourceEventType === 'pr.review-comment.created'
|
|
94
|
+
event.sourceEventType === 'pr.review-comment.created' ||
|
|
95
|
+
event.sourceEventType === 'pr.checks.failed') {
|
|
95
96
|
const comment = event.payload.comment;
|
|
96
97
|
if (comment === undefined || typeof comment !== 'object' || comment === null) {
|
|
97
98
|
return current;
|
|
@@ -452,12 +452,15 @@ export const wakeConfigSchema = z.object({
|
|
|
452
452
|
enabled: z.boolean().default(false),
|
|
453
453
|
maxPullRequestsPerRepo: z.number().int().positive().default(25),
|
|
454
454
|
commentPageSize: z.number().int().positive().default(25),
|
|
455
|
+
checks: z.object({
|
|
456
|
+
enabled: z.boolean().default(true),
|
|
457
|
+
}).default({ enabled: true }),
|
|
455
458
|
policy: z.object({
|
|
456
459
|
requiredAuthors: z.array(z.string()).default([]),
|
|
457
460
|
}).default({ requiredAuthors: [] }),
|
|
458
|
-
}).default({ enabled: false, maxPullRequestsPerRepo: 25, commentPageSize: 25, policy: { requiredAuthors: [] } }),
|
|
459
|
-
}).default({ enabled: false, repos: [], polling: { maxIssuesPerRepo: 25, commentPageSize: 25, lookbackMs: 60_000 }, policy: { requiredLabels: [], ignoredLabels: [], requiredAssignees: [] }, publication: { postStatusComments: true }, pullRequests: { enabled: false, maxPullRequestsPerRepo: 25, commentPageSize: 25, policy: { requiredAuthors: [] } } }),
|
|
460
|
-
}).default({ github: { enabled: false, repos: [], polling: { maxIssuesPerRepo: 25, commentPageSize: 25, lookbackMs: 60_000 }, policy: { requiredLabels: [], ignoredLabels: [], requiredAssignees: [] }, publication: { postStatusComments: true }, pullRequests: { enabled: false, maxPullRequestsPerRepo: 25, commentPageSize: 25, policy: { requiredAuthors: [] } } } }),
|
|
461
|
+
}).default({ enabled: false, maxPullRequestsPerRepo: 25, commentPageSize: 25, checks: { enabled: true }, policy: { requiredAuthors: [] } }),
|
|
462
|
+
}).default({ enabled: false, repos: [], polling: { maxIssuesPerRepo: 25, commentPageSize: 25, lookbackMs: 60_000 }, policy: { requiredLabels: [], ignoredLabels: [], requiredAssignees: [] }, publication: { postStatusComments: true }, pullRequests: { enabled: false, maxPullRequestsPerRepo: 25, commentPageSize: 25, checks: { enabled: true }, policy: { requiredAuthors: [] } } }),
|
|
463
|
+
}).default({ github: { enabled: false, repos: [], polling: { maxIssuesPerRepo: 25, commentPageSize: 25, lookbackMs: 60_000 }, policy: { requiredLabels: [], ignoredLabels: [], requiredAssignees: [] }, publication: { postStatusComments: true }, pullRequests: { enabled: false, maxPullRequestsPerRepo: 25, commentPageSize: 25, checks: { enabled: true }, policy: { requiredAuthors: [] } } } }),
|
|
461
464
|
sinks: z.record(z.string(), sinkEntrySchema).default({}),
|
|
462
465
|
}).superRefine((config, ctx) => {
|
|
463
466
|
const promptsRoot = config.paths.promptsRoot ?? defaultPromptsRoot();
|
package/dist/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const wakeVersion = "0.1.
|
|
1
|
+
export const wakeVersion = "0.1.5+g2910a1f";
|