@atolis-hq/wake 0.2.14 → 0.2.16
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.
|
@@ -77,6 +77,9 @@ export const indexHtml = `<!DOCTYPE html>
|
|
|
77
77
|
a:hover { text-decoration: underline; }
|
|
78
78
|
.resource-list { list-style: none; padding: 0; margin: 0 0 1rem; }
|
|
79
79
|
.resource-list li { display: flex; align-items: baseline; gap: 0.4rem; margin-bottom: 0.35rem; font-size: 0.8rem; }
|
|
80
|
+
.btn { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.18); color: #fff; border-radius: 6px; padding: 0.22rem 0.55rem; cursor: pointer; font-size: 0.78rem; margin-top: 0.4rem; }
|
|
81
|
+
.btn:hover:not(:disabled) { border-color: var(--accent-light); background: rgba(45, 212, 191, 0.16); }
|
|
82
|
+
.btn:disabled { cursor: default; opacity: 0.62; }
|
|
80
83
|
</style>
|
|
81
84
|
</head>
|
|
82
85
|
<body>
|
|
@@ -229,6 +232,23 @@ async function openItem(repo, number) {
|
|
|
229
232
|
if (detail.item.wake.workspacePath) {
|
|
230
233
|
body.appendChild(el('p', { class: 'meta', text: 'workspace: ' + detail.item.wake.workspacePath }));
|
|
231
234
|
}
|
|
235
|
+
const lastRun = detail.runs.at(-1);
|
|
236
|
+
if (lastRun && lastRun.sentinel === 'FAILED') {
|
|
237
|
+
const retryBtn = el('button', { type: 'button', class: 'btn', text: 'Retry' });
|
|
238
|
+
retryBtn.addEventListener('click', async () => {
|
|
239
|
+
retryBtn.disabled = true;
|
|
240
|
+
retryBtn.textContent = 'Queuing retry…';
|
|
241
|
+
try {
|
|
242
|
+
await postJson('/work-items/' + encodeURIComponent(detail.item.workItemKey) + '/retry');
|
|
243
|
+
retryBtn.textContent = 'Retry queued';
|
|
244
|
+
} catch (err) {
|
|
245
|
+
retryBtn.disabled = false;
|
|
246
|
+
retryBtn.textContent = 'Retry';
|
|
247
|
+
document.getElementById('status-summary').textContent = 'retry failed: ' + err.message;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
body.appendChild(retryBtn);
|
|
251
|
+
}
|
|
232
252
|
const resources = detail.item.correlatedResources || [];
|
|
233
253
|
if (resources.length > 0) {
|
|
234
254
|
body.appendChild(el('h3', { text: 'Resources' }));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createProjectionUpdater } from '../../core/projection-updater.js';
|
|
3
4
|
import { configuredTicketSource } from '../../domain/sources.js';
|
|
5
|
+
import { createEventEnvelope } from '../../lib/event-log.js';
|
|
4
6
|
import { writeJsonFile } from '../../lib/json-file.js';
|
|
5
7
|
import { indexHtml } from './ui-assets.js';
|
|
6
8
|
import { buildBoard, buildConfigView, buildEventsFeed, buildHealth, buildItemDetail, buildRuns, buildStatus, buildWorkspaces, } from './ui-data.js';
|
|
@@ -50,13 +52,18 @@ function parseItemPath(segments) {
|
|
|
50
52
|
}
|
|
51
53
|
export function createUiServer(options) {
|
|
52
54
|
const now = options.now ?? (() => new Date());
|
|
55
|
+
const projectionUpdater = createProjectionUpdater({
|
|
56
|
+
stateStore: options.stateStore,
|
|
57
|
+
resourceIndex: options.resourceIndex,
|
|
58
|
+
config: options.config,
|
|
59
|
+
});
|
|
53
60
|
return createServer((req, res) => {
|
|
54
|
-
void handleRequest(req, res, options, now).catch((error) => {
|
|
61
|
+
void handleRequest(req, res, options, now, projectionUpdater).catch((error) => {
|
|
55
62
|
sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
56
63
|
});
|
|
57
64
|
});
|
|
58
65
|
}
|
|
59
|
-
async function handleRequest(req, res, options, now) {
|
|
66
|
+
async function handleRequest(req, res, options, now, projectionUpdater) {
|
|
60
67
|
const url = new URL(req.url ?? '/', 'http://internal');
|
|
61
68
|
// Whether a token is required is a bind-time decision — the caller (see
|
|
62
69
|
// ui-command.ts) only ever supplies a token when it configured a
|
|
@@ -86,6 +93,47 @@ async function handleRequest(req, res, options, now) {
|
|
|
86
93
|
.filter((part) => part.length > 0)
|
|
87
94
|
.map((s) => decodeURIComponent(s));
|
|
88
95
|
const resource = segments[0];
|
|
96
|
+
if (req.method === 'POST' &&
|
|
97
|
+
resource === 'work-items' &&
|
|
98
|
+
segments.length === 3 &&
|
|
99
|
+
segments[2] === 'retry') {
|
|
100
|
+
const workItemKey = segments[1] ?? '';
|
|
101
|
+
const item = await stateStore.readIssueState(workItemKey);
|
|
102
|
+
if (item === null) {
|
|
103
|
+
sendJson(res, 404, { error: 'work item not found' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const context = item.context;
|
|
107
|
+
if (context.lastRunSentinel !== 'FAILED') {
|
|
108
|
+
sendJson(res, 409, { error: 'work item last run is not failed' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const occurredAt = now().toISOString();
|
|
112
|
+
const retryId = `retry-${workItemKey}-${now().getTime()}`;
|
|
113
|
+
const retryEvent = createEventEnvelope({
|
|
114
|
+
eventId: retryId,
|
|
115
|
+
workItemKey,
|
|
116
|
+
streamScope: 'work-item',
|
|
117
|
+
direction: 'internal',
|
|
118
|
+
sourceSystem: 'wake',
|
|
119
|
+
sourceEventType: 'wake.retry.requested',
|
|
120
|
+
sourceRefs: { repo: item.issue.repo, issueNumber: item.issue.number },
|
|
121
|
+
occurredAt,
|
|
122
|
+
ingestedAt: occurredAt,
|
|
123
|
+
trigger: 'immediate',
|
|
124
|
+
payload: { requestedBy: 'ui' },
|
|
125
|
+
});
|
|
126
|
+
const appended = await stateStore.appendEventEnvelope(retryEvent);
|
|
127
|
+
await projectionUpdater.rebuildFromEvents([appended]);
|
|
128
|
+
const tickRequest = {
|
|
129
|
+
requestId: randomUUID(),
|
|
130
|
+
requestedAt: now().toISOString(),
|
|
131
|
+
requestedBy: 'ui:retry',
|
|
132
|
+
};
|
|
133
|
+
await writeJsonFile(stateStore.paths.tickRequestFile, tickRequest);
|
|
134
|
+
sendJson(res, 202, { workItemKey, retryEventId: retryId });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
89
137
|
if (req.method === 'POST' && resource === 'tick' && segments.length === 1) {
|
|
90
138
|
const request = {
|
|
91
139
|
requestId: randomUUID(),
|
|
@@ -96,6 +144,22 @@ async function handleRequest(req, res, options, now) {
|
|
|
96
144
|
sendJson(res, 202, request);
|
|
97
145
|
return;
|
|
98
146
|
}
|
|
147
|
+
if (req.method === 'POST' &&
|
|
148
|
+
resource === 'runners' &&
|
|
149
|
+
segments.length === 3 &&
|
|
150
|
+
segments[2] === 'unpause') {
|
|
151
|
+
const runnerName = segments[1];
|
|
152
|
+
const ledger = await stateStore.readLedger();
|
|
153
|
+
const existingRunners = ledger?.runners ?? {};
|
|
154
|
+
if (existingRunners[runnerName] !== undefined) {
|
|
155
|
+
await stateStore.writeLedger({
|
|
156
|
+
schemaVersion: 1,
|
|
157
|
+
runners: { ...existingRunners, [runnerName]: { failureCount: 0 } },
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
sendJson(res, 200, { runnerName, unpaused: true });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
99
163
|
if (req.method !== 'GET') {
|
|
100
164
|
sendJson(res, 405, { error: `method not allowed for ${url.pathname}` });
|
|
101
165
|
return;
|
|
@@ -285,6 +285,21 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
285
285
|
},
|
|
286
286
|
});
|
|
287
287
|
}
|
|
288
|
+
if (event.sourceEventType === 'wake.retry.requested') {
|
|
289
|
+
const nextContext = { ...current.context };
|
|
290
|
+
delete nextContext.lastRunSentinel;
|
|
291
|
+
delete nextContext.lastFailureClass;
|
|
292
|
+
delete nextContext.blockedFromStage;
|
|
293
|
+
return parseIssueStateRecord({
|
|
294
|
+
...current,
|
|
295
|
+
context: nextContext,
|
|
296
|
+
wake: {
|
|
297
|
+
...current.wake,
|
|
298
|
+
syncedAt: event.ingestedAt,
|
|
299
|
+
recentEventIds: [...current.wake.recentEventIds, event.eventId].slice(-10),
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
288
303
|
if (event.sourceEventType === 'wake.workspace.cleaned') {
|
|
289
304
|
return parseIssueStateRecord({
|
|
290
305
|
...current,
|
package/dist/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const wakeVersion = "0.2.
|
|
1
|
+
export const wakeVersion = "0.2.16+g44a8d26";
|