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