@atolis-hq/wake 0.3.7 → 0.3.9

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.
@@ -108,4 +108,4 @@ export function resolveWakeVersion(options = {}) {
108
108
  return `g${headHash.slice(0, 7)}`;
109
109
  return '0.1.0-dev';
110
110
  }
111
- export const wakeVersion = "g3a1ec02";
111
+ export const wakeVersion = "gb753eb6";
@@ -101,6 +101,7 @@ async function applyIssueApprovalSignal(input) {
101
101
  orchestration,
102
102
  resourceId: resourceIdValue,
103
103
  outcome: command === '/approved' ? ActivityOutcomeKind.Done : ActivityOutcomeKind.Rejected,
104
+ resumeBlockedOnChanges: command === '/changes',
104
105
  });
105
106
  }
106
107
  async function applyPullRequestWorkflowSignal(input) {
@@ -125,25 +126,28 @@ async function applyPullRequestWorkflowSignal(input) {
125
126
  });
126
127
  }
127
128
  async function applyWorkflowSignal(input) {
128
- const { event, resources, orchestration, resourceId: resourceIdValue, outcome } = input;
129
+ const { event, resources, orchestration, resourceId: resourceIdValue, outcome, resumeBlockedOnChanges = false, } = input;
129
130
  const workItemIds = (await resources.correlations(resourceIdValue))
130
131
  .filter((correlation) => correlation.role === ResourceCorrelationRole.Primary)
131
132
  .map((correlation) => correlation.workItemId);
132
133
  for (const workflow of await orchestration.listAll()) {
133
134
  if (!workItemIds.includes(workflow.workItemId))
134
135
  continue;
135
- if (workflow.waitingFor === undefined)
136
- continue;
137
- await orchestration.acceptSignal(workflow.workflowInstanceId, {
138
- kind: workflow.waitingFor.signalKind,
139
- outcome,
140
- actorId: event.payload.actor.id,
141
- actorDecision: {
142
- authorized: event.payload.actor.kind === ReviewActorKind.Human,
143
- evidenceId: event.eventId,
144
- },
145
- providerEventId: event.eventId,
146
- }, commandContext(event));
136
+ if (workflow.waitingFor !== undefined) {
137
+ await orchestration.acceptSignal(workflow.workflowInstanceId, {
138
+ kind: workflow.waitingFor.signalKind,
139
+ outcome,
140
+ actorId: event.payload.actor.id,
141
+ actorDecision: {
142
+ authorized: event.payload.actor.kind === ReviewActorKind.Human,
143
+ evidenceId: event.eventId,
144
+ },
145
+ providerEventId: event.eventId,
146
+ }, commandContext(event));
147
+ }
148
+ else if (resumeBlockedOnChanges) {
149
+ await orchestration.resumeBlockedStageForChanges(workflow.workflowInstanceId, commandContext(event));
150
+ }
147
151
  }
148
152
  }
149
153
  function isWakeDelivery(body) {
@@ -104,5 +104,8 @@ function parseGitHubIssueKey(key) {
104
104
  };
105
105
  }
106
106
  function sameLabels(left, right) {
107
- return left.length === right.length && left.every((label, index) => label === right[index]);
107
+ const normalizedLeft = [...left].sort();
108
+ const normalizedRight = [...right].sort();
109
+ return (normalizedLeft.length === normalizedRight.length &&
110
+ normalizedLeft.every((label, index) => label === normalizedRight[index]));
108
111
  }
@@ -4,7 +4,7 @@ import { OrchestrationEventType } from '../contracts/events.js';
4
4
  import { commandName, workflowInstanceId, } from '../contracts/identifiers.js';
5
5
  import { workflowInstanceStream } from '../contracts/streams.js';
6
6
  import { WorkflowStatus } from '../contracts/vocabulary.js';
7
- import { requestOperatorRetry as decideOperatorRetry, requestSupplementalActivity as decideSupplementalActivity, } from '../domain/interpreter.js';
7
+ import { requestChangesResume as decideChangesResume, requestOperatorRetry as decideOperatorRetry, requestSupplementalActivity as decideSupplementalActivity, } from '../domain/interpreter.js';
8
8
  import { isAuthorisedActor } from '../domain/supplemental-policy.js';
9
9
  export class OperatorRetryIneligibleError extends Error {
10
10
  constructor(detail) {
@@ -136,6 +136,33 @@ export class AdvanceWorkflow {
136
136
  }
137
137
  return (await this.repository.loadRequired(id)).view;
138
138
  }
139
+ async resumeBlockedStageForChanges(id, context) {
140
+ const loaded = await this.repository.load(id);
141
+ if (loaded.view === null)
142
+ return null;
143
+ if (loaded.view.operatorRetryCommandIds.includes(context.commandId))
144
+ return loaded.view;
145
+ const definition = await this.workflows.definitionForOperation(loaded.view, loaded.sequence, context);
146
+ if (definition === null)
147
+ return (await this.repository.loadRequired(id)).view;
148
+ const decision = decideChangesResume(definition, loaded.view, {
149
+ commandId: context.commandId,
150
+ occurredAt: context.occurredAt,
151
+ causationId: context.commandId,
152
+ });
153
+ if (decision.kind === 'ignored')
154
+ return loaded.view;
155
+ try {
156
+ await this.repository.append(id, loaded.sequence, decision.events);
157
+ }
158
+ catch (error) {
159
+ const reloaded = await this.repository.loadRequired(id);
160
+ if (reloaded.view.operatorRetryCommandIds.includes(context.commandId))
161
+ return reloaded.view;
162
+ throw error;
163
+ }
164
+ return (await this.repository.loadRequired(id)).view;
165
+ }
139
166
  async get(id) {
140
167
  return (await this.repository.load(id)).view;
141
168
  }
@@ -55,6 +55,9 @@ export class OrchestrationService {
55
55
  retryBlockedFailedStage(workflowInstanceId, context) {
56
56
  return this.advanceWorkflow.retryBlockedFailedStage(workflowInstanceId, context);
57
57
  }
58
+ resumeBlockedStageForChanges(workflowInstanceId, context) {
59
+ return this.advanceWorkflow.resumeBlockedStageForChanges(workflowInstanceId, context);
60
+ }
58
61
  get(id) {
59
62
  return this.advanceWorkflow.get(id);
60
63
  }
@@ -10,7 +10,7 @@ import { finishRoute } from './transition.js';
10
10
  export { startInstance } from './activation-policy.js';
11
11
  export { acceptSignal, waitForSignal } from './signal-policy.js';
12
12
  export { requestSupplementalActivity } from './supplemental-policy.js';
13
- export { isOperatorRetryEligible, requestOperatorRetry } from './operator-retry-policy.js';
13
+ export { isChangesResumeEligible, isOperatorRetryEligible, requestChangesResume, requestOperatorRetry, } from './operator-retry-policy.js';
14
14
  export function acceptActivityOutcome(definition, state, input) {
15
15
  if (!isPendingOutcome(state, input))
16
16
  return { kind: 'ignored', reason: 'outcome is not for the pending activation' };
@@ -1,4 +1,4 @@
1
- import { ActivityOutcomeKind } from '../../activities/index.js';
1
+ import { ActivityOutcomeKind, BuiltInActivityName } from '../../activities/index.js';
2
2
  import { OrchestrationEventType } from '../contracts/events.js';
3
3
  import { stageName } from '../contracts/identifiers.js';
4
4
  import { ActivityActivationStatus, WorkflowStatus } from '../contracts/vocabulary.js';
@@ -14,6 +14,18 @@ export function isOperatorRetryEligible(view) {
14
14
  view.lastOutcome?.kind === ActivityOutcomeKind.Failed &&
15
15
  view.acceptedOutcomes.includes(pending.activationId));
16
16
  }
17
+ export function isChangesResumeEligible(view) {
18
+ const pending = view.pendingActivation;
19
+ return (view.status === WorkflowStatus.Blocked &&
20
+ view.blockReason === 'unconfigured outcome blocked' &&
21
+ pending !== undefined &&
22
+ pending.activity === BuiltInActivityName.Agent &&
23
+ pending.status === ActivityActivationStatus.Completed &&
24
+ pending.supplemental !== true &&
25
+ pending.followOnIndex === undefined &&
26
+ view.lastOutcome?.kind === ActivityOutcomeKind.Blocked &&
27
+ view.acceptedOutcomes.includes(pending.activationId));
28
+ }
17
29
  export function requestOperatorRetry(definition, state, input) {
18
30
  if (input.commandId.length === 0)
19
31
  return { kind: 'ignored', reason: 'operator retry command id is required' };
@@ -32,3 +44,23 @@ export function requestOperatorRetry(definition, state, input) {
32
44
  ];
33
45
  return { kind: 'append', events };
34
46
  }
47
+ export function requestChangesResume(definition, state, input) {
48
+ if (input.commandId.length === 0)
49
+ return { kind: 'ignored', reason: 'changes resume command id is required' };
50
+ if (!isChangesResumeEligible(state))
51
+ return {
52
+ kind: 'ignored',
53
+ reason: 'workflow is not blocked for an unconfigured agent blocked outcome',
54
+ };
55
+ const stage = definition.stages[stageName(state.currentStage)];
56
+ return {
57
+ kind: 'append',
58
+ events: [
59
+ stateDraft(state, input, OrchestrationEventType.OperatorRetryRequested, { activationId: state.pendingActivation.activationId, commandId: input.commandId }, 1),
60
+ stateDraft(state, input, OrchestrationEventType.ActivityRequested, activation(state.workflowInstanceId, nextOrdinal(state), stage.activity, stage.with, {
61
+ execution: stage.execution,
62
+ stage: stageName(state.currentStage),
63
+ }), 2),
64
+ ],
65
+ };
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {