@atolis-hq/wake 0.2.62 → 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.
- package/dist/src/adapters/http/ui-assets.js +18 -2
- package/dist/src/adapters/http/ui-data.js +4 -0
- package/dist/src/adapters/http/ui-server.js +41 -1
- package/dist/src/core/projection-updater.js +11 -1
- package/dist/src/domain/event-types.js +7 -0
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
|
@@ -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(
|
|
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';
|
|
@@ -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,
|
|
@@ -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.',
|
package/dist/src/version.js
CHANGED