@atolis-hq/wake 0.2.38 → 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.
@@ -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);
@@ -1001,6 +1011,7 @@ export function createTickRunner(deps) {
1001
1011
  payload: {
1002
1012
  action,
1003
1013
  sentinel,
1014
+ allowAutoApproval: runnerResult.metadata?.allowAutoApproval === true,
1004
1015
  ...(rawSentinel !== sentinel ? { rawSentinel } : {}),
1005
1016
  ...(nextStage !== null ? { nextStage } : {}),
1006
1017
  runId,
@@ -124,4 +124,4 @@ export function resolveWakeVersion(options = {}) {
124
124
  }
125
125
  return '0.1.0-dev';
126
126
  }
127
- export const wakeVersion = "gec357a0";
127
+ export const wakeVersion = "g4a872ec";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.2.38",
3
+ "version": "0.2.39",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/prompts/refine.md CHANGED
@@ -5,6 +5,7 @@ allowedTools: Read, Glob, Grep, Bash(git fetch), Bash(git status), Bash(git diff
5
5
  extraArgs:
6
6
  maxTurns: 40
7
7
  skipApproval: false
8
+ allowAutoApproval: true
8
9
  ---
9
10
 
10
11
  {{#if isStart}}