@atolis-hq/wake 0.3.85 → 0.3.86

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.
@@ -93,33 +93,38 @@ function projectWork(view, event, occurredAt) {
93
93
  return view;
94
94
  }
95
95
  function projectWorkflow(view, event, occurredAt) {
96
- if (event.eventType === OrchestrationEventType.InstanceStarted) {
97
- const workId = event.payload.workItemId;
98
- const card = view.cards[workId];
99
- if (card === undefined)
100
- return view;
101
- const workflows = { ...view.workflows, [event.stream.id]: workId };
102
- if ('parentWorkflowInstanceId' in event.payload)
103
- return {
104
- ...view,
105
- workflows,
106
- children: { ...view.children, [event.stream.id]: event.payload.entry },
107
- };
96
+ if (event.eventType === OrchestrationEventType.InstanceStarted)
97
+ return projectWorkflowStarted(view, event, occurredAt);
98
+ return projectWorkflowUpdate(view, event, occurredAt);
99
+ }
100
+ function projectWorkflowStarted(view, event, occurredAt) {
101
+ const workId = event.payload.workItemId;
102
+ const card = view.cards[workId];
103
+ if (card === undefined)
104
+ return view;
105
+ const workflows = { ...view.workflows, [event.stream.id]: workId };
106
+ if ('parentWorkflowInstanceId' in event.payload)
108
107
  return {
109
108
  ...view,
110
- cards: {
111
- ...view.cards,
112
- [workId]: {
113
- ...card,
114
- workflowName: event.payload.workflowName,
115
- stage: event.payload.entry,
116
- dwellSince: occurredAt,
117
- condition: BoardCondition.Ready,
118
- },
119
- },
120
109
  workflows,
110
+ children: { ...view.children, [event.stream.id]: event.payload.entry },
121
111
  };
122
- }
112
+ return {
113
+ ...view,
114
+ cards: {
115
+ ...view.cards,
116
+ [workId]: {
117
+ ...card,
118
+ workflowName: event.payload.workflowName,
119
+ stage: event.payload.entry,
120
+ dwellSince: occurredAt,
121
+ condition: BoardCondition.Ready,
122
+ },
123
+ },
124
+ workflows,
125
+ };
126
+ }
127
+ function projectWorkflowUpdate(view, event, occurredAt) {
123
128
  if (view.children?.[event.stream.id] !== undefined)
124
129
  return view;
125
130
  const located = lookupWorkflowCard(view, event.stream.id);
@@ -142,7 +147,7 @@ function projectWorkflow(view, event, occurredAt) {
142
147
  cards: {
143
148
  ...view.cards,
144
149
  [workId]: {
145
- ...card,
150
+ ...withoutBlockReason(card),
146
151
  stage: event.payload.stage,
147
152
  dwellSince: occurredAt,
148
153
  condition: BoardCondition.Ready,
@@ -158,7 +163,15 @@ function projectWorkflow(view, event, occurredAt) {
158
163
  const status = orchestrationStatusTransitions[event.eventType];
159
164
  if (status === undefined)
160
165
  return view;
161
- const withCondition = { ...card, condition: boardConditionForStatus(status) };
166
+ const withCondition = {
167
+ ...(event.eventType === OrchestrationEventType.InstanceBlocked
168
+ ? card
169
+ : withoutBlockReason(card)),
170
+ condition: boardConditionForStatus(status),
171
+ ...(event.eventType === OrchestrationEventType.InstanceBlocked
172
+ ? { blockReason: event.payload.reason }
173
+ : {}),
174
+ };
162
175
  if (event.eventType === OrchestrationEventType.SignalWaitStarted)
163
176
  return {
164
177
  ...view,
@@ -196,6 +209,10 @@ function lookupWorkflowCard(view, streamId) {
196
209
  function awaitingApprovalField(signalKind) {
197
210
  return isApprovalAwaitingSignalKind(signalKind) ? { awaitingApproval: true } : {};
198
211
  }
212
+ function withoutBlockReason(card) {
213
+ const { blockReason: _blockReason, ...withoutReason } = card;
214
+ return withoutReason;
215
+ }
199
216
  const runTerminalEventTypes = new Set([
200
217
  ExecutionEventType.RunSucceeded,
201
218
  ExecutionEventType.RunFailed,
@@ -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 = "g6de20bb";
111
+ export const wakeVersion = "g7341a7f";
@@ -14,6 +14,29 @@ export function gitHubIntakeRules(configured) {
14
14
  tags: rule.tags,
15
15
  }));
16
16
  }
17
+ // Rules are OR-composed. A native facet filter is safe only when every rule
18
+ // requires the same single value; otherwise it could hide an item another rule admits.
19
+ export function gitHubIssueQueryFilters(configured) {
20
+ const assignee = sharedRequiredValue(configured, (rule) => rule.where.requiredAssignees);
21
+ const labels = sharedRequiredValue(configured, (rule) => rule.where.labels);
22
+ return {
23
+ ...(assignee === undefined ? {} : { assignee }),
24
+ ...(labels === undefined ? {} : { labels }),
25
+ };
26
+ }
27
+ function sharedRequiredValue(configured, values) {
28
+ if (configured.length === 0)
29
+ return undefined;
30
+ const candidate = [...new Set(values(configured[0]))];
31
+ if (candidate.length !== 1)
32
+ return undefined;
33
+ return configured.every((rule) => {
34
+ const required = new Set(values(rule));
35
+ return required.size === 1 && required.has(candidate[0]);
36
+ })
37
+ ? candidate[0]
38
+ : undefined;
39
+ }
17
40
  export function gitHubIntakeFacts(payload) {
18
41
  return {
19
42
  [GitHubIntakeFacet.Kind]: [payload.kind],
@@ -19,6 +19,7 @@ export * from './application/review-command-translator.js';
19
19
  export * from './application/wake-labels.js';
20
20
  export * from './contracts/config.js';
21
21
  export * from './contracts/events.js';
22
+ export * from './contracts/issue-query.js';
22
23
  export * from './contracts/payloads.js';
23
24
  export * from './contracts/vocabulary.js';
24
25
  export * from './infrastructure/client.js';
@@ -2,10 +2,11 @@
2
2
  import { PullRequestState } from '../../../activities/index.js';
3
3
  import { GitHubListState } from '../contracts/vocabulary.js';
4
4
  import { fetchPaginatedWithEtag, fetchWithEtag } from './etag-cache.js';
5
- export function listIssues(octokit, cache, owner, repo, maxResults, since) {
5
+ export function listIssues(octokit, cache, owner, repo, maxResults, options = {}) {
6
+ const { since, filters = {} } = options;
6
7
  return fetchPaginatedWithEtag({
7
8
  cache,
8
- key: `issues:${owner}/${repo}:since:${since ?? 'bootstrap'}`,
9
+ key: `issues:${JSON.stringify([owner, repo, since ?? null, filters.assignee ?? null, filters.labels ?? null])}`,
9
10
  maxResults,
10
11
  pages: (headers) => octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
11
12
  owner,
@@ -15,6 +16,8 @@ export function listIssues(octokit, cache, owner, repo, maxResults, since) {
15
16
  direction: 'desc',
16
17
  per_page: Math.min(maxResults, 100),
17
18
  ...(since === undefined ? {} : { since }),
19
+ ...(filters.assignee === undefined ? {} : { assignee: filters.assignee }),
20
+ ...(filters.labels === undefined ? {} : { labels: filters.labels }),
18
21
  ...(headers === undefined ? {} : { headers }),
19
22
  }),
20
23
  }).then((items) => items.map(normalizeIssue));
@@ -66,7 +66,10 @@ export function createGitHubClient(token) {
66
66
  }
67
67
  function createGitHubReadClient(octokit, cache) {
68
68
  return {
69
- listIssues: (owner, repo, maxResults, since) => listIssues(octokit, cache, owner, repo, maxResults, since),
69
+ listIssues: (owner, repo, maxResults, since, filters) => listIssues(octokit, cache, owner, repo, maxResults, {
70
+ ...(since === undefined ? {} : { since }),
71
+ ...(filters === undefined ? {} : { filters }),
72
+ }),
70
73
  listPullRequests: (owner, repo, maxResults) => listPullRequests(octokit, cache, owner, repo, maxResults),
71
74
  listIssueComments: (owner, repo, issueNumber, pageSize, since, maxResults) => listIssueComments(octokit, cache, owner, repo, issueNumber, {
72
75
  pageSize,
@@ -1,3 +1,4 @@
1
+ import { gitHubIssueQueryFilters } from '../application/intake-policy.js';
1
2
  import { GitHubEventType } from '../contracts/events.js';
2
3
  import { createGitHubAdapterHealthRegistry, } from './adapter-health-registry.js';
3
4
  import { issueCommentEventsFor, reviewCommentEventsFor, reviewEventsFor, } from './comment-source.js';
@@ -65,7 +66,7 @@ export function createGitHubSource(config, client, adapter, requests = createGit
65
66
  function limitGitHubSourceClient(client, requests) {
66
67
  return {
67
68
  ...client,
68
- listIssues: (owner, repo, maxResults, since) => requests.run(() => client.listIssues(owner, repo, maxResults, since)),
69
+ listIssues: (owner, repo, maxResults, since, filters) => requests.run(() => client.listIssues(owner, repo, maxResults, since, filters)),
69
70
  listPullRequests: (owner, repo, maxResults) => requests.run(() => client.listPullRequests(owner, repo, maxResults)),
70
71
  listCheckRunsForRef: (owner, repo, ref, maxResults) => requests.run(() => client.listCheckRunsForRef(owner, repo, ref, maxResults)),
71
72
  getCombinedStatusForRef: (owner, repo, ref, maxResults) => requests.run(() => client.getCombinedStatusForRef(owner, repo, ref, maxResults)),
@@ -96,22 +97,14 @@ async function pollRepository(input) {
96
97
  repository: `${owner}/${repo}`,
97
98
  };
98
99
  const since = overlapSince(input.watermark, config.polling.lookbackMs);
99
- const [issuesResult, pullRequestsResult] = await Promise.allSettled([
100
- client.listIssues(owner, repo, config.polling.maxPerRepo, since),
101
- client.listPullRequests(owner, repo, config.polling.maxPerRepo),
102
- ]);
103
- if (isFulfilled(issuesResult))
104
- health.recordSuccess(context.repository, 'poll');
105
- else {
106
- reportPartialPollFailure(context.repository, 'issues');
107
- health.recordFailure(context.repository, 'poll', issuesResult.reason);
108
- }
109
- if (isFulfilled(pullRequestsResult))
110
- health.recordSuccess(context.repository, 'poll');
111
- else {
112
- reportPartialPollFailure(context.repository, 'pull requests');
113
- health.recordFailure(context.repository, 'poll', pullRequestsResult.reason);
114
- }
100
+ const [issuesResult, pullRequestsResult] = await fetchRepositoryItems({
101
+ client,
102
+ config,
103
+ owner,
104
+ repo,
105
+ since,
106
+ });
107
+ reportRepositoryReadResults(context, health, issuesResult, pullRequestsResult);
115
108
  const issues = isFulfilled(issuesResult) ? issuesResult.value : [];
116
109
  const pullRequestPayloads = isFulfilled(pullRequestsResult)
117
110
  ? pullRequestsResult.value.filter((pullRequest) => since === undefined || pullRequest.updated_at >= since)
@@ -150,6 +143,25 @@ async function pollRepository(input) {
150
143
  ],
151
144
  };
152
145
  }
146
+ function fetchRepositoryItems(input) {
147
+ const { client, config, owner, repo, since } = input;
148
+ return Promise.allSettled([
149
+ client.listIssues(owner, repo, config.polling.maxPerRepo, since, gitHubIssueQueryFilters(config.intake)),
150
+ client.listPullRequests(owner, repo, config.polling.maxPerRepo),
151
+ ]);
152
+ }
153
+ function reportRepositoryReadResults(context, health, issuesResult, pullRequestsResult) {
154
+ reportRepositoryReadResult(context.repository, health, 'issues', issuesResult);
155
+ reportRepositoryReadResult(context.repository, health, 'pull requests', pullRequestsResult);
156
+ }
157
+ function reportRepositoryReadResult(repository, health, read, result) {
158
+ if (isFulfilled(result)) {
159
+ health.recordSuccess(repository, 'poll');
160
+ return;
161
+ }
162
+ reportPartialPollFailure(repository, read);
163
+ health.recordFailure(repository, 'poll', result.reason);
164
+ }
153
165
  function isFulfilled(result) {
154
166
  return 'value' in result;
155
167
  }
@@ -1,3 +1,4 @@
1
+ import { ActivityOutcomeKind } from '../../../activities/index.js';
1
2
  const commandStatusShape = { accepted: true, completed: true };
2
3
  const commandStatuses = Object.keys(commandStatusShape);
3
4
  export const AcceptedCommandStatusValue = {
@@ -8,6 +9,13 @@ const runResponseShape = { active: true };
8
9
  export const RunResponseField = { Active: Object.keys(runResponseShape)[0] };
9
10
  const resourceItemFieldShape = { adapter: true };
10
11
  export const ResourceItemField = { Adapter: Object.keys(resourceItemFieldShape)[0] };
12
+ const runResolutionStatusShape = { failed: true, succeeded: true };
13
+ const runResolutionStatuses = Object.keys(runResolutionStatusShape);
14
+ export const RunResolutionStatusValue = {
15
+ Failed: runResolutionStatuses[0],
16
+ Succeeded: runResolutionStatuses[1],
17
+ };
18
+ export const ActivityOutcomeKindValue = ActivityOutcomeKind;
11
19
  const boardConditionShape = {
12
20
  ready: true,
13
21
  active: true,
@@ -1,4 +1,4 @@
1
- import { isOperatorRetryEligible, } from '../../../orchestration/index.js';
1
+ import { isOperatorRetryEligible, WorkflowStatus, } from '../../../orchestration/index.js';
2
2
  import { toWorkItemKey } from '../contracts/work.js';
3
3
  export function presentWorkflowInstance(value) {
4
4
  return {
@@ -11,6 +11,9 @@ export function presentWorkflowInstance(value) {
11
11
  : { parentWorkflowInstanceId: value.parentWorkflowInstanceId }),
12
12
  status: value.status,
13
13
  currentStage: value.currentStage,
14
+ ...(value.status === WorkflowStatus.Blocked && value.blockReason !== undefined
15
+ ? { blockReason: value.blockReason }
16
+ : {}),
14
17
  ...(isOperatorRetryEligible(value) ? { retryEligible: true } : {}),
15
18
  ...(value.waitingFor === undefined
16
19
  ? {}