@volter/supercode-orchestrator 0.4.142 → 0.4.143

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.
@@ -24,7 +24,7 @@ import { basename, dirname, join } from 'node:path';
24
24
  import { createHash, randomUUID } from 'node:crypto';
25
25
  import { fileURLToPath } from 'node:url';
26
26
  import { view, boardPath, claimer, event, json, listBoards, logsDir, now, openBoard, setActor, tx } from './store.mjs';
27
- import { doors, fire, loadWorkflow, resumeOf, runSpec, requestSessionClose, retireSupersededRuns, refreshRelationAnswers, transitionsFor, scopeOf, statusOf, templateValue, workflowFor } from './workflow.mjs';
27
+ import { doors, fire, loadWorkflow, resumeOf, runSpec, runWorkingSeconds, requestSessionClose, retireSupersededRuns, refreshRelationAnswers, transitionsFor, scopeOf, statusOf, templateValue, workflowFor } from './workflow.mjs';
28
28
  import { checkWorkspace, assertWorkspaceOwner, resolveWorkspace } from './workspace.mjs';
29
29
  import { evaluate, holds, render } from './expr.mjs';
30
30
  import {
@@ -331,13 +331,13 @@ function supervise(board, fleet, settings, report, entry, effects) {
331
331
  return;
332
332
  }
333
333
  // a run past its runtime limit (the status's `run.limits.runtime`), as Hermes times its workers out
334
- if (limits.runtime && runRow && t - Number(runRow.started_at) > Number(limits.runtime)) {
334
+ if (limits.runtime && runRow && session?.wait_started_at == null && runWorkingSeconds(runRow, t) > Number(limits.runtime)) {
335
335
  observe('timed_out', { limit_seconds: limits.runtime, error: `ran past max_runtime ${limits.runtime}s` }, 'timed_out');
336
336
  return;
337
337
  }
338
338
  if (session?.surface === 'headless') {
339
339
  // Hermes's stale check: a worker running past the limit with no heartbeat in the last hour is stopped
340
- if (limits.stale && runRow && t - Number(runRow.started_at) >= Number(limits.stale) && !(task.last_heartbeat_at && t - Number(task.last_heartbeat_at) < 3600)) {
340
+ if (limits.stale && runRow && session?.wait_started_at == null && runWorkingSeconds(runRow, t) >= Number(limits.stale) && !(task.last_heartbeat_at && t - Number(task.last_heartbeat_at) < 3600)) {
341
341
  observe('stale', { timeout_seconds: limits.stale, pid: session.pid, error: `no heartbeat after ${t - Number(runRow.started_at)}s running` }, 'reclaimed');
342
342
  return;
343
343
  }
@@ -733,6 +733,7 @@ function observeIdle(board, fleet, settings, report, entry, effects) {
733
733
  if (!(entry.workflow.statuses[status]?.on?.session_idle || entry.workflow.on?.session_idle || entry.workflow.run?.on?.session_idle)) return;
734
734
  const runRow = currentRun(db, id);
735
735
  const session = runRow?.metadata?.supercode;
736
+ if (session?.wait_started_at != null) return;
736
737
  if (!session?.session_id || !['pane', 'native'].includes(session.surface) || session.offline_since != null) return;
737
738
  if (session.surface === 'native' && fleet.native?.get(addressOf(session))?.alive !== true) return;
738
739
  const live = fleet.sessions.find((x) => x.address === addressOf(session));
@@ -815,6 +816,10 @@ export async function sessionEffect(board, e) {
815
816
  if (e.kind === 'send_to_actor') {
816
817
  const row = view(board, db => e.runId ? db.prepare('SELECT metadata FROM task_runs WHERE id=?').get(e.runId) : null);
817
818
  const session = json(row?.metadata, {})?.supercode;
819
+ if (session?.wait_started_at != null) {
820
+ tx(board, db => event(db, e.id, 'actor_notice_suppressed', { notice: e.durableId, reason: 'run is waiting', run_id: e.runId }, e.runId));
821
+ return true;
822
+ }
818
823
  const address = addressOf(session);
819
824
  let receipt;
820
825
  try {
@@ -186,13 +186,21 @@ function onlyTicks(was, body) {
186
186
  }
187
187
 
188
188
  /** Seconds a card's runs spent by role, and the rest of its life as `waiting`. */
189
+ export function runWorkingSeconds(row, at = now()) {
190
+ const metadata = typeof row.metadata === 'string' ? json(row.metadata, {}) : row.metadata;
191
+ const session = metadata?.supercode ?? {};
192
+ const end = Number(row.ended_at ?? at);
193
+ const waiting = Number(session.wait_seconds ?? 0) + (session.wait_started_at != null ? Math.max(0, end - Number(session.wait_started_at)) : 0);
194
+ return Math.max(0, end - Number(row.started_at ?? end) - waiting);
195
+ }
196
+
189
197
  function timeOf(db, task) {
190
198
  const t = now();
191
199
  const out = { coding: 0, reviewing: 0, auditing: 0, triaging: 0 };
192
200
  const key = { implementer: 'coding', reviewer: 'reviewing', auditor: 'auditing', triager: 'triaging' };
193
201
  for (const r of all(db, 'SELECT started_at, ended_at, metadata FROM task_runs WHERE task_id = ?', task.id)) {
194
202
  const role = json(r.metadata, {})?.supercode?.role ?? 'implementer';
195
- const spent = Math.max(0, Number(r.ended_at ?? t) - Number(r.started_at ?? t));
203
+ const spent = runWorkingSeconds(r, t);
196
204
  const k = key[role] ?? role;
197
205
  out[k] = (out[k] ?? 0) + spent;
198
206
  }
@@ -612,6 +620,17 @@ const ACTIONS = {
612
620
  const result = fire(ctx.db, ctx.board, ctx.entry, ctx.id, name, { roles: [].concat(as), payload, extra: { lanes: lanesIn(ctx.entry) } });
613
621
  ctx.effects.push(...result.effects);
614
622
  },
623
+ wait_run(ctx, waiting) {
624
+ if (typeof waiting !== 'boolean') throw new Error('wait_run requires a boolean');
625
+ const at = now();
626
+ runSession(ctx, session => {
627
+ if (waiting) return { ...session, wait_started_at: session.wait_started_at ?? at };
628
+ if (session.wait_started_at == null) return session;
629
+ return { ...session, wait_started_at: null,
630
+ wait_seconds: Number(session.wait_seconds ?? 0) + Math.max(0, at - Number(session.wait_started_at)),
631
+ goal_nudged_at: at };
632
+ });
633
+ },
615
634
  record(ctx, args, scope) {
616
635
  const kind = typeof args === 'string' ? args : args?.kind;
617
636
  const given = typeof args === 'object' && args?.payload ? args.payload : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.142",
3
+ "version": "0.4.143",
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": {