@volter/supercode-orchestrator 0.4.147 → 0.4.148
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/ci.mjs +104 -108
- package/board/cli.mjs +42 -17
- package/board/dispatch.mjs +141 -30
- package/board/engine.mjs +84 -16
- package/board/notices.mjs +5 -0
- package/board/request-mail.mjs +59 -0
- package/board/tools.mjs +13 -7
- package/board/workflow.mjs +20 -5
- package/board/ztrack.mjs +2 -2
- package/mcp/hermes-kanban-tools.json +50 -10
- package/package.json +2 -2
package/board/workflow.mjs
CHANGED
|
@@ -11,8 +11,9 @@ import { resolve, relative, isAbsolute } from 'node:path';
|
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
12
|
import { actorOf, appendRunObservation, event as record, json, now } from './store.mjs';
|
|
13
13
|
import { evaluate, holds, render } from './expr.mjs';
|
|
14
|
-
import { completionEvidence } from './ci.mjs';
|
|
14
|
+
import { completionEvidence, reviewCandidate } from './ci.mjs';
|
|
15
15
|
import { isWorkspacePath } from './workspace.mjs';
|
|
16
|
+
import { identifyNotice } from './notices.mjs';
|
|
16
17
|
|
|
17
18
|
const loaded = new Map();
|
|
18
19
|
|
|
@@ -251,6 +252,7 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
|
|
|
251
252
|
completion_owner_address: completionOwnerOf(db, task.id),
|
|
252
253
|
review_only: Boolean(state.review_only),
|
|
253
254
|
latest_review: latestReview(db, task.id) ?? {},
|
|
255
|
+
review_candidate: reviewCandidate(board, task.id) ?? {},
|
|
254
256
|
ci: completionEvidence(board, task.id),
|
|
255
257
|
dispatch_hold: one(db, "SELECT kind FROM task_events WHERE task_id=? AND kind IN ('dispatch_held','dispatch_released','session_adopted') ORDER BY id DESC LIMIT 1", task.id)?.kind === 'dispatch_held',
|
|
256
258
|
status,
|
|
@@ -356,11 +358,15 @@ function queueTransitionNotice(db, board, entry, task, from, name, payload, befo
|
|
|
356
358
|
const afterState = beforeRun && json(one(db, 'SELECT metadata FROM task_runs WHERE id=?', beforeRun.id)?.metadata, {})?.supercode?.state;
|
|
357
359
|
if (!beforeRun || (state(task, from) === state(after, to) && beforeState === afterState) || effects.some((e) => e.kind === 'session_notice' && e.runId === beforeRun.id)) return;
|
|
358
360
|
const session = json(beforeRun.metadata, {})?.supercode;
|
|
361
|
+
const cancellation = session?.launch_cancellation;
|
|
362
|
+
if (cancellation?.key === session?.launch_key && cancellation?.status === 'cancelled' && cancellation?.cancelled === true &&
|
|
363
|
+
cancellation?.outcome === 'not_issued' && cancellation?.live === false && cancellation?.pane === null) return;
|
|
359
364
|
const address = session?.address ?? (session?.session_id && session.machine ? `sc:${session.machine}:${session.harness ?? 'claude-code'}:${session.session_id}` : null);
|
|
360
365
|
if (!address && !session?.launch_key && !session?.pane) return;
|
|
361
366
|
const scope = scopeOf(db, board, after, { params: entry.params, event: { ...payload, verb: name, from, to }, extra: { run: { id: beforeRun.id } } });
|
|
362
|
-
const
|
|
367
|
+
const body = render(entry.workflow.notify?.worker?.run_changed ?? '{{card.id}}: {{event.verb}} changed run {{run.id}} ({{event.from}} → {{event.to}}). Stop work under the previous run; follow the board’s current state. {{event.reason ?? ""}}', scope);
|
|
363
368
|
const source = one(db, 'SELECT MAX(id) AS id FROM task_events WHERE task_id = ?', task.id)?.id;
|
|
369
|
+
const text = identifyNotice({ id: source, kind: name, run_id: beforeRun.id, created_at: now() }, body);
|
|
364
370
|
const effect = { kind: 'session_notice', id: task.id, runId: beforeRun.id, address, text, sourceEvent: source, verb: name, messageKey: `board-run-notice-${randomBytes(16).toString('hex')}` };
|
|
365
371
|
const inserted = run(db, 'INSERT INTO supercode_effects (effect) VALUES (?)', JSON.stringify(effect));
|
|
366
372
|
effects.push({ ...effect, durableId: Number(inserted.lastInsertRowid) });
|
|
@@ -385,7 +391,7 @@ export function fire(db, board, entry, id, name, { roles = [], payload = {}, ext
|
|
|
385
391
|
const candidates = transitionsFor(workflow, from, name);
|
|
386
392
|
if (!candidates.length) throw new Error(`${id} is ${from}; ${name.replace(/_/g, '-')} does not apply there`);
|
|
387
393
|
// `sender`: the roles the event was sent with (a validator can ask who is asking)
|
|
388
|
-
const scope = scopeOf(db, board, task, { params, event: payload, extra: { lanes: lanesIn(entry), ...extra, sender: roles } });
|
|
394
|
+
const scope = scopeOf(db, board, task, { params, event: payload, extra: { lanes: lanesIn(entry), ...extra, sender: roles, verb: name } });
|
|
389
395
|
const allowed = candidates.filter((t) => !t.by?.length || t.by.some((r) => roles.includes(r)));
|
|
390
396
|
if (!allowed.length) {
|
|
391
397
|
const state = name === 'complete' ? 'done' : name === 'cancel' ? 'cancelled' : null;
|
|
@@ -555,7 +561,7 @@ const FORMAT = {
|
|
|
555
561
|
blocked: ({ e, after, source }) => ({ reason: e.reason ?? null, on: e.on ?? null, waiting_on: e.waiting_on ?? null, recurrences: Number(after.block_recurrences || 0), source_status: source }),
|
|
556
562
|
block_loop_detected: ({ e, after, source, params }) => ({ reason: e.reason ?? null, kind: e.kind ?? null, recurrences: Number(after.block_recurrences || 0), limit: params.block_recurrence_limit, source_status: source }),
|
|
557
563
|
dependency_wait: ({ e, source }) => ({ reason: e.reason ?? null, on: e.on ?? null, source_status: source }),
|
|
558
|
-
review_requested: ({ e, before, after }) => ({ summary: firstLine(e.summary), ci: e.ci ?? null, implementer: before.assignee, reviewer: after.assignee !== before.assignee ? after.assignee : (e.reviewer ?? null) }),
|
|
564
|
+
review_requested: ({ e, before, after }) => ({ summary: firstLine(e.summary), ci: e.ci ?? null, pr: e.ci?.pr ?? null, commit: e.ci?.commit ?? null, implementer: before.assignee, reviewer: after.assignee !== before.assignee ? after.assignee : (e.reviewer ?? null) }),
|
|
559
565
|
// the reviewer is the lane that reviewed (the card's assignee while in review), unless the verdict names one
|
|
560
566
|
changes_requested: ({ e, before, after }) => ({ reason: e.reason ?? null, reviewer: e.reviewer ?? before.assignee ?? null, implementer: after.assignee }),
|
|
561
567
|
review_reopened: ({ after, to }) => ({ status: to, implementer: after.assignee }),
|
|
@@ -775,6 +781,9 @@ const ACTIONS = {
|
|
|
775
781
|
if (said?.workspace) {
|
|
776
782
|
const task = cardOf(ctx.db, ctx.id), ws = said.workspace;
|
|
777
783
|
if (task.current_run_id) throw new Error('end the current run before changing its workspace');
|
|
784
|
+
ACTIONS.forget_session(ctx, '*');
|
|
785
|
+
const prior = one(ctx.db, 'SELECT workspace_anchor FROM supercode_cards WHERE task_id=?', ctx.id);
|
|
786
|
+
record(ctx.db, ctx.id, 'workspace_changed', { from: { kind: task.workspace_kind, repository: prior?.workspace_anchor, branch: task.branch_name }, to: { kind: ws.kind, repository: ws.repository, branch: ws.branch } }, null);
|
|
778
787
|
run(ctx.db, 'UPDATE tasks SET workspace_kind=?,workspace_path=?,branch_name=? WHERE id=?', ws.kind, ws.path ?? null, ws.branch ?? null, ctx.id);
|
|
779
788
|
cardField(ctx, 'workspace_anchor', ws.repository ?? null);
|
|
780
789
|
cardField(ctx, 'workspace_root', null);
|
|
@@ -854,12 +863,18 @@ const ACTIONS = {
|
|
|
854
863
|
if (!to) throw new Error(`the workflow asks nobody about ${ctx.id} (ask.to is empty)`);
|
|
855
864
|
const id = `m-${randomBytes(12).toString('hex')}`;
|
|
856
865
|
ctx.scope.asked = id;
|
|
857
|
-
|
|
866
|
+
const batch = ctx.scope.verb === 'request_state' ? {
|
|
867
|
+
requester: actorOf() ?? ctx.scope.requester,
|
|
868
|
+
window_seconds: Math.max(1, Math.min(60, Number(ctx.entry.params.request_batch_seconds) || 10)),
|
|
869
|
+
} : null;
|
|
870
|
+
record(ctx.db, ctx.id, 'board_message', { to: String(to), text: batch ? String(text ?? '') : `${String(text ?? '')}\n\nAnswer with reply_to ${id}.`, message_id: id, ...(batch ? { batch } : {}) }, null);
|
|
858
871
|
ctx.effects.push({ kind: 'board_message', id: ctx.id });
|
|
859
872
|
},
|
|
860
873
|
send_to_actor(ctx, text) {
|
|
861
874
|
const effect = { kind: 'send_to_actor', id: ctx.id, runId: ctx.runId, text, messageKey: `board-actor-${randomBytes(16).toString('hex')}` };
|
|
862
875
|
const inserted = run(ctx.db, 'INSERT INTO supercode_effects (effect) VALUES (?)', JSON.stringify(effect));
|
|
876
|
+
effect.text = identifyNotice({ id: `notice-${inserted.lastInsertRowid}`, kind: 'actor_notice', run_id: ctx.runId, created_at: now() }, text);
|
|
877
|
+
run(ctx.db, 'UPDATE supercode_effects SET effect=? WHERE id=?', JSON.stringify(effect), Number(inserted.lastInsertRowid));
|
|
863
878
|
ctx.effects.push({ ...effect, durableId: Number(inserted.lastInsertRowid) });
|
|
864
879
|
},
|
|
865
880
|
};
|
package/board/ztrack.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { RuntimeRows } from './rows.mjs';
|
|
|
8
8
|
import { DatabaseSync } from 'node:sqlite';
|
|
9
9
|
import { actorOf, event, json, now, setActor } from './store.mjs';
|
|
10
10
|
import { fire, projectStatus, statusOf, workflowFor } from './workflow.mjs';
|
|
11
|
-
import { inspectCompletionEvidence, withCompletionEvidence } from './ci.mjs';
|
|
11
|
+
import { completionPhase, inspectCompletionEvidence, withCompletionEvidence } from './ci.mjs';
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
14
14
|
const hash = (text) => createHash('sha256').update(text).digest('hex');
|
|
@@ -598,7 +598,7 @@ class ZtrackBoard {
|
|
|
598
598
|
for (const row of prepared.rows) {
|
|
599
599
|
const payload = json(row.payload, {}) ?? {};
|
|
600
600
|
if (row.kind !== 'completed' || payload.__supercode_export || !this.db.prepare('SELECT 1 FROM tasks WHERE id=?').get(row.task_id)) continue;
|
|
601
|
-
prepared.evidence.set(row.id, inspectCompletionEvidence(this.board(), row.task_id, entry, payload));
|
|
601
|
+
prepared.evidence.set(row.id, inspectCompletionEvidence(this.board(), row.task_id, entry, payload, completionPhase(this.board(), row.task_id, entry, 'complete', payload, ['hermes'])));
|
|
602
602
|
}
|
|
603
603
|
return prepared;
|
|
604
604
|
} finally { compat.close(); }
|
|
@@ -102,11 +102,19 @@
|
|
|
102
102
|
},
|
|
103
103
|
"commit": {
|
|
104
104
|
"type": "string",
|
|
105
|
-
"description": "Exact
|
|
105
|
+
"description": "Exact pushed branch head for a subtask, or merged deployment commit for an arc."
|
|
106
106
|
},
|
|
107
107
|
"ci_run": {
|
|
108
108
|
"type": "integer",
|
|
109
109
|
"description": "GitHub Actions deployment run id for that exact commit."
|
|
110
|
+
},
|
|
111
|
+
"branch": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"description": "Pushed worktree branch for source completion."
|
|
114
|
+
},
|
|
115
|
+
"reviewed_sha": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "The full PR head SHA this reviewer ran, matching the recorded request."
|
|
110
118
|
}
|
|
111
119
|
},
|
|
112
120
|
"required": []
|
|
@@ -146,7 +154,7 @@
|
|
|
146
154
|
},
|
|
147
155
|
{
|
|
148
156
|
"name": "kanban_request_review",
|
|
149
|
-
"description": "
|
|
157
|
+
"description": "Request independent review of the opened PR at its exact pushed head. The dispatcher starts the declared reviewer when the arc tasks are closed. Deployment CI is not a review gate.",
|
|
150
158
|
"inputSchema": {
|
|
151
159
|
"type": "object",
|
|
152
160
|
"properties": {
|
|
@@ -173,11 +181,19 @@
|
|
|
173
181
|
},
|
|
174
182
|
"commit": {
|
|
175
183
|
"type": "string",
|
|
176
|
-
"description": "Exact
|
|
184
|
+
"description": "Exact pushed worktree branch head for independent review."
|
|
177
185
|
},
|
|
178
|
-
"
|
|
179
|
-
"type": "
|
|
180
|
-
"description": "
|
|
186
|
+
"branch": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": "The worktree branch whose pushed head is reviewed before merge."
|
|
189
|
+
},
|
|
190
|
+
"pr": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"description": "Opened PR URL for the candidate in this worktree repository."
|
|
193
|
+
},
|
|
194
|
+
"pr_head": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"description": "Full 40-character SHA of the pushed PR head to review."
|
|
181
197
|
}
|
|
182
198
|
},
|
|
183
199
|
"required": [
|
|
@@ -494,13 +510,21 @@
|
|
|
494
510
|
},
|
|
495
511
|
{
|
|
496
512
|
"name": "kanban_request_state",
|
|
497
|
-
"description": "Request a manager decision on block, pause, done or cancelled through the home workflow.
|
|
513
|
+
"description": "Request a manager decision on block, pause, done or cancelled for one or several task_ids through the home workflow. Nearby requests from the same session share one retained mail. Includes the home's rules and leaves card/run state unchanged.",
|
|
498
514
|
"inputSchema": {
|
|
499
515
|
"type": "object",
|
|
500
516
|
"properties": {
|
|
501
517
|
"task_id": {
|
|
502
518
|
"type": "string"
|
|
503
519
|
},
|
|
520
|
+
"task_ids": {
|
|
521
|
+
"type": "array",
|
|
522
|
+
"items": {
|
|
523
|
+
"type": "string"
|
|
524
|
+
},
|
|
525
|
+
"minItems": 1,
|
|
526
|
+
"description": "Cards whose state is requested together; each must be in your permitted scope."
|
|
527
|
+
},
|
|
504
528
|
"state": {
|
|
505
529
|
"type": "string",
|
|
506
530
|
"enum": [
|
|
@@ -524,15 +548,31 @@
|
|
|
524
548
|
},
|
|
525
549
|
"commit": {
|
|
526
550
|
"type": "string",
|
|
527
|
-
"description": "
|
|
551
|
+
"description": "Pushed branch head for a source verdict or subtask, or merged commit for final arc completion."
|
|
528
552
|
},
|
|
529
553
|
"ci_run": {
|
|
530
554
|
"type": "integer",
|
|
531
|
-
"description": "
|
|
555
|
+
"description": "Legacy receipt field; deployment is not a board completion requirement."
|
|
532
556
|
},
|
|
533
557
|
"deployed_sha": {
|
|
534
558
|
"type": "string",
|
|
535
|
-
"description": "
|
|
559
|
+
"description": "Legacy receipt field; deployment is not a board completion requirement."
|
|
560
|
+
},
|
|
561
|
+
"branch": {
|
|
562
|
+
"type": "string",
|
|
563
|
+
"description": "The pushed worktree branch the reviewer inspected."
|
|
564
|
+
},
|
|
565
|
+
"reviewed_sha": {
|
|
566
|
+
"type": "string",
|
|
567
|
+
"description": "Exact pushed branch head the independent reviewer actually inspected."
|
|
568
|
+
},
|
|
569
|
+
"pr": {
|
|
570
|
+
"type": "string",
|
|
571
|
+
"description": "PR URL, optional when using the recorded review candidate."
|
|
572
|
+
},
|
|
573
|
+
"pr_head": {
|
|
574
|
+
"type": "string",
|
|
575
|
+
"description": "Full PR head SHA; reviewers can use reviewed_sha instead."
|
|
536
576
|
}
|
|
537
577
|
},
|
|
538
578
|
"required": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.148",
|
|
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": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@volter/supercode-harness-sdk": "^0.3.41",
|
|
28
|
-
"@volter/ztrack": "^2.4.
|
|
28
|
+
"@volter/ztrack": "^2.4.20",
|
|
29
29
|
"yaml": "^2.9.0",
|
|
30
30
|
"@volter/supercode-teams": "^0.2.145"
|
|
31
31
|
},
|