@atolis-hq/wake 0.2.62 → 0.2.64

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,9 +154,9 @@ 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
- let analyticsWindow = '7d';
159
+ let analyticsWindow = '1d';
160
160
  let analyticsMetric = 'runs-over-time';
161
161
  let activeViewRequest = null;
162
162
  let activeViewRequestId = 0;
@@ -427,6 +427,22 @@ function renderItemDetails(detail) {
427
427
  });
428
428
  body.appendChild(retryBtn);
429
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
+ }
430
446
  const resources = detail.item.correlatedResources || [];
431
447
  if (resources.length > 0) {
432
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) {
@@ -175,6 +178,7 @@ export async function buildStatus(input) {
175
178
  const counters = {
176
179
  'needs-human': 0,
177
180
  active: 0,
181
+ scheduled: 0,
178
182
  ready: 0,
179
183
  error: 0,
180
184
  finished: 0,
@@ -421,7 +425,7 @@ const metricsMetrics = new Set([
421
425
  'work-item-totals',
422
426
  ]);
423
427
  function parseMetricsWindow(value) {
424
- return metricsWindows.has(value) ? value : '7d';
428
+ return metricsWindows.has(value) ? value : '1d';
425
429
  }
426
430
  function parseMetricsMetric(value) {
427
431
  return metricsMetrics.has(value) ? value : 'runs-over-time';
@@ -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 = "gf68a086";
127
+ export const wakeVersion = "ga2ac867";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.2.62",
3
+ "version": "0.2.64",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {