@volter/supercode-orchestrator 0.4.42 → 0.4.44

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.
@@ -383,7 +383,9 @@ export function launchRun(board, entry, id, lane, fleet, settings) {
383
383
  const surface = lane.surface === 'pane' ? 'pane' : 'headless';
384
384
  const scope = tx(board, (db) => scopeOf(db, board, task, { params: entry.params }));
385
385
  const where = whereOf(board, task);
386
- const prompt = render(spec.prompt, { ...scope, where, context: tx(board, (db) => cardContext(db, id)), doors: doors(entry.workflow, status, spec.role, id, where, surface) });
386
+ // a subtask's run also sees its arc: the arc's text, tasks and thread (where its owner's rulings are)
387
+ const arcContext = task.subtask_of ? tx(board, (db) => cardContext(db, task.subtask_of)) : '';
388
+ const prompt = render(spec.prompt, { ...scope, where, context: tx(board, (db) => cardContext(db, id)), arc_context: arcContext, doors: doors(entry.workflow, status, spec.role, id, where, surface) });
387
389
  // the run's session record knows its status, role and slot (a reviewer's slot is never the implementer's)
388
390
  const meta = { status, role: spec.role, slot: spec.session };
389
391
  return surface === 'pane'
@@ -4,6 +4,7 @@
4
4
  // must hold and what happens; this module applies it to the board file and keeps no rule of its own. Its actions are
5
5
  // the engine's primitives (the rows Hermes's tables hold); the I/O an action asks for (stopping a worker, closing a
6
6
  // pane, sending a session a message, cleaning a workspace) is answered as effects the caller runs after commit.
7
+ import { spawnSync } from 'node:child_process';
7
8
  import { event as record, json, now } from './store.mjs';
8
9
  import { evaluate, holds, render } from './expr.mjs';
9
10
 
@@ -69,11 +70,21 @@ function pattern(p) {
69
70
  return text.startsWith('(?i)') ? new RegExp(text.slice(4), 'i') : new RegExp(text);
70
71
  }
71
72
 
73
+ // the time of the latest commit in a repository checkout, or null when it is none (asked only when an expression does)
74
+ function lastCommitAt(path) {
75
+ if (!path) return null;
76
+ const r = spawnSync('git', ['-C', path, 'log', '-1', '--format=%ct', 'HEAD'], { encoding: 'utf8', timeout: 10_000 });
77
+ const t = Number(r.stdout?.trim());
78
+ return r.status === 0 && Number.isFinite(t) && t > 0 ? t : null;
79
+ }
80
+
72
81
  /** The scope an expression is evaluated in: the card (with its relations), the event, params, time, extras. */
73
82
  export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}) {
74
83
  const status = statusOf(db, task);
75
- const parents = all(db, 'SELECT t.id, t.status, t.title FROM tasks t JOIN task_links l ON l.parent_id = t.id WHERE l.child_id = ?', task.id);
76
- const children = all(db, 'SELECT t.id, t.status FROM tasks t JOIN task_links l ON l.child_id = t.id WHERE l.parent_id = ?', task.id);
84
+ // a related card's status is the workflow's, as the card's own is (`stored_as`: what kanban.db holds)
85
+ const related = 'COALESCE(c.stage, t.status) AS status, t.status AS stored_as';
86
+ const parents = all(db, `SELECT t.id, ${related}, t.title FROM tasks t JOIN task_links l ON l.parent_id = t.id LEFT JOIN supercode_cards c ON c.task_id = t.id WHERE l.child_id = ?`, task.id);
87
+ const children = all(db, `SELECT t.id, ${related} FROM tasks t JOIN task_links l ON l.child_id = t.id LEFT JOIN supercode_cards c ON c.task_id = t.id WHERE l.parent_id = ?`, task.id);
77
88
  const lastRun = one(db, 'SELECT * FROM task_runs WHERE task_id = ? AND ended_at IS NOT NULL ORDER BY ended_at DESC, id DESC LIMIT 1', task.id);
78
89
  const lastCompleted = one(db, "SELECT * FROM task_runs WHERE task_id = ? AND outcome = 'completed' ORDER BY ended_at DESC LIMIT 1", task.id);
79
90
  const asked = json(one(db, "SELECT payload FROM task_events WHERE task_id = ? AND kind = 'review_requested' ORDER BY id DESC LIMIT 1", task.id)?.payload, {}) ?? {};
@@ -94,9 +105,9 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
94
105
  goal_mode: Boolean(task.goal_mode),
95
106
  implementer: asked.implementer ?? task.assignee,
96
107
  last_reviewer: changes.reviewer ?? null,
97
- arc: task.subtask_of ? one(db, 'SELECT id, status, title, body, assignee FROM tasks WHERE id = ?', task.subtask_of) ?? null : null,
108
+ arc: task.subtask_of ? one(db, `SELECT t.id, ${related}, t.title, t.body, t.assignee FROM tasks t LEFT JOIN supercode_cards c ON c.task_id = t.id WHERE t.id = ?`, task.subtask_of) ?? null : null,
98
109
  // the cards filed as its subtasks (`create --subtask-of`), in the order they were filed
99
- subtasks: all(db, 'SELECT t.id, t.status, t.title, t.assignee FROM tasks t JOIN supercode_cards c ON c.task_id = t.id WHERE c.subtask_of = ? ORDER BY t.created_at', task.id),
110
+ subtasks: all(db, `SELECT t.id, ${related}, t.title, t.assignee FROM tasks t JOIN supercode_cards c ON c.task_id = t.id WHERE c.subtask_of = ? ORDER BY t.created_at`, task.id),
100
111
  queue_reason: json(one(db, "SELECT payload FROM task_events WHERE task_id = ? AND kind = 'respawn_guarded' AND created_at >= ? ORDER BY id DESC LIMIT 1",
101
112
  task.id, Number(one(db, 'SELECT since FROM supercode_cards WHERE task_id = ?', task.id)?.since ?? 0))?.payload, {})?.reason ?? null,
102
113
  last_block_event: one(db, "SELECT kind FROM task_events WHERE task_id = ? AND kind IN ('blocked', 'unblocked') ORDER BY id DESC LIMIT 1", task.id)?.kind ?? null,
@@ -109,6 +120,12 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
109
120
  last_events: Object.fromEntries(all(db, 'SELECT e.kind, e.created_at, e.payload FROM task_events e JOIN (SELECT kind, MAX(id) AS id FROM task_events WHERE task_id = ? GROUP BY kind) l ON l.id = e.id', task.id)
110
121
  .map((r) => [r.kind, { created_at: r.created_at, payload: json(r.payload, {}) ?? {} }])),
111
122
  comments_since: (t) => all(db, 'SELECT author, body, created_at FROM task_comments WHERE task_id = ? AND created_at >= ?', task.id, t),
123
+ // the latest commit in the card's workspace (a subtask's: its arc's), as epoch seconds; null outside a repository
124
+ last_commit_at: () => {
125
+ const own = task.workspace_kind !== 'scratch' ? task.workspace_path : null;
126
+ const arcWs = !own && task.subtask_of ? one(db, "SELECT workspace_path FROM tasks WHERE id = ? AND workspace_kind != 'scratch'", task.subtask_of)?.workspace_path : null;
127
+ return lastCommitAt(own ?? arcWs ?? null);
128
+ },
112
129
  };
113
130
  return {
114
131
  card,
package/loop.mjs CHANGED
@@ -586,6 +586,14 @@ export class Orchestrator {
586
586
  try {
587
587
  await runtime.sendInput(input);
588
588
  } catch (error) {
589
+ // A turn the harness is still running (one this loop did not start, or whose end it has not heard yet) is not a
590
+ // dead session: the line waits for that turn's end, as a refused steer does. Ending the binding on it killed a
591
+ // live lobby manager on the first message that reached it mid-turn.
592
+ if (/already in progress/.test(error.message)) {
593
+ this.log('info', `send ${keyString}: a turn is in progress; the line waits for its end`);
594
+ this.enqueue(events.worker({ profile, keyString, runtime_id: runtime.handle.runtime_id, ev: { kind: 'steer_refused', text: input }, at: this.clock() }));
595
+ return;
596
+ }
589
597
  this.enqueue(events.worker({ profile, keyString, runtime_id: runtime.handle.runtime_id, ev: { kind: 'error', message: `send failed: ${error.message}` }, at: this.clock() }));
590
598
  }
591
599
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.42",
3
+ "version": "0.4.44",
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": {