@atolis-hq/wake 0.1.5 → 0.1.6

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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/src/adapters/claude/claude-runner.js +9 -30
  3. package/dist/src/adapters/codex/codex-runner.js +5 -10
  4. package/dist/src/adapters/fake/fake-github-pull-request-activity-source.js +5 -1
  5. package/dist/src/adapters/fake/fake-runner.js +2 -2
  6. package/dist/src/adapters/fake/fake-ticketing-system.js +3 -8
  7. package/dist/src/adapters/fake/fake-workspace-manager.js +1 -1
  8. package/dist/src/adapters/fs/state-store.js +8 -10
  9. package/dist/src/adapters/github/github-auth.js +1 -1
  10. package/dist/src/adapters/github/github-issues-work-source.js +8 -14
  11. package/dist/src/adapters/github/github-pull-request-activity-source.js +44 -9
  12. package/dist/src/adapters/http/ui-data.js +23 -12
  13. package/dist/src/adapters/http/ui-server.js +14 -4
  14. package/dist/src/adapters/runner/runner-registry.js +2 -4
  15. package/dist/src/adapters/runner/stage-prompt.js +15 -18
  16. package/dist/src/cli/sandbox-command.js +8 -6
  17. package/dist/src/cli/startup-preflight.js +1 -1
  18. package/dist/src/core/control-plane.js +6 -2
  19. package/dist/src/core/policy-engine.js +10 -21
  20. package/dist/src/core/projection-updater.js +7 -17
  21. package/dist/src/core/quota-backoff.js +1 -1
  22. package/dist/src/core/sink-router.js +5 -7
  23. package/dist/src/core/tick-runner.js +7 -14
  24. package/dist/src/domain/runner-routing.js +1 -1
  25. package/dist/src/domain/schema.js +213 -58
  26. package/dist/src/domain/stages.js +1 -4
  27. package/dist/src/domain/workflows.js +1 -3
  28. package/dist/src/lib/lock.js +1 -1
  29. package/dist/src/main.js +5 -8
  30. package/dist/src/version.js +1 -1
  31. package/package.json +10 -2
  32. package/prompts/implement.md +4 -0
  33. package/prompts/refine.md +2 -0
  34. package/prompts/revise.md +4 -3
@@ -4,13 +4,11 @@ import { createRunnerCliAdapter } from '../adapters/runner/runner-cli-adapter.js
4
4
  import { runSandboxResumeCommand } from './sandbox-resume.js';
5
5
  import { runSelfUpdateCommand, runSelfUpdateLoop } from './self-update-command.js';
6
6
  import { runStopCommand } from './stop-command.js';
7
- import { buildSandboxLoggedCommand, } from './sandbox-logging.js';
7
+ import { buildSandboxLoggedCommand } from './sandbox-logging.js';
8
8
  async function ensureContainerHomeMountParents(input) {
9
9
  for (const mount of input.extraMounts) {
10
10
  const relativeTarget = posix.relative(input.containerHomeMountPath, mount.target);
11
- if (relativeTarget.length === 0 ||
12
- relativeTarget === '.' ||
13
- relativeTarget.startsWith('..')) {
11
+ if (relativeTarget.length === 0 || relativeTarget === '.' || relativeTarget.startsWith('..')) {
14
12
  continue;
15
13
  }
16
14
  const parentRelativeTarget = posix.dirname(relativeTarget);
@@ -118,7 +116,9 @@ export async function runSandboxCommand(input) {
118
116
  throw new Error('Sandbox self-update requires git/issueReporter/ledger dependencies');
119
117
  }
120
118
  const selfUpdateArgs = input.args.slice(1);
121
- const runSelfUpdate = selfUpdateArgs.includes('--loop') ? runSelfUpdateLoop : runSelfUpdateCommand;
119
+ const runSelfUpdate = selfUpdateArgs.includes('--loop')
120
+ ? runSelfUpdateLoop
121
+ : runSelfUpdateCommand;
122
122
  await runSelfUpdate({
123
123
  args: selfUpdateArgs,
124
124
  repoRoot,
@@ -146,7 +146,9 @@ export async function runSandboxCommand(input) {
146
146
  return;
147
147
  }
148
148
  if (subcommand === 'setup') {
149
- await input.docker.exec(input.config.sandbox.containerName, ['bash', '/wake/docker/setup.sh'], { interactive: true });
149
+ await input.docker.exec(input.config.sandbox.containerName, ['bash', '/wake/docker/setup.sh'], {
150
+ interactive: true,
151
+ });
150
152
  return;
151
153
  }
152
154
  if (subcommand === 'exec') {
@@ -52,7 +52,7 @@ async function defaultCheckRunnerCommand(runnerName, entry) {
52
52
  }
53
53
  catch (error) {
54
54
  const message = error instanceof Error ? error.message : String(error);
55
- throw new Error(`runner "${runnerName}" (${entry.kind}) command "${entry.command}" is not invocable: ${message}`);
55
+ throw new Error(`runner "${runnerName}" (${entry.kind}) command "${entry.command}" is not invocable: ${message}`, { cause: error });
56
56
  }
57
57
  }
58
58
  async function assertPromptsRootAccessible(promptsRoot) {
@@ -91,13 +91,17 @@ export function createControlPlane(deps) {
91
91
  run: deps.tickRunner.runIntakeTick,
92
92
  label: 'intake',
93
93
  getIdleTicks: () => consecutiveIntakeIdleTicks,
94
- setIdleTicks: (value) => { consecutiveIntakeIdleTicks = value; },
94
+ setIdleTicks: (value) => {
95
+ consecutiveIntakeIdleTicks = value;
96
+ },
95
97
  }),
96
98
  startLoop({
97
99
  run: deps.tickRunner.runRunnerTick,
98
100
  label: 'runner',
99
101
  getIdleTicks: () => consecutiveRunnerIdleTicks,
100
- setIdleTicks: (value) => { consecutiveRunnerIdleTicks = value; },
102
+ setIdleTicks: (value) => {
103
+ consecutiveRunnerIdleTicks = value;
104
+ },
101
105
  }),
102
106
  ]);
103
107
  return;
@@ -1,5 +1,5 @@
1
- import { awaitingApprovalRunnerSentinel, failedRunnerSentinel, } from '../domain/stages.js';
2
- import { builtInDefaultWorkflowDefinition, chooseAction as chooseWorkflowAction } from '../domain/workflows.js';
1
+ import { awaitingApprovalRunnerSentinel, failedRunnerSentinel } from '../domain/stages.js';
2
+ import { builtInDefaultWorkflowDefinition, chooseAction as chooseWorkflowAction, } from '../domain/workflows.js';
3
3
  function isAwaitingApproval(issue) {
4
4
  const context = issue.context;
5
5
  return context.lastRunSentinel === awaitingApprovalRunnerSentinel;
@@ -15,9 +15,7 @@ const questionCommandPattern = /^\/question\b/i;
15
15
  // lateral response to a PR surface, not a workflow stage.
16
16
  const reviewFeedbackAction = 'revise';
17
17
  function matchesCommand(body, pattern) {
18
- return body
19
- .split(/\r?\n/)
20
- .some((line) => pattern.test(line.trim()));
18
+ return body.split(/\r?\n/).some((line) => pattern.test(line.trim()));
21
19
  }
22
20
  function labelsAndAssigneesQualify(input) {
23
21
  if (input.requiredLabels.length === 0 && input.requiredAssignees.length === 0) {
@@ -31,16 +29,15 @@ function labelsAndAssigneesQualify(input) {
31
29
  if (input.ignoredLabels.some((label) => labels.has(label))) {
32
30
  return false;
33
31
  }
34
- if (input.requiredAssignees.length > 0 && !input.requiredAssignees.some((login) => assignees.has(login))) {
32
+ if (input.requiredAssignees.length > 0 &&
33
+ !input.requiredAssignees.some((login) => assignees.has(login))) {
35
34
  return false;
36
35
  }
37
36
  return true;
38
37
  }
39
38
  function latestUnhandledHumanComment(issue) {
40
39
  const context = issue.context;
41
- const handledCommentId = typeof context.lastHandledCommentId === 'string'
42
- ? context.lastHandledCommentId
43
- : undefined;
40
+ const handledCommentId = typeof context.lastHandledCommentId === 'string' ? context.lastHandledCommentId : undefined;
44
41
  // Only consider human comments that appear after the last bot comment.
45
42
  // A human /approved posted before Wake's approval-request comment must not
46
43
  // be re-consumed as approval for a later awaiting-approval cycle.
@@ -78,18 +75,10 @@ export function createPolicyEngine() {
78
75
  },
79
76
  needsWakeAction(issue, workflow = builtInDefaultWorkflowDefinition) {
80
77
  const context = issue.context;
81
- const handledCommentId = typeof context.lastHandledCommentId === 'string'
82
- ? context.lastHandledCommentId
83
- : undefined;
84
- const lastCompletedAction = typeof context.lastCompletedAction === 'string'
85
- ? context.lastCompletedAction
86
- : undefined;
87
- const lastRunSentinel = typeof context.lastRunSentinel === 'string'
88
- ? context.lastRunSentinel
89
- : undefined;
90
- const lastFailureClass = typeof context.lastFailureClass === 'string'
91
- ? context.lastFailureClass
92
- : undefined;
78
+ const handledCommentId = typeof context.lastHandledCommentId === 'string' ? context.lastHandledCommentId : undefined;
79
+ const lastCompletedAction = typeof context.lastCompletedAction === 'string' ? context.lastCompletedAction : undefined;
80
+ const lastRunSentinel = typeof context.lastRunSentinel === 'string' ? context.lastRunSentinel : undefined;
81
+ const lastFailureClass = typeof context.lastFailureClass === 'string' ? context.lastFailureClass : undefined;
93
82
  if (issue.wake.lastRunId === undefined) {
94
83
  return true;
95
84
  }
@@ -15,14 +15,12 @@ function configuredStagesForLabels(config) {
15
15
  return workflow === undefined ? undefined : workflowStageVocabulary(workflow);
16
16
  }
17
17
  function createProjectionFromIssueEvent(event, config) {
18
- const issue = event.sourceEventType === 'ticket.upsert'
19
- ? event.payload.ticket
20
- : event.payload.issue;
18
+ const issue = event.sourceEventType === 'ticket.upsert' ? event.payload.ticket : event.payload.issue;
21
19
  if (issue === undefined || typeof issue !== 'object' || issue === null) {
22
20
  return null;
23
21
  }
24
22
  const labels = Array.isArray(issue.labels)
25
- ? (issue.labels)
23
+ ? issue.labels
26
24
  : [];
27
25
  return parseIssueStateRecord({
28
26
  schemaVersion: 1,
@@ -50,8 +48,7 @@ async function applyEvent(current, event, ctx, config) {
50
48
  if (event.workItemKey === UNRESOLVED_WORK_ITEM_KEY) {
51
49
  return current;
52
50
  }
53
- if (event.sourceEventType === 'fake.issue.upsert' ||
54
- event.sourceEventType === 'ticket.upsert') {
51
+ if (event.sourceEventType === 'fake.issue.upsert' || event.sourceEventType === 'ticket.upsert') {
55
52
  const next = createProjectionFromIssueEvent(event, config);
56
53
  if (next === null) {
57
54
  return current;
@@ -150,8 +147,7 @@ async function applyEvent(current, event, ctx, config) {
150
147
  // Clear the session when the stage moves forward (new action needed) or the
151
148
  // run failed outright. Keep it for BLOCKED so the same action can resume
152
149
  // the in-progress session after a human replies.
153
- const isForwardProgression = payload.nextStage !== undefined &&
154
- payload.nextStage !== current.wake.stage;
150
+ const isForwardProgression = payload.nextStage !== undefined && payload.nextStage !== current.wake.stage;
155
151
  const stageChanged = payload.nextStage !== undefined && payload.nextStage !== current.wake.stage;
156
152
  const isFailed = payload.sentinel === 'FAILED';
157
153
  const shouldClearSession = isForwardProgression || isFailed;
@@ -163,12 +159,8 @@ async function applyEvent(current, event, ctx, config) {
163
159
  ...(payload.handledCommentId === undefined
164
160
  ? {}
165
161
  : { lastHandledCommentId: payload.handledCommentId }),
166
- ...(payload.sentinel === undefined
167
- ? {}
168
- : { lastRunSentinel: payload.sentinel }),
169
- ...(payload.action === undefined
170
- ? {}
171
- : { lastRunAction: payload.action }),
162
+ ...(payload.sentinel === undefined ? {} : { lastRunSentinel: payload.sentinel }),
163
+ ...(payload.action === undefined ? {} : { lastRunAction: payload.action }),
172
164
  ...(payload.sentinel === doneRunnerSentinel && payload.action !== undefined
173
165
  ? { lastCompletedAction: payload.action }
174
166
  : {}),
@@ -182,9 +174,7 @@ async function applyEvent(current, event, ctx, config) {
182
174
  ...current.wake,
183
175
  stage: payload.nextStage ?? current.wake.stage,
184
176
  lastRunId: payload.runId ?? current.wake.lastRunId,
185
- sessionId: shouldClearSession
186
- ? undefined
187
- : (payload.sessionId ?? current.wake.sessionId),
177
+ sessionId: shouldClearSession ? undefined : (payload.sessionId ?? current.wake.sessionId),
188
178
  sessionCli: shouldClearSession
189
179
  ? undefined
190
180
  : (payload.sessionCli ?? current.wake.sessionCli),
@@ -61,7 +61,7 @@ export function resolveQuotaPauseUntil(input) {
61
61
  };
62
62
  }
63
63
  const boundedFailureCount = Math.max(1, Math.min(input.failureCount, 6));
64
- const delayMs = Math.min(15 * 60_000 * (2 ** (boundedFailureCount - 1)), maxEstimatedPauseMs);
64
+ const delayMs = Math.min(15 * 60_000 * 2 ** (boundedFailureCount - 1), maxEstimatedPauseMs);
65
65
  return {
66
66
  pausedUntil: new Date(input.now.getTime() + delayMs).toISOString(),
67
67
  source: 'estimated',
@@ -46,18 +46,16 @@ export function createOutboundSinkRouter(input) {
46
46
  const origin = typeof event.payload.origin === 'string' ? event.payload.origin : undefined;
47
47
  const sourceOrigin = event.sourceRefs.sink ?? origin;
48
48
  if (event.sourceEventType === 'wake.labels.requested') {
49
- const projectionOrigin = typeof event.payload.origin === 'string'
50
- ? event.payload.origin
51
- : undefined;
49
+ const projectionOrigin = typeof event.payload.origin === 'string' ? event.payload.origin : undefined;
52
50
  if (projectionOrigin !== undefined) {
53
51
  targetSinks.add(projectionOrigin);
54
52
  }
55
53
  }
56
- const kind = intentKind(event);
57
54
  const resourceUri = event.sourceRefs.resourceUri;
58
- if (event.sourceEventType === 'wake.publish.intent.requested' &&
59
- sourceOrigin !== undefined) {
60
- const resourceSink = resourceUri === undefined ? sourceOrigin : sinkNameForResourceUri(resourceUri, sourceOrigin);
55
+ if (event.sourceEventType === 'wake.publish.intent.requested' && sourceOrigin !== undefined) {
56
+ const resourceSink = resourceUri === undefined
57
+ ? sourceOrigin
58
+ : sinkNameForResourceUri(resourceUri, sourceOrigin);
61
59
  // A resource-derived sink name (e.g. a PR surface) may not be
62
60
  // registered — the source that owns it can be disabled independently
63
61
  // of the origin sink. Falling back to sourceOrigin here, rather than
@@ -169,9 +169,7 @@ export function createTickRunner(deps) {
169
169
  ...(input.runnerResult.tokenUsage?.costUsd === undefined
170
170
  ? {}
171
171
  : { cost: formatCostUsd(input.runnerResult.tokenUsage.costUsd) }),
172
- ...(input.workspacePath === undefined
173
- ? {}
174
- : { workspacePath: input.workspacePath }),
172
+ ...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
175
173
  },
176
174
  derivedHints: {
177
175
  stage: input.sentinel === 'DONE' ? 'done' : input.projection.wake.stage,
@@ -468,7 +466,8 @@ export function createTickRunner(deps) {
468
466
  const owner = await deps.resourceIndex.resolve(resourceUri);
469
467
  if (persisted.workItemKey !== UNRESOLVED_WORK_ITEM_KEY &&
470
468
  owner === undefined &&
471
- (await deps.stateStore.readEventEnvelope(`${persisted.workItemKey}-origin-correlation`)) === null) {
469
+ (await deps.stateStore.readEventEnvelope(`${persisted.workItemKey}-origin-correlation`)) ===
470
+ null) {
472
471
  return [
473
472
  { envelope: persisted, persisted: true },
474
473
  ...buildOriginCorrelationEvents(persisted.workItemKey, unkeyed, resourceUri).map((envelope) => ({ envelope, persisted: false })),
@@ -653,8 +652,7 @@ export function createTickRunner(deps) {
653
652
  async function reconcileStaleRunningRecords(now) {
654
653
  const finishedAt = now.toISOString();
655
654
  const runRecords = await deps.stateStore.listRunRecords();
656
- const staleRecords = runRecords
657
- .filter((record) => isStaleRunningRecord(record, now));
655
+ const staleRecords = runRecords.filter((record) => isStaleRunningRecord(record, now));
658
656
  for (const record of staleRecords) {
659
657
  // Run records carry the work item they belong to, so this is a direct
660
658
  // O(1) read — no scan, no index, no source ambiguity. The record's
@@ -949,8 +947,7 @@ export function createTickRunner(deps) {
949
947
  if (isAwaitingApproval(issue)) {
950
948
  return policy.needsWakeAction(issue, workflow);
951
949
  }
952
- const nextAction = policy.chooseAction(issue, workflow) ??
953
- policy.chooseRetryActionAfterHumanReply(issue);
950
+ const nextAction = policy.chooseAction(issue, workflow) ?? policy.chooseRetryActionAfterHumanReply(issue);
954
951
  return nextAction !== null && policy.needsWakeAction(issue, workflow);
955
952
  });
956
953
  if (candidate === undefined) {
@@ -1042,9 +1039,7 @@ export function createTickRunner(deps) {
1042
1039
  // scratch (#258 follow-up incident: a FAILED `revise` run fell
1043
1040
  // back to a full fresh `implement` run and lost the PR-feedback
1044
1041
  // context).
1045
- const nextAction = policy.chooseRetryActionAfterHumanReply(candidate) ??
1046
- workflowAction?.action ??
1047
- null;
1042
+ const nextAction = policy.chooseRetryActionAfterHumanReply(candidate) ?? workflowAction?.action ?? null;
1048
1043
  if (nextAction === null) {
1049
1044
  return { status: 'idle' };
1050
1045
  }
@@ -1146,9 +1141,7 @@ export function createTickRunner(deps) {
1146
1141
  // An agent that writes DONE but was told not to skip approval has violated
1147
1142
  // the protocol; treat it as AWAITING_APPROVAL so the gate is enforced.
1148
1143
  const skipApproval = runnerResult.metadata?.skipApproval;
1149
- const sentinel = rawSentinel === 'DONE' && skipApproval === false
1150
- ? 'AWAITING_APPROVAL'
1151
- : rawSentinel;
1144
+ const sentinel = rawSentinel === 'DONE' && skipApproval === false ? 'AWAITING_APPROVAL' : rawSentinel;
1152
1145
  const nextStage = lifecycle.nextStageFromSentinel(claimedStage, sentinel, workflow);
1153
1146
  const finishedAt = deps.clock.now().toISOString();
1154
1147
  if (workspaceMode === 'branch') {
@@ -15,7 +15,7 @@ export function maxConfiguredRunnerTimeoutMs(config) {
15
15
  }
16
16
  const registryTimeouts = [...activeRunnerNames]
17
17
  .map((name) => config.runners[name])
18
- .map((entry) => entry === undefined || entry.kind === 'fake' ? undefined : entry.timeoutMs)
18
+ .map((entry) => (entry === undefined || entry.kind === 'fake' ? undefined : entry.timeoutMs))
19
19
  .filter((timeout) => timeout !== undefined);
20
20
  return registryTimeouts.length > 0 ? Math.max(...registryTimeouts) : Infinity;
21
21
  }