@mu-crew/mu 4.0.0 → 4.1.1

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 (35) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-5HECDBK3.js → chunk-B74N2EAS.js} +16 -16
  3. package/dist/{chunk-GQNHIPJN.js → chunk-DEYBQDTO.js} +21 -421
  4. package/dist/chunk-DEYBQDTO.js.map +1 -0
  5. package/dist/{chunk-S424D5EP.js → chunk-L7I3BLXP.js} +635 -142
  6. package/dist/chunk-L7I3BLXP.js.map +1 -0
  7. package/dist/{chunk-AW2NONYU.js → chunk-LBBZAG5C.js} +81 -7
  8. package/dist/chunk-LBBZAG5C.js.map +1 -0
  9. package/dist/{cli-GB2CAVVW.js → cli-H2PWJAJN.js} +346 -44
  10. package/dist/cli-H2PWJAJN.js.map +1 -0
  11. package/dist/cli.js +1 -1
  12. package/dist/{errors-KHOYKD7D.js → errors-G5IVXSMY.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-EN2CDP6G.js} +102 -27
  18. package/dist/tui-EN2CDP6G.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/guide/sql.md +1 -0
  25. package/docs/reference/env.md +1 -0
  26. package/docs/reference/naming.md +2 -0
  27. package/package.json +1 -1
  28. package/skills/mu/recipes/remote-workers.md +7 -6
  29. package/dist/chunk-AW2NONYU.js.map +0 -1
  30. package/dist/chunk-GQNHIPJN.js.map +0 -1
  31. package/dist/chunk-S424D5EP.js.map +0 -1
  32. package/dist/cli-GB2CAVVW.js.map +0 -1
  33. package/dist/tui-ALPHYTA2.js.map +0 -1
  34. /package/dist/{chunk-5HECDBK3.js.map → chunk-B74N2EAS.js.map} +0 -0
  35. /package/dist/{errors-KHOYKD7D.js.map → errors-G5IVXSMY.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"
@@ -4761,7 +4778,8 @@ function muTable(opts) {
4761
4778
  }
4762
4779
  function isJsonMode() {
4763
4780
  const argv = process.argv;
4764
- return argv.some((a) => a === "--json" || a.startsWith("--json=")) || isSqlVerbArgv(argv) && !argv.includes("--table") || isStateVerbArgv(argv) || isWaitVerbArgv(argv);
4781
+ return argv.some((a) => a === "--json" || a.startsWith("--json=")) || (isSqlVerbArgv(argv) || isAgentListArgv(argv)) && !argv.includes("--table") || isStateVerbArgv(argv) || isWaitVerbArgv(argv) || // `mu next` prints ONE JSON result for every outcome; its failures too.
4782
+ verbWords(argv)[0] === "next";
4765
4783
  }
4766
4784
  function verbWords(argv) {
4767
4785
  const words = [];
@@ -4776,6 +4794,10 @@ function isSqlVerbArgv(argv) {
4776
4794
  const words = verbWords(argv);
4777
4795
  return words[0] === "sql" || words[0] === "db" && words[1] === "query";
4778
4796
  }
4797
+ function isAgentListArgv(argv) {
4798
+ const [first, second] = verbWords(argv);
4799
+ return first === "agent" && second === "list";
4800
+ }
4779
4801
  function isStateVerbArgv(argv) {
4780
4802
  const [first, second] = verbWords(argv);
4781
4803
  return first === "state" || first === "me" && second !== "tasks" && second !== "next";
@@ -5847,8 +5869,10 @@ function respawnCommand(agent) {
5847
5869
  if (agent.host !== null) parts.push("--host", shellWord(agent.host));
5848
5870
  if (agent.role !== "full-access") parts.push("--role", shellWord(agent.role));
5849
5871
  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));
5872
+ if (agent.base !== null) {
5873
+ parts.push("--worktree");
5874
+ if (agent.host !== null && agent.repo !== null) parts.push("--root", shellWord(agent.repo));
5875
+ } else if (agent.cwd !== null) parts.push("--cwd", shellWord(agent.cwd));
5852
5876
  return parts.join(" ");
5853
5877
  }
5854
5878
  async function extensionOutdated(agent, sock, e) {
@@ -6198,8 +6222,8 @@ function mapMurmurState(token, since) {
6198
6222
  function parseSince(value) {
6199
6223
  return value !== void 0 && /^\d+$/.test(value) ? Number(value) : null;
6200
6224
  }
6201
- async function readMurmur(now) {
6202
- if (murmurCache !== void 0 && now - murmurCache.at < MURMUR_CACHE_MS) {
6225
+ async function readMurmur(now2) {
6226
+ if (murmurCache !== void 0 && now2 - murmurCache.at < MURMUR_CACHE_MS) {
6203
6227
  return murmurCache.stdout;
6204
6228
  }
6205
6229
  let stdout;
@@ -6208,7 +6232,7 @@ async function readMurmur(now) {
6208
6232
  } catch {
6209
6233
  stdout = null;
6210
6234
  }
6211
- murmurCache = { at: now, stdout };
6235
+ murmurCache = { at: now2, stdout };
6212
6236
  return stdout;
6213
6237
  }
6214
6238
  function remotePanes(stdout) {
@@ -6505,7 +6529,9 @@ function addHost(db, p) {
6505
6529
  if (p.max_sessions !== null && !(Number.isInteger(p.max_sessions) && p.max_sessions > 0)) {
6506
6530
  throw new HostProfileError("--max-sessions must be a positive integer");
6507
6531
  }
6508
- if (p.repo.trim() === "") throw new HostProfileError("--repo must not be empty");
6532
+ if (p.repo !== null && p.repo.trim() === "") {
6533
+ throw new HostProfileError("--repo must not be empty");
6534
+ }
6509
6535
  for (const f of ["shell", "exec", "fetch"]) {
6510
6536
  try {
6511
6537
  validateHostTemplate(p[f]);
@@ -6696,8 +6722,8 @@ function pidProvenGone(pid, start) {
6696
6722
  return e.code === "ESRCH";
6697
6723
  }
6698
6724
  if (start === null) return false;
6699
- const now = pidStart(pid);
6700
- return now !== null && now !== start;
6725
+ const now2 = pidStart(pid);
6726
+ return now2 !== null && now2 !== start;
6701
6727
  }
6702
6728
  function groupAlive(pgid, start) {
6703
6729
  try {
@@ -6706,8 +6732,8 @@ function groupAlive(pgid, start) {
6706
6732
  return e.code !== "ESRCH";
6707
6733
  }
6708
6734
  if (start === null) return true;
6709
- const now = pidStart(pgid);
6710
- return now === null || now === start;
6735
+ const now2 = pidStart(pgid);
6736
+ return now2 === null || now2 === start;
6711
6737
  }
6712
6738
  async function probeAttached(db, host, paneAlive3) {
6713
6739
  if (paneAlive3 === void 0) return [];
@@ -6834,6 +6860,7 @@ async function checkHost(db, host, opts = {}) {
6834
6860
  host,
6835
6861
  `check:${process.pid}`,
6836
6862
  async (profile, lease) => {
6863
+ const root = opts.root ?? profile.repo ?? void 0;
6837
6864
  const step = async (name, cmd, good, unmet) => {
6838
6865
  let detail;
6839
6866
  let ok = false;
@@ -6843,7 +6870,8 @@ async function checkHost(db, host, opts = {}) {
6843
6870
  lease,
6844
6871
  profile,
6845
6872
  "exec",
6846
- { cmd, cwd: profile.repo },
6873
+ // `.` without a repo: a template's `cd {cwd:sh}` stays in the login directory.
6874
+ { cmd, cwd: root ?? "." },
6847
6875
  timeoutMs
6848
6876
  );
6849
6877
  const why = firstLine(r.stderr) || firstLine(r.stdout);
@@ -6858,12 +6886,11 @@ async function checkHost(db, host, opts = {}) {
6858
6886
  return ok;
6859
6887
  };
6860
6888
  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
- );
6889
+ if (root === void 0) {
6890
+ steps.push({ step: "repo", ok: null, detail: "skipped: no repo given (--root)" });
6891
+ } else {
6892
+ await step("repo", `git -C ${remotePathWord(root)} rev-parse --git-dir`, () => root, "");
6893
+ }
6867
6894
  await step(
6868
6895
  "pi",
6869
6896
  login("pi --version"),
@@ -6903,7 +6930,7 @@ async function checkHost(db, host, opts = {}) {
6903
6930
  },
6904
6931
  opts
6905
6932
  );
6906
- return { host, ok: steps.every((s) => s.ok === true), steps };
6933
+ return { host, ok: steps.every((s) => s.ok !== false), steps };
6907
6934
  }
6908
6935
 
6909
6936
  // src/op-context.ts
@@ -7031,15 +7058,15 @@ function reapAgentRow(db, agentId, name, opts = {}) {
7031
7058
  "INSERT INTO task_notes (task_id, author, content, created_at, kind) VALUES (?, 'reaper', ?, ?, 'note')"
7032
7059
  );
7033
7060
  for (const t of stuck) {
7034
- const now = (/* @__PURE__ */ new Date()).toISOString();
7035
- reopen.run(now, t.taskId);
7061
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
7062
+ reopen.run(now2, t.taskId);
7036
7063
  withOpContext(
7037
7064
  db,
7038
7065
  { intent: "task.note", actor: "reaper" },
7039
7066
  () => note.run(
7040
7067
  t.taskId,
7041
7068
  `[reaper] previous owner ${name} gone (agent removed); status reverted IN_PROGRESS \u2192 OPEN, owner cleared`,
7042
- now
7069
+ now2
7043
7070
  )
7044
7071
  );
7045
7072
  }
@@ -7197,7 +7224,7 @@ function agentRevision(db, workstream, name, recoveryId) {
7197
7224
  if (recoveryId !== void 0) return recoveryRevision(db, workstream, name, recoveryId);
7198
7225
  const agent = db.prepare(
7199
7226
  `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
7227
+ a.spawner, a.host, a.collected_head, a.repo, a.gate_token, a.created_at
7201
7228
  FROM agents a JOIN workstreams w ON w.id = a.workstream_id
7202
7229
  WHERE w.name = ? AND a.name = ?`
7203
7230
  ).get(workstream, name);
@@ -7504,8 +7531,8 @@ function taskIdFor(db, localId, workstream) {
7504
7531
  const row = db.prepare("SELECT id FROM tasks WHERE workstream_id = ? AND local_id = ?").get(wsId, localId);
7505
7532
  return row ? row.id : null;
7506
7533
  }
7507
- function touchTask(db, taskId, now) {
7508
- const ts = now ?? (/* @__PURE__ */ new Date()).toISOString();
7534
+ function touchTask(db, taskId, now2) {
7535
+ const ts = now2 ?? (/* @__PURE__ */ new Date()).toISOString();
7509
7536
  db.prepare("UPDATE tasks SET updated_at = ? WHERE id = ? AND updated_at < ?").run(ts, taskId, ts);
7510
7537
  }
7511
7538
 
@@ -7752,13 +7779,13 @@ function listTasksByOwnerCrossWorkstream(db, owner, opts = {}) {
7752
7779
  // src/tasks/edges.ts
7753
7780
  function dedupeBlockersById(blockers) {
7754
7781
  const seen = /* @__PURE__ */ new Set();
7755
- const canonical2 = [];
7782
+ const canonical3 = [];
7756
7783
  for (const blocker of blockers) {
7757
7784
  if (seen.has(blocker.id)) continue;
7758
7785
  seen.add(blocker.id);
7759
- canonical2.push(blocker);
7786
+ canonical3.push(blocker);
7760
7787
  }
7761
- return canonical2;
7788
+ return canonical3;
7762
7789
  }
7763
7790
  function sameNumberSet(left, right) {
7764
7791
  if (left.length !== right.length) return false;
@@ -7891,13 +7918,13 @@ function addBlockEdgeImpl(db, workstream, blocked, blocker) {
7891
7918
  if (wouldCreateCycle(db, blockerId, blockedId)) {
7892
7919
  throw cycleErrorFor(db, blocker, blocked, blockerId, blockedId, blockedRow.workstreamName);
7893
7920
  }
7894
- const now = (/* @__PURE__ */ new Date()).toISOString();
7921
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
7895
7922
  const added = db.transaction(() => {
7896
7923
  const result = db.prepare(
7897
7924
  "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);
7925
+ ).run(blockerId, blockedId, now2, blockerId, blockedId);
7899
7926
  if (result.changes > 0) {
7900
- touchTask(db, blockedId, now);
7927
+ touchTask(db, blockedId, now2);
7901
7928
  return true;
7902
7929
  }
7903
7930
  return false;
@@ -7983,11 +8010,11 @@ function reparentTaskImpl(db, taskLocalId, blockers, scope) {
7983
8010
  const insertEdge = db.prepare(
7984
8011
  "INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)"
7985
8012
  );
7986
- const now = (/* @__PURE__ */ new Date()).toISOString();
8013
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
7987
8014
  for (const blockerId of blockerIds) {
7988
- insertEdge.run(blockerId, taskSurrogateId, now);
8015
+ insertEdge.run(blockerId, taskSurrogateId, now2);
7989
8016
  }
7990
- touchTask(db, taskSurrogateId, now);
8017
+ touchTask(db, taskSurrogateId, now2);
7991
8018
  return { removedEdges: removed.changes, addedEdges: blockerIds.length };
7992
8019
  }).immediate();
7993
8020
  }
@@ -8899,9 +8926,9 @@ async function teardownWorkstream(db, opts) {
8899
8926
  db,
8900
8927
  { intent: "workstream.teardown", group: "new" },
8901
8928
  () => db.transaction(() => {
8902
- const now = workstreamRevision(db, opts.workstream);
8903
- if (now !== opts.ifRevision) {
8904
- throw new WorkstreamRevisionMismatchError(opts.workstream, opts.ifRevision, now);
8929
+ const now2 = workstreamRevision(db, opts.workstream);
8930
+ if (now2 !== opts.ifRevision) {
8931
+ throw new WorkstreamRevisionMismatchError(opts.workstream, opts.ifRevision, now2);
8905
8932
  }
8906
8933
  for (const a of dead) {
8907
8934
  if (!sameRow2.get(a.incarnation, a.name, a.paneId, opts.workstream)) continue;
@@ -9110,7 +9137,7 @@ function addTaskImpl(db, opts) {
9110
9137
  if (existing) {
9111
9138
  throw new TaskExistsError(opts.localId);
9112
9139
  }
9113
- const now = (/* @__PURE__ */ new Date()).toISOString();
9140
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9114
9141
  const insertResult = db.prepare(
9115
9142
  `INSERT INTO tasks (workstream_id, local_id, title, status, substate, kind, wave, impact, effort_days, scored, created_at, updated_at)
9116
9143
  VALUES (?, ?, ?, 'OPEN', ?, ?, ?, ?, ?, ?, ?, ?)`
@@ -9124,8 +9151,8 @@ function addTaskImpl(db, opts) {
9124
9151
  opts.impact ?? DEFAULT_IMPACT,
9125
9152
  opts.effortDays ?? DEFAULT_EFFORT_DAYS,
9126
9153
  opts.impact !== void 0 || opts.effortDays !== void 0 ? 1 : 0,
9127
- now,
9128
- now
9154
+ now2,
9155
+ now2
9129
9156
  );
9130
9157
  const newTaskId = Number(insertResult.lastInsertRowid);
9131
9158
  if (opts.blockedBy && opts.blockedBy.length > 0) {
@@ -9149,7 +9176,7 @@ function addTaskImpl(db, opts) {
9149
9176
  "INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)"
9150
9177
  );
9151
9178
  for (const blocker of canonicalBlockers) {
9152
- insertEdge.run(blocker.id, newTaskId, now);
9179
+ insertEdge.run(blocker.id, newTaskId, now2);
9153
9180
  }
9154
9181
  }
9155
9182
  const row = getTask(db, opts.localId, opts.workstream);
@@ -9204,7 +9231,7 @@ function addBlocksEdges(db, input, blocks, workstream) {
9204
9231
  }
9205
9232
  umbrellas.set(row.id, local);
9206
9233
  }
9207
- const now = (/* @__PURE__ */ new Date()).toISOString();
9234
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9208
9235
  const insertEdge = db.prepare(
9209
9236
  "INSERT INTO task_edges (from_task_id, to_task_id, created_at) VALUES (?, ?, ?)"
9210
9237
  );
@@ -9215,10 +9242,10 @@ function addBlocksEdges(db, input, blocks, workstream) {
9215
9242
  if (wouldCreateCycle(db, newId, umbrellaId)) {
9216
9243
  throw cycleErrorFor(db, t.id, umbrella, newId, umbrellaId, workstream);
9217
9244
  }
9218
- insertEdge.run(newId, umbrellaId, now);
9245
+ insertEdge.run(newId, umbrellaId, now2);
9219
9246
  }
9220
9247
  }
9221
- for (const umbrellaId of umbrellas.keys()) touchTask(db, umbrellaId, now);
9248
+ for (const umbrellaId of umbrellas.keys()) touchTask(db, umbrellaId, now2);
9222
9249
  }
9223
9250
  function lookupEdgeTask(db, wsId, localId, workstream) {
9224
9251
  const row = db.prepare(
@@ -9251,18 +9278,18 @@ function insertNote(db, taskLocalId, content, opts, touch = true) {
9251
9278
  }
9252
9279
  const taskId = taskIdFor(db, task.name, task.workstreamName);
9253
9280
  if (taskId === null) throw new TaskNotFoundError(taskLocalId, task.workstreamName);
9254
- const now = (/* @__PURE__ */ new Date()).toISOString();
9281
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9255
9282
  db.transaction(() => {
9256
9283
  const r = db.prepare(
9257
9284
  "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);
9285
+ ).run(taskId, opts.author ?? null, content, now2, opts.kind ?? "note");
9286
+ if (touch) touchTask(db, taskId, now2);
9260
9287
  return r;
9261
9288
  }).immediate();
9262
9289
  return {
9263
9290
  author: opts.author ?? null,
9264
9291
  content,
9265
- createdAt: now,
9292
+ createdAt: now2,
9266
9293
  kind: opts.kind ?? "note"
9267
9294
  };
9268
9295
  }
@@ -9730,7 +9757,7 @@ async function claimTaskImpl(db, localId, opts) {
9730
9757
  throw new ClaimerNotRegisteredError(agentName, null);
9731
9758
  }
9732
9759
  }
9733
- const now = (/* @__PURE__ */ new Date()).toISOString();
9760
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9734
9761
  let result;
9735
9762
  try {
9736
9763
  result = db.prepare(
@@ -9744,7 +9771,7 @@ async function claimTaskImpl(db, localId, opts) {
9744
9771
  WHERE local_id = ?
9745
9772
  AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)
9746
9773
  AND (owner_id IS NULL OR owner_id = ?)`
9747
- ).run(claimerRow.id, now, localId, opts.workstream, claimerRow.id);
9774
+ ).run(claimerRow.id, now2, localId, opts.workstream, claimerRow.id);
9748
9775
  } catch (err) {
9749
9776
  if (err instanceof Error && err.message === GUARD_ALREADY_OWNED) {
9750
9777
  throw new TaskAlreadyOwnedError(localId, before.ownerName ?? "<unknown>");
@@ -9800,7 +9827,7 @@ async function claimSelf(db, localId, opts) {
9800
9827
  if (!before) throw new TaskNotFoundError(localId, opts.workstream);
9801
9828
  assertTaskRevision(db, before.workstreamName, localId, opts.ifRevision);
9802
9829
  assertNotParked(before, opts);
9803
- const now = (/* @__PURE__ */ new Date()).toISOString();
9830
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
9804
9831
  const result = db.prepare(
9805
9832
  `UPDATE tasks
9806
9833
  SET status = CASE WHEN status = 'OPEN' THEN 'IN_PROGRESS' ELSE status END,
@@ -9809,7 +9836,7 @@ async function claimSelf(db, localId, opts) {
9809
9836
  WHERE local_id = ?
9810
9837
  AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)
9811
9838
  AND owner_id IS NULL`
9812
- ).run(now, localId, before.workstreamName);
9839
+ ).run(now2, localId, before.workstreamName);
9813
9840
  if (result.changes === 0) {
9814
9841
  throw new TaskAlreadyOwnedError(localId, before.ownerName ?? "<unknown>");
9815
9842
  }
@@ -9977,8 +10004,18 @@ function reclaimLines() {
9977
10004
  ];
9978
10005
  }
9979
10006
  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)"`;
10007
+ if (opts.worktree && opts.root === void 0) {
10008
+ throw new RemotePrepareError(
10009
+ profile.name,
10010
+ "creating the remote worktree failed",
10011
+ `host ${profile.name} has no default repo: pass --root <remote repo path>`
10012
+ );
10013
+ }
10014
+ const root = remotePathWord(opts.root ?? "~");
10015
+ const answer = [
10016
+ `r=$(cd "$root" && pwd -P) || exit 5`,
10017
+ `printf 'MU_REPO %s\\nMU_WT %s %s\\n' "$(printf '%s' "$r" | od -An -v -tx1 | tr -d ' \\n')" "$base" "$(cd "$wt" && pwd -P)"`
10018
+ ].join("\n");
9982
10019
  const body = opts.worktree ? [
9983
10020
  `root=${root}`,
9984
10021
  `wt=${remoteWorktreeWord(opts.workstream, opts.name)}`,
@@ -9993,13 +10030,15 @@ fi` : `[ ! -e "$wt" ] || { echo "$wt already exists" >&2; exit 4; }`,
9993
10030
  `git -C "$root" worktree add --detach "$wt" "$base" >&2 || undo`,
9994
10031
  `gd=$(git -C "$wt" rev-parse --absolute-git-dir) && : > "$gd/${PREPARED_MARK}" || undo`,
9995
10032
  answer
9996
- ].join("\n") : `cd ${remotePathWord(opts.cwd ?? profile.repo)} && printf 'MU_CWD %s\\n' "$(pwd -P)"`;
10033
+ ].join("\n") : `cd ${remotePathWord(opts.cwd ?? opts.root ?? "~")} && printf 'MU_CWD %s\\n' "$(pwd -P)"`;
9997
10034
  const r = await runLeasedTemplate(
9998
10035
  db,
9999
10036
  lease,
10000
10037
  profile,
10001
10038
  "exec",
10002
- { cmd: script(body), cwd: profile.repo },
10039
+ // `.`, not `~`, without a repo: a template's `cd {cwd:sh}` stays where the login shell
10040
+ // started (a quoted `~` never expands); the script itself resolves --cwd and "$HOME".
10041
+ { cmd: script(body), cwd: opts.root ?? "." },
10003
10042
  REMOTE_PREPARE_TIMEOUT_MS
10004
10043
  );
10005
10044
  const what = opts.worktree ? "creating the remote worktree failed" : "the remote cwd is unreachable";
@@ -10021,18 +10060,28 @@ fi` : `[ ! -e "$wt" ] || { echo "$wt already exists" >&2; exit 4; }`,
10021
10060
  }
10022
10061
  const lines = r.stdout.split("\n").map((l) => l.trim());
10023
10062
  if (opts.worktree) {
10063
+ const repo = decodeRepo(
10064
+ [...lines].reverse().find((l) => /^MU_REPO [0-9a-f]+$/.test(l))?.slice("MU_REPO ".length)
10065
+ );
10024
10066
  for (const l of lines.reverse()) {
10025
10067
  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] };
10068
+ if (m?.[1] !== void 0 && m[2] !== void 0 && repo !== void 0) {
10069
+ return { cwd: m[2], base: m[1], repo };
10070
+ }
10027
10071
  }
10028
10072
  } else {
10029
10073
  for (const l of lines.reverse()) {
10030
10074
  const m = /^MU_CWD (\/.+)$/.exec(l);
10031
- if (m?.[1] !== void 0) return { cwd: m[1], base: null };
10075
+ if (m?.[1] !== void 0) return { cwd: m[1], base: null, repo: null };
10032
10076
  }
10033
10077
  }
10034
10078
  throw new RemotePrepareError(profile.name, what, `the host's answer named no path${lost}`);
10035
10079
  }
10080
+ function decodeRepo(hex) {
10081
+ if (hex === void 0 || hex.length % 2 !== 0) return void 0;
10082
+ const path = Buffer.from(hex, "hex").toString("utf8");
10083
+ return path.startsWith("/") ? path : void 0;
10084
+ }
10036
10085
  async function removeRemoteWorktree(db, host, holder, wt) {
10037
10086
  const p = shellQuote(wt.path);
10038
10087
  const body = [
@@ -10089,8 +10138,8 @@ function quiescenceLines(probe, force = false) {
10089
10138
  ];
10090
10139
  }
10091
10140
  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) {
10141
+ const { name, workstreamName: ws, host, cwd: path, base, collectedHead, repo } = agent;
10142
+ if (host === null || path === null || base === null || repo === null) {
10094
10143
  throw new Error(say("remote.no_worktree", name));
10095
10144
  }
10096
10145
  const asidePath = asideOf(path, agent.incarnation);
@@ -10104,7 +10153,7 @@ async function closeRemoteWorktree(db, agent, opts = {}) {
10104
10153
  `close:${ws}/${name}`,
10105
10154
  async (profile, lease) => {
10106
10155
  opts.checkProfile?.(profile);
10107
- const root = remotePathWord(profile.repo);
10156
+ const root = shellQuote(repo);
10108
10157
  const wt = shellQuote(path);
10109
10158
  const aside = shellQuote(asidePath);
10110
10159
  const run2 = async (body) => {
@@ -10113,7 +10162,7 @@ async function closeRemoteWorktree(db, agent, opts = {}) {
10113
10162
  lease,
10114
10163
  profile,
10115
10164
  "exec",
10116
- { cmd: script(body), cwd: profile.repo },
10165
+ { cmd: script(body), cwd: repo },
10117
10166
  REMOTE_PREPARE_TIMEOUT_MS
10118
10167
  );
10119
10168
  if (r2.exitCode === null) refuse("unreadable", `host ${host} timed out`);
@@ -10350,13 +10399,14 @@ function assertRecoveryProfile(rec, profile) {
10350
10399
  ]);
10351
10400
  };
10352
10401
  if (profile === void 0) refuse(`host ${rec.host} has no profile now`);
10353
- if (rec.hostExec === null || rec.hostRepo === null) {
10402
+ if (rec.hostExec === null) {
10354
10403
  refuse(
10355
10404
  `host ${rec.host} had no profile when the agent was removed: nothing proves the profile now named ${rec.host} reaches that worktree`
10356
10405
  );
10357
10406
  }
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)`);
10407
+ if (rec.hostRepo === null) refuse(`recovery ${rec.agentId} names no repo for its worktree`);
10408
+ if (profile?.exec !== rec.hostExec) {
10409
+ refuse(`host ${rec.host}'s profile changed since the agent was removed (exec differs)`);
10360
10410
  }
10361
10411
  }
10362
10412
  function assertRecoveryUsable(db, rec) {
@@ -10398,7 +10448,7 @@ function assertRecoveryUsable(db, rec) {
10398
10448
  }
10399
10449
  assertRecoveryProfile(
10400
10450
  rec,
10401
- db.prepare("SELECT exec, repo FROM host_profiles WHERE name = ?").get(rec.host)
10451
+ db.prepare("SELECT exec FROM host_profiles WHERE name = ?").get(rec.host)
10402
10452
  );
10403
10453
  }
10404
10454
  function assertRecoveryUnchanged(db, rec) {
@@ -10649,16 +10699,16 @@ function registerOwnedWait(db, caller, refs) {
10649
10699
  for (const r of add) if (r.kind === "agent") unmark.run(caller, doneKey(r.workstream, r.id));
10650
10700
  const baselines = { ...old?.baselines ?? {} };
10651
10701
  for (const [k, b] of incoming) baselines[k] = mergeBaseline(baselines[k], b);
10652
- const now = (/* @__PURE__ */ new Date()).toISOString();
10702
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
10653
10703
  const baselinesJson = JSON.stringify(baselines);
10654
10704
  if (row === void 0) {
10655
10705
  db.prepare(
10656
10706
  "INSERT INTO wait_registrations (caller, refs_json, state, generation, updated_at, baselines_json) VALUES (?, ?, 'armed', 0, ?, ?)"
10657
- ).run(caller, JSON.stringify(merged), now, baselinesJson);
10707
+ ).run(caller, JSON.stringify(merged), now2, baselinesJson);
10658
10708
  } else if (merged.length !== have.length || baselinesJson !== row.baselines_json) {
10659
10709
  db.prepare(
10660
10710
  "UPDATE wait_registrations SET refs_json = ?, baselines_json = ?, updated_at = ? WHERE caller = ?"
10661
- ).run(JSON.stringify(merged), baselinesJson, now, caller);
10711
+ ).run(JSON.stringify(merged), baselinesJson, now2, caller);
10662
10712
  }
10663
10713
  const after = select(db, caller);
10664
10714
  if (after === void 0) throw new WaitRegistrationError("wait registration vanished");
@@ -10836,6 +10886,7 @@ async function closeRecovery(db, rec, opts) {
10836
10886
  cwd: rec.cwd,
10837
10887
  base: rec.base,
10838
10888
  collectedHead: rec.collectedHead,
10889
+ repo: rec.hostRepo,
10839
10890
  // The ORIGINAL registry incarnation: the aside path derives from it.
10840
10891
  incarnation: rec.agentId
10841
10892
  },
@@ -10933,7 +10984,7 @@ async function readGate(db, agent) {
10933
10984
  host,
10934
10985
  "exec",
10935
10986
  `gate:${agent.workstreamName}/${agent.name}`,
10936
- { cmd: readCommand(token), cwd: getHost(db, host).repo },
10987
+ { cmd: readCommand(token), cwd: "." },
10937
10988
  { paneAlive: localPaneAlive }
10938
10989
  );
10939
10990
  } catch (e) {
@@ -10961,7 +11012,7 @@ async function cleanGateResult(db, agent) {
10961
11012
  host,
10962
11013
  "exec",
10963
11014
  `gate-clean:${agent.workstreamName}/${agent.name}`,
10964
- { cmd: cleanCommand(token), cwd: getHost(db, host).repo },
11015
+ { cmd: cleanCommand(token), cwd: "." },
10965
11016
  { paneAlive: localPaneAlive }
10966
11017
  );
10967
11018
  return r.exitCode === 0;
@@ -11201,12 +11252,12 @@ async function previewClose(db, name, opts) {
11201
11252
  const row = worktreeOf(db, opts.workstream, name, agent);
11202
11253
  const state = row === void 0 ? void 0 : await worktreeState(row.path, row.base, row.root);
11203
11254
  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) {
11255
+ const now2 = getAgentForCleanup(db, name, opts.workstream);
11256
+ const nowRow = worktreeOf(db, opts.workstream, name, now2);
11257
+ if (now2?.incarnation !== agent?.incarnation || now2?.paneId !== agent?.paneId || nowRow?.path !== row?.path) {
11207
11258
  return void 0;
11208
11259
  }
11209
- const rec = opts.recoveryId !== void 0 || now === void 0 && nowRow === void 0 ? selectRemoteRecovery(db, opts.workstream, name, opts.recoveryId) : void 0;
11260
+ const rec = opts.recoveryId !== void 0 || now2 === void 0 && nowRow === void 0 ? selectRemoteRecovery(db, opts.workstream, name, opts.recoveryId) : void 0;
11210
11261
  if (rec === void 0 && opts.recoveryId !== void 0) {
11211
11262
  return {
11212
11263
  agentName: name,
@@ -11243,13 +11294,13 @@ async function previewClose(db, name, opts) {
11243
11294
  return {
11244
11295
  agentName: name,
11245
11296
  workstream: opts.workstream,
11246
- kind: now ? "agent" : nowRow ? "orphan-worktree" : "none",
11297
+ kind: now2 ? "agent" : nowRow ? "orphan-worktree" : "none",
11247
11298
  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) : [],
11299
+ incarnation: now2?.incarnation ?? null,
11300
+ paneId: now2?.paneId ?? null,
11301
+ ownedTasks: now2 ? ownedTasksOf(db, now2) : [],
11251
11302
  worktree: nowRow && state ? { path: nowRow.path, state } : null,
11252
- remote: remoteOf(now),
11303
+ remote: remoteOf(now2),
11253
11304
  recovery: null,
11254
11305
  refusesDirty: state === "dirty" && !discard,
11255
11306
  discard,
@@ -11295,8 +11346,8 @@ async function closeLocked(db, name, opts) {
11295
11346
  const mux = await activeMux().catch(() => void 0);
11296
11347
  const running = await runInFlight(agent);
11297
11348
  const replaced = () => {
11298
- const now = getAgentForCleanup(db, name, opts.workstream);
11299
- return now !== void 0 && !sameIncarnation(now, agent);
11349
+ const now2 = getAgentForCleanup(db, name, opts.workstream);
11350
+ return now2 !== void 0 && !sameIncarnation(now2, agent);
11300
11351
  };
11301
11352
  if (replaced()) throw new AgentReplacedError(name, opts.workstream);
11302
11353
  fence();
@@ -11551,8 +11602,417 @@ async function watchAgentRun(agent, sock, signal, opts) {
11551
11602
  return { initial, afterRuns, settled };
11552
11603
  }
11553
11604
 
11554
- // src/agents/receipts.ts
11605
+ // src/delegates.ts
11555
11606
  import { createHash as createHash5 } from "crypto";
11607
+ import { dirname as dirname10, join as join8 } from "path";
11608
+
11609
+ // src/delegate-verdict.ts
11610
+ var VERDICT_LABELS = ["CONFIRMED", "REFUTED", "UNVERIFIED"];
11611
+ function formatElapsed(ms) {
11612
+ const t = Math.max(0, Math.round(ms / 1e3));
11613
+ if (t < 60) return `${t}s`;
11614
+ const m = Math.floor(t / 60);
11615
+ if (m < 60) return `${m}m ${String(t % 60).padStart(2, "0")}s`;
11616
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, "0")}m`;
11617
+ }
11618
+ var VERDICT_LINE2 = /^(?:\w+: )?VERDICT:/;
11619
+ var LABEL_RE = new RegExp(`\\b(${VERDICT_LABELS.join("|")})\\b`);
11620
+ function firstVerdictLine(lines) {
11621
+ return lines.findIndex((l) => VERDICT_LINE2.test(l));
11622
+ }
11623
+ function verdictLineLabel(line) {
11624
+ if (!VERDICT_LINE2.test(line)) return null;
11625
+ const after = line.slice(line.indexOf("VERDICT:") + "VERDICT:".length);
11626
+ return LABEL_RE.exec(after)?.[1];
11627
+ }
11628
+ function parseVerdict(answer) {
11629
+ const lines = answer.trimEnd().split("\n");
11630
+ const at = firstVerdictLine(lines);
11631
+ if (at < 0) return void 0;
11632
+ return verdictLineLabel(lines[at] ?? "") ?? void 0;
11633
+ }
11634
+ function recordNote(label, name, outcome, lastText, elapsedMs) {
11635
+ if (outcome !== "done" || !lastText?.trim())
11636
+ return `REFUTER ${label}: no verdict (${outcome === "done" ? "empty" : outcome ?? "no result"})`;
11637
+ const lines = lastText.trimEnd().split("\n");
11638
+ const at = firstVerdictLine(lines);
11639
+ const body = at >= 0 ? lines.slice(at).join("\n") : `NO VERDICT LINE: ${lastText.trimEnd()}`;
11640
+ return `REFUTER ${label} (${name}, ${formatElapsed(elapsedMs)}):
11641
+ ${body}`;
11642
+ }
11643
+
11644
+ // src/delegates.ts
11645
+ function delegateSessionDir(dbPath, workstream, task) {
11646
+ return join8(dirname10(dbPath), "delegate-sessions", workstream, task);
11647
+ }
11648
+ function delegateProcessDir(dbPath, callId) {
11649
+ return join8(
11650
+ dirname10(dbPath),
11651
+ "delegate-process",
11652
+ createHash5("sha256").update(callId).digest("hex").slice(0, 16)
11653
+ );
11654
+ }
11655
+ var TERMINAL_DELEGATE_STATES = ["done", "failed", "cancelled"];
11656
+ var TERMINAL = TERMINAL_DELEGATE_STATES;
11657
+ var isTerminal = (s) => TERMINAL.includes(s);
11658
+ var DelegateCallConflictError = class extends Error {
11659
+ constructor(callId, field) {
11660
+ super(say("delegate.conflict", callId, field));
11661
+ this.callId = callId;
11662
+ this.field = field;
11663
+ }
11664
+ callId;
11665
+ field;
11666
+ name = "DelegateCallConflictError";
11667
+ };
11668
+ var DelegateStateError = class extends Error {
11669
+ constructor(callId, from, to) {
11670
+ super(say("delegate.state", callId, from, to));
11671
+ this.callId = callId;
11672
+ this.from = from;
11673
+ this.to = to;
11674
+ }
11675
+ callId;
11676
+ from;
11677
+ to;
11678
+ name = "DelegateStateError";
11679
+ };
11680
+ var DelegateNotFoundError = class extends Error {
11681
+ constructor(callId) {
11682
+ super(say("delegate.not_found", callId));
11683
+ this.callId = callId;
11684
+ }
11685
+ callId;
11686
+ name = "DelegateNotFoundError";
11687
+ };
11688
+ var SELECT_CALL = `SELECT d.*, t.local_id AS local_id, w.name AS workstream, t.wave AS wave
11689
+ FROM delegate_calls d
11690
+ JOIN tasks t ON t.id = d.task_id
11691
+ JOIN workstreams w ON w.id = t.workstream_id`;
11692
+ function rowOf(r) {
11693
+ let request = { text: "" };
11694
+ try {
11695
+ if (r.request) request = JSON.parse(r.request);
11696
+ } catch {
11697
+ }
11698
+ return {
11699
+ callId: r.call_id,
11700
+ task: r.local_id,
11701
+ workstream: r.workstream,
11702
+ caller: r.caller,
11703
+ state: r.state,
11704
+ wave: r.wave,
11705
+ record: r.record_workstream !== null && r.record_task !== null ? { workstream: r.record_workstream, task: r.record_task, label: r.record_label } : null,
11706
+ request,
11707
+ agent: r.agent,
11708
+ verdict: r.verdict ?? null,
11709
+ error: r.error,
11710
+ updatedAt: r.updated_at
11711
+ };
11712
+ }
11713
+ function getDelegateCall(db, callId) {
11714
+ const r = db.prepare(`${SELECT_CALL} WHERE d.call_id = ?`).get(callId);
11715
+ return r ? rowOf(r) : void 0;
11716
+ }
11717
+ function mustGet(db, callId) {
11718
+ const row = getDelegateCall(db, callId);
11719
+ if (!row) throw new DelegateNotFoundError(callId);
11720
+ return row;
11721
+ }
11722
+ function listDelegates(db, caller, workstream, opts = {}) {
11723
+ const where = [];
11724
+ const args = [];
11725
+ if (caller !== void 0) {
11726
+ where.push("d.caller = ?");
11727
+ args.push(caller);
11728
+ }
11729
+ if (workstream !== void 0) {
11730
+ where.push("w.name = ?");
11731
+ args.push(workstream);
11732
+ }
11733
+ if (opts.wave !== void 0) {
11734
+ where.push("t.wave = ?");
11735
+ args.push(opts.wave);
11736
+ }
11737
+ if (opts.states !== void 0) {
11738
+ where.push(`d.state IN (${opts.states.map(() => "?").join(",")})`);
11739
+ args.push(...opts.states);
11740
+ }
11741
+ if (opts.taskStatuses !== void 0) {
11742
+ where.push(`t.status IN (${opts.taskStatuses.map(() => "?").join(",")})`);
11743
+ args.push(...opts.taskStatuses);
11744
+ }
11745
+ if (opts.updatedAfter !== void 0) {
11746
+ where.push("d.updated_at > ?");
11747
+ args.push(opts.updatedAfter);
11748
+ }
11749
+ const sql = `${SELECT_CALL}${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY d.task_id`;
11750
+ return db.prepare(sql).all(...args).map(rowOf);
11751
+ }
11752
+ function listDelegatesOf(db, callers, workstream, opts = {}) {
11753
+ const set = [...new Set(callers)];
11754
+ if (set.length === 0) return [];
11755
+ const where = [`d.caller IN (${set.map(() => "?").join(",")})`];
11756
+ const args = [...set];
11757
+ if (workstream !== void 0) {
11758
+ where.push("w.name = ?");
11759
+ args.push(workstream);
11760
+ }
11761
+ if (opts.wave !== void 0) {
11762
+ where.push("t.wave = ?");
11763
+ args.push(opts.wave);
11764
+ }
11765
+ const sql = `${SELECT_CALL} WHERE ${where.join(" AND ")} ORDER BY d.task_id`;
11766
+ return db.prepare(sql).all(...args).map(rowOf);
11767
+ }
11768
+ function canonical2(v) {
11769
+ if (Array.isArray(v)) return `[${v.map(canonical2).join(",")}]`;
11770
+ if (typeof v === "object" && v !== null) {
11771
+ const o = v;
11772
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical2(o[k])}`).join(",")}}`;
11773
+ }
11774
+ return JSON.stringify(v);
11775
+ }
11776
+ function taskIdFor2(callId, label) {
11777
+ const stem = (label ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 20).replace(/-+$/, "");
11778
+ const h = createHash5("sha256").update(callId).digest("hex").slice(0, 8);
11779
+ return `delegate_${stem ? `${stem}_` : ""}${h}`;
11780
+ }
11781
+ var now = () => (/* @__PURE__ */ new Date()).toISOString();
11782
+ function registerDelegateCall(db, opts) {
11783
+ const state = opts.state ?? "queued";
11784
+ return withOpContext(
11785
+ db,
11786
+ { intent: "delegate.register", actor: opts.caller, group: "new" },
11787
+ () => db.transaction(() => {
11788
+ const existing = getDelegateCall(db, opts.callId);
11789
+ if (existing) {
11790
+ const same = (field, a, b) => {
11791
+ if (canonical2(a) !== canonical2(b))
11792
+ throw new DelegateCallConflictError(opts.callId, field);
11793
+ };
11794
+ same("caller", existing.caller, opts.caller);
11795
+ same("workstream", existing.workstream, opts.workstream);
11796
+ same("wave", existing.wave, opts.wave ?? null);
11797
+ same(
11798
+ "record target",
11799
+ existing.record ? { workstream: existing.record.workstream, task: existing.record.task } : null,
11800
+ opts.record ?? null
11801
+ );
11802
+ same("request", existing.request, opts.request);
11803
+ return { call: existing, created: false };
11804
+ }
11805
+ if (opts.record && !getTask(db, opts.record.task, opts.record.workstream))
11806
+ throw new TaskNotFoundError(opts.record.task, opts.record.workstream);
11807
+ const local = taskIdFor2(opts.callId, opts.request.label);
11808
+ const headline = (opts.request.label ?? opts.request.text).replace(/\s+/g, " ").trim();
11809
+ addTask(db, {
11810
+ localId: local,
11811
+ workstream: opts.workstream,
11812
+ title: `delegate: ${headline.slice(0, 60)}`,
11813
+ kind: "delegate",
11814
+ ...opts.wave !== void 0 ? { wave: opts.wave } : {}
11815
+ });
11816
+ addNote(db, local, opts.request.text, {
11817
+ workstream: opts.workstream,
11818
+ kind: "brief",
11819
+ author: opts.caller,
11820
+ group: "inherit"
11821
+ });
11822
+ if (state !== "starting")
11823
+ setTaskStatus(db, local, "OPEN", { workstream: opts.workstream, substate: "parked" });
11824
+ const taskId = db.prepare(
11825
+ "SELECT t.id AS id FROM tasks t JOIN workstreams w ON w.id = t.workstream_id WHERE w.name = ? AND t.local_id = ?"
11826
+ ).get(opts.workstream, local).id;
11827
+ db.prepare(
11828
+ `INSERT INTO delegate_calls
11829
+ (task_id, caller, call_id, state, record_workstream, record_task, record_label, updated_at, request)
11830
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
11831
+ ).run(
11832
+ taskId,
11833
+ opts.caller,
11834
+ opts.callId,
11835
+ state,
11836
+ opts.record?.workstream ?? null,
11837
+ opts.record?.task ?? null,
11838
+ opts.request.label ?? null,
11839
+ now(),
11840
+ canonical2(opts.request)
11841
+ );
11842
+ return { call: mustGet(db, opts.callId), created: true };
11843
+ }).immediate()
11844
+ );
11845
+ }
11846
+ var MOVES = {
11847
+ queued: ["refused"],
11848
+ starting: ["queued", "refused"],
11849
+ running: ["starting"],
11850
+ refused: ["queued", "starting"]
11851
+ };
11852
+ function markDelegate(db, callId, to, extra = {}) {
11853
+ const row = mustGet(db, callId);
11854
+ const from = MOVES[to] ?? [];
11855
+ if (!from.includes(row.state)) throw new DelegateStateError(callId, row.state, to);
11856
+ return withOpContext(
11857
+ db,
11858
+ { intent: `delegate.${to}`, actor: row.caller, group: "new" },
11859
+ () => db.transaction(() => {
11860
+ const r = db.prepare(
11861
+ `UPDATE delegate_calls
11862
+ SET state = ?, updated_at = ?, agent = COALESCE(?, agent),
11863
+ error = CASE WHEN ? = 'refused' THEN COALESCE(?, error) ELSE NULL END
11864
+ WHERE call_id = ? AND state = ?`
11865
+ ).run(to, now(), extra.agent ?? null, to, extra.error ?? null, callId, row.state);
11866
+ if (r.changes !== 1) throw new DelegateStateError(callId, mustGet(db, callId).state, to);
11867
+ if (to === "starting")
11868
+ setTaskStatus(db, row.task, "OPEN", { workstream: row.workstream, substate: "todo" });
11869
+ if (to === "refused")
11870
+ setTaskStatus(db, row.task, "OPEN", { workstream: row.workstream, substate: "parked" });
11871
+ return mustGet(db, callId);
11872
+ }).immediate()
11873
+ );
11874
+ }
11875
+ function closedBy(db, task, workstream) {
11876
+ const row = db.prepare(
11877
+ `SELECT n.author AS author FROM task_notes n
11878
+ JOIN tasks t ON t.id = n.task_id JOIN workstreams w ON w.id = t.workstream_id
11879
+ WHERE w.name = ? AND t.local_id = ? AND t.status = 'CLOSED' AND n.kind = 'close'
11880
+ ORDER BY n.id DESC LIMIT 1`
11881
+ ).get(workstream, task);
11882
+ return row?.author ?? null;
11883
+ }
11884
+ function finishText(outcome, text) {
11885
+ if (outcome === "done") return text ?? "";
11886
+ return `no answer (${outcome})${text?.trim() ? `
11887
+
11888
+ ${text.trimEnd()}` : ""}`;
11889
+ }
11890
+ function finishDelegate(db, callId, opts) {
11891
+ const before = mustGet(db, callId);
11892
+ const unchanged = (row) => ({
11893
+ changed: false,
11894
+ state: row.state,
11895
+ task: row.task,
11896
+ record: row.record ? { workstream: row.record.workstream, task: row.record.task } : null,
11897
+ recorded: false,
11898
+ verdict: row.verdict
11899
+ });
11900
+ if (isTerminal(before.state)) return unchanged(before);
11901
+ db.prepare(
11902
+ "UPDATE delegate_calls SET state = 'recording', updated_at = ? WHERE call_id = ? AND state NOT IN ('done','failed','cancelled')"
11903
+ ).run(now(), callId);
11904
+ const outcome = opts.outcome === "done" && !opts.text?.trim() ? "empty" : opts.outcome;
11905
+ const answered = outcome === "done";
11906
+ const cancelled = outcome === "cancelled";
11907
+ const label = before.record?.label ?? before.request.label ?? opts.author;
11908
+ const verdict = answered ? parseVerdict(opts.text ?? "") ?? null : null;
11909
+ const record = before.record ? { workstream: before.record.workstream, task: before.record.task } : null;
11910
+ const report = finishText(outcome, opts.text);
11911
+ const wantRecord = opts.record !== false;
11912
+ const write = (recordIt, error) => withOpContext(
11913
+ db,
11914
+ { intent: "delegate.finish", actor: opts.author, group: "new" },
11915
+ () => db.transaction(() => {
11916
+ const cur = mustGet(db, callId);
11917
+ if (isTerminal(cur.state)) return unchanged(cur);
11918
+ if (recordIt && record)
11919
+ addNote(
11920
+ db,
11921
+ record.task,
11922
+ recordNote(label, opts.author, outcome, opts.text, opts.elapsedMs ?? 0),
11923
+ {
11924
+ workstream: record.workstream,
11925
+ kind: "verdict",
11926
+ author: opts.author,
11927
+ group: "inherit"
11928
+ }
11929
+ );
11930
+ const ok = answered && error === void 0;
11931
+ const state = ok ? "done" : cancelled ? "cancelled" : "failed";
11932
+ const selfClosed = cur.agent !== null && closedBy(db, cur.task, cur.workstream) === cur.agent;
11933
+ if (selfClosed)
11934
+ addNote(db, cur.task, report, {
11935
+ workstream: cur.workstream,
11936
+ kind: "report",
11937
+ author: opts.author,
11938
+ group: "inherit"
11939
+ });
11940
+ closeTask(db, cur.task, {
11941
+ workstream: cur.workstream,
11942
+ author: opts.author,
11943
+ group: "inherit",
11944
+ ...selfClosed ? {} : { report },
11945
+ as: ok ? "done" : cancelled ? "wontfix" : "rejected",
11946
+ ...ok ? { evidence: `answered ${verdict ?? "with no VERDICT line"} (call ${callId})` } : { why: error ?? `${outcome}: no answer (call ${callId})` }
11947
+ });
11948
+ db.prepare(
11949
+ "UPDATE delegate_calls SET state = ?, verdict = ?, error = ?, updated_at = ? WHERE call_id = ?"
11950
+ ).run(state, ok ? verdict : null, error ?? null, now(), callId);
11951
+ return {
11952
+ changed: true,
11953
+ state,
11954
+ task: cur.task,
11955
+ record,
11956
+ recorded: recordIt && record !== null,
11957
+ verdict: ok ? verdict : null,
11958
+ ...error !== void 0 ? { error } : {}
11959
+ };
11960
+ }).immediate()
11961
+ );
11962
+ try {
11963
+ return write(wantRecord);
11964
+ } catch (e) {
11965
+ const msg = e instanceof Error ? e.message : String(e);
11966
+ return write(
11967
+ false,
11968
+ `record ${record ? `${record.workstream}/${record.task} ` : ""}failed: ${msg}`
11969
+ );
11970
+ }
11971
+ }
11972
+ function verdictTally(db, workstream, wave, callers) {
11973
+ return tallyCalls(
11974
+ callers === void 0 ? listDelegates(db, void 0, workstream, { wave }) : listDelegatesOf(db, callers, workstream, { wave })
11975
+ );
11976
+ }
11977
+ function tallyCalls(calls) {
11978
+ const out = { findings: [], queued: 0, running: 0, refused: [] };
11979
+ const byId = /* @__PURE__ */ new Map();
11980
+ for (const c of calls) {
11981
+ const id = c.record ? `${c.record.workstream}/${c.record.task}` : `${c.workstream}/${c.task}`;
11982
+ let f = byId.get(id);
11983
+ if (!f) {
11984
+ f = { id, confirmed: 0, refuted: 0, unverified: 0, pending: 0 };
11985
+ byId.set(id, f);
11986
+ out.findings.push(f);
11987
+ }
11988
+ switch (c.state) {
11989
+ case "done":
11990
+ if (c.verdict === "CONFIRMED") f.confirmed++;
11991
+ else if (c.verdict === "REFUTED") f.refuted++;
11992
+ else f.unverified++;
11993
+ break;
11994
+ case "failed":
11995
+ case "cancelled":
11996
+ f.unverified++;
11997
+ break;
11998
+ case "refused":
11999
+ f.pending++;
12000
+ out.refused.push(c.callId);
12001
+ break;
12002
+ case "queued":
12003
+ f.pending++;
12004
+ out.queued++;
12005
+ break;
12006
+ default:
12007
+ f.pending++;
12008
+ out.running++;
12009
+ }
12010
+ }
12011
+ return out;
12012
+ }
12013
+
12014
+ // src/agents/receipts.ts
12015
+ import { createHash as createHash6 } from "crypto";
11556
12016
  var fromRaw2 = (r) => ({
11557
12017
  taskId: r.task_id,
11558
12018
  attemptKey: r.attempt_key,
@@ -11573,19 +12033,20 @@ var fromRaw2 = (r) => ({
11573
12033
  function requestSha(req) {
11574
12034
  const parts = [req.name, req.nextFree, req.text];
11575
12035
  if (req.host !== void 0) parts.push(req.host);
11576
- return createHash5("sha256").update(JSON.stringify(parts)).digest("hex");
12036
+ if (req.host !== void 0 && req.root !== void 0) parts.push({ root: req.root });
12037
+ return createHash6("sha256").update(JSON.stringify(parts)).digest("hex");
11577
12038
  }
11578
12039
  function getReceipt(db, taskId, attemptKey) {
11579
12040
  const row = db.prepare("SELECT * FROM spawn_receipts WHERE task_id = ? AND attempt_key = ?").get(taskId, attemptKey);
11580
12041
  return row ? fromRaw2(row) : void 0;
11581
12042
  }
11582
12043
  function reserveReceipt(db, r) {
11583
- const now = (/* @__PURE__ */ new Date()).toISOString();
12044
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
11584
12045
  db.prepare(
11585
12046
  `INSERT INTO spawn_receipts
11586
12047
  (task_id, attempt_key, name, workstream, request_sha, msg_id, state, created_at, updated_at)
11587
12048
  VALUES (?, ?, ?, ?, ?, ?, 'reserved', ?, ?)`
11588
- ).run(r.taskId, r.attemptKey, r.name, r.workstream, r.requestSha, r.msgId, now, now);
12049
+ ).run(r.taskId, r.attemptKey, r.name, r.workstream, r.requestSha, r.msgId, now2, now2);
11589
12050
  const row = getReceipt(db, r.taskId, r.attemptKey);
11590
12051
  if (row === void 0) throw new Error("spawn receipt vanished after reservation");
11591
12052
  return row;
@@ -11656,19 +12117,15 @@ async function tryPinBrief(db, req) {
11656
12117
  }
11657
12118
  function stagedOf(db, row) {
11658
12119
  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
- }
12120
+ if (row.cwd === null || row.base === null || row.repo === null) return void 0;
12121
+ return {
12122
+ path: row.cwd,
12123
+ base: row.base,
12124
+ root: row.repo,
12125
+ createdAt: row.createdAt,
12126
+ incarnation: row.incarnation,
12127
+ host: row.host
12128
+ };
11672
12129
  }
11673
12130
  const wt = row.cwd === null ? void 0 : getWorktreeAt(db, row.cwd);
11674
12131
  if (wt === void 0 || wt.agent !== row.name) return void 0;
@@ -11731,7 +12188,8 @@ async function run(db, req, taskId, key, guard, keep = false) {
11731
12188
  name: req.spawn.name,
11732
12189
  nextFree: req.spawn.nextFree === true,
11733
12190
  text: req.text,
11734
- host: req.spawn.host
12191
+ host: req.spawn.host,
12192
+ root: req.spawn.root
11735
12193
  });
11736
12194
  const saved = getReceipt(db, taskId, key);
11737
12195
  const auto = guard !== void 0;
@@ -11790,6 +12248,10 @@ async function fresh(db, req, taskId, key, sha, reuse = false) {
11790
12248
  const rec = reuse ? resetReceipt(db, taskId, key, base) : reserveReceipt(db, { taskId, attemptKey: key, workstream: ws, ...base });
11791
12249
  return drive(db, req, taskId, key, rec, void 0, void 0);
11792
12250
  }
12251
+ function delegateSession(db, task, ws) {
12252
+ if (getTask(db, task, ws)?.kind !== "delegate") return {};
12253
+ return { piSessionDir: delegateSessionDir(db.name, ws, task) };
12254
+ }
11793
12255
  async function drive(db, req, taskId, key, start, resumed, guard, keep = false) {
11794
12256
  const ws = req.spawn.workstream;
11795
12257
  if (resumed === void 0) guard?.("spawn");
@@ -11826,6 +12288,7 @@ async function drive(db, req, taskId, key, start, resumed, guard, keep = false)
11826
12288
  spawnThrew = true;
11827
12289
  const spawned = await spawnAgent3(db, {
11828
12290
  ...req.spawn,
12291
+ ...delegateSession(db, req.task, ws),
11829
12292
  onIdentity: (r) => {
11830
12293
  rec = updateReceipt(db, taskId, key, {
11831
12294
  state: "pane",
@@ -11865,25 +12328,25 @@ async function drive(db, req, taskId, key, start, resumed, guard, keep = false)
11865
12328
  }
11866
12329
  req.spawn.signal?.throwIfAborted();
11867
12330
  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) {
12331
+ const now2 = getAgentForCleanup(db, target.name, ws);
12332
+ if (now2 === void 0 || now2.incarnation !== target.incarnation || now2.paneId !== target.paneId) {
11870
12333
  throw new AgentReplacedError(target.name, ws, "sent to");
11871
12334
  }
11872
12335
  guard?.("send");
11873
12336
  const t = getTask(db, req.task, ws);
11874
12337
  if (t === void 0) throw new TaskNotFoundError(req.task, ws);
11875
12338
  if (t.status === "CLOSED") throw new SpawnTaskStateError(req.task, "was closed");
11876
- if (t.ownerId !== now.incarnation) {
12339
+ if (t.ownerId !== now2.incarnation) {
11877
12340
  if (t.ownerId !== null) {
11878
12341
  throw new TaskAlreadyOwnedError(req.task, t.ownerName ?? "<unknown>");
11879
12342
  }
11880
- throw new SpawnTaskStateError(req.task, `is no longer claimed by ${now.name}`);
12343
+ throw new SpawnTaskStateError(req.task, `is no longer claimed by ${now2.name}`);
11881
12344
  }
11882
12345
  const journaled = updateReceipt(db, taskId, key, {
11883
12346
  state: "sending",
11884
- transport: expectsCtl(now, agentCtlSocket(db, now)) ? "ctl" : "mux"
12347
+ transport: expectsCtl(now2, agentCtlSocket(db, now2)) ? "ctl" : "mux"
11885
12348
  });
11886
- return { row: now, rec: journaled };
12349
+ return { row: now2, rec: journaled };
11887
12350
  }).immediate();
11888
12351
  row = checked.row;
11889
12352
  rec = checked.rec;
@@ -11995,13 +12458,13 @@ function idleThresholdMs() {
11995
12458
  if (!Number.isFinite(n) || n < 0) return DEFAULT_IDLE_THRESHOLD_MS;
11996
12459
  return n;
11997
12460
  }
11998
- function computeAgentIdle(db, agent, now = Date.now()) {
12461
+ function computeAgentIdle(db, agent, now2 = Date.now()) {
11999
12462
  if (agent.state !== "needs_input" || agent.since === null) return false;
12000
12463
  const threshold = idleThresholdMs();
12001
12464
  if (threshold <= 0) return false;
12002
12465
  const since = Date.parse(agent.since);
12003
12466
  if (!Number.isFinite(since)) return false;
12004
- if (now - since < threshold) return false;
12467
+ if (now2 - since < threshold) return false;
12005
12468
  if (isScratchWorkstream(agent.workstreamName)) return true;
12006
12469
  const wsId = tryResolveWorkstreamId(db, agent.workstreamName);
12007
12470
  if (wsId === null) return false;
@@ -12028,6 +12491,7 @@ var SELECT_AGENT_COLS = `
12028
12491
  a.spawner AS spawner,
12029
12492
  a.host AS host,
12030
12493
  a.collected_head AS collected_head,
12494
+ a.repo AS repo,
12031
12495
  a.gate_token AS gate_token,
12032
12496
  a.created_at AS created_at,
12033
12497
  a.updated_at AS updated_at
@@ -12049,6 +12513,7 @@ function rowFromDb3(row) {
12049
12513
  spawner: row.spawner,
12050
12514
  host: row.host,
12051
12515
  collectedHead: row.collected_head,
12516
+ repo: row.repo,
12052
12517
  gateToken: row.gate_token,
12053
12518
  createdAt: row.created_at,
12054
12519
  updatedAt: row.updated_at
@@ -12064,13 +12529,13 @@ function agentIdByName(db, name, workstream) {
12064
12529
  function insertAgent(db, input) {
12065
12530
  ensureWorkstream(db, input.workstream);
12066
12531
  const workstreamId = resolveWorkstreamId(db, input.workstream);
12067
- const now = (/* @__PURE__ */ new Date()).toISOString();
12532
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
12068
12533
  try {
12069
12534
  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)
12535
+ `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
12536
  VALUES (@name, @workstreamId, COALESCE(@cli, 'pi'), @paneId, 'spawning',
12072
12537
  COALESCE(@role, 'full-access'), @tab, @cwd, @base, @command, @commandSource,
12073
- @spawner, @host, @collectedHead, @gateToken, @now, @now)`
12538
+ @spawner, @host, @collectedHead, @repo, @gateToken, @now, @now)`
12074
12539
  ).run({
12075
12540
  name: input.name,
12076
12541
  workstreamId,
@@ -12085,8 +12550,9 @@ function insertAgent(db, input) {
12085
12550
  spawner: input.spawner ?? null,
12086
12551
  host: input.host ?? null,
12087
12552
  collectedHead: input.collectedHead ?? null,
12553
+ repo: input.repo ?? null,
12088
12554
  gateToken: input.gateToken ?? null,
12089
- now
12555
+ now: now2
12090
12556
  });
12091
12557
  } catch (err) {
12092
12558
  if (err.code === "SQLITE_CONSTRAINT_UNIQUE") {
@@ -12207,7 +12673,7 @@ async function readLiveAgents(db, workstream, only, states) {
12207
12673
  const baseAgents = listAgents(db, { workstream }).filter(
12208
12674
  (a) => only === void 0 || a.name === only
12209
12675
  );
12210
- const stateDir = dirname10(db.name);
12676
+ const stateDir = dirname11(db.name);
12211
12677
  let readings;
12212
12678
  if (states === void 0) readings = await readAgentStates(baseAgents, { stateDir });
12213
12679
  else {
@@ -12217,7 +12683,7 @@ async function readLiveAgents(db, workstream, only, states) {
12217
12683
  }
12218
12684
  readings = states.cache;
12219
12685
  }
12220
- const now = Date.now();
12686
+ const now2 = Date.now();
12221
12687
  return baseAgents.map((agent) => {
12222
12688
  const reading = readings.get(agentKey(agent)) ?? {
12223
12689
  state: "unknown",
@@ -12235,14 +12701,14 @@ async function readLiveAgents(db, workstream, only, states) {
12235
12701
  ...reading.reason !== void 0 ? { reason: reading.reason } : {},
12236
12702
  ...reading.tool !== void 0 ? { tool: reading.tool } : {}
12237
12703
  };
12238
- return computeAgentIdle(db, live2, now) ? { ...live2, idle: true } : live2;
12704
+ return computeAgentIdle(db, live2, now2) ? { ...live2, idle: true } : live2;
12239
12705
  });
12240
12706
  }
12241
12707
 
12242
12708
  // src/agents/spawn-lock.ts
12243
- import { join as join8 } from "path";
12709
+ import { join as join9 } from "path";
12244
12710
  function lockPathForSession(session) {
12245
- return join8(locksDir(), `spawn-${session}.lock`);
12711
+ return join9(locksDir(), `spawn-${session}.lock`);
12246
12712
  }
12247
12713
  var DEFAULT_ACQUIRE_TIMEOUT_MS2 = 15e3;
12248
12714
  var STALE_LOCK_MS = 3e4;
@@ -12257,11 +12723,11 @@ async function withSpawnLock(session, fn, opts) {
12257
12723
 
12258
12724
  // src/agents/staging.ts
12259
12725
  var STAGING_DEAD_MS = 6e4;
12260
- function deadStagings(db, workstream, now = Date.now()) {
12726
+ function deadStagings(db, workstream, now2 = Date.now()) {
12261
12727
  return listAgents(db, workstream === void 0 ? {} : { workstream }).filter((a) => {
12262
12728
  if (!isPendingPaneId(a.paneId) || a.host !== null) return false;
12263
12729
  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;
12730
+ return now2 - Math.max(Date.parse(a.updatedAt), made ? Date.parse(made) : 0) > STAGING_DEAD_MS;
12265
12731
  });
12266
12732
  }
12267
12733
  async function reclaimDeadStagings(db, workstream) {
@@ -12451,7 +12917,7 @@ async function spawnAgent3(db, opts) {
12451
12917
  const gateToken = isCommandGate(opts) ? newGateToken() : void 0;
12452
12918
  if (gateToken !== void 0 && opts.worktree) {
12453
12919
  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"
12920
+ "--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
12921
  );
12456
12922
  }
12457
12923
  const command = opts.command ?? (profile ? cli : resolved.command);
@@ -12480,7 +12946,8 @@ async function spawnAgent3(db, opts) {
12480
12946
  workstream: opts.workstream,
12481
12947
  name: opts.name,
12482
12948
  worktree: false,
12483
- ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {}
12949
+ ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
12950
+ ...remoteRoot(opts, profile)
12484
12951
  });
12485
12952
  } else if (opts.worktree) {
12486
12953
  staged = await prestageWorktree(db, opts, cli, spawner);
@@ -12491,8 +12958,8 @@ async function spawnAgent3(db, opts) {
12491
12958
  throw err;
12492
12959
  }
12493
12960
  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 });
12961
+ const ctlSocket = ctlSocketPath(opts.workstream, opts.name, dirname12(db.name));
12962
+ mkdirSync3(dirname12(ctlSocket), { recursive: true, mode: 448 });
12496
12963
  const paneEnv = profile ? {} : {
12497
12964
  MU_MANAGED_AGENT: "1",
12498
12965
  MU_AGENT_NAME: opts.name,
@@ -12501,6 +12968,9 @@ async function spawnAgent3(db, opts) {
12501
12968
  MU_DB_PATH: resolve3(db.name !== "" && db.name !== ":memory:" ? db.name : defaultDbPath()),
12502
12969
  MU_STATE_DIR: resolve3(defaultStateDir())
12503
12970
  };
12971
+ if (!profile && opts.piSessionDir !== void 0 && speaksMuCtl(cli, command)) {
12972
+ paneEnv.PI_CODING_AGENT_SESSION_DIR = resolve3(opts.piSessionDir);
12973
+ }
12504
12974
  const sshSock = process.env.SSH_AUTH_SOCK;
12505
12975
  if (sshSock) {
12506
12976
  try {
@@ -12720,12 +13190,16 @@ async function prestageLocked(db, opts, cli, spawner) {
12720
13190
  throw err;
12721
13191
  }
12722
13192
  }
13193
+ function remoteRoot(opts, profile) {
13194
+ const root = opts.root ?? profile.repo ?? void 0;
13195
+ return root === void 0 ? {} : { root };
13196
+ }
12723
13197
  async function requireHostSpawn(db, opts) {
12724
13198
  const host = opts.host ?? "";
12725
13199
  const profile = getHost(db, host);
12726
- if (opts.root !== void 0) {
13200
+ if (opts.worktree && opts.root === void 0 && profile.repo === null) {
12727
13201
  throw new HostProfileError(
12728
- "--root names a local repo and cannot be used with --host (the worktree is made in the profile's --repo)"
13202
+ `host ${host} has no default repo: pass --root <remote repo path> with --worktree`
12729
13203
  );
12730
13204
  }
12731
13205
  const mux = await activeMux();
@@ -12751,6 +13225,7 @@ async function prestageRemote(db, opts, cli, spawner, lease, profile) {
12751
13225
  name: opts.name,
12752
13226
  worktree: true,
12753
13227
  ...opts.from !== void 0 ? { from: opts.from } : {},
13228
+ ...remoteRoot(opts, profile),
12754
13229
  // An unused leftover of a prepare whose answer was lost is taken over;
12755
13230
  // never while a removed agent's recovery record may name that tree.
12756
13231
  reclaim: !recoveriesFor(db, opts.workstream, opts.name).some(
@@ -12760,7 +13235,7 @@ async function prestageRemote(db, opts, cli, spawner, lease, profile) {
12760
13235
  return {
12761
13236
  path: made.cwd,
12762
13237
  base: made.base ?? "",
12763
- root: profile.repo,
13238
+ root: made.repo ?? "",
12764
13239
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12765
13240
  incarnation: placeholder.incarnation,
12766
13241
  host: profile.name
@@ -12799,7 +13274,7 @@ function finalizeAgentRow(db, args) {
12799
13274
  });
12800
13275
  }
12801
13276
  const patched = db.prepare(
12802
- `UPDATE agents SET pane_id = ?, cwd = ?, base = ?, command = ?, command_source = ?, spawner = ?, host = ?, updated_at = ?
13277
+ `UPDATE agents SET pane_id = ?, cwd = ?, base = ?, command = ?, command_source = ?, spawner = ?, host = ?, repo = ?, updated_at = ?
12803
13278
  WHERE id = ? AND pane_id = ?
12804
13279
  AND name = ?
12805
13280
  AND workstream_id = (SELECT id FROM workstreams WHERE name = ?)`
@@ -12811,6 +13286,7 @@ function finalizeAgentRow(db, args) {
12811
13286
  commandSource,
12812
13287
  spawner,
12813
13288
  host ?? null,
13289
+ staged.host !== void 0 ? staged.root : null,
12814
13290
  (/* @__PURE__ */ new Date()).toISOString(),
12815
13291
  staged.incarnation,
12816
13292
  pendingPaneIdFor(opts.name),
@@ -13446,6 +13922,7 @@ export {
13446
13922
  pc,
13447
13923
  muTable,
13448
13924
  isJsonMode,
13925
+ isAgentListArgv,
13449
13926
  isStateVerbArgv,
13450
13927
  usageLines,
13451
13928
  printUsageHuman,
@@ -13688,6 +14165,22 @@ export {
13688
14165
  observeAgentStates,
13689
14166
  settledCloseInput,
13690
14167
  watchAgentRun,
14168
+ verdictLineLabel,
14169
+ parseVerdict,
14170
+ delegateSessionDir,
14171
+ delegateProcessDir,
14172
+ TERMINAL_DELEGATE_STATES,
14173
+ DelegateCallConflictError,
14174
+ DelegateStateError,
14175
+ DelegateNotFoundError,
14176
+ getDelegateCall,
14177
+ listDelegates,
14178
+ listDelegatesOf,
14179
+ registerDelegateCall,
14180
+ markDelegate,
14181
+ finishDelegate,
14182
+ verdictTally,
14183
+ tallyCalls,
13691
14184
  requestSha,
13692
14185
  getReceipt,
13693
14186
  reserveReceipt,
@@ -13763,4 +14256,4 @@ export {
13763
14256
  murmurAvailable,
13764
14257
  readAgentStates
13765
14258
  };
13766
- //# sourceMappingURL=chunk-S424D5EP.js.map
14259
+ //# sourceMappingURL=chunk-L7I3BLXP.js.map