@atolis-hq/wake 0.2.39 → 0.2.41

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.
@@ -0,0 +1,36 @@
1
+ import { AUTONOMOUS_DECISION_AUDIT_EVENT } from '../domain/schema.js';
2
+ function formatValue(value) {
3
+ if (value === undefined)
4
+ return 'n/a';
5
+ if (typeof value === 'string')
6
+ return value;
7
+ return JSON.stringify(value);
8
+ }
9
+ function auditEventsForWorkItem(events, workItemKey) {
10
+ return events
11
+ .filter((event) => event.workItemKey === workItemKey &&
12
+ event.sourceEventType === AUTONOMOUS_DECISION_AUDIT_EVENT)
13
+ .sort((left, right) => left.ingestedAt.localeCompare(right.ingestedAt));
14
+ }
15
+ export async function runAuditCommand(input) {
16
+ const log = input.log ?? console.log;
17
+ const [workItemKey] = input.args;
18
+ if (workItemKey === undefined) {
19
+ throw new Error('Usage: wake audit <workItemKey>');
20
+ }
21
+ const events = auditEventsForWorkItem(await input.stateStore.listEventEnvelopes(), workItemKey);
22
+ if (events.length === 0) {
23
+ log(`No autonomous audit events found for ${workItemKey}.`);
24
+ return;
25
+ }
26
+ log(`Autonomous audit history for ${workItemKey}`);
27
+ for (const event of events) {
28
+ const payload = event.payload;
29
+ log('');
30
+ log(`${formatValue(payload.timestamp)} ${formatValue(payload.decisionType)}`);
31
+ log(` runId: ${formatValue(payload.runId)}`);
32
+ log(` workflowRevision: ${formatValue(payload.workflowRevision)}`);
33
+ log(` inputsConsidered: ${formatValue(payload.inputsConsidered)}`);
34
+ log(` outcome: ${formatValue(payload.outcome)}`);
35
+ }
36
+ }
@@ -97,10 +97,12 @@ export async function runSelfUpdateCommand(input) {
97
97
  };
98
98
  try {
99
99
  await input.git.checkoutTag(tag);
100
+ // Matches wake sandbox build's WAKE_BUILD_TAG handling (sandbox-command.ts).
100
101
  await input.docker.build({
101
102
  image: newImage,
102
103
  dockerfile: input.dockerfilePath,
103
104
  contextDir: input.repoRoot,
105
+ buildArgs: { WAKE_BUILD_TAG: tag },
104
106
  });
105
107
  await input.docker.update({ ...updateInput, image: newImage });
106
108
  input.logger.info('[self-update] recreated container; entrypoint will keep wake start running');
@@ -0,0 +1,82 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { AUTONOMOUS_DECISION_AUDIT_EVENT } from '../domain/schema.js';
5
+ import { createEventEnvelope } from '../lib/event-log.js';
6
+ function stableJson(value) {
7
+ if (value === null || typeof value !== 'object') {
8
+ return JSON.stringify(value);
9
+ }
10
+ if (Array.isArray(value)) {
11
+ return `[${value.map((entry) => stableJson(entry)).join(',')}]`;
12
+ }
13
+ const record = value;
14
+ return `{${Object.keys(record)
15
+ .sort()
16
+ .map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`)
17
+ .join(',')}}`;
18
+ }
19
+ async function promptRevisionInput(input) {
20
+ if (input.action === undefined) {
21
+ return {};
22
+ }
23
+ const promptsRoot = input.config.paths.promptsRoot;
24
+ if (promptsRoot === undefined) {
25
+ return { prompt: { action: input.action, status: 'not-configured' } };
26
+ }
27
+ for (const suffix of ['.md', '.start.md', '.resume.md']) {
28
+ const path = join(promptsRoot, `${input.action}${suffix}`);
29
+ try {
30
+ return {
31
+ prompt: {
32
+ action: input.action,
33
+ path,
34
+ sha256: createHash('sha256')
35
+ .update(await readFile(path, 'utf8'))
36
+ .digest('hex'),
37
+ },
38
+ };
39
+ }
40
+ catch {
41
+ // Try the next supported prompt template name.
42
+ }
43
+ }
44
+ return { prompt: { action: input.action, status: 'missing' } };
45
+ }
46
+ export async function workflowRevision(input) {
47
+ const revisionInput = {
48
+ workflowName: input.workflowName,
49
+ workflow: input.workflow,
50
+ ...(await promptRevisionInput({
51
+ config: input.config,
52
+ ...(input.action === undefined ? {} : { action: input.action }),
53
+ })),
54
+ };
55
+ return `sha256:${createHash('sha256').update(stableJson(revisionInput)).digest('hex')}`;
56
+ }
57
+ export function createAutonomousDecisionAuditEvent(input) {
58
+ return createEventEnvelope({
59
+ eventId: input.eventId,
60
+ workItemKey: input.workItemKey,
61
+ streamScope: 'work-item',
62
+ direction: 'internal',
63
+ sourceSystem: 'wake',
64
+ sourceEventType: AUTONOMOUS_DECISION_AUDIT_EVENT,
65
+ sourceRefs: {
66
+ ...(input.sourceRefs ?? {}),
67
+ runId: input.runId,
68
+ },
69
+ occurredAt: input.timestamp,
70
+ ingestedAt: input.timestamp,
71
+ trigger: 'context-only',
72
+ payload: {
73
+ decisionType: input.decisionType,
74
+ workItemId: input.workItemKey,
75
+ runId: input.runId,
76
+ workflowRevision: input.workflowRevision,
77
+ inputsConsidered: input.inputsConsidered,
78
+ outcome: input.outcome,
79
+ timestamp: input.timestamp,
80
+ },
81
+ });
82
+ }
@@ -17,6 +17,7 @@ import { createOutbox } from './outbox.js';
17
17
  import { createEventResolver } from './event-resolver.js';
18
18
  import { createStaleRunReconciler } from './stale-run-reconciler.js';
19
19
  import { createWorkspaceCleanup } from './workspace-cleanup.js';
20
+ import { createAutonomousDecisionAuditEvent, workflowRevision as computeWorkflowRevision, } from './audit-events.js';
20
21
  import { createRunLease, isRunLeaseExpired, renewRunLease, runLeaseRenewalIntervalMs, } from './run-lease.js';
21
22
  import { currentProcessIdentity, processIdentityMatches } from '../lib/process-identity.js';
22
23
  import { readJsonFile, writeJsonFile } from '../lib/json-file.js';
@@ -172,6 +173,9 @@ export function createTickRunner(deps) {
172
173
  function eventStampNow() {
173
174
  return deps.clock.now().toISOString();
174
175
  }
176
+ async function appendAuditEvent(input) {
177
+ await deps.stateStore.appendEventEnvelope(createAutonomousDecisionAuditEvent(input));
178
+ }
175
179
  // The watchlist is every resource currently correlated to an open work
176
180
  // item, deduplicated by exact resourceUri. It is derived once per tick from
177
181
  // the pre-poll projection snapshot (see runTick's ordering note) and handed
@@ -309,6 +313,38 @@ export function createTickRunner(deps) {
309
313
  await retryUnconfirmedDeliveries();
310
314
  const watchlistProjections = await deps.stateStore.listIssueStates();
311
315
  const inboundEvents = await ingestInboundEvents(await deps.workSource.pollEvents({ watch: deriveWatchlist(watchlistProjections) }));
316
+ for (const event of inboundEvents) {
317
+ const trigger = event.payload.trigger;
318
+ const workflowName = typeof event.payload.workflow === 'string' ? event.payload.workflow : undefined;
319
+ const workflow = workflowName === undefined ? undefined : deps.config.workflows[workflowName];
320
+ if (trigger?.kind !== 'schedule' || workflowName === undefined || workflow === undefined) {
321
+ continue;
322
+ }
323
+ const timestamp = eventStampNow();
324
+ await appendAuditEvent({
325
+ eventId: `${event.eventId}-audit-trigger-fired`,
326
+ decisionType: 'trigger.fired',
327
+ workItemKey: event.workItemKey,
328
+ runId: event.eventId,
329
+ workflowRevision: await computeWorkflowRevision({
330
+ config: deps.config,
331
+ workflowName,
332
+ workflow,
333
+ }),
334
+ inputsConsidered: {
335
+ workflow: workflowName,
336
+ schedule: workflow.trigger?.schedule,
337
+ trigger,
338
+ sourceEventId: event.eventId,
339
+ },
340
+ outcome: {
341
+ fired: true,
342
+ sourceEventType: event.sourceEventType,
343
+ },
344
+ timestamp,
345
+ sourceRefs: event.sourceRefs,
346
+ });
347
+ }
312
348
  const projections = await deps.stateStore.listIssueStates();
313
349
  await cleanupClosedIssueWorkspaces(projections);
314
350
  if (inboundEvents.length > 0) {
@@ -661,6 +697,35 @@ export function createTickRunner(deps) {
661
697
  },
662
698
  });
663
699
  await deps.stateStore.appendEventEnvelope(approvalCompletedEvent);
700
+ if (automaticApproval) {
701
+ await appendAuditEvent({
702
+ eventId: `${approvalId}-audit-auto-resolution`,
703
+ decisionType: 'approval.auto-resolved',
704
+ workItemKey: candidate.workItemKey,
705
+ runId: approvalId,
706
+ workflowRevision: await computeWorkflowRevision({
707
+ config: deps.config,
708
+ workflowName,
709
+ workflow,
710
+ action: approvalResolution.pendingAction,
711
+ }),
712
+ inputsConsidered: {
713
+ labels: candidate.issue.labels,
714
+ pendingAction: approvalResolution.pendingAction,
715
+ pendingApprovalAllowAutoApproval: candidate.context.pendingApprovalAllowAutoApproval === true,
716
+ },
717
+ outcome: {
718
+ approved: true,
719
+ nextStage,
720
+ reason: approvalResolution.reason,
721
+ },
722
+ timestamp: approvedAt,
723
+ sourceRefs: {
724
+ repo: candidate.issue.repo,
725
+ issueNumber: candidate.issue.number,
726
+ },
727
+ });
728
+ }
664
729
  await projectionUpdater.rebuildFromEvents([approvalCompletedEvent]);
665
730
  await deliverOutboundEvent(createLabelsEvent({
666
731
  projection: candidate,
@@ -791,6 +856,38 @@ export function createTickRunner(deps) {
791
856
  },
792
857
  });
793
858
  await deps.stateStore.appendEventEnvelope(claimedEvent);
859
+ if (watcherRun && watcherDispatch !== null && watcherTriggerForRun !== undefined) {
860
+ await appendAuditEvent({
861
+ eventId: `${runId}-audit-watcher-dispatched`,
862
+ decisionType: 'watcher.dispatched',
863
+ workItemKey: candidate.workItemKey,
864
+ runId,
865
+ workflowRevision: await computeWorkflowRevision({
866
+ config: deps.config,
867
+ workflowName,
868
+ workflow: deps.config.workflows[workflowName] ?? workflow,
869
+ action,
870
+ }),
871
+ inputsConsidered: {
872
+ parentWorkflowName: watcherDispatch.parentWorkflowName,
873
+ parentStage: watcherDispatch.parentStage,
874
+ watcherIndex: watcherDispatch.watcherIndex,
875
+ watcherStatus: watcherStatus(candidate),
876
+ trigger: watcherTriggerForRun,
877
+ },
878
+ outcome: {
879
+ dispatched: true,
880
+ targetWorkflowName: workflowName,
881
+ action,
882
+ claimedStage,
883
+ },
884
+ timestamp: claimedAt,
885
+ sourceRefs: {
886
+ repo: candidate.issue.repo,
887
+ issueNumber: candidate.issue.number,
888
+ },
889
+ });
890
+ }
794
891
  await transitionRunLifecycle('CLAIMED');
795
892
  await projectionUpdater.rebuildFromEvents([claimedEvent]);
796
893
  if (watcherStateKeyForRun !== undefined && watcherTriggerForRun !== undefined) {
@@ -907,6 +1004,44 @@ export function createTickRunner(deps) {
907
1004
  runnerResult,
908
1005
  occurredAt: finishedAt,
909
1006
  });
1007
+ await appendAuditEvent({
1008
+ eventId: `${runId}-audit-review-verdict`,
1009
+ decisionType: 'review.verdict',
1010
+ workItemKey: candidate.workItemKey,
1011
+ runId,
1012
+ workflowRevision: await computeWorkflowRevision({
1013
+ config: deps.config,
1014
+ workflowName,
1015
+ workflow: deps.config.workflows[workflowName] ?? workflow,
1016
+ action,
1017
+ }),
1018
+ inputsConsidered: {
1019
+ sourceRevision,
1020
+ watcherTrigger: watcherTriggerForRun,
1021
+ verifiedTargetResourceUri: prReviewTargetResourceUri,
1022
+ rawSentinel,
1023
+ },
1024
+ outcome: {
1025
+ sentinel,
1026
+ verdict: prReviewTargetResourceUri === null
1027
+ ? 'uncertain'
1028
+ : sentinel === 'DONE'
1029
+ ? 'approved'
1030
+ : sentinel === 'FAILED'
1031
+ ? 'changes-requested'
1032
+ : 'uncertain',
1033
+ reasoning: parsedRunnerResult.body,
1034
+ runner: {
1035
+ model: runnerResult.model,
1036
+ cli: runnerResult.cli,
1037
+ },
1038
+ },
1039
+ timestamp: finishedAt,
1040
+ sourceRefs: {
1041
+ repo: candidate.issue.repo,
1042
+ issueNumber: candidate.issue.number,
1043
+ },
1044
+ });
910
1045
  }
911
1046
  else if (workspaceMode === 'branch') {
912
1047
  await registerReportedArtifacts({
@@ -210,6 +210,7 @@ export const WORK_ITEM_CREATED_EVENT = 'wake.workitem.created';
210
210
  export const CORRELATION_REGISTERED_EVENT = 'wake.correlation.registered';
211
211
  export const CORRELATION_RETRACTED_EVENT = 'wake.correlation.retracted';
212
212
  export const CORRELATION_PRIMARY_CONFLICT_EVENT = 'wake.correlation.primary-conflict';
213
+ export const AUTONOMOUS_DECISION_AUDIT_EVENT = 'wake.audit.autonomous-decision';
213
214
  /**
214
215
  * Shared key for events whose resource failed mint qualification (spec D1').
215
216
  * Never a real work item: `readIssueState(UNRESOLVED_WORK_ITEM_KEY)` always
package/dist/src/main.js CHANGED
@@ -19,6 +19,7 @@ import { createGitHubArtifactVerifier } from './adapters/github/github-artifact-
19
19
  import { createGitHubClient } from './adapters/github/github-client.js';
20
20
  import { createGitHubIssuesWorkSource } from './adapters/github/github-issues-work-source.js';
21
21
  import { createGitHubPullRequestActivitySource } from './adapters/github/github-pull-request-activity-source.js';
22
+ import { runAuditCommand } from './cli/audit-command.js';
22
23
  import { runCorrelateCommand } from './cli/correlate-command.js';
23
24
  import { runDoctorCommand } from './cli/doctor-command.js';
24
25
  import { runInitCommand } from './cli/init-command.js';
@@ -701,6 +702,15 @@ async function runCorrelate(args) {
701
702
  readFlag: readFlagBeforeCommandTerminator,
702
703
  });
703
704
  }
705
+ async function runAudit(args) {
706
+ const wakeRoot = resolve(readFlagBeforeCommandTerminator('--wake-root', args) ?? process.cwd());
707
+ const stateStore = createStateStore({ wakeRoot });
708
+ await stateStore.ensureWakeRoot();
709
+ await runAuditCommand({
710
+ args,
711
+ stateStore,
712
+ });
713
+ }
704
714
  async function runSmoke(args) {
705
715
  const runtime = await buildRuntime(args);
706
716
  const explicitKind = args[0] === 'claude' || args[0] === 'codex' || args[0] === 'cursor' ? args[0] : undefined;
@@ -783,6 +793,7 @@ export function printUsage(stream) {
783
793
  ' wake stop Stop the sandbox container gracefully',
784
794
  ' wake smoke Smoke-test the configured runner',
785
795
  ' wake ui Run the control-plane UI server',
796
+ ' wake audit Show autonomous decision audit history',
786
797
  ' wake correlate Manually correlate a resource to a work item',
787
798
  ' wake doctor Diagnose config/GitHub/Docker/sandbox setup problems',
788
799
  ' wake --version Print the installed Wake version',
@@ -792,14 +803,22 @@ export function printUsage(stream) {
792
803
  ' 1. wake init ./wake-home',
793
804
  ' 2. cd wake-home && wake start',
794
805
  '',
795
- 'Runtime commands (tick/start/ui/smoke/correlate/validate-state) auto-delegate into the sandbox',
806
+ 'Runtime commands (tick/start/ui/smoke/audit/correlate/validate-state) auto-delegate into the sandbox',
796
807
  'when docker/Dockerfile exists at --wake-root (i.e. after `wake sandbox build`),',
797
808
  'defaulting --wake-root to the current directory. Pass --no-sandbox to run',
798
809
  'directly on the host instead.',
799
810
  '',
800
811
  ].join('\n'));
801
812
  }
802
- const runtimeCommands = new Set(['tick', 'start', 'ui', 'smoke', 'correlate', 'validate-state']);
813
+ const runtimeCommands = new Set([
814
+ 'tick',
815
+ 'start',
816
+ 'ui',
817
+ 'smoke',
818
+ 'audit',
819
+ 'correlate',
820
+ 'validate-state',
821
+ ]);
803
822
  export async function dispatchMainCommand(input) {
804
823
  const command = input.args[0] ?? 'help';
805
824
  if (command === '--version' || command === '-v') {
@@ -855,6 +874,12 @@ export async function dispatchMainCommand(input) {
855
874
  else if (command === 'ui') {
856
875
  await input.runUi(hostArgs);
857
876
  }
877
+ else if (command === 'audit') {
878
+ if (input.runAudit === undefined) {
879
+ throw new CliUsageError('audit command is not available in this runtime');
880
+ }
881
+ await input.runAudit(hostArgs);
882
+ }
858
883
  else if (command === 'smoke') {
859
884
  await input.runSmoke(hostArgs);
860
885
  }
@@ -991,6 +1016,7 @@ async function main() {
991
1016
  runStart,
992
1017
  runSmoke,
993
1018
  runUi,
1019
+ runAudit,
994
1020
  runCorrelate,
995
1021
  runValidateState,
996
1022
  execIntoSandbox: async (commandArgs) => {
@@ -124,4 +124,4 @@ export function resolveWakeVersion(options = {}) {
124
124
  }
125
125
  return '0.1.0-dev';
126
126
  }
127
- export const wakeVersion = "g4a872ec";
127
+ export const wakeVersion = "gecbffd3";
package/docker/Dockerfile CHANGED
@@ -31,7 +31,10 @@ RUN useradd --create-home --shell /bin/bash wake \
31
31
  ENV CODEX_HOME=/home/wake/.codex-runtime
32
32
  ENV PATH=/home/wake/.local/bin:$PATH
33
33
 
34
- RUN curl https://cursor.com/install -fsS | HOME=/home/wake bash \
34
+ # Bump this date to force a fresh cursor.com/install instead of an indefinitely cached one.
35
+ ARG CURSOR_CACHE_BUST=2026-07-26
36
+ RUN echo "cursor cache bust: ${CURSOR_CACHE_BUST}" \
37
+ && curl https://cursor.com/install -fsS | HOME=/home/wake bash \
35
38
  && printf '#!/bin/bash\n[ "$1" = "agent" ] && shift\nexec ~/.local/bin/agent "$@"\n' \
36
39
  > /home/wake/.local/bin/cursor \
37
40
  && chmod +x /home/wake/.local/bin/cursor \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -35,6 +35,9 @@ Review requirements:
35
35
  references where possible.
36
36
  - If no issues are found, say so clearly and mention any meaningful test gaps
37
37
  or residual risks.
38
+ - Flag overly verbose or narrative comments (multi-sentence explanations of
39
+ what the code does, or why a change was made) as a convention mismatch —
40
+ comments should be short, stating only non-obvious rationale.
38
41
 
39
42
  Wake will provide the issue data and comments below in a delimited untrusted
40
43
  data block.