@volter/supercode-orchestrator 0.4.71 → 0.4.73
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/board/cli.mjs +14 -5
- package/board/workflow.mjs +26 -2
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { holdDispatcherLock } from '../hermes-locks.mjs';
|
|
|
13
13
|
import { backingOf, boardExists, boardPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, tx } from './store.mjs';
|
|
14
14
|
import * as engine from './engine.mjs';
|
|
15
15
|
import { adoptSession, boardAddress, boardLabel, boardSettings, callerSession, lanesOf, localMachine, observeFleet, paused, plan, startCard, tick } from './dispatch.mjs';
|
|
16
|
-
import { loadWorkflow, statusOf, useWorkflow, workflowEntry, workflowFor } from './workflow.mjs';
|
|
16
|
+
import { loadWorkflow, scopeOf, statusOf, useWorkflow, workflowEntry, workflowFor } from './workflow.mjs';
|
|
17
17
|
import { evaluate } from './expr.mjs';
|
|
18
18
|
|
|
19
19
|
const HELP = `supercode workflow <verb> [--root <home>] [--board <slug>] [--json]
|
|
@@ -356,16 +356,20 @@ export async function runWorkflow(argv) {
|
|
|
356
356
|
got.runs = engine.runs(b.db, id, { stateType: args['state-type'] ?? null, stateName: args['state-name'] ?? null });
|
|
357
357
|
got.diagnostics = engine.diagnose(b.db, got.task, { failureLimit: settings.failureLimit });
|
|
358
358
|
got.stage = statusOf(b.db, got.task);
|
|
359
|
+
got.time = scopeOf(b.db, b, got.task, { params: workflowFor(root).params }).card.time;
|
|
359
360
|
return got;
|
|
360
361
|
});
|
|
361
362
|
const t = card.task;
|
|
362
363
|
const latest = [...card.runs].reverse().find((r) => r.summary)?.summary ?? null;
|
|
363
364
|
if (args.json) {
|
|
364
|
-
say(args, '', { task: cardDict(t), latest_summary: latest, parents: card.parents.map((p) => p.id), children: card.children, subtasks: card.subtasks, comments: card.comments, events: card.events, runs: card.runs, subscriptions: card.subscriptions, diagnostics: card.diagnostics });
|
|
365
|
+
say(args, '', { task: cardDict(t), latest_summary: latest, parents: card.parents.map((p) => p.id), children: card.children, subtasks: card.subtasks, comments: card.comments, events: card.events, runs: card.runs, time: card.time, subscriptions: card.subscriptions, diagnostics: card.diagnostics });
|
|
365
366
|
return 0;
|
|
366
367
|
}
|
|
367
368
|
const out = [`Task ${t.id}: ${t.title}`, ` status: ${card.stage}${card.stage !== t.status ? ` (${t.status} in kanban.db)` : ''}`, ` assignee: ${t.assignee || '-'}`];
|
|
368
369
|
if (t.machine) out.push(` machine: ${t.machine}`);
|
|
370
|
+
// where the card's time went: each role's runs, and the rest waiting
|
|
371
|
+
const mins = (sec) => `${Math.round(sec / 60)}m`;
|
|
372
|
+
out.push(` time: ${Object.entries(card.time).filter(([, v]) => v > 0).map(([k, v]) => `${k} ${mins(v)}`).join(', ') || '-'}`);
|
|
369
373
|
if (t.subtask_of) out.push(` subtask of: ${t.subtask_of}`);
|
|
370
374
|
if (t.tenant) out.push(` tenant: ${t.tenant}`);
|
|
371
375
|
out.push(` workspace: ${t.workspace_kind}${t.workspace_path ? ` @ ${t.workspace_path}` : ''}`);
|
|
@@ -387,9 +391,14 @@ export async function runWorkflow(argv) {
|
|
|
387
391
|
if (t.result) out.push('', 'Result:', t.result);
|
|
388
392
|
else if (latest) out.push('', 'Latest summary:', latest);
|
|
389
393
|
if (card.comments.length) { out.push('', `Comments (${card.comments.length}):`); for (const c of card.comments) out.push(` [${ts(c.created_at)}] ${c.author}: ${c.body}`); }
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
394
|
+
// the card's history, without the board's own bookkeeping (a claim, a session kept or started, a goal ask, a
|
|
395
|
+
// message queued); `--all` shows every event, `--json` always does
|
|
396
|
+
const PLUMBING = new Set(['claimed', 'session_started', 'session_resumed', 'session_adopted', 'session_replaced', 'session_closed',
|
|
397
|
+
'goal_set', 'goal_continued', 'decision_asked', 'board_message', 'board_message_sent', 'heartbeat', 'promoted', 'respawn_guarded']);
|
|
398
|
+
const history = args.all ? card.events : card.events.filter((e) => !PLUMBING.has(e.kind));
|
|
399
|
+
if (history.length) {
|
|
400
|
+
out.push('', `Events (${history.length}${history.length < card.events.length ? ` of ${card.events.length}; --all shows every one` : ''}):`);
|
|
401
|
+
for (const e of history.slice(-20)) out.push(` [${ts(e.created_at)}]${e.run_id ? ` [run ${e.run_id}]` : ''} ${e.kind}${e.payload ? ` ${JSON.stringify(e.payload)}` : ''}`);
|
|
393
402
|
}
|
|
394
403
|
if (card.runs.length) {
|
|
395
404
|
out.push('', `Runs (${card.runs.length}):`);
|
package/board/workflow.mjs
CHANGED
|
@@ -92,15 +92,20 @@ function answerTo(id) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const OPEN_OUTCOME = /^(\s*[-*] )\[ \] (.+)$/;
|
|
95
|
-
/** Whether `body` is `was` with only outcomes ticked: each changed line
|
|
96
|
-
* `- [x] X: <pointer>`; no other line added, removed or changed. */
|
|
95
|
+
/** Whether `body` is `was` with only outcomes ticked: each changed line is under the Acceptance heading, was `- [ ] X`
|
|
96
|
+
* and is now `- [x] X` or `- [x] X: <pointer>`; no other line added, removed or changed. */
|
|
97
97
|
function onlyTicks(was, body) {
|
|
98
98
|
if (typeof body !== 'string') return false;
|
|
99
99
|
const before = String(was ?? '').split('\n');
|
|
100
100
|
const after = body.split('\n');
|
|
101
101
|
if (before.length !== after.length) return false;
|
|
102
|
+
// an outcome is a line of the card's Acceptance section (its heading at any level), not a task or any other checkbox
|
|
103
|
+
let section = null;
|
|
102
104
|
return before.every((line, i) => {
|
|
105
|
+
const heading = /^#{1,6}\s+(.+?)\s*$/.exec(line);
|
|
106
|
+
if (heading) section = heading[1].toLowerCase();
|
|
103
107
|
if (line === after[i]) return true;
|
|
108
|
+
if (section !== 'acceptance') return false;
|
|
104
109
|
const open = OPEN_OUTCOME.exec(line);
|
|
105
110
|
if (!open) return false;
|
|
106
111
|
const ticked = `${open[1]}[x] ${open[2]}`;
|
|
@@ -108,6 +113,22 @@ function onlyTicks(was, body) {
|
|
|
108
113
|
});
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
/** Seconds a card's runs spent by role, and the rest of its life as `waiting`. */
|
|
117
|
+
function timeOf(db, task) {
|
|
118
|
+
const t = now();
|
|
119
|
+
const out = { coding: 0, reviewing: 0, auditing: 0, triaging: 0 };
|
|
120
|
+
const key = { implementer: 'coding', reviewer: 'reviewing', auditor: 'auditing', triager: 'triaging' };
|
|
121
|
+
for (const r of all(db, 'SELECT started_at, ended_at, metadata FROM task_runs WHERE task_id = ?', task.id)) {
|
|
122
|
+
const role = json(r.metadata, {})?.supercode?.role ?? 'implementer';
|
|
123
|
+
const spent = Math.max(0, Number(r.ended_at ?? t) - Number(r.started_at ?? t));
|
|
124
|
+
const k = key[role] ?? role;
|
|
125
|
+
out[k] = (out[k] ?? 0) + spent;
|
|
126
|
+
}
|
|
127
|
+
const worked = Object.values(out).reduce((a, b) => a + b, 0);
|
|
128
|
+
out.waiting = Math.max(0, t - Number(task.created_at ?? t) - worked);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
111
132
|
/** The session message `id` was filed for (`supercode message show`): who was asked; or null. */
|
|
112
133
|
function askedOf(id) {
|
|
113
134
|
if (!id) return null;
|
|
@@ -155,6 +176,9 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
|
|
|
155
176
|
// the message whose answer the standing block waits for (`block --waiting-on`), and that answer's id once it has landed
|
|
156
177
|
waiting_on: waitingOn,
|
|
157
178
|
answer: () => answerTo(waitingOn),
|
|
179
|
+
// the seconds its runs spent, by the role each run was for (implementer: coding, reviewer: reviewing, …), an open
|
|
180
|
+
// run counted to now; `waiting` is the rest of its life since it was filed
|
|
181
|
+
time: timeOf(db, task),
|
|
158
182
|
// whether an edit of its text only ticks outcome lines (the card's own session's door to its acceptance)
|
|
159
183
|
only_ticks: (body) => onlyTicks(task.body, body),
|
|
160
184
|
asked_of: () => askedOf(waitingOn),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.73",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
|
|
6
6
|
"exports": {
|