@atolis-hq/wake 0.2.37 → 0.2.39
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/claude/claude-runner.js +1 -0
- package/dist/src/adapters/codex/codex-runner.js +1 -0
- package/dist/src/adapters/cursor/cursor-runner.js +1 -0
- package/dist/src/adapters/github/github-issues-work-source.js +69 -0
- package/dist/src/adapters/runner/stage-prompt.js +2 -0
- package/dist/src/core/policy-engine.js +10 -0
- package/dist/src/core/projection-updater.js +7 -0
- package/dist/src/core/tick-runner.js +17 -2
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/prompts/refine.md +1 -0
|
@@ -321,6 +321,7 @@ export function createClaudeRunner(options) {
|
|
|
321
321
|
stderr: result.stderr,
|
|
322
322
|
raw: parsed,
|
|
323
323
|
skipApproval: stagePrompt.skipApproval,
|
|
324
|
+
allowAutoApproval: stagePrompt.allowAutoApproval,
|
|
324
325
|
...(promptTranscriptPath === undefined ? {} : { promptTranscriptPath }),
|
|
325
326
|
...(responseTranscriptPath === undefined ? {} : { responseTranscriptPath }),
|
|
326
327
|
...(sandboxLog?.metadata ?? {}),
|
|
@@ -359,6 +359,7 @@ export function createCodexRunner(options) {
|
|
|
359
359
|
.filter((line) => line.trim().length > 0)
|
|
360
360
|
.map((line) => JSON.parse(line)),
|
|
361
361
|
skipApproval: stagePrompt.skipApproval,
|
|
362
|
+
allowAutoApproval: stagePrompt.allowAutoApproval,
|
|
362
363
|
...(promptTranscriptPath === undefined ? {} : { promptTranscriptPath }),
|
|
363
364
|
...(responseTranscriptPath === undefined ? {} : { responseTranscriptPath }),
|
|
364
365
|
...(sandboxLog?.metadata ?? {}),
|
|
@@ -324,6 +324,7 @@ export function createCursorRunner(options) {
|
|
|
324
324
|
stderr: result.stderr,
|
|
325
325
|
raw: parsed,
|
|
326
326
|
skipApproval: stagePrompt.skipApproval,
|
|
327
|
+
allowAutoApproval: stagePrompt.allowAutoApproval,
|
|
327
328
|
...(promptTranscriptPath === undefined ? {} : { promptTranscriptPath }),
|
|
328
329
|
...(responseTranscriptPath === undefined ? {} : { responseTranscriptPath }),
|
|
329
330
|
...(sandboxLog?.metadata ?? {}),
|
|
@@ -18,6 +18,8 @@ const pollOverlapMs = 60 * 60 * 1000;
|
|
|
18
18
|
// expectedEcho bookkeeping surviving a crash (#145).
|
|
19
19
|
const wakeCommentMarker = '<!-- wake:agent -->';
|
|
20
20
|
const githubSource = 'github';
|
|
21
|
+
const autoApprovalLabel = 'wake:auto';
|
|
22
|
+
const autoApprovalCommands = new Set(['yolo', 'autoapprove']);
|
|
21
23
|
export function wakeIdempotencyMarker(idempotencyKey) {
|
|
22
24
|
return typeof idempotencyKey === 'string'
|
|
23
25
|
? `<!-- wake:idempotency ${idempotencyKey} -->`
|
|
@@ -136,6 +138,31 @@ function issueAssignees(issue) {
|
|
|
136
138
|
.map((assignee) => assignee.login)
|
|
137
139
|
.filter((login) => typeof login === 'string');
|
|
138
140
|
}
|
|
141
|
+
function autoApprovalCommandName(body) {
|
|
142
|
+
for (const line of (body ?? '').split(/\r?\n/)) {
|
|
143
|
+
const match = /^\/([A-Za-z0-9_.-]+)\b/.exec(line.trim());
|
|
144
|
+
if (match?.[1] !== undefined && autoApprovalCommands.has(match[1].toLowerCase())) {
|
|
145
|
+
return match[1].toLowerCase();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function issueWithAutoApprovalLabel(issue) {
|
|
151
|
+
return {
|
|
152
|
+
...issue,
|
|
153
|
+
labels: [...issueLabels(issue), autoApprovalLabel],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function ensureAutoApprovalLabel(input) {
|
|
157
|
+
if (issueLabels(input.issue).includes(autoApprovalLabel)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
await input.setLabels(input.owner, input.repoName, input.issueNumber, [
|
|
161
|
+
...issueLabels(input.issue),
|
|
162
|
+
autoApprovalLabel,
|
|
163
|
+
]);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
139
166
|
function isExpectedLabelEcho(issue, local) {
|
|
140
167
|
if (local === null || local.wake.expectedEcho.labels.length === 0) {
|
|
141
168
|
return false;
|
|
@@ -342,6 +369,7 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
342
369
|
}
|
|
343
370
|
const comments = await deps.client.listComments(owner, repo, input.projection.issue.number, deps.config.sources.github.polling.commentPageSize);
|
|
344
371
|
let latestCommentRevision = '';
|
|
372
|
+
let autoApprovalLabelAdded = false;
|
|
345
373
|
for (const comment of comments) {
|
|
346
374
|
if (comment.updated_at > latestCommentRevision) {
|
|
347
375
|
latestCommentRevision = comment.updated_at;
|
|
@@ -353,6 +381,19 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
353
381
|
if (input.projection.wake.expectedEcho.commentIds.includes(String(comment.id))) {
|
|
354
382
|
continue;
|
|
355
383
|
}
|
|
384
|
+
if (!autoApprovalLabelAdded &&
|
|
385
|
+
autoApprovalCommandName(comment.body) !== null &&
|
|
386
|
+
comment.user?.type !== 'Bot' &&
|
|
387
|
+
!(comment.body ?? '').includes(wakeCommentMarker) &&
|
|
388
|
+
(deps.selfLogin === undefined || comment.user?.login !== deps.selfLogin)) {
|
|
389
|
+
autoApprovalLabelAdded = await ensureAutoApprovalLabel({
|
|
390
|
+
owner,
|
|
391
|
+
repoName: repo,
|
|
392
|
+
issueNumber: input.projection.issue.number,
|
|
393
|
+
issue,
|
|
394
|
+
setLabels: deps.client.setLabels,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
356
397
|
events.push(normalizeTicketCommentEvent({
|
|
357
398
|
repo: repoRef,
|
|
358
399
|
issueNumber: input.projection.issue.number,
|
|
@@ -362,6 +403,13 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
362
403
|
...(known?.updatedAt === undefined ? {} : { existingUpdatedAt: known.updatedAt }),
|
|
363
404
|
}));
|
|
364
405
|
}
|
|
406
|
+
if (autoApprovalLabelAdded) {
|
|
407
|
+
events.push(normalizeTicketUpsert({
|
|
408
|
+
repo: repoRef,
|
|
409
|
+
issue: issueWithAutoApprovalLabel(issue),
|
|
410
|
+
ingestedAt,
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
365
413
|
return {
|
|
366
414
|
events,
|
|
367
415
|
sourceRevision: latestCommentRevision.length > 0
|
|
@@ -408,6 +456,7 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
408
456
|
}));
|
|
409
457
|
}
|
|
410
458
|
const comments = await deps.client.listComments(owner, repo, issue.number, deps.config.sources.github.polling.commentPageSize);
|
|
459
|
+
let autoApprovalLabelAdded = false;
|
|
411
460
|
for (const comment of comments) {
|
|
412
461
|
const known = local?.comments.find((entry) => entry.id === String(comment.id));
|
|
413
462
|
if (known?.updatedAt === comment.updated_at) {
|
|
@@ -416,6 +465,19 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
416
465
|
if (local?.wake.expectedEcho.commentIds.includes(String(comment.id)) === true) {
|
|
417
466
|
continue;
|
|
418
467
|
}
|
|
468
|
+
if (!autoApprovalLabelAdded &&
|
|
469
|
+
autoApprovalCommandName(comment.body) !== null &&
|
|
470
|
+
comment.user?.type !== 'Bot' &&
|
|
471
|
+
!(comment.body ?? '').includes(wakeCommentMarker) &&
|
|
472
|
+
(deps.selfLogin === undefined || comment.user?.login !== deps.selfLogin)) {
|
|
473
|
+
autoApprovalLabelAdded = await ensureAutoApprovalLabel({
|
|
474
|
+
owner,
|
|
475
|
+
repoName: repo,
|
|
476
|
+
issueNumber: issue.number,
|
|
477
|
+
issue,
|
|
478
|
+
setLabels: deps.client.setLabels,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
419
481
|
events.push(normalizeTicketCommentEvent({
|
|
420
482
|
repo: repoRef,
|
|
421
483
|
issueNumber: issue.number,
|
|
@@ -425,6 +487,13 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
425
487
|
...(known?.updatedAt === undefined ? {} : { existingUpdatedAt: known.updatedAt }),
|
|
426
488
|
}));
|
|
427
489
|
}
|
|
490
|
+
if (autoApprovalLabelAdded) {
|
|
491
|
+
events.push(normalizeTicketUpsert({
|
|
492
|
+
repo: repoRef,
|
|
493
|
+
issue: issueWithAutoApprovalLabel(issue),
|
|
494
|
+
ingestedAt,
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
428
497
|
}
|
|
429
498
|
await deps.stateStore.writeSourceState({
|
|
430
499
|
schemaVersion: 1,
|
|
@@ -183,6 +183,7 @@ export async function buildStagePrompt(input) {
|
|
|
183
183
|
Object.assign(context, input.contextOverrides);
|
|
184
184
|
}
|
|
185
185
|
const skipApproval = template.frontmatter.skipApproval === 'true';
|
|
186
|
+
const allowAutoApproval = !skipApproval && template.frontmatter.allowAutoApproval === 'true';
|
|
186
187
|
const permissionMode = template.frontmatter.permissionMode;
|
|
187
188
|
const commentsToAddress = newCommentsSinceLastRun(input.projection);
|
|
188
189
|
const priorComments = previousCommentsThroughLastRun(input.projection);
|
|
@@ -237,6 +238,7 @@ export async function buildStagePrompt(input) {
|
|
|
237
238
|
value: template.frontmatter.maxTurns,
|
|
238
239
|
}),
|
|
239
240
|
skipApproval,
|
|
241
|
+
allowAutoApproval,
|
|
240
242
|
...(permissionMode === undefined ? {} : { permissionMode }),
|
|
241
243
|
};
|
|
242
244
|
}
|
|
@@ -23,6 +23,7 @@ const approvedCommandPattern = /^\/approved\b/i;
|
|
|
23
23
|
const changesCommandPattern = /^\/changes\b/i;
|
|
24
24
|
const prReviewApprovalMarker = '<!-- wake:pr-review-approved -->';
|
|
25
25
|
const prReviewChangesMarker = '<!-- wake:pr-review-changes-requested -->';
|
|
26
|
+
const autoApprovalLabel = 'wake:auto';
|
|
26
27
|
// The action Wake runs when a correlated PR gets new reviewer feedback while
|
|
27
28
|
// the work item is awaiting approval. Not configurable per workflow: it's a
|
|
28
29
|
// lateral response to a PR surface, not a workflow stage.
|
|
@@ -175,6 +176,15 @@ export function createPolicyEngine() {
|
|
|
175
176
|
if (pendingAction === undefined) {
|
|
176
177
|
return null;
|
|
177
178
|
}
|
|
179
|
+
if (context.pendingApprovalAllowAutoApproval === true &&
|
|
180
|
+
issue.issue.labels.includes(autoApprovalLabel)) {
|
|
181
|
+
return {
|
|
182
|
+
approved: true,
|
|
183
|
+
pendingAction,
|
|
184
|
+
automatic: true,
|
|
185
|
+
reason: 'Issue has wake:auto and the pending action prompt declared allowAutoApproval: true.',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
178
188
|
if (latestComment?.isBotAuthored === true &&
|
|
179
189
|
latestComment.resourceUri !== undefined &&
|
|
180
190
|
latestComment.body.includes(prReviewApprovalMarker)) {
|
|
@@ -244,6 +244,9 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
244
244
|
...(payload.sentinel === 'AWAITING_APPROVAL' && payload.action !== undefined
|
|
245
245
|
? { pendingApprovalAction: payload.action }
|
|
246
246
|
: {}),
|
|
247
|
+
...(payload.sentinel === 'AWAITING_APPROVAL'
|
|
248
|
+
? { pendingApprovalAllowAutoApproval: payload.allowAutoApproval === true }
|
|
249
|
+
: {}),
|
|
247
250
|
...(payload.executionOutcome !== undefined
|
|
248
251
|
? { lastExecutionOutcome: payload.executionOutcome }
|
|
249
252
|
: {}),
|
|
@@ -257,6 +260,10 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
257
260
|
else if (payload.sentinel !== undefined) {
|
|
258
261
|
delete nextContext.blockedFromStage;
|
|
259
262
|
}
|
|
263
|
+
if (payload.sentinel !== 'AWAITING_APPROVAL' && !isCompletedCustomCommand) {
|
|
264
|
+
delete nextContext.pendingApprovalAction;
|
|
265
|
+
delete nextContext.pendingApprovalAllowAutoApproval;
|
|
266
|
+
}
|
|
260
267
|
return parseIssueStateRecord({
|
|
261
268
|
...current,
|
|
262
269
|
context: nextContext,
|
|
@@ -622,6 +622,7 @@ export function createTickRunner(deps) {
|
|
|
622
622
|
else if (approvalResolution.approved) {
|
|
623
623
|
const approvalId = `approval-${candidate.issue.number}-${deps.clock.now().getTime()}`;
|
|
624
624
|
const approvedAt = deps.clock.now().toISOString();
|
|
625
|
+
const automaticApproval = approvalResolution.automatic === true;
|
|
625
626
|
const nextStage = lifecycle.nextStageFromSentinel(candidate.wake.stage, 'DONE', workflow);
|
|
626
627
|
if (nextStage === null) {
|
|
627
628
|
return { status: 'idle' };
|
|
@@ -646,8 +647,17 @@ export function createTickRunner(deps) {
|
|
|
646
647
|
sentinel: 'DONE',
|
|
647
648
|
nextStage,
|
|
648
649
|
runId: approvalId,
|
|
649
|
-
reason: 'human:approved',
|
|
650
|
-
handledCommentId: latestHumanCommentId(candidate),
|
|
650
|
+
reason: automaticApproval ? 'auto:approved' : 'human:approved',
|
|
651
|
+
...(automaticApproval ? {} : { handledCommentId: latestHumanCommentId(candidate) }),
|
|
652
|
+
...(automaticApproval
|
|
653
|
+
? {
|
|
654
|
+
autoResolution: {
|
|
655
|
+
kind: 'awaiting-approval',
|
|
656
|
+
classification: 'auto-approval',
|
|
657
|
+
reasoning: approvalResolution.reason,
|
|
658
|
+
},
|
|
659
|
+
}
|
|
660
|
+
: {}),
|
|
651
661
|
},
|
|
652
662
|
});
|
|
653
663
|
await deps.stateStore.appendEventEnvelope(approvalCompletedEvent);
|
|
@@ -889,6 +899,8 @@ export function createTickRunner(deps) {
|
|
|
889
899
|
const finishedAt = deps.clock.now().toISOString();
|
|
890
900
|
let prReviewTargetResourceUri = null;
|
|
891
901
|
if (watcherRun) {
|
|
902
|
+
// Artifact correlation is intentionally scoped to any watcher-dispatched
|
|
903
|
+
// run, not to an action literally named "pr-review".
|
|
892
904
|
prReviewTargetResourceUri = await resolvePrReviewTarget({
|
|
893
905
|
projection: candidate,
|
|
894
906
|
runId,
|
|
@@ -999,6 +1011,7 @@ export function createTickRunner(deps) {
|
|
|
999
1011
|
payload: {
|
|
1000
1012
|
action,
|
|
1001
1013
|
sentinel,
|
|
1014
|
+
allowAutoApproval: runnerResult.metadata?.allowAutoApproval === true,
|
|
1002
1015
|
...(rawSentinel !== sentinel ? { rawSentinel } : {}),
|
|
1003
1016
|
...(nextStage !== null ? { nextStage } : {}),
|
|
1004
1017
|
runId,
|
|
@@ -1054,6 +1067,8 @@ export function createTickRunner(deps) {
|
|
|
1054
1067
|
: {}),
|
|
1055
1068
|
});
|
|
1056
1069
|
if (watcherRun) {
|
|
1070
|
+
// Watcher-dispatched runs own PR verdict delivery and correlation
|
|
1071
|
+
// registration regardless of the target workflow/action name.
|
|
1057
1072
|
if (prReviewTargetResourceUri !== null &&
|
|
1058
1073
|
(sentinel === 'DONE' || sentinel === 'FAILED')) {
|
|
1059
1074
|
await deliverOutboundEvent({
|
package/dist/src/version.js
CHANGED
package/package.json
CHANGED