@mu-crew/mu 4.0.0 → 4.1.0

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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-5HECDBK3.js → chunk-4ULY3ZKO.js} +16 -16
  3. package/dist/{chunk-AW2NONYU.js → chunk-7M634JMD.js} +81 -7
  4. package/dist/chunk-7M634JMD.js.map +1 -0
  5. package/dist/{chunk-GQNHIPJN.js → chunk-SS7NQSSZ.js} +21 -421
  6. package/dist/chunk-SS7NQSSZ.js.map +1 -0
  7. package/dist/{chunk-S424D5EP.js → chunk-UCZPJX7K.js} +628 -141
  8. package/dist/chunk-UCZPJX7K.js.map +1 -0
  9. package/dist/{cli-GB2CAVVW.js → cli-TV3N47AZ.js} +340 -42
  10. package/dist/cli-TV3N47AZ.js.map +1 -0
  11. package/dist/cli.js +1 -1
  12. package/dist/{errors-KHOYKD7D.js → errors-GRONKWFS.js} +2 -2
  13. package/dist/extension/mu-pi.js +28 -14
  14. package/dist/extension/mu-pi.js.map +1 -1
  15. package/dist/index.d.ts +63 -11
  16. package/dist/index.js +15 -15
  17. package/dist/{tui-ALPHYTA2.js → tui-FUBQ326D.js} +102 -27
  18. package/dist/tui-FUBQ326D.js.map +1 -0
  19. package/docs/ARCHITECTURE.md +5 -4
  20. package/docs/VOCABULARY.md +6 -5
  21. package/docs/guide/cleanup.md +8 -0
  22. package/docs/guide/delegate.md +11 -3
  23. package/docs/guide/remote.md +19 -12
  24. package/docs/reference/env.md +1 -0
  25. package/docs/reference/naming.md +2 -0
  26. package/package.json +1 -1
  27. package/skills/mu/recipes/remote-workers.md +7 -6
  28. package/dist/chunk-AW2NONYU.js.map +0 -1
  29. package/dist/chunk-GQNHIPJN.js.map +0 -1
  30. package/dist/chunk-S424D5EP.js.map +0 -1
  31. package/dist/cli-GB2CAVVW.js.map +0 -1
  32. package/dist/tui-ALPHYTA2.js.map +0 -1
  33. /package/dist/{chunk-5HECDBK3.js.map → chunk-4ULY3ZKO.js.map} +0 -0
  34. /package/dist/{errors-KHOYKD7D.js.map → errors-GRONKWFS.js.map} +0 -0
@@ -1999,7 +1999,7 @@ Matched line: ${line}${scroll}`,
1999
1999
  "agent.spawn_cwd_worktree": () => "--cwd conflicts with --worktree (the worktree is the cwd)",
2000
2000
  "agent.spawn_needs_worktree": (flag) => `--${flag} requires --worktree`,
2001
2001
  "agent.spawn_gate_flag": (flag) => `--${flag} cannot be used with a remote command gate (--host with a non-pi --command): it runs one command and reports its exit; wait on it`,
2002
- "agent.spawn_gate_worktree": () => "--worktree cannot be used with a remote command gate (--host with a non-pi --command): a gate runs in the profile's repo or --cwd",
2002
+ "agent.spawn_gate_worktree": () => "--worktree cannot be used with a remote command gate (--host with a non-pi --command): a gate runs in --cwd, else the profile's default repo",
2003
2003
  "agent.spawn_attempt_task": () => "--attempt needs --task",
2004
2004
  "agent.spawn_task_default": (task, ws, title) => `Task ${task} (workstream ${ws}): ${title}. Read: mu task notes ${task} -w ${ws}`,
2005
2005
  "agent.spawn_name_hint": (name) => `hint: agent name "${name}" does not match the smallest-unused-suffix convention (<role>-<n>; e.g. worker-1, reviewer-2). Accepted; consider renaming if you spawn additional workers.
@@ -2097,12 +2097,12 @@ function say(key, ...facts) {
2097
2097
  // src/agents/spawn.ts
2098
2098
  import { execFile as execFile4 } from "child_process";
2099
2099
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, rmSync as rmSync3, statSync as statSync2 } from "fs";
2100
- import { basename as basename2, dirname as dirname11, resolve as resolve3 } from "path";
2100
+ import { basename as basename2, dirname as dirname12, resolve as resolve3 } from "path";
2101
2101
  import { setTimeout as delay } from "timers/promises";
2102
2102
  import { promisify as promisify3 } from "util";
2103
2103
 
2104
2104
  // src/agents.ts
2105
- import { dirname as dirname10 } from "path";
2105
+ import { dirname as dirname11 } from "path";
2106
2106
 
2107
2107
  // src/agent-state.ts
2108
2108
  import { accessSync, constants, existsSync as existsSync2 } from "fs";
@@ -2792,12 +2792,16 @@ var OWNED = (a) => `${a}.host IS NOT NULL AND ${a}.cwd IS NOT NULL AND ${a}.cwd
2792
2792
  var COLUMNS = `agent_id, workstream, workstream_id, workstream_created_at, name, pane_id, host, cwd,
2793
2793
  base, collected_head, aside, host_exec, host_repo, reason, preserved_at`;
2794
2794
  var NOW = "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
2795
+ var LEGACY_RECOVERY_TRIGGERS = [
2796
+ "remote_recovery_agent_del",
2797
+ "remote_recovery_workstream_del"
2798
+ ];
2795
2799
  var REMOTE_RECOVERY_DDL = `
2796
2800
  -- remote_recovery: what a finalized remote worker row said about its
2797
2801
  -- worktree, written by triggers when the row is deleted (machine-local; see
2798
2802
  -- src/db-recovery.ts). No foreign key on purpose: it must outlive the agent,
2799
- -- its workstream and its host profile. host_exec / host_repo pin the
2800
- -- profile's identity when the profile existed. A receipt of the resource,
2803
+ -- its workstream and its host profile. host_exec pins the profile's
2804
+ -- identity when the profile existed; host_repo is the worktree's repo. A receipt of the resource,
2801
2805
  -- not a live channel.
2802
2806
  CREATE TABLE IF NOT EXISTS remote_recovery (
2803
2807
  agent_id INTEGER PRIMARY KEY,
@@ -2817,7 +2821,7 @@ CREATE TABLE IF NOT EXISTS remote_recovery (
2817
2821
  preserved_at TEXT NOT NULL
2818
2822
  );
2819
2823
 
2820
- CREATE TRIGGER IF NOT EXISTS remote_recovery_agent_del
2824
+ CREATE TRIGGER IF NOT EXISTS remote_recovery_agent_del_v2
2821
2825
  AFTER DELETE ON agents
2822
2826
  WHEN ${OWNED("OLD")}
2823
2827
  AND EXISTS (SELECT 1 FROM workstreams WHERE id = OLD.workstream_id)
@@ -2826,19 +2830,19 @@ BEGIN
2826
2830
  SELECT OLD.id, ws.name, ws.id, ws.created_at, OLD.name, OLD.pane_id, OLD.host, OLD.cwd,
2827
2831
  OLD.base, OLD.collected_head, OLD.cwd || '.mu-removing-' || OLD.id,
2828
2832
  (SELECT exec FROM host_profiles WHERE name = OLD.host),
2829
- (SELECT repo FROM host_profiles WHERE name = OLD.host),
2833
+ OLD.repo,
2830
2834
  ${REASON}, ${NOW}
2831
2835
  FROM workstreams ws WHERE ws.id = OLD.workstream_id;
2832
2836
  END;
2833
2837
 
2834
- CREATE TRIGGER IF NOT EXISTS remote_recovery_workstream_del
2838
+ CREATE TRIGGER IF NOT EXISTS remote_recovery_workstream_del_v2
2835
2839
  BEFORE DELETE ON workstreams
2836
2840
  BEGIN
2837
2841
  INSERT OR IGNORE INTO remote_recovery (${COLUMNS})
2838
2842
  SELECT a.id, OLD.name, OLD.id, OLD.created_at, a.name, a.pane_id, a.host, a.cwd,
2839
2843
  a.base, a.collected_head, a.cwd || '.mu-removing-' || a.id,
2840
2844
  (SELECT exec FROM host_profiles WHERE name = a.host),
2841
- (SELECT repo FROM host_profiles WHERE name = a.host),
2845
+ a.repo,
2842
2846
  'workstream-delete', ${NOW}
2843
2847
  FROM agents a
2844
2848
  WHERE a.workstream_id = OLD.id AND ${OWNED("a")};
@@ -3093,7 +3097,13 @@ function upgradeSchema(db) {
3093
3097
  throw err;
3094
3098
  }
3095
3099
  }
3100
+ function hostProfilesRepoRequired(db) {
3101
+ const row = db.prepare(`SELECT "notnull" AS nn FROM pragma_table_info('host_profiles') WHERE name = 'repo'`).get();
3102
+ return row?.nn === 1;
3103
+ }
3096
3104
  function applySchemaDdl(db) {
3105
+ if (hostProfilesRepoRequired(db)) db.exec("DROP TABLE host_profiles");
3106
+ for (const t of LEGACY_RECOVERY_TRIGGERS) db.exec(`DROP TRIGGER IF EXISTS ${t}`);
3097
3107
  db.exec(CURRENT_SCHEMA);
3098
3108
  for (const [table, column, ddl] of ADDED_COLUMNS) {
3099
3109
  if (!hasColumn(db, table, column)) db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddl}`);
@@ -3165,6 +3175,8 @@ var ADDED_COLUMNS = [
3165
3175
  ["agents", "collected_head", "TEXT"],
3166
3176
  // The remote command gate's spawn-unique token (machine-local): its marker and result key.
3167
3177
  ["agents", "gate_token", "TEXT"],
3178
+ // A remote worktree's repo, chosen per spawn (a host profile names a machine).
3179
+ ["agents", "repo", "TEXT"],
3168
3180
  ["wait_registrations", "baselines_json", "TEXT NOT NULL DEFAULT '{}'"],
3169
3181
  ["task_auto", "enabled", "INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1))"],
3170
3182
  ...["request", "verdict", "agent"].map((c) => ["delegate_calls", c, "TEXT"]),
@@ -3182,6 +3194,8 @@ function schemaIsCurrent(db) {
3182
3194
  if (expected.tables.some((t) => !have.has(`table:${t}`))) return false;
3183
3195
  if (expected.triggers.some((t) => !have.has(`trigger:${t}`))) return false;
3184
3196
  if (ADDED_COLUMNS.some(([t, c]) => !hasColumn(db, t, c))) return false;
3197
+ if (LEGACY_RECOVERY_TRIGGERS.some((t) => have.has(`trigger:${t}`))) return false;
3198
+ if (hostProfilesRepoRequired(db)) return false;
3185
3199
  if (expected.indexes.some((i) => !have.has(`index:${i}`))) return false;
3186
3200
  for (const [name, sql] of expected.views) {
3187
3201
  if (have.get(`view:${name}`) !== sql) return false;
@@ -3400,6 +3414,7 @@ CREATE TABLE IF NOT EXISTS agents (
3400
3414
  spawner TEXT, -- who spawned it: '<workstream>/<agent>' or 'pi:<session>' (machine-local); NULL: unknown
3401
3415
  host TEXT, -- the remote host profile it runs on (machine-local); NULL: local
3402
3416
  collected_head TEXT, -- HEAD last collected from a remote worktree (machine-local)
3417
+ repo TEXT, -- a remote worktree's repo on its host, made absolute at spawn (machine-local); NULL: none
3403
3418
  gate_token TEXT, -- remote command gate: spawn-unique token, also the key of its remote result record (machine-local); NULL: not a gate
3404
3419
  created_at TEXT NOT NULL,
3405
3420
  updated_at TEXT NOT NULL,
@@ -3517,6 +3532,8 @@ CREATE TABLE IF NOT EXISTS delegate_calls (
3517
3532
  request TEXT, -- spawn request (JSON)
3518
3533
  verdict TEXT, agent TEXT -- parsed label of a done answer; the agent that ran it
3519
3534
  );
3535
+ -- The dashboard's attention scan reads refused and recently failed calls each tick.
3536
+ CREATE INDEX IF NOT EXISTS idx_delegate_calls_state ON delegate_calls (state, updated_at);
3520
3537
 
3521
3538
  -- human_needs: the caller that recorded a kind=human task (machine-local).
3522
3539
  -- The task, its reason brief and its close are portable; who asked is not
@@ -3643,7 +3660,7 @@ CREATE TABLE IF NOT EXISTS host_profiles (
3643
3660
  shell TEXT NOT NULL,
3644
3661
  exec TEXT NOT NULL,
3645
3662
  fetch TEXT NOT NULL,
3646
- repo TEXT NOT NULL,
3663
+ repo TEXT, -- default remote repo for spawns without --root; NULL: none
3647
3664
  max_sessions INTEGER CHECK (max_sessions > 0)
3648
3665
  );
3649
3666
 
@@ -3871,20 +3888,20 @@ function compareHlc(a, b) {
3871
3888
  if (a > b) return 1;
3872
3889
  return 0;
3873
3890
  }
3874
- function nextHlc(db, now = Date.now()) {
3891
+ function nextHlc(db, now2 = Date.now()) {
3875
3892
  const row = db.prepare(
3876
3893
  `UPDATE machine_identity
3877
3894
  SET last_wall = MAX(last_wall, @now),
3878
3895
  last_counter = CASE WHEN @now > last_wall THEN 0 ELSE last_counter + 1 END
3879
3896
  WHERE id = 1
3880
3897
  RETURNING machine_id, last_wall, last_counter`
3881
- ).get({ now: Math.floor(now) });
3898
+ ).get({ now: Math.floor(now2) });
3882
3899
  if (!row) throw new MachineIdentityMissingError();
3883
3900
  return formatHlc({ wallMs: row.last_wall, counter: row.last_counter, machineId: row.machine_id });
3884
3901
  }
3885
- function receiveHlc(db, remoteHlc, now = Date.now()) {
3902
+ function receiveHlc(db, remoteHlc, now2 = Date.now()) {
3886
3903
  const remote = parseHlc(remoteHlc);
3887
- const wallNow = Math.floor(now);
3904
+ const wallNow = Math.floor(now2);
3888
3905
  const read = prepareCached(
3889
3906
  db,
3890
3907
  "SELECT machine_id, last_wall, last_counter FROM machine_identity WHERE id = 1"
@@ -5847,8 +5864,10 @@ function respawnCommand(agent) {
5847
5864
  if (agent.host !== null) parts.push("--host", shellWord(agent.host));
5848
5865
  if (agent.role !== "full-access") parts.push("--role", shellWord(agent.role));
5849
5866
  if (agent.tab !== null) parts.push("--tab", shellWord(agent.tab));
5850
- if (agent.base !== null) parts.push("--worktree");
5851
- else if (agent.cwd !== null) parts.push("--cwd", shellWord(agent.cwd));
5867
+ if (agent.base !== null) {
5868
+ parts.push("--worktree");
5869
+ if (agent.host !== null && agent.repo !== null) parts.push("--root", shellWord(agent.repo));
5870
+ } else if (agent.cwd !== null) parts.push("--cwd", shellWord(agent.cwd));
5852
5871
  return parts.join(" ");
5853
5872
  }
5854
5873
  async function extensionOutdated(agent, sock, e) {
@@ -6198,8 +6217,8 @@ function mapMurmurState(token, since) {
6198
6217
  function parseSince(value) {
6199
6218
  return value !== void 0 && /^\d+$/.test(value) ? Number(value) : null;
6200
6219
  }
6201
- async function readMurmur(now) {
6202
- if (murmurCache !== void 0 && now - murmurCache.at < MURMUR_CACHE_MS) {
6220
+ async function readMurmur(now2) {
6221
+ if (murmurCache !== void 0 && now2 - murmurCache.at < MURMUR_CACHE_MS) {
6203
6222
  return murmurCache.stdout;
6204
6223
  }
6205
6224
  let stdout;
@@ -6208,7 +6227,7 @@ async function readMurmur(now) {
6208
6227
  } catch {
6209
6228
  stdout = null;
6210
6229
  }
6211
- murmurCache = { at: now, stdout };
6230
+ murmurCache = { at: now2, stdout };
6212
6231
  return stdout;
6213
6232
  }
6214
6233
  function remotePanes(stdout) {
@@ -6505,7 +6524,9 @@ function addHost(db, p) {
6505
6524
  if (p.max_sessions !== null && !(Number.isInteger(p.max_sessions) && p.max_sessions > 0)) {
6506
6525
  throw new HostProfileError("--max-sessions must be a positive integer");
6507
6526
  }
6508
- if (p.repo.trim() === "") throw new HostProfileError("--repo must not be empty");
6527
+ if (p.repo !== null && p.repo.trim() === "") {
6528
+ throw new HostProfileError("--repo must not be empty");
6529
+ }
6509
6530
  for (const f of ["shell", "exec", "fetch"]) {
6510
6531
  try {
6511
6532
  validateHostTemplate(p[f]);
@@ -6696,8 +6717,8 @@ function pidProvenGone(pid, start) {
6696
6717
  return e.code === "ESRCH";
6697
6718
  }
6698
6719
  if (start === null) return false;
6699
- const now = pidStart(pid);
6700
- return now !== null && now !== start;
6720
+ const now2 = pidStart(pid);
6721
+ return now2 !== null && now2 !== start;
6701
6722
  }
6702
6723
  function groupAlive(pgid, start) {
6703
6724
  try {
@@ -6706,8 +6727,8 @@ function groupAlive(pgid, start) {
6706
6727
  return e.code !== "ESRCH";
6707
6728
  }
6708
6729
  if (start === null) return true;
6709
- const now = pidStart(pgid);
6710
- return now === null || now === start;
6730
+ const now2 = pidStart(pgid);
6731
+ return now2 === null || now2 === start;
6711
6732
  }
6712
6733
  async function probeAttached(db, host, paneAlive3) {
6713
6734
  if (paneAlive3 === void 0) return [];
@@ -6834,6 +6855,7 @@ async function checkHost(db, host, opts = {}) {
6834
6855
  host,
6835
6856
  `check:${process.pid}`,
6836
6857
  async (profile, lease) => {
6858
+ const root = opts.root ?? profile.repo ?? void 0;
6837
6859
  const step = async (name, cmd, good, unmet) => {
6838
6860
  let detail;
6839
6861
  let ok = false;
@@ -6843,7 +6865,8 @@ async function checkHost(db, host, opts = {}) {
6843
6865
  lease,
6844
6866
  profile,
6845
6867
  "exec",
6846
- { cmd, cwd: profile.repo },
6868
+ // `.` without a repo: a template's `cd {cwd:sh}` stays in the login directory.
6869
+ { cmd, cwd: root ?? "." },
6847
6870
  timeoutMs
6848
6871
  );
6849
6872
  const why = firstLine(r.stderr) || firstLine(r.stdout);
@@ -6858,12 +6881,11 @@ async function checkHost(db, host, opts = {}) {
6858
6881
  return ok;
6859
6882
  };
6860
6883
  if (await step("reachable", "true", () => "exec answered", "")) {
6861
- await step(
6862
- "repo",
6863
- `git -C ${remotePathWord(profile.repo)} rev-parse --git-dir`,
6864
- () => profile.repo,
6865
- ""
6866
- );
6884
+ if (root === void 0) {
6885
+ steps.push({ step: "repo", ok: null, detail: "skipped: no repo given (--root)" });
6886
+ } else {
6887
+ await step("repo", `git -C ${remotePathWord(root)} rev-parse --git-dir`, () => root, "");
6888
+ }
6867
6889
  await step(
6868
6890
  "pi",
6869
6891
  login("pi --version"),
@@ -6903,7 +6925,7 @@ async function checkHost(db, host, opts = {}) {
6903
6925
  },
6904
6926
  opts
6905
6927
  );
6906
- return { host, ok: steps.every((s) => s.ok === true), steps };
6928
+ return { host, ok: steps.every((s) => s.ok !== false), steps };
6907
6929
  }
6908
6930
 
6909
6931
  // src/op-context.ts
@@ -7031,15 +7053,15 @@ function reapAgentRow(db, agentId, name, opts = {}) {
7031
7053
  "INSERT INTO task_notes (task_id, author, content, created_at, kind) VALUES (?, 'reaper', ?, ?, 'note')"
7032
7054
  );
7033
7055
  for (const t of stuck) {
7034
- const now = (/* @__PURE__ */ new Date()).toISOString();
7035
- reopen.run(now, t.taskId);
7056
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
7057
+ reopen.run(now2, t.taskId);
7036
7058
  withOpContext(
7037
7059
  db,
7038
7060
  { intent: "task.note", actor: "reaper" },
7039
7061
  () => note.run(
7040
7062
  t.taskId,
7041
7063
  `[reaper] previous owner ${name} gone (agent removed); status reverted IN_PROGRESS \u2192 OPEN, owner cleared`,
7042
- now
7064
+ now2
7043
7065
  )
7044
7066
  );
7045
7067
  }
@@ -7197,7 +7219,7 @@ function agentRevision(db, workstream, name, recoveryId) {
7197
7219
  if (recoveryId !== void 0) return recoveryRevision(db, workstream, name, recoveryId);
7198
7220
  const agent = db.prepare(
7199
7221
  `SELECT a.id, a.pane_id, a.cli, a.role, a.tab, a.cwd, a.base, a.command, a.command_source,
7200
- a.spawner, a.host, a.collected_head, a.gate_token, a.created_at
7222
+ a.spawner, a.host, a.collected_head, a.repo, a.gate_token, a.created_at
7201
7223
  FROM agents a JOIN workstreams w ON w.id = a.workstream_id
7202
7224
  WHERE w.name = ? AND a.name = ?`
7203
7225
  ).get(workstream, name);
@@ -7504,8 +7526,8 @@ function taskIdFor(db, localId, workstream) {
7504
7526
  const row = db.prepare("SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?").get(wsId, localId);
7505
7527
  return row ? row.id : null;
7506
7528
  }
7507
- function touchTask(db, taskId, now) {
7508
- const ts = now ?? (/* @__PURE__ */ new Date()).toISOString();
7529
+ function touchTask(db, taskId, now2) {
7530
+ const ts = now2 ?? (/* @__PURE__ */ new Date()).toISOString();
7509
7531
  db.prepare("UPDATE tasks SET updated_at = ? WHERE id = ? AND updated_at < ?").run(ts, taskId, ts);
7510
7532
  }
7511
7533
 
@@ -7752,13 +7774,13 @@ function listTasksByOwnerCrossWorkstream(db, owner, opts = {}) {
7752
7774
  // src/tasks/edges.ts
7753
7775
  function dedupeBlockersById(blockers) {
7754
7776
  const seen = /* @__PURE__ */ new Set();
7755
- const canonical2 = [];
7777
+ const canonical3 = [];
7756
7778
  for (const blocker of blockers) {
7757
7779
  if (seen.has(blocker.id)) continue;
7758
7780
  seen.add(blocker.id);
7759
- canonical2.push(blocker);
7781
+ canonical3.push(blocker);
7760
7782
  }
7761
- return canonical2;
7783
+ return canonical3;
7762
7784
  }
7763
7785
  function sameNumberSet(left, right) {
7764
7786
  if (left.length !== right.length) return false;
@@ -7891,13 +7913,13 @@ function addBlockEdgeImpl(db, workstream, blocked, blocker) {
7891
7913
  if (wouldCreateCycle(db, blockerId, blockedId)) {
7892
7914
  throw cycleErrorFor(db, blocker, blocked, blockerId, blockedId, blockedRow.workstreamName);
7893
7915
  }
7894
- const now = (/* @__PURE__ */ new Date()).toISOString();
7916
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
7895
7917
  const added = db.transaction(() => {
7896
7918
  const result = db.prepare(
7897
7919
  "INSERT INTO task_edges (from_task_id, to_task_id, created_at) SELECT ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM task_edges WHERE from_task_id = ? AND to_task_id = ?)"
7898
- ).run(blockerId, blockedId, now, blockerId, blockedId);
7920
+ ).run(blockerId, blockedId, now2, blockerId, blockedId);
7899
7921
  if (result.changes > 0) {
7900
- touchTask(db, blockedId, now);
7922
+ touchTask(db, blockedId, now2);
7901
7923
  return true;
7902
7924
  }
7903
7925
  return false;
@@ -7983,11 +8005,11 @@ function reparentTaskImpl(db, taskLocalId, blockers, scope) {
7983
8005
  const insertEdge = db.prepare(
7984
8006
  "INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)"
7985
8007
  );
7986
- const now = (/* @__PURE__ */ new Date()).toISOString();
8008
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
7987
8009
  for (const blockerId of blockerIds) {
7988
- insertEdge.run(blockerId, taskSurrogateId, now);
8010
+ insertEdge.run(blockerId, taskSurrogateId, now2);
7989
8011
  }
7990
- touchTask(db, taskSurrogateId, now);
8012
+ touchTask(db, taskSurrogateId, now2);
7991
8013
  return { removedEdges: removed.changes, addedEdges: blockerIds.length };
7992
8014
  }).immediate();
7993
8015
  }
@@ -8899,9 +8921,9 @@ async function teardownWorkstream(db, opts) {
8899
8921
  db,
8900
8922
  { intent: "workstream.teardown", group: "new" },
8901
8923
  () => db.transaction(() => {
8902
- const now = workstreamRevision(db, opts.workstream);
8903
- if (now !== opts.ifRevision) {
8904
- throw new WorkstreamRevisionMismatchError(opts.workstream, opts.ifRevision, now);
8924
+ const now2 = workstreamRevision(db, opts.workstream);
8925
+ if (now2 !== opts.ifRevision) {
8926
+ throw new WorkstreamRevisionMismatchError(opts.workstream, opts.ifRevision, now2);
8905
8927
  }
8906
8928
  for (const a of dead) {
8907
8929
  if (!sameRow2.get(a.incarnation, a.name, a.paneId, opts.workstream)) continue;
@@ -9110,7 +9132,7 @@ function addTaskImpl(db, opts) {
9110
9132
  if (existing) {
9111
9133
  throw new TaskExistsError(opts.localId);
9112
9134
  }
9113
- const now = (/* @__PURE__ */ new Date()).toISOString();
9135
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9114
9136
  const insertResult = db.prepare(
9115
9137
  `INSERT INTO tasks (workstream_id, local_id, title, status, substate, kind, wave, impact, effort_days, scored, created_at, updated_at)
9116
9138
  VALUES (?, ?, ?, 'OPEN', ?, ?, ?, ?, ?, ?, ?, ?)`
@@ -9124,8 +9146,8 @@ function addTaskImpl(db, opts) {
9124
9146
  opts.impact ?? DEFAULT_IMPACT,
9125
9147
  opts.effortDays ?? DEFAULT_EFFORT_DAYS,
9126
9148
  opts.impact !== void 0 || opts.effortDays !== void 0 ? 1 : 0,
9127
- now,
9128
- now
9149
+ now2,
9150
+ now2
9129
9151
  );
9130
9152
  const newTaskId = Number(insertResult.lastInsertRowid);
9131
9153
  if (opts.blockedBy && opts.blockedBy.length > 0) {
@@ -9149,7 +9171,7 @@ function addTaskImpl(db, opts) {
9149
9171
  "INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)"
9150
9172
  );
9151
9173
  for (const blocker of canonicalBlockers) {
9152
- insertEdge.run(blocker.id, newTaskId, now);
9174
+ insertEdge.run(blocker.id, newTaskId, now2);
9153
9175
  }
9154
9176
  }
9155
9177
  const row = getTask(db, opts.localId, opts.workstream);
@@ -9204,7 +9226,7 @@ function addBlocksEdges(db, input, blocks, workstream) {
9204
9226
  }
9205
9227
  umbrellas.set(row.id, local);
9206
9228
  }
9207
- const now = (/* @__PURE__ */ new Date()).toISOString();
9229
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9208
9230
  const insertEdge = db.prepare(
9209
9231
  "INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)"
9210
9232
  );
@@ -9215,10 +9237,10 @@ function addBlocksEdges(db, input, blocks, workstream) {
9215
9237
  if (wouldCreateCycle(db, newId, umbrellaId)) {
9216
9238
  throw cycleErrorFor(db, t.id, umbrella, newId, umbrellaId, workstream);
9217
9239
  }
9218
- insertEdge.run(newId, umbrellaId, now);
9240
+ insertEdge.run(newId, umbrellaId, now2);
9219
9241
  }
9220
9242
  }
9221
- for (const umbrellaId of umbrellas.keys()) touchTask(db, umbrellaId, now);
9243
+ for (const umbrellaId of umbrellas.keys()) touchTask(db, umbrellaId, now2);
9222
9244
  }
9223
9245
  function lookupEdgeTask(db, wsId, localId, workstream) {
9224
9246
  const row = db.prepare(
@@ -9251,18 +9273,18 @@ function insertNote(db, taskLocalId, content, opts, touch = true) {
9251
9273
  }
9252
9274
  const taskId = taskIdFor(db, task.name, task.workstreamName);
9253
9275
  if (taskId === null) throw new TaskNotFoundError(taskLocalId, task.workstreamName);
9254
- const now = (/* @__PURE__ */ new Date()).toISOString();
9276
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9255
9277
  db.transaction(() => {
9256
9278
  const r = db.prepare(
9257
9279
  "INSERT INTO task_notes (task_id, author, content, created_at, kind) VALUES (?, ?, ?, ?, ?)"
9258
- ).run(taskId, opts.author ?? null, content, now, opts.kind ?? "note");
9259
- if (touch) touchTask(db, taskId, now);
9280
+ ).run(taskId, opts.author ?? null, content, now2, opts.kind ?? "note");
9281
+ if (touch) touchTask(db, taskId, now2);
9260
9282
  return r;
9261
9283
  }).immediate();
9262
9284
  return {
9263
9285
  author: opts.author ?? null,
9264
9286
  content,
9265
- createdAt: now,
9287
+ createdAt: now2,
9266
9288
  kind: opts.kind ?? "note"
9267
9289
  };
9268
9290
  }
@@ -9730,7 +9752,7 @@ async function claimTaskImpl(db, localId, opts) {
9730
9752
  throw new ClaimerNotRegisteredError(agentName, null);
9731
9753
  }
9732
9754
  }
9733
- const now = (/* @__PURE__ */ new Date()).toISOString();
9755
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9734
9756
  let result;
9735
9757
  try {
9736
9758
  result = db.prepare(
@@ -9744,7 +9766,7 @@ async function claimTaskImpl(db, localId, opts) {
9744
9766
  WHERE local_id = ?
9745
9767
  AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)
9746
9768
  AND (owner_id IS NULL OR owner_id = ?)`
9747
- ).run(claimerRow.id, now, localId, opts.workstream, claimerRow.id);
9769
+ ).run(claimerRow.id, now2, localId, opts.workstream, claimerRow.id);
9748
9770
  } catch (err) {
9749
9771
  if (err instanceof Error && err.message === GUARD_ALREADY_OWNED) {
9750
9772
  throw new TaskAlreadyOwnedError(localId, before.ownerName ?? "<unknown>");
@@ -9800,7 +9822,7 @@ async function claimSelf(db, localId, opts) {
9800
9822
  if (!before) throw new TaskNotFoundError(localId, opts.workstream);
9801
9823
  assertTaskRevision(db, before.workstreamName, localId, opts.ifRevision);
9802
9824
  assertNotParked(before, opts);
9803
- const now = (/* @__PURE__ */ new Date()).toISOString();
9825
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9804
9826
  const result = db.prepare(
9805
9827
  `UPDATE tasks
9806
9828
  SET status = CASE WHEN status = 'OPEN' THEN 'IN_PROGRESS' ELSE status END,
@@ -9809,7 +9831,7 @@ async function claimSelf(db, localId, opts) {
9809
9831
  WHERE local_id = ?
9810
9832
  AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)
9811
9833
  AND owner_id IS NULL`
9812
- ).run(now, localId, before.workstreamName);
9834
+ ).run(now2, localId, before.workstreamName);
9813
9835
  if (result.changes === 0) {
9814
9836
  throw new TaskAlreadyOwnedError(localId, before.ownerName ?? "<unknown>");
9815
9837
  }
@@ -9977,8 +9999,18 @@ function reclaimLines() {
9977
9999
  ];
9978
10000
  }
9979
10001
  async function prepareRemote(db, lease, profile, opts) {
9980
- const root = remotePathWord(profile.repo);
9981
- const answer = `printf 'MU_WT %s %s\\n' "$base" "$(cd "$wt" && pwd -P)"`;
10002
+ if (opts.worktree && opts.root === void 0) {
10003
+ throw new RemotePrepareError(
10004
+ profile.name,
10005
+ "creating the remote worktree failed",
10006
+ `host ${profile.name} has no default repo: pass --root <remote repo path>`
10007
+ );
10008
+ }
10009
+ const root = remotePathWord(opts.root ?? "~");
10010
+ const answer = [
10011
+ `r=$(cd "$root" && pwd -P) || exit 5`,
10012
+ `printf 'MU_REPO %s\\nMU_WT %s %s\\n' "$(printf '%s' "$r" | od -An -v -tx1 | tr -d ' \\n')" "$base" "$(cd "$wt" && pwd -P)"`
10013
+ ].join("\n");
9982
10014
  const body = opts.worktree ? [
9983
10015
  `root=${root}`,
9984
10016
  `wt=${remoteWorktreeWord(opts.workstream, opts.name)}`,
@@ -9993,13 +10025,15 @@ fi` : `[ ! -e "$wt" ] || { echo "$wt already exists" >&2; exit 4; }`,
9993
10025
  `git -C "$root" worktree add --detach "$wt" "$base" >&2 || undo`,
9994
10026
  `gd=$(git -C "$wt" rev-parse --absolute-git-dir) && : > "$gd/${PREPARED_MARK}" || undo`,
9995
10027
  answer
9996
- ].join("\n") : `cd ${remotePathWord(opts.cwd ?? profile.repo)} && printf 'MU_CWD %s\\n' "$(pwd -P)"`;
10028
+ ].join("\n") : `cd ${remotePathWord(opts.cwd ?? opts.root ?? "~")} && printf 'MU_CWD %s\\n' "$(pwd -P)"`;
9997
10029
  const r = await runLeasedTemplate(
9998
10030
  db,
9999
10031
  lease,
10000
10032
  profile,
10001
10033
  "exec",
10002
- { cmd: script(body), cwd: profile.repo },
10034
+ // `.`, not `~`, without a repo: a template's `cd {cwd:sh}` stays where the login shell
10035
+ // started (a quoted `~` never expands); the script itself resolves --cwd and "$HOME".
10036
+ { cmd: script(body), cwd: opts.root ?? "." },
10003
10037
  REMOTE_PREPARE_TIMEOUT_MS
10004
10038
  );
10005
10039
  const what = opts.worktree ? "creating the remote worktree failed" : "the remote cwd is unreachable";
@@ -10021,18 +10055,28 @@ fi` : `[ ! -e "$wt" ] || { echo "$wt already exists" >&2; exit 4; }`,
10021
10055
  }
10022
10056
  const lines = r.stdout.split("\n").map((l) => l.trim());
10023
10057
  if (opts.worktree) {
10058
+ const repo = decodeRepo(
10059
+ [...lines].reverse().find((l) => /^MU_REPO [0-9a-f]+$/.test(l))?.slice("MU_REPO ".length)
10060
+ );
10024
10061
  for (const l of lines.reverse()) {
10025
10062
  const m = /^MU_WT ([0-9a-f]{40,64}) (\/.+)$/.exec(l);
10026
- if (m?.[1] !== void 0 && m[2] !== void 0) return { cwd: m[2], base: m[1] };
10063
+ if (m?.[1] !== void 0 && m[2] !== void 0 && repo !== void 0) {
10064
+ return { cwd: m[2], base: m[1], repo };
10065
+ }
10027
10066
  }
10028
10067
  } else {
10029
10068
  for (const l of lines.reverse()) {
10030
10069
  const m = /^MU_CWD (\/.+)$/.exec(l);
10031
- if (m?.[1] !== void 0) return { cwd: m[1], base: null };
10070
+ if (m?.[1] !== void 0) return { cwd: m[1], base: null, repo: null };
10032
10071
  }
10033
10072
  }
10034
10073
  throw new RemotePrepareError(profile.name, what, `the host's answer named no path${lost}`);
10035
10074
  }
10075
+ function decodeRepo(hex) {
10076
+ if (hex === void 0 || hex.length % 2 !== 0) return void 0;
10077
+ const path = Buffer.from(hex, "hex").toString("utf8");
10078
+ return path.startsWith("/") ? path : void 0;
10079
+ }
10036
10080
  async function removeRemoteWorktree(db, host, holder, wt) {
10037
10081
  const p = shellQuote(wt.path);
10038
10082
  const body = [
@@ -10089,8 +10133,8 @@ function quiescenceLines(probe, force = false) {
10089
10133
  ];
10090
10134
  }
10091
10135
  async function closeRemoteWorktree(db, agent, opts = {}) {
10092
- const { name, workstreamName: ws, host, cwd: path, base, collectedHead } = agent;
10093
- if (host === null || path === null || base === null) {
10136
+ const { name, workstreamName: ws, host, cwd: path, base, collectedHead, repo } = agent;
10137
+ if (host === null || path === null || base === null || repo === null) {
10094
10138
  throw new Error(say("remote.no_worktree", name));
10095
10139
  }
10096
10140
  const asidePath = asideOf(path, agent.incarnation);
@@ -10104,7 +10148,7 @@ async function closeRemoteWorktree(db, agent, opts = {}) {
10104
10148
  `close:${ws}/${name}`,
10105
10149
  async (profile, lease) => {
10106
10150
  opts.checkProfile?.(profile);
10107
- const root = remotePathWord(profile.repo);
10151
+ const root = shellQuote(repo);
10108
10152
  const wt = shellQuote(path);
10109
10153
  const aside = shellQuote(asidePath);
10110
10154
  const run2 = async (body) => {
@@ -10113,7 +10157,7 @@ async function closeRemoteWorktree(db, agent, opts = {}) {
10113
10157
  lease,
10114
10158
  profile,
10115
10159
  "exec",
10116
- { cmd: script(body), cwd: profile.repo },
10160
+ { cmd: script(body), cwd: repo },
10117
10161
  REMOTE_PREPARE_TIMEOUT_MS
10118
10162
  );
10119
10163
  if (r2.exitCode === null) refuse("unreadable", `host ${host} timed out`);
@@ -10350,13 +10394,14 @@ function assertRecoveryProfile(rec, profile) {
10350
10394
  ]);
10351
10395
  };
10352
10396
  if (profile === void 0) refuse(`host ${rec.host} has no profile now`);
10353
- if (rec.hostExec === null || rec.hostRepo === null) {
10397
+ if (rec.hostExec === null) {
10354
10398
  refuse(
10355
10399
  `host ${rec.host} had no profile when the agent was removed: nothing proves the profile now named ${rec.host} reaches that worktree`
10356
10400
  );
10357
10401
  }
10358
- if (profile?.exec !== rec.hostExec || profile.repo !== rec.hostRepo) {
10359
- refuse(`host ${rec.host}'s profile changed since the agent was removed (exec or repo differs)`);
10402
+ if (rec.hostRepo === null) refuse(`recovery ${rec.agentId} names no repo for its worktree`);
10403
+ if (profile?.exec !== rec.hostExec) {
10404
+ refuse(`host ${rec.host}'s profile changed since the agent was removed (exec differs)`);
10360
10405
  }
10361
10406
  }
10362
10407
  function assertRecoveryUsable(db, rec) {
@@ -10398,7 +10443,7 @@ function assertRecoveryUsable(db, rec) {
10398
10443
  }
10399
10444
  assertRecoveryProfile(
10400
10445
  rec,
10401
- db.prepare("SELECT exec, repo FROM host_profiles WHERE name = ?").get(rec.host)
10446
+ db.prepare("SELECT exec FROM host_profiles WHERE name = ?").get(rec.host)
10402
10447
  );
10403
10448
  }
10404
10449
  function assertRecoveryUnchanged(db, rec) {
@@ -10649,16 +10694,16 @@ function registerOwnedWait(db, caller, refs) {
10649
10694
  for (const r of add) if (r.kind === "agent") unmark.run(caller, doneKey(r.workstream, r.id));
10650
10695
  const baselines = { ...old?.baselines ?? {} };
10651
10696
  for (const [k, b] of incoming) baselines[k] = mergeBaseline(baselines[k], b);
10652
- const now = (/* @__PURE__ */ new Date()).toISOString();
10697
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
10653
10698
  const baselinesJson = JSON.stringify(baselines);
10654
10699
  if (row === void 0) {
10655
10700
  db.prepare(
10656
10701
  "INSERT INTO wait_registrations (caller, refs_json, state, generation, updated_at, baselines_json) VALUES (?, ?, 'armed', 0, ?, ?)"
10657
- ).run(caller, JSON.stringify(merged), now, baselinesJson);
10702
+ ).run(caller, JSON.stringify(merged), now2, baselinesJson);
10658
10703
  } else if (merged.length !== have.length || baselinesJson !== row.baselines_json) {
10659
10704
  db.prepare(
10660
10705
  "UPDATE wait_registrations SET refs_json = ?, baselines_json = ?, updated_at = ? WHERE caller = ?"
10661
- ).run(JSON.stringify(merged), baselinesJson, now, caller);
10706
+ ).run(JSON.stringify(merged), baselinesJson, now2, caller);
10662
10707
  }
10663
10708
  const after = select(db, caller);
10664
10709
  if (after === void 0) throw new WaitRegistrationError("wait registration vanished");
@@ -10836,6 +10881,7 @@ async function closeRecovery(db, rec, opts) {
10836
10881
  cwd: rec.cwd,
10837
10882
  base: rec.base,
10838
10883
  collectedHead: rec.collectedHead,
10884
+ repo: rec.hostRepo,
10839
10885
  // The ORIGINAL registry incarnation: the aside path derives from it.
10840
10886
  incarnation: rec.agentId
10841
10887
  },
@@ -10933,7 +10979,7 @@ async function readGate(db, agent) {
10933
10979
  host,
10934
10980
  "exec",
10935
10981
  `gate:${agent.workstreamName}/${agent.name}`,
10936
- { cmd: readCommand(token), cwd: getHost(db, host).repo },
10982
+ { cmd: readCommand(token), cwd: "." },
10937
10983
  { paneAlive: localPaneAlive }
10938
10984
  );
10939
10985
  } catch (e) {
@@ -10961,7 +11007,7 @@ async function cleanGateResult(db, agent) {
10961
11007
  host,
10962
11008
  "exec",
10963
11009
  `gate-clean:${agent.workstreamName}/${agent.name}`,
10964
- { cmd: cleanCommand(token), cwd: getHost(db, host).repo },
11010
+ { cmd: cleanCommand(token), cwd: "." },
10965
11011
  { paneAlive: localPaneAlive }
10966
11012
  );
10967
11013
  return r.exitCode === 0;
@@ -11201,12 +11247,12 @@ async function previewClose(db, name, opts) {
11201
11247
  const row = worktreeOf(db, opts.workstream, name, agent);
11202
11248
  const state = row === void 0 ? void 0 : await worktreeState(row.path, row.base, row.root);
11203
11249
  const preview = db.transaction(() => {
11204
- const now = getAgentForCleanup(db, name, opts.workstream);
11205
- const nowRow = worktreeOf(db, opts.workstream, name, now);
11206
- if (now?.incarnation !== agent?.incarnation || now?.paneId !== agent?.paneId || nowRow?.path !== row?.path) {
11250
+ const now2 = getAgentForCleanup(db, name, opts.workstream);
11251
+ const nowRow = worktreeOf(db, opts.workstream, name, now2);
11252
+ if (now2?.incarnation !== agent?.incarnation || now2?.paneId !== agent?.paneId || nowRow?.path !== row?.path) {
11207
11253
  return void 0;
11208
11254
  }
11209
- const rec = opts.recoveryId !== void 0 || now === void 0 && nowRow === void 0 ? selectRemoteRecovery(db, opts.workstream, name, opts.recoveryId) : void 0;
11255
+ const rec = opts.recoveryId !== void 0 || now2 === void 0 && nowRow === void 0 ? selectRemoteRecovery(db, opts.workstream, name, opts.recoveryId) : void 0;
11210
11256
  if (rec === void 0 && opts.recoveryId !== void 0) {
11211
11257
  return {
11212
11258
  agentName: name,
@@ -11243,13 +11289,13 @@ async function previewClose(db, name, opts) {
11243
11289
  return {
11244
11290
  agentName: name,
11245
11291
  workstream: opts.workstream,
11246
- kind: now ? "agent" : nowRow ? "orphan-worktree" : "none",
11292
+ kind: now2 ? "agent" : nowRow ? "orphan-worktree" : "none",
11247
11293
  revision: opts.recoveryId === void 0 ? agentRevision(db, opts.workstream, name) : 0,
11248
- incarnation: now?.incarnation ?? null,
11249
- paneId: now?.paneId ?? null,
11250
- ownedTasks: now ? ownedTasksOf(db, now) : [],
11294
+ incarnation: now2?.incarnation ?? null,
11295
+ paneId: now2?.paneId ?? null,
11296
+ ownedTasks: now2 ? ownedTasksOf(db, now2) : [],
11251
11297
  worktree: nowRow && state ? { path: nowRow.path, state } : null,
11252
- remote: remoteOf(now),
11298
+ remote: remoteOf(now2),
11253
11299
  recovery: null,
11254
11300
  refusesDirty: state === "dirty" && !discard,
11255
11301
  discard,
@@ -11295,8 +11341,8 @@ async function closeLocked(db, name, opts) {
11295
11341
  const mux = await activeMux().catch(() => void 0);
11296
11342
  const running = await runInFlight(agent);
11297
11343
  const replaced = () => {
11298
- const now = getAgentForCleanup(db, name, opts.workstream);
11299
- return now !== void 0 && !sameIncarnation(now, agent);
11344
+ const now2 = getAgentForCleanup(db, name, opts.workstream);
11345
+ return now2 !== void 0 && !sameIncarnation(now2, agent);
11300
11346
  };
11301
11347
  if (replaced()) throw new AgentReplacedError(name, opts.workstream);
11302
11348
  fence();
@@ -11551,8 +11597,417 @@ async function watchAgentRun(agent, sock, signal, opts) {
11551
11597
  return { initial, afterRuns, settled };
11552
11598
  }
11553
11599
 
11554
- // src/agents/receipts.ts
11600
+ // src/delegates.ts
11555
11601
  import { createHash as createHash5 } from "crypto";
11602
+ import { dirname as dirname10, join as join8 } from "path";
11603
+
11604
+ // src/delegate-verdict.ts
11605
+ var VERDICT_LABELS = ["CONFIRMED", "REFUTED", "UNVERIFIED"];
11606
+ function formatElapsed(ms) {
11607
+ const t = Math.max(0, Math.round(ms / 1e3));
11608
+ if (t < 60) return `${t}s`;
11609
+ const m = Math.floor(t / 60);
11610
+ if (m < 60) return `${m}m ${String(t % 60).padStart(2, "0")}s`;
11611
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, "0")}m`;
11612
+ }
11613
+ var VERDICT_LINE2 = /^(?:\w+: )?VERDICT:/;
11614
+ var LABEL_RE = new RegExp(`\\b(${VERDICT_LABELS.join("|")})\\b`);
11615
+ function firstVerdictLine(lines) {
11616
+ return lines.findIndex((l) => VERDICT_LINE2.test(l));
11617
+ }
11618
+ function verdictLineLabel(line) {
11619
+ if (!VERDICT_LINE2.test(line)) return null;
11620
+ const after = line.slice(line.indexOf("VERDICT:") + "VERDICT:".length);
11621
+ return LABEL_RE.exec(after)?.[1];
11622
+ }
11623
+ function parseVerdict(answer) {
11624
+ const lines = answer.trimEnd().split("\n");
11625
+ const at = firstVerdictLine(lines);
11626
+ if (at < 0) return void 0;
11627
+ return verdictLineLabel(lines[at] ?? "") ?? void 0;
11628
+ }
11629
+ function recordNote(label, name, outcome, lastText, elapsedMs) {
11630
+ if (outcome !== "done" || !lastText?.trim())
11631
+ return `REFUTER ${label}: no verdict (${outcome === "done" ? "empty" : outcome ?? "no result"})`;
11632
+ const lines = lastText.trimEnd().split("\n");
11633
+ const at = firstVerdictLine(lines);
11634
+ const body = at >= 0 ? lines.slice(at).join("\n") : `NO VERDICT LINE: ${lastText.trimEnd()}`;
11635
+ return `REFUTER ${label} (${name}, ${formatElapsed(elapsedMs)}):
11636
+ ${body}`;
11637
+ }
11638
+
11639
+ // src/delegates.ts
11640
+ function delegateSessionDir(dbPath, workstream, task) {
11641
+ return join8(dirname10(dbPath), "delegate-sessions", workstream, task);
11642
+ }
11643
+ function delegateProcessDir(dbPath, callId) {
11644
+ return join8(
11645
+ dirname10(dbPath),
11646
+ "delegate-process",
11647
+ createHash5("sha256").update(callId).digest("hex").slice(0, 16)
11648
+ );
11649
+ }
11650
+ var TERMINAL_DELEGATE_STATES = ["done", "failed", "cancelled"];
11651
+ var TERMINAL = TERMINAL_DELEGATE_STATES;
11652
+ var isTerminal = (s) => TERMINAL.includes(s);
11653
+ var DelegateCallConflictError = class extends Error {
11654
+ constructor(callId, field) {
11655
+ super(say("delegate.conflict", callId, field));
11656
+ this.callId = callId;
11657
+ this.field = field;
11658
+ }
11659
+ callId;
11660
+ field;
11661
+ name = "DelegateCallConflictError";
11662
+ };
11663
+ var DelegateStateError = class extends Error {
11664
+ constructor(callId, from, to) {
11665
+ super(say("delegate.state", callId, from, to));
11666
+ this.callId = callId;
11667
+ this.from = from;
11668
+ this.to = to;
11669
+ }
11670
+ callId;
11671
+ from;
11672
+ to;
11673
+ name = "DelegateStateError";
11674
+ };
11675
+ var DelegateNotFoundError = class extends Error {
11676
+ constructor(callId) {
11677
+ super(say("delegate.not_found", callId));
11678
+ this.callId = callId;
11679
+ }
11680
+ callId;
11681
+ name = "DelegateNotFoundError";
11682
+ };
11683
+ var SELECT_CALL = `SELECT d.*, t.local_id AS local_id, w.name AS workstream, t.wave AS wave
11684
+ FROM delegate_calls d
11685
+ JOIN tasks t ON t.id = d.task_id
11686
+ JOIN workstreams w ON w.id = t.workstream_id`;
11687
+ function rowOf(r) {
11688
+ let request = { text: "" };
11689
+ try {
11690
+ if (r.request) request = JSON.parse(r.request);
11691
+ } catch {
11692
+ }
11693
+ return {
11694
+ callId: r.call_id,
11695
+ task: r.local_id,
11696
+ workstream: r.workstream,
11697
+ caller: r.caller,
11698
+ state: r.state,
11699
+ wave: r.wave,
11700
+ record: r.record_workstream !== null && r.record_task !== null ? { workstream: r.record_workstream, task: r.record_task, label: r.record_label } : null,
11701
+ request,
11702
+ agent: r.agent,
11703
+ verdict: r.verdict ?? null,
11704
+ error: r.error,
11705
+ updatedAt: r.updated_at
11706
+ };
11707
+ }
11708
+ function getDelegateCall(db, callId) {
11709
+ const r = db.prepare(`${SELECT_CALL} WHERE d.call_id = ?`).get(callId);
11710
+ return r ? rowOf(r) : void 0;
11711
+ }
11712
+ function mustGet(db, callId) {
11713
+ const row = getDelegateCall(db, callId);
11714
+ if (!row) throw new DelegateNotFoundError(callId);
11715
+ return row;
11716
+ }
11717
+ function listDelegates(db, caller, workstream, opts = {}) {
11718
+ const where = [];
11719
+ const args = [];
11720
+ if (caller !== void 0) {
11721
+ where.push("d.caller = ?");
11722
+ args.push(caller);
11723
+ }
11724
+ if (workstream !== void 0) {
11725
+ where.push("w.name = ?");
11726
+ args.push(workstream);
11727
+ }
11728
+ if (opts.wave !== void 0) {
11729
+ where.push("t.wave = ?");
11730
+ args.push(opts.wave);
11731
+ }
11732
+ if (opts.states !== void 0) {
11733
+ where.push(`d.state IN (${opts.states.map(() => "?").join(",")})`);
11734
+ args.push(...opts.states);
11735
+ }
11736
+ if (opts.taskStatuses !== void 0) {
11737
+ where.push(`t.status IN (${opts.taskStatuses.map(() => "?").join(",")})`);
11738
+ args.push(...opts.taskStatuses);
11739
+ }
11740
+ if (opts.updatedAfter !== void 0) {
11741
+ where.push("d.updated_at > ?");
11742
+ args.push(opts.updatedAfter);
11743
+ }
11744
+ const sql = `${SELECT_CALL}${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY d.task_id`;
11745
+ return db.prepare(sql).all(...args).map(rowOf);
11746
+ }
11747
+ function listDelegatesOf(db, callers, workstream, opts = {}) {
11748
+ const set = [...new Set(callers)];
11749
+ if (set.length === 0) return [];
11750
+ const where = [`d.caller IN (${set.map(() => "?").join(",")})`];
11751
+ const args = [...set];
11752
+ if (workstream !== void 0) {
11753
+ where.push("w.name = ?");
11754
+ args.push(workstream);
11755
+ }
11756
+ if (opts.wave !== void 0) {
11757
+ where.push("t.wave = ?");
11758
+ args.push(opts.wave);
11759
+ }
11760
+ const sql = `${SELECT_CALL} WHERE ${where.join(" AND ")} ORDER BY d.task_id`;
11761
+ return db.prepare(sql).all(...args).map(rowOf);
11762
+ }
11763
+ function canonical2(v) {
11764
+ if (Array.isArray(v)) return `[${v.map(canonical2).join(",")}]`;
11765
+ if (typeof v === "object" && v !== null) {
11766
+ const o = v;
11767
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical2(o[k])}`).join(",")}}`;
11768
+ }
11769
+ return JSON.stringify(v);
11770
+ }
11771
+ function taskIdFor2(callId, label) {
11772
+ const stem = (label ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 20).replace(/-+$/, "");
11773
+ const h = createHash5("sha256").update(callId).digest("hex").slice(0, 8);
11774
+ return `delegate_${stem ? `${stem}_` : ""}${h}`;
11775
+ }
11776
+ var now = () => (/* @__PURE__ */ new Date()).toISOString();
11777
+ function registerDelegateCall(db, opts) {
11778
+ const state = opts.state ?? "queued";
11779
+ return withOpContext(
11780
+ db,
11781
+ { intent: "delegate.register", actor: opts.caller, group: "new" },
11782
+ () => db.transaction(() => {
11783
+ const existing = getDelegateCall(db, opts.callId);
11784
+ if (existing) {
11785
+ const same = (field, a, b) => {
11786
+ if (canonical2(a) !== canonical2(b))
11787
+ throw new DelegateCallConflictError(opts.callId, field);
11788
+ };
11789
+ same("caller", existing.caller, opts.caller);
11790
+ same("workstream", existing.workstream, opts.workstream);
11791
+ same("wave", existing.wave, opts.wave ?? null);
11792
+ same(
11793
+ "record target",
11794
+ existing.record ? { workstream: existing.record.workstream, task: existing.record.task } : null,
11795
+ opts.record ?? null
11796
+ );
11797
+ same("request", existing.request, opts.request);
11798
+ return { call: existing, created: false };
11799
+ }
11800
+ if (opts.record && !getTask(db, opts.record.task, opts.record.workstream))
11801
+ throw new TaskNotFoundError(opts.record.task, opts.record.workstream);
11802
+ const local = taskIdFor2(opts.callId, opts.request.label);
11803
+ const headline = (opts.request.label ?? opts.request.text).replace(/\s+/g, " ").trim();
11804
+ addTask(db, {
11805
+ localId: local,
11806
+ workstream: opts.workstream,
11807
+ title: `delegate: ${headline.slice(0, 60)}`,
11808
+ kind: "delegate",
11809
+ ...opts.wave !== void 0 ? { wave: opts.wave } : {}
11810
+ });
11811
+ addNote(db, local, opts.request.text, {
11812
+ workstream: opts.workstream,
11813
+ kind: "brief",
11814
+ author: opts.caller,
11815
+ group: "inherit"
11816
+ });
11817
+ if (state !== "starting")
11818
+ setTaskStatus(db, local, "OPEN", { workstream: opts.workstream, substate: "parked" });
11819
+ const taskId = db.prepare(
11820
+ "SELECT t.id AS id FROM tasks t JOIN workstreams w ON w.id = t.workstream_id WHERE w.name = ? AND t.local_id = ?"
11821
+ ).get(opts.workstream, local).id;
11822
+ db.prepare(
11823
+ `INSERT INTO delegate_calls
11824
+ (task_id, caller, call_id, state, record_workstream, record_task, record_label, updated_at, request)
11825
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
11826
+ ).run(
11827
+ taskId,
11828
+ opts.caller,
11829
+ opts.callId,
11830
+ state,
11831
+ opts.record?.workstream ?? null,
11832
+ opts.record?.task ?? null,
11833
+ opts.request.label ?? null,
11834
+ now(),
11835
+ canonical2(opts.request)
11836
+ );
11837
+ return { call: mustGet(db, opts.callId), created: true };
11838
+ }).immediate()
11839
+ );
11840
+ }
11841
+ var MOVES = {
11842
+ queued: ["refused"],
11843
+ starting: ["queued", "refused"],
11844
+ running: ["starting"],
11845
+ refused: ["queued", "starting"]
11846
+ };
11847
+ function markDelegate(db, callId, to, extra = {}) {
11848
+ const row = mustGet(db, callId);
11849
+ const from = MOVES[to] ?? [];
11850
+ if (!from.includes(row.state)) throw new DelegateStateError(callId, row.state, to);
11851
+ return withOpContext(
11852
+ db,
11853
+ { intent: `delegate.${to}`, actor: row.caller, group: "new" },
11854
+ () => db.transaction(() => {
11855
+ const r = db.prepare(
11856
+ `UPDATE delegate_calls
11857
+ SET state = ?, updated_at = ?, agent = COALESCE(?, agent),
11858
+ error = CASE WHEN ? = 'refused' THEN COALESCE(?, error) ELSE NULL END
11859
+ WHERE call_id = ? AND state = ?`
11860
+ ).run(to, now(), extra.agent ?? null, to, extra.error ?? null, callId, row.state);
11861
+ if (r.changes !== 1) throw new DelegateStateError(callId, mustGet(db, callId).state, to);
11862
+ if (to === "starting")
11863
+ setTaskStatus(db, row.task, "OPEN", { workstream: row.workstream, substate: "todo" });
11864
+ if (to === "refused")
11865
+ setTaskStatus(db, row.task, "OPEN", { workstream: row.workstream, substate: "parked" });
11866
+ return mustGet(db, callId);
11867
+ }).immediate()
11868
+ );
11869
+ }
11870
+ function closedBy(db, task, workstream) {
11871
+ const row = db.prepare(
11872
+ `SELECT n.author AS author FROM task_notes n
11873
+ JOIN tasks t ON t.id = n.task_id JOIN workstreams w ON w.id = t.workstream_id
11874
+ WHERE w.name = ? AND t.local_id = ? AND t.status = 'CLOSED' AND n.kind = 'close'
11875
+ ORDER BY n.id DESC LIMIT 1`
11876
+ ).get(workstream, task);
11877
+ return row?.author ?? null;
11878
+ }
11879
+ function finishText(outcome, text) {
11880
+ if (outcome === "done") return text ?? "";
11881
+ return `no answer (${outcome})${text?.trim() ? `
11882
+
11883
+ ${text.trimEnd()}` : ""}`;
11884
+ }
11885
+ function finishDelegate(db, callId, opts) {
11886
+ const before = mustGet(db, callId);
11887
+ const unchanged = (row) => ({
11888
+ changed: false,
11889
+ state: row.state,
11890
+ task: row.task,
11891
+ record: row.record ? { workstream: row.record.workstream, task: row.record.task } : null,
11892
+ recorded: false,
11893
+ verdict: row.verdict
11894
+ });
11895
+ if (isTerminal(before.state)) return unchanged(before);
11896
+ db.prepare(
11897
+ "UPDATE delegate_calls SET state = 'recording', updated_at = ? WHERE call_id = ? AND state NOT IN ('done','failed','cancelled')"
11898
+ ).run(now(), callId);
11899
+ const outcome = opts.outcome === "done" && !opts.text?.trim() ? "empty" : opts.outcome;
11900
+ const answered = outcome === "done";
11901
+ const cancelled = outcome === "cancelled";
11902
+ const label = before.record?.label ?? before.request.label ?? opts.author;
11903
+ const verdict = answered ? parseVerdict(opts.text ?? "") ?? null : null;
11904
+ const record = before.record ? { workstream: before.record.workstream, task: before.record.task } : null;
11905
+ const report = finishText(outcome, opts.text);
11906
+ const wantRecord = opts.record !== false;
11907
+ const write = (recordIt, error) => withOpContext(
11908
+ db,
11909
+ { intent: "delegate.finish", actor: opts.author, group: "new" },
11910
+ () => db.transaction(() => {
11911
+ const cur = mustGet(db, callId);
11912
+ if (isTerminal(cur.state)) return unchanged(cur);
11913
+ if (recordIt && record)
11914
+ addNote(
11915
+ db,
11916
+ record.task,
11917
+ recordNote(label, opts.author, outcome, opts.text, opts.elapsedMs ?? 0),
11918
+ {
11919
+ workstream: record.workstream,
11920
+ kind: "verdict",
11921
+ author: opts.author,
11922
+ group: "inherit"
11923
+ }
11924
+ );
11925
+ const ok = answered && error === void 0;
11926
+ const state = ok ? "done" : cancelled ? "cancelled" : "failed";
11927
+ const selfClosed = cur.agent !== null && closedBy(db, cur.task, cur.workstream) === cur.agent;
11928
+ if (selfClosed)
11929
+ addNote(db, cur.task, report, {
11930
+ workstream: cur.workstream,
11931
+ kind: "report",
11932
+ author: opts.author,
11933
+ group: "inherit"
11934
+ });
11935
+ closeTask(db, cur.task, {
11936
+ workstream: cur.workstream,
11937
+ author: opts.author,
11938
+ group: "inherit",
11939
+ ...selfClosed ? {} : { report },
11940
+ as: ok ? "done" : cancelled ? "wontfix" : "rejected",
11941
+ ...ok ? { evidence: `answered ${verdict ?? "with no VERDICT line"} (call ${callId})` } : { why: error ?? `${outcome}: no answer (call ${callId})` }
11942
+ });
11943
+ db.prepare(
11944
+ "UPDATE delegate_calls SET state = ?, verdict = ?, error = ?, updated_at = ? WHERE call_id = ?"
11945
+ ).run(state, ok ? verdict : null, error ?? null, now(), callId);
11946
+ return {
11947
+ changed: true,
11948
+ state,
11949
+ task: cur.task,
11950
+ record,
11951
+ recorded: recordIt && record !== null,
11952
+ verdict: ok ? verdict : null,
11953
+ ...error !== void 0 ? { error } : {}
11954
+ };
11955
+ }).immediate()
11956
+ );
11957
+ try {
11958
+ return write(wantRecord);
11959
+ } catch (e) {
11960
+ const msg = e instanceof Error ? e.message : String(e);
11961
+ return write(
11962
+ false,
11963
+ `record ${record ? `${record.workstream}/${record.task} ` : ""}failed: ${msg}`
11964
+ );
11965
+ }
11966
+ }
11967
+ function verdictTally(db, workstream, wave, callers) {
11968
+ return tallyCalls(
11969
+ callers === void 0 ? listDelegates(db, void 0, workstream, { wave }) : listDelegatesOf(db, callers, workstream, { wave })
11970
+ );
11971
+ }
11972
+ function tallyCalls(calls) {
11973
+ const out = { findings: [], queued: 0, running: 0, refused: [] };
11974
+ const byId = /* @__PURE__ */ new Map();
11975
+ for (const c of calls) {
11976
+ const id = c.record ? `${c.record.workstream}/${c.record.task}` : `${c.workstream}/${c.task}`;
11977
+ let f = byId.get(id);
11978
+ if (!f) {
11979
+ f = { id, confirmed: 0, refuted: 0, unverified: 0, pending: 0 };
11980
+ byId.set(id, f);
11981
+ out.findings.push(f);
11982
+ }
11983
+ switch (c.state) {
11984
+ case "done":
11985
+ if (c.verdict === "CONFIRMED") f.confirmed++;
11986
+ else if (c.verdict === "REFUTED") f.refuted++;
11987
+ else f.unverified++;
11988
+ break;
11989
+ case "failed":
11990
+ case "cancelled":
11991
+ f.unverified++;
11992
+ break;
11993
+ case "refused":
11994
+ f.pending++;
11995
+ out.refused.push(c.callId);
11996
+ break;
11997
+ case "queued":
11998
+ f.pending++;
11999
+ out.queued++;
12000
+ break;
12001
+ default:
12002
+ f.pending++;
12003
+ out.running++;
12004
+ }
12005
+ }
12006
+ return out;
12007
+ }
12008
+
12009
+ // src/agents/receipts.ts
12010
+ import { createHash as createHash6 } from "crypto";
11556
12011
  var fromRaw2 = (r) => ({
11557
12012
  taskId: r.task_id,
11558
12013
  attemptKey: r.attempt_key,
@@ -11573,19 +12028,20 @@ var fromRaw2 = (r) => ({
11573
12028
  function requestSha(req) {
11574
12029
  const parts = [req.name, req.nextFree, req.text];
11575
12030
  if (req.host !== void 0) parts.push(req.host);
11576
- return createHash5("sha256").update(JSON.stringify(parts)).digest("hex");
12031
+ if (req.host !== void 0 && req.root !== void 0) parts.push({ root: req.root });
12032
+ return createHash6("sha256").update(JSON.stringify(parts)).digest("hex");
11577
12033
  }
11578
12034
  function getReceipt(db, taskId, attemptKey) {
11579
12035
  const row = db.prepare("SELECT * FROM spawn_receipts WHERE task_id = ? AND attempt_key = ?").get(taskId, attemptKey);
11580
12036
  return row ? fromRaw2(row) : void 0;
11581
12037
  }
11582
12038
  function reserveReceipt(db, r) {
11583
- const now = (/* @__PURE__ */ new Date()).toISOString();
12039
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
11584
12040
  db.prepare(
11585
12041
  `INSERT INTO spawn_receipts
11586
12042
  (task_id, attempt_key, name, workstream, request_sha, msg_id, state, created_at, updated_at)
11587
12043
  VALUES (?, ?, ?, ?, ?, ?, 'reserved', ?, ?)`
11588
- ).run(r.taskId, r.attemptKey, r.name, r.workstream, r.requestSha, r.msgId, now, now);
12044
+ ).run(r.taskId, r.attemptKey, r.name, r.workstream, r.requestSha, r.msgId, now2, now2);
11589
12045
  const row = getReceipt(db, r.taskId, r.attemptKey);
11590
12046
  if (row === void 0) throw new Error("spawn receipt vanished after reservation");
11591
12047
  return row;
@@ -11656,19 +12112,15 @@ async function tryPinBrief(db, req) {
11656
12112
  }
11657
12113
  function stagedOf(db, row) {
11658
12114
  if (row.host !== null) {
11659
- if (row.cwd === null || row.base === null) return void 0;
11660
- try {
11661
- return {
11662
- path: row.cwd,
11663
- base: row.base,
11664
- root: getHost(db, row.host).repo,
11665
- createdAt: row.createdAt,
11666
- incarnation: row.incarnation,
11667
- host: row.host
11668
- };
11669
- } catch {
11670
- return void 0;
11671
- }
12115
+ if (row.cwd === null || row.base === null || row.repo === null) return void 0;
12116
+ return {
12117
+ path: row.cwd,
12118
+ base: row.base,
12119
+ root: row.repo,
12120
+ createdAt: row.createdAt,
12121
+ incarnation: row.incarnation,
12122
+ host: row.host
12123
+ };
11672
12124
  }
11673
12125
  const wt = row.cwd === null ? void 0 : getWorktreeAt(db, row.cwd);
11674
12126
  if (wt === void 0 || wt.agent !== row.name) return void 0;
@@ -11731,7 +12183,8 @@ async function run(db, req, taskId, key, guard, keep = false) {
11731
12183
  name: req.spawn.name,
11732
12184
  nextFree: req.spawn.nextFree === true,
11733
12185
  text: req.text,
11734
- host: req.spawn.host
12186
+ host: req.spawn.host,
12187
+ root: req.spawn.root
11735
12188
  });
11736
12189
  const saved = getReceipt(db, taskId, key);
11737
12190
  const auto = guard !== void 0;
@@ -11790,6 +12243,10 @@ async function fresh(db, req, taskId, key, sha, reuse = false) {
11790
12243
  const rec = reuse ? resetReceipt(db, taskId, key, base) : reserveReceipt(db, { taskId, attemptKey: key, workstream: ws, ...base });
11791
12244
  return drive(db, req, taskId, key, rec, void 0, void 0);
11792
12245
  }
12246
+ function delegateSession(db, task, ws) {
12247
+ if (getTask(db, task, ws)?.kind !== "delegate") return {};
12248
+ return { piSessionDir: delegateSessionDir(db.name, ws, task) };
12249
+ }
11793
12250
  async function drive(db, req, taskId, key, start, resumed, guard, keep = false) {
11794
12251
  const ws = req.spawn.workstream;
11795
12252
  if (resumed === void 0) guard?.("spawn");
@@ -11826,6 +12283,7 @@ async function drive(db, req, taskId, key, start, resumed, guard, keep = false)
11826
12283
  spawnThrew = true;
11827
12284
  const spawned = await spawnAgent3(db, {
11828
12285
  ...req.spawn,
12286
+ ...delegateSession(db, req.task, ws),
11829
12287
  onIdentity: (r) => {
11830
12288
  rec = updateReceipt(db, taskId, key, {
11831
12289
  state: "pane",
@@ -11865,25 +12323,25 @@ async function drive(db, req, taskId, key, start, resumed, guard, keep = false)
11865
12323
  }
11866
12324
  req.spawn.signal?.throwIfAborted();
11867
12325
  const checked = db.transaction(() => {
11868
- const now = getAgentForCleanup(db, target.name, ws);
11869
- if (now === void 0 || now.incarnation !== target.incarnation || now.paneId !== target.paneId) {
12326
+ const now2 = getAgentForCleanup(db, target.name, ws);
12327
+ if (now2 === void 0 || now2.incarnation !== target.incarnation || now2.paneId !== target.paneId) {
11870
12328
  throw new AgentReplacedError(target.name, ws, "sent to");
11871
12329
  }
11872
12330
  guard?.("send");
11873
12331
  const t = getTask(db, req.task, ws);
11874
12332
  if (t === void 0) throw new TaskNotFoundError(req.task, ws);
11875
12333
  if (t.status === "CLOSED") throw new SpawnTaskStateError(req.task, "was closed");
11876
- if (t.ownerId !== now.incarnation) {
12334
+ if (t.ownerId !== now2.incarnation) {
11877
12335
  if (t.ownerId !== null) {
11878
12336
  throw new TaskAlreadyOwnedError(req.task, t.ownerName ?? "<unknown>");
11879
12337
  }
11880
- throw new SpawnTaskStateError(req.task, `is no longer claimed by ${now.name}`);
12338
+ throw new SpawnTaskStateError(req.task, `is no longer claimed by ${now2.name}`);
11881
12339
  }
11882
12340
  const journaled = updateReceipt(db, taskId, key, {
11883
12341
  state: "sending",
11884
- transport: expectsCtl(now, agentCtlSocket(db, now)) ? "ctl" : "mux"
12342
+ transport: expectsCtl(now2, agentCtlSocket(db, now2)) ? "ctl" : "mux"
11885
12343
  });
11886
- return { row: now, rec: journaled };
12344
+ return { row: now2, rec: journaled };
11887
12345
  }).immediate();
11888
12346
  row = checked.row;
11889
12347
  rec = checked.rec;
@@ -11995,13 +12453,13 @@ function idleThresholdMs() {
11995
12453
  if (!Number.isFinite(n) || n < 0) return DEFAULT_IDLE_THRESHOLD_MS;
11996
12454
  return n;
11997
12455
  }
11998
- function computeAgentIdle(db, agent, now = Date.now()) {
12456
+ function computeAgentIdle(db, agent, now2 = Date.now()) {
11999
12457
  if (agent.state !== "needs_input" || agent.since === null) return false;
12000
12458
  const threshold = idleThresholdMs();
12001
12459
  if (threshold <= 0) return false;
12002
12460
  const since = Date.parse(agent.since);
12003
12461
  if (!Number.isFinite(since)) return false;
12004
- if (now - since < threshold) return false;
12462
+ if (now2 - since < threshold) return false;
12005
12463
  if (isScratchWorkstream(agent.workstreamName)) return true;
12006
12464
  const wsId = tryResolveWorkstreamId(db, agent.workstreamName);
12007
12465
  if (wsId === null) return false;
@@ -12028,6 +12486,7 @@ var SELECT_AGENT_COLS = `
12028
12486
  a.spawner AS spawner,
12029
12487
  a.host AS host,
12030
12488
  a.collected_head AS collected_head,
12489
+ a.repo AS repo,
12031
12490
  a.gate_token AS gate_token,
12032
12491
  a.created_at AS created_at,
12033
12492
  a.updated_at AS updated_at
@@ -12049,6 +12508,7 @@ function rowFromDb3(row) {
12049
12508
  spawner: row.spawner,
12050
12509
  host: row.host,
12051
12510
  collectedHead: row.collected_head,
12511
+ repo: row.repo,
12052
12512
  gateToken: row.gate_token,
12053
12513
  createdAt: row.created_at,
12054
12514
  updatedAt: row.updated_at
@@ -12064,13 +12524,13 @@ function agentIdByName(db, name, workstream) {
12064
12524
  function insertAgent(db, input) {
12065
12525
  ensureWorkstream(db, input.workstream);
12066
12526
  const workstreamId = resolveWorkstreamId(db, input.workstream);
12067
- const now = (/* @__PURE__ */ new Date()).toISOString();
12527
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
12068
12528
  try {
12069
12529
  db.prepare(
12070
- `INSERT INTO agents (name, workstream_id, cli, pane_id, status, role, tab, cwd, base, command, command_source, spawner, host, collected_head, gate_token, created_at, updated_at)
12530
+ `INSERT INTO agents (name, workstream_id, cli, pane_id, status, role, tab, cwd, base, command, command_source, spawner, host, collected_head, repo, gate_token, created_at, updated_at)
12071
12531
  VALUES (@name, @workstreamId, COALESCE(@cli, 'pi'), @paneId, 'spawning',
12072
12532
  COALESCE(@role, 'full-access'), @tab, @cwd, @base, @command, @commandSource,
12073
- @spawner, @host, @collectedHead, @gateToken, @now, @now)`
12533
+ @spawner, @host, @collectedHead, @repo, @gateToken, @now, @now)`
12074
12534
  ).run({
12075
12535
  name: input.name,
12076
12536
  workstreamId,
@@ -12085,8 +12545,9 @@ function insertAgent(db, input) {
12085
12545
  spawner: input.spawner ?? null,
12086
12546
  host: input.host ?? null,
12087
12547
  collectedHead: input.collectedHead ?? null,
12548
+ repo: input.repo ?? null,
12088
12549
  gateToken: input.gateToken ?? null,
12089
- now
12550
+ now: now2
12090
12551
  });
12091
12552
  } catch (err) {
12092
12553
  if (err.code === "SQLITE_CONSTRAINT_UNIQUE") {
@@ -12207,7 +12668,7 @@ async function readLiveAgents(db, workstream, only, states) {
12207
12668
  const baseAgents = listAgents(db, { workstream }).filter(
12208
12669
  (a) => only === void 0 || a.name === only
12209
12670
  );
12210
- const stateDir = dirname10(db.name);
12671
+ const stateDir = dirname11(db.name);
12211
12672
  let readings;
12212
12673
  if (states === void 0) readings = await readAgentStates(baseAgents, { stateDir });
12213
12674
  else {
@@ -12217,7 +12678,7 @@ async function readLiveAgents(db, workstream, only, states) {
12217
12678
  }
12218
12679
  readings = states.cache;
12219
12680
  }
12220
- const now = Date.now();
12681
+ const now2 = Date.now();
12221
12682
  return baseAgents.map((agent) => {
12222
12683
  const reading = readings.get(agentKey(agent)) ?? {
12223
12684
  state: "unknown",
@@ -12235,14 +12696,14 @@ async function readLiveAgents(db, workstream, only, states) {
12235
12696
  ...reading.reason !== void 0 ? { reason: reading.reason } : {},
12236
12697
  ...reading.tool !== void 0 ? { tool: reading.tool } : {}
12237
12698
  };
12238
- return computeAgentIdle(db, live2, now) ? { ...live2, idle: true } : live2;
12699
+ return computeAgentIdle(db, live2, now2) ? { ...live2, idle: true } : live2;
12239
12700
  });
12240
12701
  }
12241
12702
 
12242
12703
  // src/agents/spawn-lock.ts
12243
- import { join as join8 } from "path";
12704
+ import { join as join9 } from "path";
12244
12705
  function lockPathForSession(session) {
12245
- return join8(locksDir(), `spawn-${session}.lock`);
12706
+ return join9(locksDir(), `spawn-${session}.lock`);
12246
12707
  }
12247
12708
  var DEFAULT_ACQUIRE_TIMEOUT_MS2 = 15e3;
12248
12709
  var STALE_LOCK_MS = 3e4;
@@ -12257,11 +12718,11 @@ async function withSpawnLock(session, fn, opts) {
12257
12718
 
12258
12719
  // src/agents/staging.ts
12259
12720
  var STAGING_DEAD_MS = 6e4;
12260
- function deadStagings(db, workstream, now = Date.now()) {
12721
+ function deadStagings(db, workstream, now2 = Date.now()) {
12261
12722
  return listAgents(db, workstream === void 0 ? {} : { workstream }).filter((a) => {
12262
12723
  if (!isPendingPaneId(a.paneId) || a.host !== null) return false;
12263
12724
  const made = a.cwd === null ? void 0 : getWorktreeAt(db, a.cwd)?.createdAt;
12264
- return now - Math.max(Date.parse(a.updatedAt), made ? Date.parse(made) : 0) > STAGING_DEAD_MS;
12725
+ return now2 - Math.max(Date.parse(a.updatedAt), made ? Date.parse(made) : 0) > STAGING_DEAD_MS;
12265
12726
  });
12266
12727
  }
12267
12728
  async function reclaimDeadStagings(db, workstream) {
@@ -12451,7 +12912,7 @@ async function spawnAgent3(db, opts) {
12451
12912
  const gateToken = isCommandGate(opts) ? newGateToken() : void 0;
12452
12913
  if (gateToken !== void 0 && opts.worktree) {
12453
12914
  throw new HostProfileError(
12454
- "--worktree cannot be used with a remote command gate (--host with a non-pi --command): a gate runs in the profile's repo or --cwd"
12915
+ "--worktree cannot be used with a remote command gate (--host with a non-pi --command): a gate runs in --cwd, else the profile's default repo"
12455
12916
  );
12456
12917
  }
12457
12918
  const command = opts.command ?? (profile ? cli : resolved.command);
@@ -12480,7 +12941,8 @@ async function spawnAgent3(db, opts) {
12480
12941
  workstream: opts.workstream,
12481
12942
  name: opts.name,
12482
12943
  worktree: false,
12483
- ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {}
12944
+ ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
12945
+ ...remoteRoot(opts, profile)
12484
12946
  });
12485
12947
  } else if (opts.worktree) {
12486
12948
  staged = await prestageWorktree(db, opts, cli, spawner);
@@ -12491,8 +12953,8 @@ async function spawnAgent3(db, opts) {
12491
12953
  throw err;
12492
12954
  }
12493
12955
  const paneCwd = staged?.path ?? site?.cwd ?? resolve3(opts.cwd ?? process.cwd());
12494
- const ctlSocket = ctlSocketPath(opts.workstream, opts.name, dirname11(db.name));
12495
- mkdirSync3(dirname11(ctlSocket), { recursive: true, mode: 448 });
12956
+ const ctlSocket = ctlSocketPath(opts.workstream, opts.name, dirname12(db.name));
12957
+ mkdirSync3(dirname12(ctlSocket), { recursive: true, mode: 448 });
12496
12958
  const paneEnv = profile ? {} : {
12497
12959
  MU_MANAGED_AGENT: "1",
12498
12960
  MU_AGENT_NAME: opts.name,
@@ -12501,6 +12963,9 @@ async function spawnAgent3(db, opts) {
12501
12963
  MU_DB_PATH: resolve3(db.name !== "" && db.name !== ":memory:" ? db.name : defaultDbPath()),
12502
12964
  MU_STATE_DIR: resolve3(defaultStateDir())
12503
12965
  };
12966
+ if (!profile && opts.piSessionDir !== void 0 && speaksMuCtl(cli, command)) {
12967
+ paneEnv.PI_CODING_AGENT_SESSION_DIR = resolve3(opts.piSessionDir);
12968
+ }
12504
12969
  const sshSock = process.env.SSH_AUTH_SOCK;
12505
12970
  if (sshSock) {
12506
12971
  try {
@@ -12720,12 +13185,16 @@ async function prestageLocked(db, opts, cli, spawner) {
12720
13185
  throw err;
12721
13186
  }
12722
13187
  }
13188
+ function remoteRoot(opts, profile) {
13189
+ const root = opts.root ?? profile.repo ?? void 0;
13190
+ return root === void 0 ? {} : { root };
13191
+ }
12723
13192
  async function requireHostSpawn(db, opts) {
12724
13193
  const host = opts.host ?? "";
12725
13194
  const profile = getHost(db, host);
12726
- if (opts.root !== void 0) {
13195
+ if (opts.worktree && opts.root === void 0 && profile.repo === null) {
12727
13196
  throw new HostProfileError(
12728
- "--root names a local repo and cannot be used with --host (the worktree is made in the profile's --repo)"
13197
+ `host ${host} has no default repo: pass --root <remote repo path> with --worktree`
12729
13198
  );
12730
13199
  }
12731
13200
  const mux = await activeMux();
@@ -12751,6 +13220,7 @@ async function prestageRemote(db, opts, cli, spawner, lease, profile) {
12751
13220
  name: opts.name,
12752
13221
  worktree: true,
12753
13222
  ...opts.from !== void 0 ? { from: opts.from } : {},
13223
+ ...remoteRoot(opts, profile),
12754
13224
  // An unused leftover of a prepare whose answer was lost is taken over;
12755
13225
  // never while a removed agent's recovery record may name that tree.
12756
13226
  reclaim: !recoveriesFor(db, opts.workstream, opts.name).some(
@@ -12760,7 +13230,7 @@ async function prestageRemote(db, opts, cli, spawner, lease, profile) {
12760
13230
  return {
12761
13231
  path: made.cwd,
12762
13232
  base: made.base ?? "",
12763
- root: profile.repo,
13233
+ root: made.repo ?? "",
12764
13234
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12765
13235
  incarnation: placeholder.incarnation,
12766
13236
  host: profile.name
@@ -12799,7 +13269,7 @@ function finalizeAgentRow(db, args) {
12799
13269
  });
12800
13270
  }
12801
13271
  const patched = db.prepare(
12802
- `UPDATE agents SET pane_id = ?, cwd = ?, base = ?, command = ?, command_source = ?, spawner = ?, host = ?, updated_at = ?
13272
+ `UPDATE agents SET pane_id = ?, cwd = ?, base = ?, command = ?, command_source = ?, spawner = ?, host = ?, repo = ?, updated_at = ?
12803
13273
  WHERE id = ? AND pane_id = ?
12804
13274
  AND name = ?
12805
13275
  AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`
@@ -12811,6 +13281,7 @@ function finalizeAgentRow(db, args) {
12811
13281
  commandSource,
12812
13282
  spawner,
12813
13283
  host ?? null,
13284
+ staged.host !== void 0 ? staged.root : null,
12814
13285
  (/* @__PURE__ */ new Date()).toISOString(),
12815
13286
  staged.incarnation,
12816
13287
  pendingPaneIdFor(opts.name),
@@ -13688,6 +14159,22 @@ export {
13688
14159
  observeAgentStates,
13689
14160
  settledCloseInput,
13690
14161
  watchAgentRun,
14162
+ verdictLineLabel,
14163
+ parseVerdict,
14164
+ delegateSessionDir,
14165
+ delegateProcessDir,
14166
+ TERMINAL_DELEGATE_STATES,
14167
+ DelegateCallConflictError,
14168
+ DelegateStateError,
14169
+ DelegateNotFoundError,
14170
+ getDelegateCall,
14171
+ listDelegates,
14172
+ listDelegatesOf,
14173
+ registerDelegateCall,
14174
+ markDelegate,
14175
+ finishDelegate,
14176
+ verdictTally,
14177
+ tallyCalls,
13691
14178
  requestSha,
13692
14179
  getReceipt,
13693
14180
  reserveReceipt,
@@ -13763,4 +14250,4 @@ export {
13763
14250
  murmurAvailable,
13764
14251
  readAgentStates
13765
14252
  };
13766
- //# sourceMappingURL=chunk-S424D5EP.js.map
14253
+ //# sourceMappingURL=chunk-UCZPJX7K.js.map