@atolis-hq/wake 0.2.61 → 0.2.63

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.
@@ -52,7 +52,7 @@ export const indexHtml = `<!DOCTYPE html>
52
52
  nav button:hover { color: #fff; }
53
53
  nav button.active { color: var(--accent-light); border-bottom-color: var(--accent); }
54
54
  main { padding: 1rem; }
55
- .columns { display: grid; grid-template-columns: repeat(5, minmax(180px, 1fr)); gap: 0.6rem; overflow-x: auto; }
55
+ .columns { display: grid; grid-template-columns: repeat(6, minmax(180px, 1fr)); gap: 0.6rem; overflow-x: auto; }
56
56
  .col { background: #1a1d23; border-radius: 10px; padding: 0.5rem; min-height: 200px; }
57
57
  .col h2 { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: #9aa2ad; margin: 0.2rem 0.4rem 0.5rem; }
58
58
  .card { background: #22262e; border: 1px solid #2c313a; border-radius: 8px; padding: 0.5rem; margin-bottom: 0.5rem; cursor: pointer; font-size: 0.8rem; transition: border-color 0.12s ease; }
@@ -154,7 +154,7 @@ export const indexHtml = `<!DOCTYPE html>
154
154
  </div>
155
155
  <script>
156
156
  const API = '/api/v1';
157
- const CONDITIONS = ['ready', 'active', 'needs-human', 'error', 'finished'];
157
+ const CONDITIONS = ['ready', 'scheduled', 'active', 'needs-human', 'error', 'finished'];
158
158
  let currentView = 'board';
159
159
  let analyticsWindow = '7d';
160
160
  let analyticsMetric = 'runs-over-time';
@@ -312,7 +312,10 @@ async function renderBoard(context) {
312
312
  ...(item.labels && item.labels.length > 0
313
313
  ? [el('div', { class: 'meta' }, item.labels.map((label) => el('span', { class: 'chip chip-label', text: label })))]
314
314
  : []),
315
- el('div', { class: 'meta', text: item.lastRunSentinel ? 'last: ' + item.lastRunAction + ' → ' + item.lastRunSentinel : item.conditionReason }),
315
+ ...(item.activeMainRun
316
+ ? []
317
+ : [el('div', { class: 'meta', text: item.lastRunSentinel ? 'last: ' + item.lastRunAction + ' → ' + item.lastRunSentinel : item.conditionReason })]),
318
+ ...(item.activeMainRun ? [renderChildRun(item.activeMainRun)] : []),
316
319
  ...((item.activeChildRuns || []).map(renderChildRun)),
317
320
  ]));
318
321
  return el('div', { class: 'col' + (items.length === 0 ? ' col-empty' : '') }, [
@@ -424,6 +427,22 @@ function renderItemDetails(detail) {
424
427
  });
425
428
  body.appendChild(retryBtn);
426
429
  }
430
+ if (detail.item.issue.labels.includes('wake:scheduled-workflow')) {
431
+ const runNowBtn = el('button', { type: 'button', class: 'btn', text: 'Run now' });
432
+ runNowBtn.addEventListener('click', async () => {
433
+ runNowBtn.disabled = true;
434
+ runNowBtn.textContent = 'Requesting run...';
435
+ try {
436
+ await postJson('/work-items/' + encodeURIComponent(detail.item.workItemKey) + '/run');
437
+ runNowBtn.textContent = 'Run requested';
438
+ } catch (err) {
439
+ runNowBtn.disabled = false;
440
+ runNowBtn.textContent = 'Run now';
441
+ document.getElementById('status-summary').textContent = 'run request failed: ' + err.message;
442
+ }
443
+ });
444
+ body.appendChild(runNowBtn);
445
+ }
427
446
  const resources = detail.item.correlatedResources || [];
