@atolis-hq/wake 0.2.66 → 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) => el('div', {
305
- class: 'card',
306
- onclick: () => openItemModal(item.repo, item.number),
307
- }, [
308
- el('div', { class: 'title', text: item.repo + '#' + item.number + ' ' + item.title }),
309
- el('div', { class: 'meta' }, [
310
- el('span', { class: 'chip', text: item.stage }),
311
- ...(cond !== 'finished' ? [document.createTextNode(fmtMs(item.timeInStageMs) + ' in stage')] : []),
312
- ]),
313
- ...(item.labels && item.labels.length > 0
314
- ? [el('div', { class: 'meta' }, item.labels.map((label) => el('span', { class: 'chip chip-label', text: label })))]
315
- : []),
316
- ...(item.activeMainRun
317
- ? []
318
- : [el('div', { class: 'meta', text: item.lastRunSentinel ? 'last: ' + item.lastRunAction + ' → ' + item.lastRunSentinel : item.conditionReason })]),
319
- ...(item.activeMainRun ? [renderChildRun(item.activeMainRun)] : []),
320
- ...((item.activeChildRuns || []).map(renderChildRun)),
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,
@@ -124,4 +124,4 @@ export function resolveWakeVersion(options = {}) {
124
124
  }
125
125
  return '0.1.0-dev';
126
126
  }
127
- export const wakeVersion = "gda2b78e";
127
+ export const wakeVersion = "g903477c";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.2.66",
3
+ "version": "0.2.67",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {