@atolis-hq/wake 0.2.65 → 0.2.67
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.
|
@@ -47,6 +47,12 @@ export const indexHtml = `<!DOCTYPE html>
|
|
|
47
47
|
.pill-polling { background: #1f3350; color: #7fb3ff; }
|
|
48
48
|
.pill-working { background: #2d1f50; color: #c79bff; }
|
|
49
49
|
.pill-paused { background: #4a3510; color: #ffcf7f; }
|
|
50
|
+
.pill-ready { background: #1f3d2c; color: #7fe3a3; }
|
|
51
|
+
.pill-active { background: #2d1f50; color: #c79bff; }
|
|
52
|
+
.pill-scheduled { background: #1f3350; color: #7fb3ff; }
|
|
53
|
+
.pill-needs-human { background: #4a3510; color: #ffcf7f; }
|
|
54
|
+
.pill-error { background: #3d1f1f; color: #ff8f7f; }
|
|
55
|
+
.pill-finished { background: #252830; color: #9aa2ad; }
|
|
50
56
|
nav { display: flex; gap: 0.25rem; padding: 0.4rem 1rem 0 0.3rem; background: var(--brand-darker); border-bottom: 1px solid #2c313a; }
|
|
51
57
|
nav button { background: none; border: none; border-bottom: 2px solid transparent; color: rgba(255, 255, 255, 0.65); padding: 0.4rem 0.7rem 0.45rem; margin-bottom: -1px; cursor: pointer; font-size: 0.85rem; transition: color 0.12s ease; }
|
|
52
58
|
nav button:hover { color: #fff; }
|
|
@@ -61,8 +67,15 @@ export const indexHtml = `<!DOCTYPE html>
|
|
|
61
67
|
.card .meta { color: #9aa2ad; font-size: 0.72rem; }
|
|
62
68
|
.child-run { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 0.45rem; align-items: center; margin-top: 0.45rem; padding: 0.4rem; border-radius: 6px; background: #181c22; border: 1px solid #334155; color: #cbd5e1; font-size: 0.72rem; }
|
|
63
69
|
.child-run .dot { width: 0.48rem; height: 0.48rem; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px rgba(45, 212, 191, 0.14); }
|
|
70
|
+
.child-run .dot-watch { background: #ffcf7f; box-shadow: 0 0 0 3px rgba(255, 207, 127, 0.14); }
|
|
64
71
|
.child-run .run-title { font-weight: 650; color: #e5e7eb; }
|
|
65
72
|
.child-run .run-meta { grid-column: 2; color: #94a3b8; }
|
|
73
|
+
.card-stats { color: #8892a0; font-size: 0.7rem; margin-top: 0.3rem; }
|
|
74
|
+
.card-ready { border-left: 3px solid #7fe3a3; }
|
|
75
|
+
.card-active { border-left: 3px solid #c79bff; }
|
|
76
|
+
.card-scheduled { border-left: 3px solid #7fb3ff; }
|
|
77
|
+
.card-needs-human { border-left: 3px solid #ffcf7f; }
|
|
78
|
+
.card-error { border-left: 3px solid #ff8f7f; }
|
|
66
79
|
.chip { display: inline-block; background: #2c313a; border-radius: 4px; padding: 0.05rem 0.35rem; font-size: 0.68rem; margin-right: 0.2rem; }
|
|
67
80
|
.chip-label { background: transparent; border: 1px solid #3a4150; color: #9aa2ad; margin-bottom: 0.2rem; }
|
|
68
81
|
table { border-collapse: collapse; width: 100%; font-size: 0.8rem; }
|
|
@@ -290,9 +303,24 @@ async function renderBoard(context) {
|
|
|
290
303
|
const board = await getJson('/board', context.signal);
|
|
291
304
|
if (!isActiveRequest(context.requestId)) return;
|
|
292
305
|
const main = document.getElementById('main');
|
|
306
|
+
const COND_PILL = {
|
|
307
|
+
ready: 'pill-ready',
|
|
308
|
+
active: 'pill-active',
|
|
309
|
+
scheduled: 'pill-scheduled',
|
|
310
|
+
'needs-human': 'pill-needs-human',
|
|
311
|
+
error: 'pill-error',
|
|
312
|
+
finished: 'pill-finished',
|
|
313
|
+
};
|
|
314
|
+
const COND_CARD = {
|
|
315
|
+
ready: 'card-ready',
|
|
316
|
+
active: 'card-active',
|
|
317
|
+
scheduled: 'card-scheduled',
|
|
318
|
+
'needs-human': 'card-needs-human',
|
|
319
|
+
error: 'card-error',
|
|
320
|
+
};
|
|
293
321
|
const renderChildRun = (run) => el('div', { class: 'child-run' }, [
|
|
294
|
-
el('span', { class: 'dot' }),
|
|
295
|
-
el('div', { class: 'run-title', text: run.action + ' running' }),
|
|
322
|
+
el('span', { class: 'dot' + (run.isWatcher ? ' dot-watch' : '') }),
|
|
323
|
+
el('div', { class: 'run-title', text: (run.isWatcher ? '⟳ ' : '') + run.action + ' running' }),
|
|
296
324
|
el('div', {
|
|
297
325
|
class: 'run-meta',
|
|
298
326
|
text: [run.runnerName, run.tier, fmtMs(run.ageMs)].filter(Boolean).join(' · '),
|
|
@@ -301,24 +329,29 @@ async function renderBoard(context) {
|
|
|
301
329
|
const columns = el('div', { class: 'columns' }, CONDITIONS.map((cond) => {
|
|
302
330
|
const items = board.filter((c) => c.condition === cond);
|
|
303
331
|
if (cond === 'finished') items.sort((a, b) => a.timeInStageMs - b.timeInStageMs);
|
|
304
|
-
const cards = items.map((item) =>
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
: [
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
332
|
+
const cards = items.map((item) => {
|
|
333
|
+
const nonWakeLabels = (item.labels || []).filter((l) => !l.startsWith('wake:'));
|
|
334
|
+
const pillClass = 'pill ' + (COND_PILL[item.condition] || 'pill-finished');
|
|
335
|
+
const cardCondClass = COND_CARD[item.condition] ?? '';
|
|
336
|
+
const statsText = item.totalRuns + ' runs | ' + fmtCost(item.totalCostUsd) + ' | ' + fmtCompact(item.totalTokens) + ' tokens';
|
|
337
|
+
const isFinished = item.condition === 'finished';
|
|
338
|
+
return el('div', {
|
|
339
|
+
class: 'card' + (cardCondClass ? ' ' + cardCondClass : ''),
|
|
340
|
+
onclick: () => openItemModal(item.repo, item.number),
|
|
341
|
+
}, [
|
|
342
|
+
el('div', { class: 'title', text: item.repo + '#' + item.number + ' ' + item.title }),
|
|
343
|
+
el('div', { class: 'meta', style: 'display:flex;align-items:center;gap:0.3rem;flex-wrap:wrap;margin-top:0.2rem;' }, [
|
|
344
|
+
el('span', { class: pillClass, text: item.condition }),
|
|
345
|
+
document.createTextNode('| ' + item.workflow + ' | ' + item.stage + (isFinished ? '' : ' | ' + fmtMs(item.timeInStageMs) + ' in stage')),
|
|
346
|
+
]),
|
|
347
|
+
el('div', { class: 'card-stats', text: statsText }),
|
|
348
|
+
...(nonWakeLabels.length > 0
|
|
349
|
+
? [el('div', { class: 'meta', style: 'margin-top:0.2rem;' }, nonWakeLabels.map((label) => el('span', { class: 'chip chip-label', text: label })))]
|
|
350
|
+
: []),
|
|
351
|
+
...(item.activeMainRun ? [renderChildRun(item.activeMainRun)] : []),
|
|
352
|
+
...((item.activeChildRuns || []).map(renderChildRun)),
|
|
353
|
+
]);
|
|
354
|
+
});
|
|
322
355
|
return el('div', { class: 'col' + (items.length === 0 ? ' col-empty' : '') }, [
|
|
323
356
|
el('h2', { text: cond + ' (' + items.length + ')' }),
|
|
324
357
|
...cards,
|
|
@@ -529,6 +562,13 @@ function fmtTokens(tokenUsage) {
|
|
|
529
562
|
return total >= 1000 ? (total / 1000).toFixed(1) + 'k' : String(total);
|
|
530
563
|
}
|
|
531
564
|
|
|
565
|
+
function fmtCompact(value) {
|
|
566
|
+
const n = Number(value || 0);
|
|
567
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
|
|
568
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
|
|
569
|
+
return String(n);
|
|
570
|
+
}
|
|
571
|
+
|
|
532
572
|
function fmtNumber(value) {
|
|
533
573
|
return Number(value || 0).toLocaleString();
|
|
534
574
|
}
|
|
@@ -2,7 +2,7 @@ import { readFile, readdir, stat } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { buildResourceUri } from '../../domain/resource-uri.js';
|
|
4
4
|
import { isTerminalStage } from '../../domain/stages.js';
|
|
5
|
-
import { workflowForProjection } from '../../domain/workflows.js';
|
|
5
|
+
import { workflowForProjection, workflowNameForProjection } from '../../domain/workflows.js';
|
|
6
6
|
function parseLockMetadata(raw) {
|
|
7
7
|
try {
|
|
8
8
|
const parsed = JSON.parse(raw);
|
|
@@ -89,6 +89,7 @@ function activeChildRunsForItem(item, runs, now) {
|
|
|
89
89
|
status: run.status,
|
|
90
90
|
startedAt: run.startedAt,
|
|
91
91
|
ageMs: now.getTime() - Date.parse(run.startedAt),
|
|
92
|
+
isWatcher: run.metadata?.watcher === true,
|
|
92
93
|
...(run.routing?.runnerName === undefined ? {} : { runnerName: run.routing.runnerName }),
|
|
93
94
|
...(run.routing?.runnerKind === undefined ? {} : { runnerKind: run.routing.runnerKind }),
|
|
94
95
|
...(run.routing?.tier === undefined ? {} : { tier: run.routing.tier }),
|
|
@@ -106,6 +107,25 @@ export async function buildBoard(input) {
|
|
|
106
107
|
// with items x run-history size instead of just run-history size.
|
|
107
108
|
const runs = await input.stateStore.listRunRecordSummaries();
|
|
108
109
|
const runsById = new Map(runs.map((run) => [run.runId, run]));
|
|
110
|
+
const runTotalsByItem = new Map();
|
|
111
|
+
for (const run of runs) {
|
|
112
|
+
const existing = runTotalsByItem.get(run.workItemKey) ?? {
|
|
113
|
+
totalRuns: 0,
|
|
114
|
+
totalTokens: 0,
|
|
115
|
+
totalCostUsd: 0,
|
|
116
|
+
};
|
|
117
|
+
existing.totalRuns += 1;
|
|
118
|
+
const usage = run.tokenUsage;
|
|
119
|
+
if (usage !== undefined) {
|
|
120
|
+
existing.totalTokens +=
|
|
121
|
+
(usage.inputTokens ?? 0) +
|
|
122
|
+
(usage.outputTokens ?? 0) +
|
|
123
|
+
(usage.cacheCreationInputTokens ?? 0) +
|
|
124
|
+
(usage.cacheReadInputTokens ?? 0);
|
|
125
|
+
existing.totalCostUsd += usage.costUsd ?? 0;
|
|
126
|
+
}
|
|
127
|
+
runTotalsByItem.set(run.workItemKey, existing);
|
|
128
|
+
}
|
|
109
129
|
return items.map((item) => {
|
|
110
130
|
const lastRun = item.wake.lastRunId === undefined ? null : (runsById.get(item.wake.lastRunId) ?? null);
|
|
111
131
|
const { condition, reason } = deriveCondition(item, lastRun, input.config);
|
|
@@ -122,16 +142,25 @@ export async function buildBoard(input) {
|
|
|
122
142
|
...(lastRun.routing?.tier === undefined ? {} : { tier: lastRun.routing.tier }),
|
|
123
143
|
}
|
|
124
144
|
: undefined;
|
|
145
|
+
const runTotals = runTotalsByItem.get(item.workItemKey) ?? {
|
|
146
|
+
totalRuns: 0,
|
|
147
|
+
totalTokens: 0,
|
|
148
|
+
totalCostUsd: 0,
|
|
149
|
+
};
|
|
125
150
|
return {
|
|
126
151
|
repo: item.issue.repo,
|
|
127
152
|
number: item.issue.number,
|
|
128
153
|
title: item.issue.title,
|
|
129
154
|
url: item.issue.url,
|
|
130
155
|
stage: item.wake.stage,
|
|
156
|
+
workflow: workflowNameForProjection(item, input.config),
|
|
131
157
|
labels: item.issue.labels,
|
|
132
158
|
condition,
|
|
133
159
|
conditionReason: reason,
|
|
134
160
|
timeInStageMs: timeInStageMs(item, input.now),
|
|
161
|
+
totalRuns: runTotals.totalRuns,
|
|
162
|
+
totalTokens: runTotals.totalTokens,
|
|
163
|
+
totalCostUsd: runTotals.totalCostUsd,
|
|
135
164
|
lastRunAction: lastRun?.action,
|
|
136
165
|
lastRunSentinel: lastRun?.sentinel,
|
|
137
166
|
lastRunStatus: lastRun?.status,
|
|
@@ -85,10 +85,6 @@ export function createPolicyEngine() {
|
|
|
85
85
|
if (issue.issue.labels.some((label) => alwaysManualIgnoredLabels.includes(label))) {
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
|
-
const context = issue.context;
|
|
89
|
-
if (config.workflowSelectors.length > 0) {
|
|
90
|
-
return typeof context.workflow === 'string';
|
|
91
|
-
}
|
|
92
88
|
// Defense-in-depth: the issues source filters PR-shaped items at poll
|
|
93
89
|
// time, so no NEW projection can ever have isPullRequest: true. But a
|
|
94
90
|
// pre-existing state/<workId>.json written by a pre-this-branch
|
|
@@ -97,13 +93,23 @@ export function createPolicyEngine() {
|
|
|
97
93
|
if (issue.issue.isPullRequest) {
|
|
98
94
|
return false;
|
|
99
95
|
}
|
|
100
|
-
|
|
96
|
+
// Source policy is always checked first — workflow selectors are optional secondary filter.
|
|
97
|
+
if (!labelsAndAssigneesQualify({
|
|
101
98
|
labels: issue.issue.labels,
|
|
102
99
|
assignees: issue.issue.assignees,
|
|
103
100
|
requiredLabels: config.sources.github.policy.requiredLabels,
|
|
104
101
|
ignoredLabels: config.sources.github.policy.ignoredLabels,
|
|
105
102
|
requiredAssignees: config.sources.github.policy.requiredAssignees,
|
|
106
|
-
})
|
|
103
|
+
})) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
// If workflow selectors are configured as the routing mechanism, the issue must have
|
|
107
|
+
// a workflow assigned (set by a matching selector when the issue was first minted).
|
|
108
|
+
if (config.workflowSelectors.length > 0) {
|
|
109
|
+
const context = issue.context;
|
|
110
|
+
return typeof context.workflow === 'string';
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
107
113
|
},
|
|
108
114
|
needsWakeAction(issue, workflow = builtInDefaultWorkflowDefinition, config) {
|
|
109
115
|
const context = issue.context;
|
|
@@ -289,9 +295,7 @@ export function createPolicyEngine() {
|
|
|
289
295
|
const workflow = unresolved.payload.workflow;
|
|
290
296
|
return typeof workflow === 'string' && config.workflows[workflow] !== undefined;
|
|
291
297
|
}
|
|
292
|
-
|
|
293
|
-
return selectWorkflowForEvent(unresolved, config) !== null;
|
|
294
|
-
}
|
|
298
|
+
// Source policy enforcement is always checked first — workflow selectors are optional secondary routing.
|
|
295
299
|
if (kind === 'issue') {
|
|
296
300
|
// Real github source stamps payload.ticket (sourceEventType
|
|
297
301
|
// 'ticket.upsert'); the fake ticketing harness stamps payload.issue
|
|
@@ -304,13 +308,20 @@ export function createPolicyEngine() {
|
|
|
304
308
|
if (ticket === undefined) {
|
|
305
309
|
return false;
|
|
306
310
|
}
|
|
307
|
-
|
|
311
|
+
if (!labelsAndAssigneesQualify({
|
|
308
312
|
labels: Array.isArray(ticket.labels) ? ticket.labels : [],
|
|
309
313
|
assignees: Array.isArray(ticket.assignees) ? ticket.assignees : [],
|
|
310
314
|
requiredLabels: config.sources.github.policy.requiredLabels,
|
|
311
315
|
ignoredLabels: config.sources.github.policy.ignoredLabels,
|
|
312
316
|
requiredAssignees: config.sources.github.policy.requiredAssignees,
|
|
313
|
-
})
|
|
317
|
+
})) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
// Issue passed source policy; if workflow selectors are configured, also check routing.
|
|
321
|
+
if (config.workflowSelectors.length > 0) {
|
|
322
|
+
return selectWorkflowForEvent(unresolved, config) !== null;
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
314
325
|
}
|
|
315
326
|
if (kind === 'pr') {
|
|
316
327
|
if (!config.sources.github.pullRequests.enabled) {
|
|
@@ -318,10 +329,17 @@ export function createPolicyEngine() {
|
|
|
318
329
|
}
|
|
319
330
|
const pr = unresolved.payload.pr;
|
|
320
331
|
const requiredAuthors = config.sources.github.pullRequests.policy.requiredAuthors;
|
|
321
|
-
|
|
322
|
-
|
|
332
|
+
// If requiredAuthors is empty, source policy allows any author.
|
|
333
|
+
if (requiredAuthors.length > 0) {
|
|
334
|
+
if (typeof pr?.author !== 'string' || !requiredAuthors.includes(pr.author)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
323
337
|
}
|
|
324
|
-
|
|
338
|
+
// PR passed source policy; if workflow selectors are configured, also check routing.
|
|
339
|
+
if (config.workflowSelectors.length > 0) {
|
|
340
|
+
return selectWorkflowForEvent(unresolved, config) !== null;
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
325
343
|
}
|
|
326
344
|
return false;
|
|
327
345
|
},
|
package/dist/src/version.js
CHANGED