@atolis-hq/wake 0.2.91 → 0.2.93
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.
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { buildResourceUri } from '../../domain/resource-uri.js';
|
|
4
4
|
import { isTerminalStage } from '../../domain/stages.js';
|
|
5
5
|
import { isWorkItemDeleted, isWorkItemFrozen } from '../../domain/work-item-lifecycle.js';
|
|
6
|
-
import { workflowForProjection, workflowNameForProjection } from '../../domain/workflows.js';
|
|
6
|
+
import { universalQueueStage, workflowForProjection, workflowNameForProjection, } from '../../domain/workflows.js';
|
|
7
7
|
import { readFileLockStatus } from '../../lib/lock.js';
|
|
8
8
|
async function readLockInfo(lockFile, now, processInspector) {
|
|
9
9
|
const status = await readFileLockStatus(lockFile, {
|
|
@@ -45,7 +45,7 @@ function deriveCondition(item, lastRun, config) {
|
|
|
45
45
|
return { condition: 'needs-human', reason: `sentinel ${lastRun?.sentinel ?? stage}` };
|
|
46
46
|
}
|
|
47
47
|
const workflow = workflowForProjection(item, config);
|
|
48
|
-
const hasRoute =
|
|
48
|
+
const hasRoute = stage === universalQueueStage ? workflow !== null : workflow?.stages[stage] !== undefined;
|
|
49
49
|
if (!hasRoute) {
|
|
50
50
|
return { condition: 'error', reason: `no route configured for stage "${stage}"` };
|
|
51
51
|
}
|
|
@@ -982,30 +982,33 @@ function redact(value, keyHint = '') {
|
|
|
982
982
|
export async function buildConfigView(input) {
|
|
983
983
|
const ledger = await input.stateStore.readLedger();
|
|
984
984
|
const runnerHealth = ledger?.runners ?? {};
|
|
985
|
-
const routingTable = Object.entries(input.config.
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
985
|
+
const routingTable = Object.entries(input.config.workflows).flatMap(([workflow, definition]) => {
|
|
986
|
+
return Object.entries(definition.stages).map(([stage, route]) => {
|
|
987
|
+
const tier = route.tier ?? input.config.defaultTier;
|
|
988
|
+
const candidates = input.config.tiers[tier] ?? [];
|
|
989
|
+
const runnerName = route.runner ?? candidates[0];
|
|
990
|
+
const runner = runnerName !== undefined ? input.config.runners[runnerName] : undefined;
|
|
991
|
+
// Full fallback order for the tier (#67), each candidate's current pause
|
|
992
|
+
// state so the UI can show not just who's active but who Wake would fall
|
|
993
|
+
// sideways to next, and rotate back to once a pause expires.
|
|
994
|
+
const candidateHealth = candidates.map((name) => {
|
|
995
|
+
const health = runnerHealth[name];
|
|
996
|
+
const pausedUntil = health?.pausedUntil;
|
|
997
|
+
const paused = pausedUntil !== undefined && Date.parse(pausedUntil) > input.now.getTime();
|
|
998
|
+
return { runnerName: name, paused, pausedUntil };
|
|
999
|
+
});
|
|
1000
|
+
return {
|
|
1001
|
+
workflow,
|
|
1002
|
+
stage,
|
|
1003
|
+
action: route.action,
|
|
1004
|
+
tier,
|
|
1005
|
+
runnerName,
|
|
1006
|
+
runnerKind: runner?.kind,
|
|
1007
|
+
model: runner !== undefined && runner.kind !== 'fake' ? runner.model : undefined,
|
|
1008
|
+
timeoutMs: runner !== undefined && runner.kind !== 'fake' ? runner.timeoutMs : undefined,
|
|
1009
|
+
candidates: candidateHealth,
|
|
1010
|
+
};
|
|
998
1011
|
});
|
|
999
|
-
return {
|
|
1000
|
-
stage,
|
|
1001
|
-
action: route.action,
|
|
1002
|
-
tier,
|
|
1003
|
-
runnerName,
|
|
1004
|
-
runnerKind: runner?.kind,
|
|
1005
|
-
model: runner !== undefined && runner.kind !== 'fake' ? runner.model : undefined,
|
|
1006
|
-
timeoutMs: runner !== undefined && runner.kind !== 'fake' ? runner.timeoutMs : undefined,
|
|
1007
|
-
candidates: candidateHealth,
|
|
1008
|
-
};
|
|
1009
1012
|
});
|
|
1010
1013
|
return { config: redact(input.config), routingTable };
|
|
1011
1014
|
}
|
|
@@ -374,14 +374,12 @@ export function createTickRunner(deps) {
|
|
|
374
374
|
const targetResourceUri = input.approvalResolution.targetResourceUri;
|
|
375
375
|
const commentId = input.approvalResolution.triggeringCommentId.replace(/[^a-z0-9]+/gi, '-');
|
|
376
376
|
const reviewBody = reviewerMessageFromApprovalComment(input.approvalResolution.triggeringCommentBody ?? '');
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// failing doesn't stop the other from being attempted.
|
|
384
|
-
const blockedReasons = [];
|
|
377
|
+
// approve and autoMerge are independent: one failing doesn't stop the
|
|
378
|
+
// other from being attempted. If autoMerge succeeds, a failed approval is
|
|
379
|
+
// not a policy block on its own; GitHub can reject self-approval while
|
|
380
|
+
// still accepting auto-merge for repos that do not require review.
|
|
381
|
+
const approvalBlockedReasons = [];
|
|
382
|
+
const autoMergeBlockedReasons = [];
|
|
385
383
|
const approvedEventId = `pr-merge-approved-${commentId}`;
|
|
386
384
|
if (input.mergePolicy.approve &&
|
|
387
385
|
(await deps.stateStore.readEventEnvelope(approvedEventId)) === null) {
|
|
@@ -408,10 +406,11 @@ export function createTickRunner(deps) {
|
|
|
408
406
|
}));
|
|
409
407
|
}
|
|
410
408
|
catch (error) {
|
|
411
|
-
|
|
409
|
+
approvalBlockedReasons.push(`Merge policy blocked the approval step: ${describeMergeActorError(error)}`);
|
|
412
410
|
}
|
|
413
411
|
}
|
|
414
412
|
const autoMergeEventId = `pr-auto-merge-enabled-${commentId}`;
|
|
413
|
+
let autoMergeSucceeded = false;
|
|
415
414
|
if (input.mergePolicy.autoMerge &&
|
|
416
415
|
(await deps.stateStore.readEventEnvelope(autoMergeEventId)) === null) {
|
|
417
416
|
try {
|
|
@@ -435,14 +434,52 @@ export function createTickRunner(deps) {
|
|
|
435
434
|
idempotencyKey: `${input.approvalResolution.triggeringCommentId}:pr-auto-merge`,
|
|
436
435
|
},
|
|
437
436
|
}));
|
|
437
|
+
autoMergeSucceeded = true;
|
|
438
438
|
}
|
|
439
439
|
catch (error) {
|
|
440
|
-
|
|
440
|
+
autoMergeBlockedReasons.push(`Merge policy blocked the auto-merge step: ${describeMergeActorError(error)}`);
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
|
+
else if (input.mergePolicy.autoMerge) {
|
|
444
|
+
autoMergeSucceeded = true;
|
|
445
|
+
}
|
|
446
|
+
const blockedReasons = autoMergeSucceeded && input.mergePolicy.autoMerge
|
|
447
|
+
? autoMergeBlockedReasons
|
|
448
|
+
: [...approvalBlockedReasons, ...autoMergeBlockedReasons];
|
|
443
449
|
if (blockedReasons.length > 0) {
|
|
444
450
|
return { blocked: true, reason: blockedReasons.join(' ') };
|
|
445
451
|
}
|
|
452
|
+
// Approval failed but auto-merge covered it (e.g. GitHub rejecting
|
|
453
|
+
// self-approval on the bot's own PR) - not a policy block, but the
|
|
454
|
+
// failure should still leave a visible trail instead of vanishing
|
|
455
|
+
// silently, since it may also be a real permissions/config problem.
|
|
456
|
+
if (approvalBlockedReasons.length > 0) {
|
|
457
|
+
const occurredAt = eventStampNow();
|
|
458
|
+
await deliverOutboundEvent(createEventEnvelope({
|
|
459
|
+
eventId: `pr-merge-approval-note-${commentId}`,
|
|
460
|
+
workItemKey: input.projection.workItemKey,
|
|
461
|
+
streamScope: 'work-item',
|
|
462
|
+
direction: 'outbound',
|
|
463
|
+
sourceSystem: 'wake',
|
|
464
|
+
sourceEventType: PUBLISH_INTENT_REQUESTED_EVENT,
|
|
465
|
+
sourceRefs: {
|
|
466
|
+
repo: input.projection.issue.repo,
|
|
467
|
+
issueNumber: input.projection.issue.number,
|
|
468
|
+
resourceUri: targetResourceUri,
|
|
469
|
+
},
|
|
470
|
+
occurredAt,
|
|
471
|
+
ingestedAt: occurredAt,
|
|
472
|
+
trigger: 'context-only',
|
|
473
|
+
payload: {
|
|
474
|
+
kind: 'status-update',
|
|
475
|
+
origin: input.projection.origin ?? 'github',
|
|
476
|
+
body: `Approval step did not block the merge, but did not succeed on its own: ${approvalBlockedReasons.join(' ')}`,
|
|
477
|
+
idempotencyKey: `${commentId}:pr-merge-approval-note`,
|
|
478
|
+
deliveryState: 'PENDING',
|
|
479
|
+
},
|
|
480
|
+
derivedHints: { stage: input.projection.wake.stage },
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
446
483
|
return { blocked: false };
|
|
447
484
|
}
|
|
448
485
|
// The one deterministic approval transition: /approved, wake:auto, and a
|
|
@@ -2054,7 +2091,7 @@ export function createTickRunner(deps) {
|
|
|
2054
2091
|
},
|
|
2055
2092
|
payload: {
|
|
2056
2093
|
...publishIntent.payload,
|
|
2057
|
-
kind:
|
|
2094
|
+
kind: 'status-update',
|
|
2058
2095
|
body: sentinel === 'DONE'
|
|
2059
2096
|
? `${parsedRunnerResult.body}\n\n${prReviewApprovalMarker}`
|
|
2060
2097
|
: `${parsedRunnerResult.body}\n\n<!-- wake:pr-review-changes-requested -->`,
|
|
@@ -8,11 +8,6 @@ export function maxConfiguredRunnerTimeoutMs(config) {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
for (const stageRoute of Object.values(config.stages)) {
|
|
12
|
-
if (stageRoute.runner !== undefined) {
|
|
13
|
-
activeRunnerNames.add(stageRoute.runner);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
11
|
for (const commandRoute of Object.values(config.commands)) {
|
|
17
12
|
if (commandRoute.runner !== undefined) {
|
|
18
13
|
activeRunnerNames.add(commandRoute.runner);
|
|
@@ -805,10 +805,6 @@ const wakeConfigBaseSchema = z.object({
|
|
|
805
805
|
tier: 'standard',
|
|
806
806
|
},
|
|
807
807
|
}),
|
|
808
|
-
stages: z.record(z.string(), stageRouteSchema).default({
|
|
809
|
-
queue: { action: 'refine', tier: 'light' },
|
|
810
|
-
implement: { action: 'implement', tier: 'standard' },
|
|
811
|
-
}),
|
|
812
808
|
ui: z
|
|
813
809
|
.object({
|
|
814
810
|
enabled: z.boolean().default(false),
|
|
@@ -925,7 +921,6 @@ export const wakeWorkflowConfigSchema = wakeConfigBaseSchema.pick({
|
|
|
925
921
|
workflows: true,
|
|
926
922
|
workflowSelectors: true,
|
|
927
923
|
commands: true,
|
|
928
|
-
stages: true,
|
|
929
924
|
});
|
|
930
925
|
export const wakeConfigSchema = wakeConfigBaseSchema.superRefine((config, ctx) => {
|
|
931
926
|
const promptsRoot = config.paths.promptsRoot ?? defaultPromptsRoot();
|
package/dist/src/version.js
CHANGED