@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.
@@ -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 text = 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);
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
- record(ctx.db, ctx.id, 'board_message', { to: String(to), text: `${String(text ?? '')}\n\nAnswer with reply_to ${id}.`, message_id: id }, null);
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 commit deployed by the CI run."
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": "Hand the task off for review: implementation, self-review, and verification are complete and you want a human (or reviewer) to look before it is marked done. Moves the task to the 'review' column and notifies the subscriber. Unlike ``kanban_block`` this is NOT a blocker \u2014 it never counts toward unblock-loop detection, so a task can cycle through review across follow-ups without ever being falsely escalated to triage. Use this instead of blocking with a free-form 'review-required:' reason.",
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 commit deployed by the CI run."
184
+ "description": "Exact pushed worktree branch head for independent review."
177
185
  },
178
- "ci_run": {
179
- "type": "integer",
180
- "description": "GitHub Actions deployment run id for that exact commit."
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. Includes its rules in manager mail and leaves card/run state unchanged.",
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": "Exact commit deployed by the CI run."
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": "GitHub Actions deployment run id for that exact commit."
555
+ "description": "Legacy receipt field; deployment is not a board completion requirement."
532
556
  },
533
557
  "deployed_sha": {
534
558
  "type": "string",
535
- "description": "The deployed commit the independent reviewer actually inspected."
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.147",
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.19",
28
+ "@volter/ztrack": "^2.4.20",
29
29
  "yaml": "^2.9.0",
30
30
  "@volter/supercode-teams": "^0.2.145"
31
31
  },