428
447
  if (resources.length > 0) {
429
448
  body.appendChild(el('h3', { text: 'Resources' }));
@@ -68,6 +68,9 @@ function deriveCondition(item, lastRun, config) {
68
68
  if (lastRun?.sentinel === 'FAILED') {
69
69
  return { condition: 'error', reason: 'last run failed; awaiting operator/retry policy' };
70
70
  }
71
+ if (item.issue.labels.includes('wake:scheduled-workflow')) {
72
+ return { condition: 'scheduled', reason: 'scheduled workflow awaiting next run' };
73
+ }
71
74
  return { condition: 'ready', reason: 'has a route and no blocking condition' };
72
75
  }
73
76
  function timeInStageMs(item, now) {
@@ -107,6 +110,18 @@ export async function buildBoard(input) {
107
110
  const lastRun = item.wake.lastRunId === undefined ? null : (runsById.get(item.wake.lastRunId) ?? null);
108
111
  const { condition, reason } = deriveCondition(item, lastRun, input.config);
109
112
  const activeChildRuns = activeChildRunsForItem(item, runs, input.now);
113
+ const activeMainRun = lastRun?.status === 'running'
114
+ ? {
115
+ runId: lastRun.runId,
116
+ action: lastRun.action,
117
+ startedAt: lastRun.startedAt,
118
+ ageMs: input.now.getTime() - Date.parse(lastRun.startedAt),
119
+ ...(lastRun.routing?.runnerName === undefined
120
+ ? {}
121
+ : { runnerName: lastRun.routing.runnerName }),
122
+ ...(lastRun.routing?.tier === undefined ? {} : { tier: lastRun.routing.tier }),
123
+ }
124
+ : undefined;
110
125
  return {
111
126
  repo: item.issue.repo,
112
127
  number: item.issue.number,
@@ -120,6 +135,7 @@ export async function buildBoard(input) {
120
135
  lastRunAction: lastRun?.action,
121
136
  lastRunSentinel: lastRun?.sentinel,
122
137
  lastRunStatus: lastRun?.status,
138
+ ...(activeMainRun === undefined ? {} : { activeMainRun }),
123
139
  ...(activeChildRuns.length === 0 ? {} : { activeChildRuns }),
124
140
  sessionId: item.wake.sessionId,
125
141
  workspacePath: item.wake.workspacePath,
@@ -162,6 +178,7 @@ export async function buildStatus(input) {
162
178
  const counters = {
163
179
  'needs-human': 0,
164
180
  active: 0,
181
+ scheduled: 0,
165
182
  ready: 0,
166
183
  error: 0,
167
184
  finished: 0,
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
3
3
  import { mkdir, rm, writeFile } from 'node:fs/promises';
4
4
  import { dirname } from 'node:path';
5
5
  import { createProjectionUpdater } from '../../core/projection-updater.js';
6
- import { RETRY_REQUESTED_EVENT } from '../../domain/event-types.js';
6
+ import { RETRY_REQUESTED_EVENT, RUN_REQUESTED_EVENT } from '../../domain/event-types.js';
7
7
  import { configuredTicketSource } from '../../domain/sources.js';
8
8
  import { createEventEnvelope } from '../../lib/event-log.js';
9
9
  import { writeJsonFile } from '../../lib/json-file.js';
@@ -137,6 +137,46 @@ async function handleRequest(req, res, options, now, projectionUpdater) {
137
137
  sendJson(res, 202, { workItemKey, retryEventId: retryId });
138
138
  return;
139
139
  }
140
+ if (req.method === 'POST' &&
141
+ resource === 'work-items' &&
142
+ segments.length === 3 &&
143
+ segments[2] === 'run') {
144
+ const workItemKey = segments[1] ?? '';
145
+ const item = await stateStore.readIssueState(workItemKey);
146
+ if (item === null) {
147
+ sendJson(res, 404, { error: 'work item not found' });
148
+ return;
149
+ }
150
+ if (!item.issue.labels.includes('wake:scheduled-workflow')) {
151
+ sendJson(res, 409, { error: 'work item is not a scheduled workflow' });
152
+ return;
153
+ }
154
+ const occurredAt = now().toISOString();
155
+ const runRequestId = `run-${workItemKey}-${now().getTime()}`;
156
+ const runRequestEvent = createEventEnvelope({
157
+ eventId: runRequestId,
158
+ workItemKey,
159
+ streamScope: 'work-item',
160
+ direction: 'internal',
161
+ sourceSystem: 'wake',
162
+ sourceEventType: RUN_REQUESTED_EVENT,
163
+ sourceRefs: { repo: item.issue.repo, issueNumber: item.issue.number },
164
+ occurredAt,
165
+ ingestedAt: occurredAt,
166
+ trigger: 'immediate',
167
+ payload: { requestedBy: 'ui' },
168
+ });
169
+ const appended = await stateStore.appendEventEnvelope(runRequestEvent);
170
+ await projectionUpdater.rebuildFromEvents([appended]);
171
+ const tickRequest = {
172
+ requestId: randomUUID(),
173
+ requestedAt: now().toISOString(),
174
+ requestedBy: 'ui:run',
175
+ };
176
+ await writeJsonFile(stateStore.paths.tickRequestFile, tickRequest);
177
+ sendJson(res, 202, { workItemKey, runEventId: runRequestId });
178
+ return;
179
+ }
140
180
  if (req.method === 'GET' &&
141
181
  resource === 'work-items' &&
142
182
  segments.length === 3 &&
@@ -1,4 +1,4 @@
1
- import { CORRELATION_PRIMARY_CONFLICT_EVENT, CORRELATION_REGISTERED_EVENT, CORRELATION_RETRACTED_EVENT, RETRY_REQUESTED_EVENT, RUN_CLAIMED_EVENT, RUN_COMPLETED_EVENT, WORKFLOW_SELECTED_EVENT, WORKSPACE_CLEANED_EVENT, } from '../domain/event-types.js';
1
+ import { CORRELATION_PRIMARY_CONFLICT_EVENT, CORRELATION_REGISTERED_EVENT, CORRELATION_RETRACTED_EVENT, RETRY_REQUESTED_EVENT, RUN_REQUESTED_EVENT, RUN_CLAIMED_EVENT, RUN_COMPLETED_EVENT, WORKFLOW_SELECTED_EVENT, WORKSPACE_CLEANED_EVENT, } from '../domain/event-types.js';
2
2
  import { UNRESOLVED_WORK_ITEM_KEY, parseIssueStateRecord } from '../domain/schema.js';
3
3
  import { doneRunnerSentinel, stageFromLabels } from '../domain/stages.js';
4
4
  import { builtInDefaultWorkflowDefinition, defaultWorkflowName, selectWorkflowForEvent, workflowStageVocabulary, } from '../domain/workflows.js';
@@ -343,6 +343,16 @@ async function applyEvent(current, event, ctx, config) {
343
343
  },
344
344
  });
345
345
  }
346
+ if (event.sourceEventType === RUN_REQUESTED_EVENT) {
347
+ return parseIssueStateRecord({
348
+ ...current,
349
+ wake: {
350
+ ...current.wake,
351
+ syncedAt: event.ingestedAt,
352
+ recentEventIds: [...current.wake.recentEventIds, event.eventId].slice(-10),
353
+ },
354
+ });
355
+ }
346
356
  if (event.sourceEventType === WORKSPACE_CLEANED_EVENT) {
347
357
  return parseIssueStateRecord({
348
358
  ...current,
@@ -10,6 +10,7 @@ export const PUBLISH_FAILED_EVENT = 'wake.publish.failed';
10
10
  export const PUBLISH_INTENT_REQUESTED_EVENT = 'wake.publish.intent.requested';
11
11
  export const PUBLISH_SENT_UNCONFIRMED_EVENT = 'wake.publish.sent-unconfirmed';
12
12
  export const RETRY_REQUESTED_EVENT = 'wake.retry.requested';
13
+ export const RUN_REQUESTED_EVENT = 'wake.run.requested';
13
14
  export const RUN_CLAIMED_EVENT = 'wake.run.claimed';
14
15
  export const RUN_COMPLETED_EVENT = 'wake.run.completed';
15
16
  export const WORKFLOW_SELECTED_EVENT = 'wake.workflow.selected';
@@ -29,6 +30,7 @@ export const wakeEventTypeValues = [
29
30
  PUBLISH_INTENT_REQUESTED_EVENT,
30
31
  PUBLISH_SENT_UNCONFIRMED_EVENT,
31
32
  RETRY_REQUESTED_EVENT,
33
+ RUN_REQUESTED_EVENT,
32
34
  RUN_CLAIMED_EVENT,
33
35
  RUN_COMPLETED_EVENT,
34
36
  WORKFLOW_SELECTED_EVENT,
@@ -97,6 +99,11 @@ export const wakeEventTypeDefinitions = [
97
99
  description: 'Requests that a failed work item be retried.',
98
100
  payloadShape: '{ requestedBy }',
99
101
  },
102
+ {
103
+ type: RUN_REQUESTED_EVENT,
104
+ description: 'Requests that a scheduled work item be run immediately.',
105
+ payloadShape: '{ requestedBy }',
106
+ },
100
107
  {
101
108
  type: RUN_CLAIMED_EVENT,
102
109
  description: 'Records that Wake claimed a work item for an agent run.',
@@ -124,4 +124,4 @@ export function resolveWakeVersion(options = {}) {
124
124
  }
125
125
  return '0.1.0-dev';
126
126
  }
127
- export const wakeVersion = "g99d278b";
127
+ export const wakeVersion = "g1247508";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.2.61",
3
+ "version": "0.2.63",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {