@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.
- package/README.md +1 -1
- package/dist/{chunk-5HECDBK3.js → chunk-B74N2EAS.js} +16 -16
- package/dist/{chunk-GQNHIPJN.js → chunk-DEYBQDTO.js} +21 -421
- package/dist/chunk-DEYBQDTO.js.map +1 -0
- package/dist/{chunk-S424D5EP.js → chunk-L7I3BLXP.js} +635 -142
- package/dist/chunk-L7I3BLXP.js.map +1 -0
- package/dist/{chunk-AW2NONYU.js → chunk-LBBZAG5C.js} +81 -7
- package/dist/chunk-LBBZAG5C.js.map +1 -0
- package/dist/{cli-GB2CAVVW.js → cli-H2PWJAJN.js} +346 -44
- package/dist/cli-H2PWJAJN.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{errors-KHOYKD7D.js → errors-G5IVXSMY.js} +2 -2
- package/dist/extension/mu-pi.js +28 -14
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +63 -11
- package/dist/index.js +15 -15
- package/dist/{tui-ALPHYTA2.js → tui-EN2CDP6G.js} +102 -27
- package/dist/tui-EN2CDP6G.js.map +1 -0
- package/docs/ARCHITECTURE.md +5 -4
- package/docs/VOCABULARY.md +6 -5
- package/docs/guide/cleanup.md +8 -0
- package/docs/guide/delegate.md +11 -3
- package/docs/guide/remote.md +19 -12
- package/docs/guide/sql.md +1 -0
- package/docs/reference/env.md +1 -0
- package/docs/reference/naming.md +2 -0
- package/package.json +1 -1
- package/skills/mu/recipes/remote-workers.md +7 -6
- package/dist/chunk-AW2NONYU.js.map +0 -1
- package/dist/chunk-GQNHIPJN.js.map +0 -1
- package/dist/chunk-S424D5EP.js.map +0 -1
- package/dist/cli-GB2CAVVW.js.map +0 -1
- package/dist/tui-ALPHYTA2.js.map +0 -1
- /package/dist/{chunk-5HECDBK3.js.map → chunk-B74N2EAS.js.map} +0 -0
- /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
|
|
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
|
|
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
|
|
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
|
|
2800
|
-
--
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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(
|
|
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,
|
|
3902
|
+
function receiveHlc(db, remoteHlc, now2 = Date.now()) {
|
|
3886
3903
|
const remote = parseHlc(remoteHlc);
|
|
3887
|
-
const wallNow = Math.floor(
|
|
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)
|
|
5851
|
-
|
|
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(
|
|
6202
|
-
if (murmurCache !== void 0 &&
|
|
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:
|
|
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() === "")
|
|
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
|
|
6700
|
-
return
|
|
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
|
|
6710
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
6862
|
-
"repo",
|
|
6863
|
-
|
|
6864
|
-
() =>
|
|
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
|
|
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
|
|
7035
|
-
reopen.run(
|
|
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
|
-
|
|
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,
|
|
7508
|
-
const ts =
|
|
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
|
|
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
|
-
|
|
7786
|
+
canonical3.push(blocker);
|
|
7760
7787
|
}
|
|
7761
|
-
return
|
|
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
|
|
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,
|
|
7925
|
+
).run(blockerId, blockedId, now2, blockerId, blockedId);
|
|
7899
7926
|
if (result.changes > 0) {
|
|
7900
|
-
touchTask(db, blockedId,
|
|
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
|
|
8013
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
7987
8014
|
for (const blockerId of blockerIds) {
|
|
7988
|
-
insertEdge.run(blockerId, taskSurrogateId,
|
|
8015
|
+
insertEdge.run(blockerId, taskSurrogateId, now2);
|
|
7989
8016
|
}
|
|
7990
|
-
touchTask(db, taskSurrogateId,
|
|
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
|
|
8903
|
-
if (
|
|
8904
|
-
throw new WorkstreamRevisionMismatchError(opts.workstream, opts.ifRevision,
|
|
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
|
|
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
|
-
|
|
9128
|
-
|
|
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,
|
|
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
|
|
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,
|
|
9245
|
+
insertEdge.run(newId, umbrellaId, now2);
|
|
9219
9246
|
}
|
|
9220
9247
|
}
|
|
9221
|
-
for (const umbrellaId of umbrellas.keys()) touchTask(db, umbrellaId,
|
|
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
|
|
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,
|
|
9259
|
-
if (touch) touchTask(db, taskId,
|
|
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:
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
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
|
-
|
|
9981
|
-
|
|
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 ??
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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 (
|
|
10359
|
-
|
|
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
|
|
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
|
|
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),
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
11205
|
-
const nowRow = worktreeOf(db, opts.workstream, name,
|
|
11206
|
-
if (
|
|
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 ||
|
|
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:
|
|
11297
|
+
kind: now2 ? "agent" : nowRow ? "orphan-worktree" : "none",
|
|
11247
11298
|
revision: opts.recoveryId === void 0 ? agentRevision(db, opts.workstream, name) : 0,
|
|
11248
|
-
incarnation:
|
|
11249
|
-
paneId:
|
|
11250
|
-
ownedTasks:
|
|
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(
|
|
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
|
|
11299
|
-
return
|
|
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/
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
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
|
|
11869
|
-
if (
|
|
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 !==
|
|
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 ${
|
|
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(
|
|
12347
|
+
transport: expectsCtl(now2, agentCtlSocket(db, now2)) ? "ctl" : "mux"
|
|
11885
12348
|
});
|
|
11886
|
-
return { row:
|
|
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,
|
|
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 (
|
|
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
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
12709
|
+
import { join as join9 } from "path";
|
|
12244
12710
|
function lockPathForSession(session) {
|
|
12245
|
-
return
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
12495
|
-
mkdirSync3(
|
|
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
|
|
13200
|
+
if (opts.worktree && opts.root === void 0 && profile.repo === null) {
|
|
12727
13201
|
throw new HostProfileError(
|
|
12728
|
-
|
|
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:
|
|
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-
|
|
14259
|
+
//# sourceMappingURL=chunk-L7I3BLXP.js.map
|