@mu-crew/mu 3.6.2 → 3.8.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/dist/cli.js +12652 -12264
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +302 -48
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +59 -13
- package/dist/index.js +1376 -1205
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/architecture/control-socket.md +10 -5
- package/docs/architecture/mux.md +4 -1
- package/docs/guide/delegate.md +25 -3
- package/docs/guide/dispatch.md +22 -5
- package/docs/guide/getting-started.md +2 -2
- package/docs/guide/recovery.md +11 -6
- package/docs/guide/upgrade.md +4 -4
- package/docs/reference/env.md +2 -2
- package/docs/specs/2026-10-03-keep-driving-nudge.md +21 -0
- package/package.json +1 -1
- package/skills/mu/SKILL.md +40 -16
- package/skills/mu/recipes/adversarial-review.md +14 -6
- package/skills/mu/recipes/brief.md +29 -1
- package/skills/mu/recipes/codemode-driver.md +19 -9
- package/skills/mu/recipes/deep-research.md +6 -5
- package/skills/mu/recipes/drift-audit.md +72 -0
- package/skills/mu/recipes/findings.md +24 -15
- package/skills/mu/recipes/hypothesis-panel.md +5 -3
- package/skills/mu/recipes/long-run.md +13 -3
- package/skills/mu/recipes/loop-until-done.md +4 -3
- package/skills/mu/recipes/orchestrator-loop.md +29 -16
- package/skills/mu/recipes/plan.md +10 -6
- package/skills/mu/recipes/refute.md +3 -2
- package/skills/mu/recipes/remote-workers.md +23 -6
- package/skills/mu/recipes/review-panel.md +2 -2
- package/skills/mu/recipes/rules-audit.md +10 -5
- package/skills/mu/recipes/tasks-or-calls.md +40 -23
- package/skills/mu/recipes/tournament.md +4 -3
- package/skills/mu/recipes/ultrathink.md +2 -2
- package/skills/mu/recipes/worker.md +3 -2
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { execa as execa3 } from "execa";
|
|
|
12
12
|
|
|
13
13
|
// src/agents/transport.ts
|
|
14
14
|
import { existsSync as existsSync8 } from "fs";
|
|
15
|
-
import { dirname as
|
|
15
|
+
import { dirname as dirname7 } from "path";
|
|
16
16
|
|
|
17
17
|
// src/ctl/client.ts
|
|
18
18
|
import { createConnection } from "net";
|
|
@@ -68,7 +68,7 @@ var CtlUnknownOpError = class extends Error {
|
|
|
68
68
|
function clientTimeout(req, override) {
|
|
69
69
|
if (override !== void 0) return override;
|
|
70
70
|
if (req.op === "fresh" || req.op === "command") return CTL_FRESH_TIMEOUT_MS;
|
|
71
|
-
if (req.op !== "wait") return CTL_DEFAULT_TIMEOUT_MS;
|
|
71
|
+
if (req.op !== "wait" && req.op !== "interrupt") return CTL_DEFAULT_TIMEOUT_MS;
|
|
72
72
|
return req.timeoutMs === void 0 ? void 0 : req.timeoutMs + CTL_WAIT_SLACK_MS;
|
|
73
73
|
}
|
|
74
74
|
function parseReply(line) {
|
|
@@ -144,6 +144,7 @@ async function ctlProbe(sock, timeoutMs = CTL_DEFAULT_TIMEOUT_MS) {
|
|
|
144
144
|
|
|
145
145
|
// src/ctl/path.ts
|
|
146
146
|
import { createHash } from "crypto";
|
|
147
|
+
import { rmSync } from "fs";
|
|
147
148
|
import { dirname as dirname2, join as join2 } from "path";
|
|
148
149
|
|
|
149
150
|
// src/db.ts
|
|
@@ -854,285 +855,598 @@ function ctlSocketPath(workstream, agent, stateDir) {
|
|
|
854
855
|
const base = stateDir ?? (dbPath ? dirname2(dbPath) : defaultStateDir());
|
|
855
856
|
return sockPathUnder(join2(base, "sock"), workstream, agent);
|
|
856
857
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
HerdrNotImplementedError: () => HerdrNotImplementedError,
|
|
864
|
-
HerdrSyntaxError: () => HerdrSyntaxError,
|
|
865
|
-
HerdrUnsupportedCliError: () => HerdrUnsupportedCliError,
|
|
866
|
-
HerdrWorkspaceGroupCloseError: () => HerdrWorkspaceGroupCloseError,
|
|
867
|
-
MuxError: () => MuxError,
|
|
868
|
-
NoMultiplexerError: () => NoMultiplexerError,
|
|
869
|
-
PaneNotFoundError: () => PaneNotFoundError,
|
|
870
|
-
activeMux: () => activeMux,
|
|
871
|
-
detectMux: () => detectMux,
|
|
872
|
-
herdrBackend: () => herdrBackend,
|
|
873
|
-
isHerdrStatusUsable: () => isHerdrStatusUsable,
|
|
874
|
-
muxByName: () => muxByName,
|
|
875
|
-
parseAgentNameFromTitle: () => parseAgentNameFromTitle,
|
|
876
|
-
resetHerdrExecutor: () => resetHerdrExecutor,
|
|
877
|
-
resetMux: () => resetMux,
|
|
878
|
-
setHerdrExecutor: () => setHerdrExecutor,
|
|
879
|
-
setMuxForTests: () => setMuxForTests,
|
|
880
|
-
tmuxBackend: () => tmuxBackend
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
// src/mux/herdr.ts
|
|
884
|
-
import { execa as execa2 } from "execa";
|
|
885
|
-
|
|
886
|
-
// src/mux/tmux.ts
|
|
887
|
-
import { execa } from "execa";
|
|
888
|
-
|
|
889
|
-
// src/mux/input-timing.ts
|
|
890
|
-
var TAIL_WINDOW_LINES = 100;
|
|
891
|
-
var TAIL_LINES = 20;
|
|
892
|
-
var BUSY_MARKERS = ["to interrupt)"];
|
|
893
|
-
var BRAILLE_SPINNER_RE = /[\u2800-\u28ff]/;
|
|
894
|
-
function extractTail(scrollback) {
|
|
895
|
-
const lines = scrollback.split("\n");
|
|
896
|
-
const window = lines.slice(-TAIL_WINDOW_LINES);
|
|
897
|
-
let end = window.length;
|
|
898
|
-
while (end > 0 && (window[end - 1] ?? "").trim() === "") end--;
|
|
899
|
-
return window.slice(Math.max(0, end - TAIL_LINES), end).join("\n");
|
|
900
|
-
}
|
|
901
|
-
function paneLooksBusy(scrollback) {
|
|
902
|
-
const tail = extractTail(scrollback);
|
|
903
|
-
return BUSY_MARKERS.some((marker) => tail.includes(marker)) || BRAILLE_SPINNER_RE.test(tail);
|
|
858
|
+
function unlinkCtlSocket(db, name, workstream) {
|
|
859
|
+
if (db.memory) return;
|
|
860
|
+
try {
|
|
861
|
+
rmSync(ctlSocketPath(workstream, name, dirname2(db.name)), { force: true });
|
|
862
|
+
} catch {
|
|
863
|
+
}
|
|
904
864
|
}
|
|
905
865
|
|
|
906
|
-
// src/
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
var
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
this.paneId = paneId;
|
|
921
|
-
this.backend = backend;
|
|
922
|
-
}
|
|
923
|
-
paneId;
|
|
924
|
-
backend;
|
|
925
|
-
name = "PaneNotFoundError";
|
|
926
|
-
errorNextSteps() {
|
|
927
|
-
return [
|
|
928
|
-
...this.backend?.paneNotFoundNextSteps(this.paneId) ?? [],
|
|
929
|
-
{
|
|
930
|
-
intent: "List workstreams to choose the right scope",
|
|
931
|
-
command: "mu workstream list"
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
intent: "List registered agents and orphan panes in that scope",
|
|
935
|
-
command: "mu agent list -w <workstream>"
|
|
936
|
-
}
|
|
937
|
-
];
|
|
866
|
+
// src/logs.ts
|
|
867
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
868
|
+
|
|
869
|
+
// src/hlc.ts
|
|
870
|
+
var WALL_WIDTH = 15;
|
|
871
|
+
var COUNTER_WIDTH = 6;
|
|
872
|
+
var SEP = ".";
|
|
873
|
+
var MAX_WALL = 10 ** WALL_WIDTH - 1;
|
|
874
|
+
var MAX_COUNTER = 10 ** COUNTER_WIDTH - 1;
|
|
875
|
+
var HlcParseError = class extends Error {
|
|
876
|
+
constructor(value) {
|
|
877
|
+
super(`Malformed HLC: ${JSON.stringify(value)}`);
|
|
878
|
+
this.value = value;
|
|
879
|
+
this.name = "HlcParseError";
|
|
938
880
|
}
|
|
881
|
+
value;
|
|
939
882
|
};
|
|
940
|
-
var
|
|
941
|
-
constructor(
|
|
883
|
+
var HlcOverflowError = class extends Error {
|
|
884
|
+
constructor(field, value) {
|
|
942
885
|
super(
|
|
943
|
-
`
|
|
886
|
+
field === "counter" ? `HLC counter overflow (${value} > ${MAX_COUNTER}): more than ${MAX_COUNTER + 1} ops in one millisecond` : `HLC wall-clock overflow (${value} > ${MAX_WALL}): system clock is far in the future`
|
|
944
887
|
);
|
|
945
|
-
this.
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
name = "NoMultiplexerError";
|
|
949
|
-
errorNextSteps() {
|
|
950
|
-
return [
|
|
951
|
-
{ intent: "Run health check", command: "mu doctor" },
|
|
952
|
-
{ intent: "Verify tmux is installed", command: "tmux -V" },
|
|
953
|
-
{ intent: "Force a specific backend", command: "MU_MUX=tmux mu <verb>" }
|
|
954
|
-
];
|
|
888
|
+
this.field = field;
|
|
889
|
+
this.value = value;
|
|
890
|
+
this.name = "HlcOverflowError";
|
|
955
891
|
}
|
|
892
|
+
field;
|
|
893
|
+
value;
|
|
956
894
|
};
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
const detail = stderr.trim() || stdout.trim() || "no output";
|
|
962
|
-
super(`tmux ${args.join(" ")} failed (exit ${exitCode}): ${detail}`);
|
|
963
|
-
this.args = args;
|
|
964
|
-
this.stderr = stderr;
|
|
965
|
-
this.stdout = stdout;
|
|
966
|
-
this.exitCode = exitCode;
|
|
967
|
-
this.name = "TmuxError";
|
|
968
|
-
}
|
|
969
|
-
args;
|
|
970
|
-
stderr;
|
|
971
|
-
stdout;
|
|
972
|
-
exitCode;
|
|
973
|
-
errorNextSteps() {
|
|
974
|
-
return [
|
|
975
|
-
{ intent: "Run health check", command: "mu doctor" },
|
|
976
|
-
{
|
|
977
|
-
intent: "Verify tmux is running and reachable",
|
|
978
|
-
command: "tmux info | head"
|
|
979
|
-
},
|
|
980
|
-
{
|
|
981
|
-
intent: "Check the failing tmux command in isolation",
|
|
982
|
-
command: `tmux ${this.args.join(" ")}`
|
|
983
|
-
}
|
|
984
|
-
];
|
|
895
|
+
var MachineIdentityMissingError = class extends Error {
|
|
896
|
+
constructor() {
|
|
897
|
+
super("machine_identity row (id = 1) is missing; the DB was not opened via openDb()");
|
|
898
|
+
this.name = "MachineIdentityMissingError";
|
|
985
899
|
}
|
|
986
900
|
};
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
901
|
+
function formatHlc(hlc) {
|
|
902
|
+
const { wallMs, counter, machineId: machineId2 } = hlc;
|
|
903
|
+
if (!Number.isInteger(wallMs) || wallMs < 0) throw new HlcParseError(String(wallMs));
|
|
904
|
+
if (!Number.isInteger(counter) || counter < 0) throw new HlcParseError(String(counter));
|
|
905
|
+
if (wallMs > MAX_WALL) throw new HlcOverflowError("wall", wallMs);
|
|
906
|
+
if (counter > MAX_COUNTER) throw new HlcOverflowError("counter", counter);
|
|
907
|
+
if (machineId2.length === 0 || machineId2.includes(SEP)) throw new HlcParseError(machineId2);
|
|
908
|
+
return `${String(wallMs).padStart(WALL_WIDTH, "0")}${SEP}${String(counter).padStart(
|
|
909
|
+
COUNTER_WIDTH,
|
|
910
|
+
"0"
|
|
911
|
+
)}${SEP}${machineId2}`;
|
|
990
912
|
}
|
|
991
|
-
function
|
|
992
|
-
|
|
993
|
-
|
|
913
|
+
function parseHlc(value) {
|
|
914
|
+
const parts = value.split(SEP);
|
|
915
|
+
if (parts.length !== 3) throw new HlcParseError(value);
|
|
916
|
+
const [wall, counter, machineId2] = parts;
|
|
917
|
+
if (wall === void 0 || counter === void 0 || machineId2 === void 0) {
|
|
918
|
+
throw new HlcParseError(value);
|
|
994
919
|
}
|
|
920
|
+
if (wall.length !== WALL_WIDTH || counter.length !== COUNTER_WIDTH || machineId2.length === 0) {
|
|
921
|
+
throw new HlcParseError(value);
|
|
922
|
+
}
|
|
923
|
+
if (!/^\d+$/.test(wall) || !/^\d+$/.test(counter)) throw new HlcParseError(value);
|
|
924
|
+
return { wallMs: Number(wall), counter: Number(counter), machineId: machineId2 };
|
|
995
925
|
}
|
|
996
|
-
function
|
|
997
|
-
|
|
998
|
-
if (
|
|
999
|
-
|
|
1000
|
-
if (Number.isNaN(parsed) || parsed < 0) return 500;
|
|
1001
|
-
return parsed;
|
|
926
|
+
function compareHlc(a, b) {
|
|
927
|
+
if (a < b) return -1;
|
|
928
|
+
if (a > b) return 1;
|
|
929
|
+
return 0;
|
|
1002
930
|
}
|
|
1003
|
-
function
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
931
|
+
function nextHlc(db, now = Date.now()) {
|
|
932
|
+
const row = db.prepare(
|
|
933
|
+
`UPDATE machine_identity
|
|
934
|
+
SET last_wall = MAX(last_wall, @now),
|
|
935
|
+
last_counter = CASE WHEN @now > last_wall THEN 0 ELSE last_counter + 1 END
|
|
936
|
+
WHERE id = 1
|
|
937
|
+
RETURNING machine_id, last_wall, last_counter`
|
|
938
|
+
).get({ now: Math.floor(now) });
|
|
939
|
+
if (!row) throw new MachineIdentityMissingError();
|
|
940
|
+
return formatHlc({ wallMs: row.last_wall, counter: row.last_counter, machineId: row.machine_id });
|
|
1007
941
|
}
|
|
1008
|
-
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
942
|
+
function receiveHlc(db, remoteHlc, now = Date.now()) {
|
|
943
|
+
const remote = parseHlc(remoteHlc);
|
|
944
|
+
const wallNow = Math.floor(now);
|
|
945
|
+
const read = db.prepare(
|
|
946
|
+
"SELECT machine_id, last_wall, last_counter FROM machine_identity WHERE id = 1"
|
|
947
|
+
);
|
|
948
|
+
const write = db.prepare(
|
|
949
|
+
"UPDATE machine_identity SET last_wall = @wall, last_counter = @counter WHERE id = 1"
|
|
950
|
+
);
|
|
951
|
+
const advance = db.transaction(() => {
|
|
952
|
+
const row = read.get();
|
|
953
|
+
if (!row) throw new MachineIdentityMissingError();
|
|
954
|
+
const wall = Math.max(row.last_wall, remote.wallMs, wallNow);
|
|
955
|
+
const localAtMax = row.last_wall === wall;
|
|
956
|
+
const remoteAtMax = remote.wallMs === wall;
|
|
957
|
+
let counter;
|
|
958
|
+
if (!localAtMax && !remoteAtMax) {
|
|
959
|
+
counter = 0;
|
|
960
|
+
} else if (localAtMax && remoteAtMax) {
|
|
961
|
+
counter = Math.max(row.last_counter, remote.counter) + 1;
|
|
962
|
+
} else if (localAtMax) {
|
|
963
|
+
counter = row.last_counter + 1;
|
|
964
|
+
} else {
|
|
965
|
+
counter = remote.counter + 1;
|
|
966
|
+
}
|
|
967
|
+
if (counter > MAX_COUNTER) throw new HlcOverflowError("counter", counter);
|
|
968
|
+
write.run({ wall, counter });
|
|
969
|
+
return formatHlc({ wallMs: wall, counter, machineId: row.machine_id });
|
|
970
|
+
});
|
|
971
|
+
return advance.immediate();
|
|
1021
972
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
973
|
+
|
|
974
|
+
// src/legacy-ops.ts
|
|
975
|
+
var LEGACY_LOG_ONLY_INTENTS = /* @__PURE__ */ new Set(["workstream.export"]);
|
|
976
|
+
function isLegacyLogOnlyIntent(intent) {
|
|
977
|
+
return intent !== null && LEGACY_LOG_ONLY_INTENTS.has(intent);
|
|
1024
978
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
return
|
|
1031
|
-
}
|
|
1032
|
-
var currentSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1033
|
-
function setSleepForTests(impl) {
|
|
1034
|
-
const previous = currentSleep;
|
|
1035
|
-
currentSleep = impl;
|
|
1036
|
-
return previous;
|
|
979
|
+
var LEGACY_INTENT_SYNONYMS = /* @__PURE__ */ new Map([
|
|
980
|
+
["workstream.teardown", ["workstream.destroy"]]
|
|
981
|
+
]);
|
|
982
|
+
function intentSpellings(intent) {
|
|
983
|
+
const legacy = LEGACY_INTENT_SYNONYMS.get(intent);
|
|
984
|
+
return legacy === void 0 ? [intent] : [intent, ...legacy];
|
|
1037
985
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
986
|
+
var LEGACY_LOG_ONLY_SQL_EXCLUSION = `(intent IS NULL OR intent NOT IN (${[
|
|
987
|
+
...LEGACY_LOG_ONLY_INTENTS
|
|
988
|
+
].map((i) => `'${i}'`).join(", ")}))`;
|
|
989
|
+
|
|
990
|
+
// src/logs.ts
|
|
991
|
+
var SELECT_LOG_COLS = `
|
|
992
|
+
l.seq AS seq,
|
|
993
|
+
l.key AS workstream,
|
|
994
|
+
l.actor AS source,
|
|
995
|
+
l.intent AS intent,
|
|
996
|
+
l.group_id AS group_id,
|
|
997
|
+
l.op AS op,
|
|
998
|
+
l.entity AS kind,
|
|
999
|
+
l.payload AS payload,
|
|
1000
|
+
l.created_at AS created_at
|
|
1001
|
+
`;
|
|
1002
|
+
var LOG_FROM_JOIN = "FROM ops l";
|
|
1003
|
+
function workstreamScopeSql(column = "l.key") {
|
|
1004
|
+
return `(${column} = ? OR ${column} LIKE ? ESCAPE '\\')`;
|
|
1040
1005
|
}
|
|
1041
|
-
function
|
|
1042
|
-
|
|
1006
|
+
function workstreamScopeParams(workstream) {
|
|
1007
|
+
const escaped = workstream.replace(/[\\%_]/g, (c) => `\\${c}`);
|
|
1008
|
+
return [workstream, `${escaped}/%`];
|
|
1043
1009
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1010
|
+
var TOUCH_OP_FILTER = `NOT (
|
|
1011
|
+
l.intent IS NOT NULL
|
|
1012
|
+
AND l.op = 'put'
|
|
1013
|
+
AND l.payload LIKE '{"updated_at":%'
|
|
1014
|
+
AND LENGTH(l.payload) < 45
|
|
1015
|
+
)`;
|
|
1016
|
+
var MACHINE_WIDE_KEY = "";
|
|
1017
|
+
var GroupIdAmbiguousError = class extends Error {
|
|
1018
|
+
constructor(prefix, candidates) {
|
|
1019
|
+
super(
|
|
1020
|
+
`group id ${JSON.stringify(prefix)} is ambiguous: matches ${candidates.length} groups (${candidates.slice(0, 4).map((c) => c.slice(0, 12)).join(", ")}${candidates.length > 4 ? ", \u2026" : ""})`
|
|
1021
|
+
);
|
|
1022
|
+
this.prefix = prefix;
|
|
1023
|
+
this.candidates = candidates;
|
|
1024
|
+
this.name = "GroupIdAmbiguousError";
|
|
1025
|
+
}
|
|
1026
|
+
prefix;
|
|
1027
|
+
candidates;
|
|
1028
|
+
errorNextSteps() {
|
|
1029
|
+
return [
|
|
1030
|
+
{ intent: "List recent groups with their ids", command: "mu undo" },
|
|
1031
|
+
{
|
|
1032
|
+
intent: "Retry with more characters",
|
|
1033
|
+
command: `mu log --group ${this.candidates[0] ?? "<full-id>"}`
|
|
1034
|
+
}
|
|
1035
|
+
];
|
|
1053
1036
|
}
|
|
1037
|
+
};
|
|
1038
|
+
function groupIdFromPrefix(db, prefix) {
|
|
1039
|
+
if (prefix === "") return null;
|
|
1040
|
+
const rows = db.prepare(
|
|
1041
|
+
`SELECT DISTINCT group_id AS groupId FROM ops
|
|
1042
|
+
WHERE group_id = @exact OR group_id LIKE @prefix || '%' ESCAPE '\\'`
|
|
1043
|
+
).all({ exact: prefix, prefix: prefix.replace(/[\\%_]/g, (c) => `\\${c}`) });
|
|
1044
|
+
const exact = rows.find((r) => r.groupId === prefix);
|
|
1045
|
+
if (exact !== void 0) return exact.groupId;
|
|
1046
|
+
const first = rows[0];
|
|
1047
|
+
if (rows.length === 1 && first !== void 0) return first.groupId;
|
|
1048
|
+
if (rows.length === 0) return null;
|
|
1049
|
+
throw new GroupIdAmbiguousError(
|
|
1050
|
+
prefix,
|
|
1051
|
+
rows.map((r) => r.groupId)
|
|
1052
|
+
);
|
|
1054
1053
|
}
|
|
1055
|
-
|
|
1056
|
-
const
|
|
1057
|
-
return
|
|
1054
|
+
function machineId(db) {
|
|
1055
|
+
const row = db.prepare("SELECT machine_id FROM machine_identity WHERE id = 1").get();
|
|
1056
|
+
return row?.machine_id ?? "unknown";
|
|
1058
1057
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1058
|
+
function rowFromDb(row) {
|
|
1059
|
+
return {
|
|
1060
|
+
seq: row.seq,
|
|
1061
|
+
workstreamName: row.workstream === MACHINE_WIDE_KEY ? null : row.workstream,
|
|
1062
|
+
source: row.source ?? "system",
|
|
1063
|
+
intent: row.intent,
|
|
1064
|
+
group: row.group_id,
|
|
1065
|
+
op: row.op,
|
|
1066
|
+
kind: row.kind,
|
|
1067
|
+
payload: row.payload,
|
|
1068
|
+
createdAt: row.created_at
|
|
1069
|
+
};
|
|
1068
1070
|
}
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1071
|
+
function appendLog(db, opts) {
|
|
1072
|
+
const kind = opts.kind ?? "message";
|
|
1073
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1074
|
+
const key = opts.workstream ?? MACHINE_WIDE_KEY;
|
|
1075
|
+
const group = randomUUID2();
|
|
1076
|
+
const result = db.prepare(
|
|
1077
|
+
`INSERT INTO ops (hlc, machine_id, group_id, actor, intent, entity, key, op, payload, created_at)
|
|
1078
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'put', ?, ?)`
|
|
1079
|
+
).run(
|
|
1080
|
+
nextHlc(db),
|
|
1081
|
+
machineId(db),
|
|
1082
|
+
group,
|
|
1083
|
+
opts.source,
|
|
1084
|
+
opts.intent ?? null,
|
|
1085
|
+
kind,
|
|
1086
|
+
key,
|
|
1087
|
+
opts.payload,
|
|
1088
|
+
createdAt
|
|
1089
|
+
);
|
|
1090
|
+
return {
|
|
1091
|
+
seq: Number(result.lastInsertRowid),
|
|
1092
|
+
workstreamName: opts.workstream,
|
|
1093
|
+
source: opts.source,
|
|
1094
|
+
intent: opts.intent ?? null,
|
|
1095
|
+
group,
|
|
1096
|
+
op: "put",
|
|
1097
|
+
kind,
|
|
1098
|
+
payload: opts.payload,
|
|
1099
|
+
createdAt
|
|
1100
|
+
};
|
|
1079
1101
|
}
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
);
|
|
1102
|
+
function listLogs(db, opts = {}) {
|
|
1103
|
+
const conditions = [];
|
|
1104
|
+
const params = [];
|
|
1105
|
+
if (opts.workstream === null) {
|
|
1106
|
+
conditions.push("l.key = ?");
|
|
1107
|
+
params.push(MACHINE_WIDE_KEY);
|
|
1108
|
+
} else if (opts.workstream !== void 0) {
|
|
1109
|
+
conditions.push(workstreamScopeSql());
|
|
1110
|
+
params.push(...workstreamScopeParams(opts.workstream));
|
|
1089
1111
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
const out2 = await tmux(["list-windows", "-t", session, "-F", "#{window_id} #{window_name}"]);
|
|
1094
|
-
return parseWindows(out2);
|
|
1112
|
+
if (opts.since !== void 0) {
|
|
1113
|
+
conditions.push("l.seq > ?");
|
|
1114
|
+
params.push(opts.since);
|
|
1095
1115
|
}
|
|
1096
|
-
|
|
1097
|
-
"
|
|
1098
|
-
|
|
1099
|
-
"-F",
|
|
1100
|
-
"#{session_name} #{window_id} #{window_name}"
|
|
1101
|
-
]);
|
|
1102
|
-
const windows = [];
|
|
1103
|
-
for (const line of out.split("\n")) {
|
|
1104
|
-
if (line.length === 0) continue;
|
|
1105
|
-
const [sessionName, id, name] = line.split(" ");
|
|
1106
|
-
if (!sessionName || !id || name === void 0) continue;
|
|
1107
|
-
windows.push({ id, name, sessionName });
|
|
1116
|
+
if (opts.source !== void 0) {
|
|
1117
|
+
conditions.push("l.actor = ?");
|
|
1118
|
+
params.push(opts.source);
|
|
1108
1119
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
const windows = [];
|
|
1113
|
-
for (const line of output.split("\n")) {
|
|
1114
|
-
if (line.length === 0) continue;
|
|
1115
|
-
const [id, name] = line.split(" ");
|
|
1116
|
-
if (!id || name === void 0) continue;
|
|
1117
|
-
windows.push({ id, name });
|
|
1120
|
+
if (opts.kind !== void 0) {
|
|
1121
|
+
conditions.push("l.entity = ?");
|
|
1122
|
+
params.push(opts.kind);
|
|
1118
1123
|
}
|
|
1119
|
-
|
|
1124
|
+
if (opts.intent !== void 0) {
|
|
1125
|
+
const spellings = intentSpellings(opts.intent);
|
|
1126
|
+
conditions.push(`l.intent IN (${spellings.map(() => "?").join(", ")})`);
|
|
1127
|
+
params.push(...spellings);
|
|
1128
|
+
}
|
|
1129
|
+
if (opts.group !== void 0) {
|
|
1130
|
+
conditions.push("l.group_id = ?");
|
|
1131
|
+
params.push(opts.group);
|
|
1132
|
+
}
|
|
1133
|
+
conditions.push(TOUCH_OP_FILTER);
|
|
1134
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1135
|
+
if (opts.limit !== void 0 && opts.since === void 0) {
|
|
1136
|
+
const rowsDesc = db.prepare(`SELECT ${SELECT_LOG_COLS} ${LOG_FROM_JOIN} ${where} ORDER BY l.seq DESC LIMIT ?`).all(...params, opts.limit);
|
|
1137
|
+
return rowsDesc.reverse().map(rowFromDb);
|
|
1138
|
+
}
|
|
1139
|
+
let sql = `SELECT ${SELECT_LOG_COLS} ${LOG_FROM_JOIN} ${where} ORDER BY l.seq ASC`;
|
|
1140
|
+
if (opts.limit !== void 0) {
|
|
1141
|
+
sql += " LIMIT ?";
|
|
1142
|
+
params.push(opts.limit);
|
|
1143
|
+
}
|
|
1144
|
+
const rows = db.prepare(sql).all(...params);
|
|
1145
|
+
return rows.map(rowFromDb);
|
|
1120
1146
|
}
|
|
1121
|
-
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
if (opts.cwd) args.push("-c", opts.cwd);
|
|
1127
|
-
appendEnvFlags(args, opts.env);
|
|
1128
|
-
args.push("-P", "-F", "#{pane_id}", opts.command);
|
|
1129
|
-
const out = (await tmux(args)).trim();
|
|
1130
|
-
assertValidPaneId(out);
|
|
1131
|
-
return out;
|
|
1147
|
+
function latestSeq(db, workstream) {
|
|
1148
|
+
const row = workstream === void 0 ? db.prepare(`SELECT MAX(seq) AS s FROM ops l WHERE ${TOUCH_OP_FILTER}`).get() : db.prepare(
|
|
1149
|
+
`SELECT MAX(seq) AS s FROM ops l WHERE ${workstreamScopeSql()} AND ${TOUCH_OP_FILTER}`
|
|
1150
|
+
).get(...workstreamScopeParams(workstream));
|
|
1151
|
+
return row.s ?? 0;
|
|
1132
1152
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1153
|
+
function emitEvent(db, workstream, intent, payload, source = "system") {
|
|
1154
|
+
appendLog(db, { workstream, source, kind: entityForIntent(intent), payload, intent });
|
|
1155
|
+
}
|
|
1156
|
+
function entityForIntent(intent) {
|
|
1157
|
+
const dot = intent.indexOf(".");
|
|
1158
|
+
return dot === -1 ? intent : intent.slice(0, dot);
|
|
1159
|
+
}
|
|
1160
|
+
function lastClaimOp(db, workstream, localId) {
|
|
1161
|
+
const row = db.prepare(
|
|
1162
|
+
`SELECT actor, created_at FROM ops
|
|
1163
|
+
WHERE key = ? AND intent = 'task.claim'
|
|
1164
|
+
ORDER BY seq DESC LIMIT 1`
|
|
1165
|
+
).get(`${workstream}/${localId}`);
|
|
1166
|
+
return row ?? null;
|
|
1167
|
+
}
|
|
1168
|
+
function lastClaimEventAt(db, workstream, localId) {
|
|
1169
|
+
return lastClaimOp(db, workstream, localId)?.created_at ?? null;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/mux.ts
|
|
1173
|
+
var mux_exports = {};
|
|
1174
|
+
__export(mux_exports, {
|
|
1175
|
+
HerdrCommandOverrideError: () => HerdrCommandOverrideError,
|
|
1176
|
+
HerdrError: () => HerdrError,
|
|
1177
|
+
HerdrNotImplementedError: () => HerdrNotImplementedError,
|
|
1178
|
+
HerdrSyntaxError: () => HerdrSyntaxError,
|
|
1179
|
+
HerdrUnsupportedCliError: () => HerdrUnsupportedCliError,
|
|
1180
|
+
HerdrWorkspaceGroupCloseError: () => HerdrWorkspaceGroupCloseError,
|
|
1181
|
+
MuxError: () => MuxError,
|
|
1182
|
+
NoMultiplexerError: () => NoMultiplexerError,
|
|
1183
|
+
PaneNotFoundError: () => PaneNotFoundError,
|
|
1184
|
+
activeMux: () => activeMux,
|
|
1185
|
+
detectMux: () => detectMux,
|
|
1186
|
+
herdrBackend: () => herdrBackend,
|
|
1187
|
+
isHerdrStatusUsable: () => isHerdrStatusUsable,
|
|
1188
|
+
muxByName: () => muxByName,
|
|
1189
|
+
parseAgentNameFromTitle: () => parseAgentNameFromTitle,
|
|
1190
|
+
resetHerdrExecutor: () => resetHerdrExecutor,
|
|
1191
|
+
resetMux: () => resetMux,
|
|
1192
|
+
setHerdrExecutor: () => setHerdrExecutor,
|
|
1193
|
+
setMuxForTests: () => setMuxForTests,
|
|
1194
|
+
tmuxBackend: () => tmuxBackend
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
// src/mux/herdr.ts
|
|
1198
|
+
import { execa as execa2 } from "execa";
|
|
1199
|
+
|
|
1200
|
+
// src/mux/tmux.ts
|
|
1201
|
+
import { execa } from "execa";
|
|
1202
|
+
|
|
1203
|
+
// src/mux/input-timing.ts
|
|
1204
|
+
var TAIL_WINDOW_LINES = 100;
|
|
1205
|
+
var TAIL_LINES = 20;
|
|
1206
|
+
var BUSY_MARKERS = ["to interrupt)"];
|
|
1207
|
+
var BRAILLE_SPINNER_RE = /[\u2800-\u28ff]/;
|
|
1208
|
+
function extractTail(scrollback) {
|
|
1209
|
+
const lines = scrollback.split("\n");
|
|
1210
|
+
const window = lines.slice(-TAIL_WINDOW_LINES);
|
|
1211
|
+
let end = window.length;
|
|
1212
|
+
while (end > 0 && (window[end - 1] ?? "").trim() === "") end--;
|
|
1213
|
+
return window.slice(Math.max(0, end - TAIL_LINES), end).join("\n");
|
|
1214
|
+
}
|
|
1215
|
+
function paneLooksBusy(scrollback) {
|
|
1216
|
+
const tail = extractTail(scrollback);
|
|
1217
|
+
return BUSY_MARKERS.some((marker) => tail.includes(marker)) || BRAILLE_SPINNER_RE.test(tail);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/mux/types.ts
|
|
1221
|
+
function parseAgentNameFromTitle(title) {
|
|
1222
|
+
const idx = title.indexOf(" \xB7 ");
|
|
1223
|
+
return idx === -1 ? title.trim() : title.slice(0, idx).trim();
|
|
1224
|
+
}
|
|
1225
|
+
var MuxError = class extends Error {
|
|
1226
|
+
name = "MuxError";
|
|
1227
|
+
errorNextSteps() {
|
|
1228
|
+
return [{ intent: "Run health check", command: "mu doctor" }];
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
var PaneNotFoundError = class extends Error {
|
|
1232
|
+
constructor(paneId, backend) {
|
|
1233
|
+
super(`${backend?.name ?? "mux"} pane not found: ${paneId}`);
|
|
1234
|
+
this.paneId = paneId;
|
|
1235
|
+
this.backend = backend;
|
|
1236
|
+
}
|
|
1237
|
+
paneId;
|
|
1238
|
+
backend;
|
|
1239
|
+
name = "PaneNotFoundError";
|
|
1240
|
+
errorNextSteps() {
|
|
1241
|
+
return [
|
|
1242
|
+
...this.backend?.paneNotFoundNextSteps(this.paneId) ?? [],
|
|
1243
|
+
{
|
|
1244
|
+
intent: "List workstreams to choose the right scope",
|
|
1245
|
+
command: "mu workstream list"
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
intent: "List registered agents and orphan panes in that scope",
|
|
1249
|
+
command: "mu agent list -w <workstream>"
|
|
1250
|
+
}
|
|
1251
|
+
];
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
var NoMultiplexerError = class extends Error {
|
|
1255
|
+
constructor(tried) {
|
|
1256
|
+
super(
|
|
1257
|
+
`no supported multiplexer found (tried: ${tried.join(", ")}). mu drives agents through tmux panes.`
|
|
1258
|
+
);
|
|
1259
|
+
this.tried = tried;
|
|
1260
|
+
}
|
|
1261
|
+
tried;
|
|
1262
|
+
name = "NoMultiplexerError";
|
|
1263
|
+
errorNextSteps() {
|
|
1264
|
+
return [
|
|
1265
|
+
{ intent: "Run health check", command: "mu doctor" },
|
|
1266
|
+
{ intent: "Verify tmux is installed", command: "tmux -V" },
|
|
1267
|
+
{ intent: "Force a specific backend", command: "MU_MUX=tmux mu <verb>" }
|
|
1268
|
+
];
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// src/mux/tmux.ts
|
|
1273
|
+
var TmuxError = class extends MuxError {
|
|
1274
|
+
constructor(args, stderr, stdout, exitCode) {
|
|
1275
|
+
const detail = stderr.trim() || stdout.trim() || "no output";
|
|
1276
|
+
super(`tmux ${args.join(" ")} failed (exit ${exitCode}): ${detail}`);
|
|
1277
|
+
this.args = args;
|
|
1278
|
+
this.stderr = stderr;
|
|
1279
|
+
this.stdout = stdout;
|
|
1280
|
+
this.exitCode = exitCode;
|
|
1281
|
+
this.name = "TmuxError";
|
|
1282
|
+
}
|
|
1283
|
+
args;
|
|
1284
|
+
stderr;
|
|
1285
|
+
stdout;
|
|
1286
|
+
exitCode;
|
|
1287
|
+
errorNextSteps() {
|
|
1288
|
+
return [
|
|
1289
|
+
{ intent: "Run health check", command: "mu doctor" },
|
|
1290
|
+
{
|
|
1291
|
+
intent: "Verify tmux is running and reachable",
|
|
1292
|
+
command: "tmux info | head"
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
intent: "Check the failing tmux command in isolation",
|
|
1296
|
+
command: `tmux ${this.args.join(" ")}`
|
|
1297
|
+
}
|
|
1298
|
+
];
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
var PANE_ID_RE = /^%\d+$/;
|
|
1302
|
+
function isValidPaneId(s) {
|
|
1303
|
+
return PANE_ID_RE.test(s);
|
|
1304
|
+
}
|
|
1305
|
+
function assertValidPaneId(s) {
|
|
1306
|
+
if (!isValidPaneId(s)) {
|
|
1307
|
+
throw new TypeError(`invalid tmux pane id: ${JSON.stringify(s)} (expected /^%\\d+$/)`);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function defaultSendDelayMs() {
|
|
1311
|
+
const raw = process.env.MU_SEND_DELAY_MS;
|
|
1312
|
+
if (raw === void 0) return 500;
|
|
1313
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1314
|
+
if (Number.isNaN(parsed) || parsed < 0) return 500;
|
|
1315
|
+
return parsed;
|
|
1316
|
+
}
|
|
1317
|
+
function tmuxGlobalFlags() {
|
|
1318
|
+
const socket = process.env.MU_TMUX_SOCKET;
|
|
1319
|
+
if (socket === void 0 || socket.length === 0) return [];
|
|
1320
|
+
return ["-L", socket, "-f", "/dev/null"];
|
|
1321
|
+
}
|
|
1322
|
+
var realExecutor = async (args) => {
|
|
1323
|
+
const result = await execa("tmux", [...tmuxGlobalFlags(), ...args], { reject: false });
|
|
1324
|
+
return {
|
|
1325
|
+
stdout: result.stdout ?? "",
|
|
1326
|
+
stderr: result.stderr ?? "",
|
|
1327
|
+
exitCode: result.exitCode ?? null
|
|
1328
|
+
};
|
|
1329
|
+
};
|
|
1330
|
+
var currentExecutor = realExecutor;
|
|
1331
|
+
function setTmuxExecutor(executor) {
|
|
1332
|
+
const previous = currentExecutor;
|
|
1333
|
+
currentExecutor = executor;
|
|
1334
|
+
return previous;
|
|
1335
|
+
}
|
|
1336
|
+
function resetTmuxExecutor() {
|
|
1337
|
+
currentExecutor = realExecutor;
|
|
1338
|
+
}
|
|
1339
|
+
async function tmux(args) {
|
|
1340
|
+
const result = await currentExecutor(args);
|
|
1341
|
+
if (result.exitCode !== 0) {
|
|
1342
|
+
throw new TmuxError([...args], result.stderr, result.stdout, result.exitCode);
|
|
1343
|
+
}
|
|
1344
|
+
return result.stdout;
|
|
1345
|
+
}
|
|
1346
|
+
var currentSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1347
|
+
function setSleepForTests(impl) {
|
|
1348
|
+
const previous = currentSleep;
|
|
1349
|
+
currentSleep = impl;
|
|
1350
|
+
return previous;
|
|
1351
|
+
}
|
|
1352
|
+
function resetSleep() {
|
|
1353
|
+
currentSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1354
|
+
}
|
|
1355
|
+
function sleep(ms) {
|
|
1356
|
+
return currentSleep(ms);
|
|
1357
|
+
}
|
|
1358
|
+
async function listSessions() {
|
|
1359
|
+
try {
|
|
1360
|
+
const out = await tmux(["list-sessions", "-F", "#{session_name}"]);
|
|
1361
|
+
return out.split("\n").filter((line) => line.length > 0).map((name) => ({ name }));
|
|
1362
|
+
} catch (err) {
|
|
1363
|
+
if (err instanceof TmuxError && /no server running|no sessions/i.test(err.stderr)) {
|
|
1364
|
+
return [];
|
|
1365
|
+
}
|
|
1366
|
+
throw err;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
async function sessionExists(name) {
|
|
1370
|
+
const result = await currentExecutor(["has-session", "-t", name]);
|
|
1371
|
+
return result.exitCode === 0;
|
|
1372
|
+
}
|
|
1373
|
+
async function newSession(name, opts = {}) {
|
|
1374
|
+
const args = ["new-session"];
|
|
1375
|
+
if (opts.detached !== false) args.push("-d");
|
|
1376
|
+
args.push("-s", name);
|
|
1377
|
+
if (opts.windowName) args.push("-n", opts.windowName);
|
|
1378
|
+
if (opts.cwd) args.push("-c", opts.cwd);
|
|
1379
|
+
appendEnvFlags(args, opts.env);
|
|
1380
|
+
if (opts.command) args.push(opts.command);
|
|
1381
|
+
await tmux(args);
|
|
1382
|
+
}
|
|
1383
|
+
async function newSessionWithPane(name, opts) {
|
|
1384
|
+
const args = ["new-session"];
|
|
1385
|
+
if (opts.detached !== false) args.push("-d");
|
|
1386
|
+
args.push("-s", name, "-n", opts.windowName);
|
|
1387
|
+
if (opts.cwd) args.push("-c", opts.cwd);
|
|
1388
|
+
appendEnvFlags(args, opts.env);
|
|
1389
|
+
args.push("-P", "-F", "#{pane_id}", opts.command);
|
|
1390
|
+
const out = (await tmux(args)).trim();
|
|
1391
|
+
assertValidPaneId(out);
|
|
1392
|
+
return out;
|
|
1393
|
+
}
|
|
1394
|
+
async function killSession(name) {
|
|
1395
|
+
const result = await currentExecutor(["kill-session", "-t", name]);
|
|
1396
|
+
if (result.exitCode !== 0 && !/can't find session|session not found|no server running|no current target/i.test(result.stderr)) {
|
|
1397
|
+
throw new TmuxError(
|
|
1398
|
+
["kill-session", "-t", name],
|
|
1399
|
+
result.stderr,
|
|
1400
|
+
result.stdout,
|
|
1401
|
+
result.exitCode
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
async function listWindows(session) {
|
|
1406
|
+
if (session) {
|
|
1407
|
+
const out2 = await tmux(["list-windows", "-t", session, "-F", "#{window_id} #{window_name}"]);
|
|
1408
|
+
return parseWindows(out2);
|
|
1409
|
+
}
|
|
1410
|
+
const out = await tmux([
|
|
1411
|
+
"list-windows",
|
|
1412
|
+
"-a",
|
|
1413
|
+
"-F",
|
|
1414
|
+
"#{session_name} #{window_id} #{window_name}"
|
|
1415
|
+
]);
|
|
1416
|
+
const windows = [];
|
|
1417
|
+
for (const line of out.split("\n")) {
|
|
1418
|
+
if (line.length === 0) continue;
|
|
1419
|
+
const [sessionName, id, name] = line.split(" ");
|
|
1420
|
+
if (!sessionName || !id || name === void 0) continue;
|
|
1421
|
+
windows.push({ id, name, sessionName });
|
|
1422
|
+
}
|
|
1423
|
+
return windows;
|
|
1424
|
+
}
|
|
1425
|
+
function parseWindows(output) {
|
|
1426
|
+
const windows = [];
|
|
1427
|
+
for (const line of output.split("\n")) {
|
|
1428
|
+
if (line.length === 0) continue;
|
|
1429
|
+
const [id, name] = line.split(" ");
|
|
1430
|
+
if (!id || name === void 0) continue;
|
|
1431
|
+
windows.push({ id, name });
|
|
1432
|
+
}
|
|
1433
|
+
return windows;
|
|
1434
|
+
}
|
|
1435
|
+
async function newWindow(opts) {
|
|
1436
|
+
const args = ["new-window"];
|
|
1437
|
+
if (opts.detached !== false) args.push("-d");
|
|
1438
|
+
if (opts.session) args.push("-t", opts.session);
|
|
1439
|
+
args.push("-n", opts.name);
|
|
1440
|
+
if (opts.cwd) args.push("-c", opts.cwd);
|
|
1441
|
+
appendEnvFlags(args, opts.env);
|
|
1442
|
+
args.push("-P", "-F", "#{pane_id}", opts.command);
|
|
1443
|
+
const out = (await tmux(args)).trim();
|
|
1444
|
+
assertValidPaneId(out);
|
|
1445
|
+
return out;
|
|
1446
|
+
}
|
|
1447
|
+
async function listPanesInSession(session) {
|
|
1448
|
+
const args = [
|
|
1449
|
+
"list-panes",
|
|
1136
1450
|
"-s",
|
|
1137
1451
|
"-t",
|
|
1138
1452
|
session,
|
|
@@ -1655,970 +1969,663 @@ var TAB_ID_RE = new RegExp(`^w${HERDR_ORDINAL}:t${HERDR_ORDINAL}$`);
|
|
|
1655
1969
|
function isValidPaneId2(s) {
|
|
1656
1970
|
return PANE_ID_RE2.test(s);
|
|
1657
1971
|
}
|
|
1658
|
-
function assertValidPaneId2(s) {
|
|
1659
|
-
if (!isValidPaneId2(s)) {
|
|
1660
|
-
throw new TypeError(
|
|
1661
|
-
`invalid herdr pane id: ${JSON.stringify(s)} (expected ${PANE_ID_RE2.source})`
|
|
1662
|
-
);
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
function herdrGlobalFlags() {
|
|
1666
|
-
const session = process.env.MU_HERDR_SESSION;
|
|
1667
|
-
if (session === void 0 || session.length === 0) return [];
|
|
1668
|
-
return ["--session", session];
|
|
1669
|
-
}
|
|
1670
|
-
var realExecutor2 = async (args) => {
|
|
1671
|
-
const result = await execa2("herdr", [...herdrGlobalFlags(), ...args], { reject: false });
|
|
1672
|
-
return {
|
|
1673
|
-
stdout: result.stdout ?? "",
|
|
1674
|
-
stderr: result.stderr ?? "",
|
|
1675
|
-
exitCode: result.exitCode ?? null
|
|
1676
|
-
};
|
|
1677
|
-
};
|
|
1678
|
-
var currentExecutor2 = realExecutor2;
|
|
1679
|
-
function setHerdrExecutor(executor) {
|
|
1680
|
-
const previous = currentExecutor2;
|
|
1681
|
-
currentExecutor2 = executor;
|
|
1682
|
-
return previous;
|
|
1683
|
-
}
|
|
1684
|
-
function resetHerdrExecutor() {
|
|
1685
|
-
currentExecutor2 = realExecutor2;
|
|
1686
|
-
}
|
|
1687
|
-
function isRecord(v) {
|
|
1688
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1689
|
-
}
|
|
1690
|
-
function asString(v) {
|
|
1691
|
-
return typeof v === "string" ? v : void 0;
|
|
1692
|
-
}
|
|
1693
|
-
function parseErrorEnvelope(stderr) {
|
|
1694
|
-
const trimmed = stderr.trim();
|
|
1695
|
-
if (trimmed.length === 0) return void 0;
|
|
1696
|
-
let parsed;
|
|
1697
|
-
try {
|
|
1698
|
-
parsed = JSON.parse(trimmed);
|
|
1699
|
-
} catch {
|
|
1700
|
-
return void 0;
|
|
1701
|
-
}
|
|
1702
|
-
if (!isRecord(parsed)) return void 0;
|
|
1703
|
-
const err = parsed.error;
|
|
1704
|
-
if (!isRecord(err)) return void 0;
|
|
1705
|
-
const code = asString(err.code);
|
|
1706
|
-
const message = asString(err.message);
|
|
1707
|
-
if (code === void 0) return void 0;
|
|
1708
|
-
return { code, message: message ?? code };
|
|
1709
|
-
}
|
|
1710
|
-
async function herdr(args) {
|
|
1711
|
-
const result = await currentExecutor2(args);
|
|
1712
|
-
if (result.exitCode === 2) {
|
|
1713
|
-
throw new HerdrSyntaxError([...args], result.stderr || result.stdout);
|
|
1714
|
-
}
|
|
1715
|
-
if (result.exitCode !== 0) {
|
|
1716
|
-
throw new HerdrError(
|
|
1717
|
-
[...args],
|
|
1718
|
-
result.stderr,
|
|
1719
|
-
result.stdout,
|
|
1720
|
-
result.exitCode,
|
|
1721
|
-
parseErrorEnvelope(result.stderr)?.code
|
|
1722
|
-
);
|
|
1723
|
-
}
|
|
1724
|
-
const trimmed = result.stdout.trim();
|
|
1725
|
-
if (trimmed.length === 0) return {};
|
|
1726
|
-
let parsed;
|
|
1727
|
-
try {
|
|
1728
|
-
parsed = JSON.parse(trimmed);
|
|
1729
|
-
} catch {
|
|
1730
|
-
throw new HerdrError([...args], result.stderr, result.stdout, result.exitCode);
|
|
1731
|
-
}
|
|
1732
|
-
if (!isRecord(parsed)) return {};
|
|
1733
|
-
const inner = parsed.result;
|
|
1734
|
-
return isRecord(inner) ? inner : {};
|
|
1735
|
-
}
|
|
1736
|
-
async function herdrTolerating(args, tolerate) {
|
|
1737
|
-
try {
|
|
1738
|
-
return await herdr(args);
|
|
1739
|
-
} catch (err) {
|
|
1740
|
-
if (err instanceof HerdrError && err.code !== void 0 && tolerate.includes(err.code)) {
|
|
1741
|
-
return void 0;
|
|
1742
|
-
}
|
|
1743
|
-
throw err;
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
function readWorkspace(v) {
|
|
1747
|
-
if (!isRecord(v)) return void 0;
|
|
1748
|
-
const workspaceId = asString(v.workspace_id);
|
|
1749
|
-
if (workspaceId === void 0) return void 0;
|
|
1750
|
-
return { workspaceId, label: asString(v.label) ?? "" };
|
|
1751
|
-
}
|
|
1752
|
-
function readTab(v) {
|
|
1753
|
-
if (!isRecord(v)) return void 0;
|
|
1754
|
-
const id = asString(v.tab_id);
|
|
1755
|
-
if (id === void 0) return void 0;
|
|
1756
|
-
return { id, name: asString(v.label) ?? "" };
|
|
1757
|
-
}
|
|
1758
|
-
function readPane(v) {
|
|
1759
|
-
if (!isRecord(v)) return void 0;
|
|
1760
|
-
const paneId = asString(v.pane_id);
|
|
1761
|
-
if (paneId === void 0) return void 0;
|
|
1762
|
-
return {
|
|
1763
|
-
paneId,
|
|
1764
|
-
// herdr's optional `label` is what `pane rename` writes and is
|
|
1765
|
-
// therefore mu's pane-title equivalent.
|
|
1766
|
-
title: asString(v.label) ?? "",
|
|
1767
|
-
// herdr does not report a foreground command on `pane list`; the
|
|
1768
|
-
// authoritative answer costs a `pane process-info` round trip per
|
|
1769
|
-
// pane. Callers that need it use `paneCommand()`.
|
|
1770
|
-
command: "",
|
|
1771
|
-
windowId: asString(v.tab_id)
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
function readArray(result, key) {
|
|
1775
|
-
const v = result[key];
|
|
1776
|
-
return Array.isArray(v) ? v : [];
|
|
1777
|
-
}
|
|
1778
|
-
async function listWorkspaces() {
|
|
1779
|
-
const result = await herdr(["workspace", "list"]);
|
|
1780
|
-
const out = [];
|
|
1781
|
-
for (const raw of readArray(result, "workspaces")) {
|
|
1782
|
-
const ws = readWorkspace(raw);
|
|
1783
|
-
if (ws) out.push(ws);
|
|
1784
|
-
}
|
|
1785
|
-
return out;
|
|
1786
|
-
}
|
|
1787
|
-
async function listSessions2() {
|
|
1788
|
-
return (await listWorkspaces()).map((ws) => ({ name: ws.label }));
|
|
1789
|
-
}
|
|
1790
|
-
async function resolveWorkspaceId(name) {
|
|
1791
|
-
for (const ws of await listWorkspaces()) {
|
|
1792
|
-
if (ws.label === name) return ws.workspaceId;
|
|
1793
|
-
}
|
|
1794
|
-
return void 0;
|
|
1795
|
-
}
|
|
1796
|
-
async function requireWorkspaceId(name) {
|
|
1797
|
-
const id = await resolveWorkspaceId(name);
|
|
1798
|
-
if (id === void 0) {
|
|
1799
|
-
throw new HerdrError(
|
|
1800
|
-
["workspace", "list"],
|
|
1801
|
-
`{"error":{"code":"workspace_not_found","message":"no herdr workspace labelled ${name}"}}`,
|
|
1802
|
-
"",
|
|
1803
|
-
1,
|
|
1804
|
-
"workspace_not_found"
|
|
1805
|
-
);
|
|
1806
|
-
}
|
|
1807
|
-
return id;
|
|
1808
|
-
}
|
|
1809
|
-
async function sessionExists2(name) {
|
|
1810
|
-
return await resolveWorkspaceId(name) !== void 0;
|
|
1811
|
-
}
|
|
1812
|
-
async function newSession2(name, opts = {}) {
|
|
1813
|
-
rejectCommand("newSession", opts.command);
|
|
1814
|
-
const args = ["workspace", "create", "--label", name, "--no-focus"];
|
|
1815
|
-
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
1816
|
-
appendEnvFlags2(args, opts.env);
|
|
1817
|
-
await herdr(args);
|
|
1818
|
-
}
|
|
1819
|
-
async function newSessionWithPane2(name, opts) {
|
|
1820
|
-
rejectCommand("newSessionWithPane", opts.command);
|
|
1821
|
-
const args = ["workspace", "create", "--label", name, "--no-focus"];
|
|
1822
|
-
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
1823
|
-
appendEnvFlags2(args, opts.env);
|
|
1824
|
-
const result = await herdr(args);
|
|
1825
|
-
return readCreatedPaneId(result, "root_pane", args);
|
|
1826
|
-
}
|
|
1827
|
-
async function killSession2(name) {
|
|
1828
|
-
const id = await resolveWorkspaceId(name);
|
|
1829
|
-
if (id === void 0) return;
|
|
1830
|
-
try {
|
|
1831
|
-
await herdrTolerating(["workspace", "close", id], ["workspace_not_found"]);
|
|
1832
|
-
} catch (err) {
|
|
1833
|
-
if (err instanceof HerdrError && err.code === GROUP_CLOSE_REQUIRED_CODE) {
|
|
1834
|
-
throw new HerdrWorkspaceGroupCloseError(name, id, err.message);
|
|
1835
|
-
}
|
|
1836
|
-
throw err;
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
async function listWindows2(session) {
|
|
1840
|
-
if (session !== void 0) {
|
|
1841
|
-
const id = await resolveWorkspaceId(session);
|
|
1842
|
-
if (id === void 0) return [];
|
|
1843
|
-
return readTabs(await herdr(["tab", "list", "--workspace", id]));
|
|
1844
|
-
}
|
|
1845
|
-
const windows = [];
|
|
1846
|
-
for (const ws of await listWorkspaces()) {
|
|
1847
|
-
for (const tab of readTabs(await herdr(["tab", "list", "--workspace", ws.workspaceId]))) {
|
|
1848
|
-
windows.push({ ...tab, sessionName: ws.label });
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
return windows;
|
|
1852
|
-
}
|
|
1853
|
-
function readTabs(result) {
|
|
1854
|
-
const out = [];
|
|
1855
|
-
for (const raw of readArray(result, "tabs")) {
|
|
1856
|
-
const tab = readTab(raw);
|
|
1857
|
-
if (tab) out.push(tab);
|
|
1858
|
-
}
|
|
1859
|
-
return out;
|
|
1860
|
-
}
|
|
1861
|
-
async function newWindow2(opts) {
|
|
1862
|
-
rejectCommand("newWindow", opts.command);
|
|
1863
|
-
const args = ["tab", "create"];
|
|
1864
|
-
if (opts.session !== void 0) {
|
|
1865
|
-
args.push("--workspace", await requireWorkspaceId(opts.session));
|
|
1866
|
-
}
|
|
1867
|
-
args.push("--label", opts.name, "--no-focus");
|
|
1868
|
-
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
1869
|
-
appendEnvFlags2(args, opts.env);
|
|
1870
|
-
return readCreatedPaneId(await herdr(args), "root_pane", args);
|
|
1871
|
-
}
|
|
1872
|
-
async function selectLayout2(_window, _layout) {
|
|
1873
|
-
}
|
|
1874
|
-
async function listPanesInSession2(session) {
|
|
1875
|
-
const id = await resolveWorkspaceId(session);
|
|
1876
|
-
if (id === void 0) return [];
|
|
1877
|
-
const result = await herdrTolerating(
|
|
1878
|
-
["pane", "list", "--workspace", id],
|
|
1879
|
-
["workspace_not_found"]
|
|
1880
|
-
);
|
|
1881
|
-
if (result === void 0) return [];
|
|
1882
|
-
return readPanes(result).map((p) => ({ ...p, sessionName: session }));
|
|
1883
|
-
}
|
|
1884
|
-
async function listPanes2(target) {
|
|
1885
|
-
if (target !== void 0 && target !== "*") {
|
|
1886
|
-
if (TAB_ID_RE.test(target)) {
|
|
1887
|
-
const workspaceId = target.split(":")[0] ?? target;
|
|
1888
|
-
const panes2 = readPanes(await herdr(["pane", "list", "--workspace", workspaceId]));
|
|
1889
|
-
return panes2.filter((p) => p.windowId === target);
|
|
1890
|
-
}
|
|
1891
|
-
if (WORKSPACE_ID_RE.test(target)) {
|
|
1892
|
-
return readPanes(await herdr(["pane", "list", "--workspace", target]));
|
|
1893
|
-
}
|
|
1894
|
-
return listPanesInSession2(target);
|
|
1895
|
-
}
|
|
1896
|
-
const ambient = process.env.HERDR_WORKSPACE_ID;
|
|
1897
|
-
if (target === void 0 && ambient !== void 0 && WORKSPACE_ID_RE.test(ambient)) {
|
|
1898
|
-
return readPanes(await herdr(["pane", "list", "--workspace", ambient]));
|
|
1899
|
-
}
|
|
1900
|
-
const panes = [];
|
|
1901
|
-
for (const ws of await listWorkspaces()) {
|
|
1902
|
-
for (const pane of readPanes(await herdr(["pane", "list", "--workspace", ws.workspaceId]))) {
|
|
1903
|
-
panes.push({ ...pane, sessionName: ws.label });
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
return panes;
|
|
1907
|
-
}
|
|
1908
|
-
function readPanes(result) {
|
|
1909
|
-
const out = [];
|
|
1910
|
-
for (const raw of readArray(result, "panes")) {
|
|
1911
|
-
const pane = readPane(raw);
|
|
1912
|
-
if (pane) out.push(pane);
|
|
1972
|
+
function assertValidPaneId2(s) {
|
|
1973
|
+
if (!isValidPaneId2(s)) {
|
|
1974
|
+
throw new TypeError(
|
|
1975
|
+
`invalid herdr pane id: ${JSON.stringify(s)} (expected ${PANE_ID_RE2.source})`
|
|
1976
|
+
);
|
|
1913
1977
|
}
|
|
1914
|
-
return out;
|
|
1915
1978
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
"pane",
|
|
1921
|
-
"split",
|
|
1922
|
-
opts.target,
|
|
1923
|
-
"--direction",
|
|
1924
|
-
opts.horizontal === false ? "down" : "right",
|
|
1925
|
-
"--no-focus"
|
|
1926
|
-
];
|
|
1927
|
-
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
1928
|
-
appendEnvFlags2(args, opts.env);
|
|
1929
|
-
return readCreatedPaneId(await herdr(args), "pane", args);
|
|
1979
|
+
function herdrGlobalFlags() {
|
|
1980
|
+
const session = process.env.MU_HERDR_SESSION;
|
|
1981
|
+
if (session === void 0 || session.length === 0) return [];
|
|
1982
|
+
return ["--session", session];
|
|
1930
1983
|
}
|
|
1931
|
-
async
|
|
1932
|
-
|
|
1933
|
-
|
|
1984
|
+
var realExecutor2 = async (args) => {
|
|
1985
|
+
const result = await execa2("herdr", [...herdrGlobalFlags(), ...args], { reject: false });
|
|
1986
|
+
return {
|
|
1987
|
+
stdout: result.stdout ?? "",
|
|
1988
|
+
stderr: result.stderr ?? "",
|
|
1989
|
+
exitCode: result.exitCode ?? null
|
|
1990
|
+
};
|
|
1991
|
+
};
|
|
1992
|
+
var currentExecutor2 = realExecutor2;
|
|
1993
|
+
function setHerdrExecutor(executor) {
|
|
1994
|
+
const previous = currentExecutor2;
|
|
1995
|
+
currentExecutor2 = executor;
|
|
1996
|
+
return previous;
|
|
1934
1997
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
const pane = await getPane(paneId);
|
|
1938
|
-
return pane !== void 0;
|
|
1998
|
+
function resetHerdrExecutor() {
|
|
1999
|
+
currentExecutor2 = realExecutor2;
|
|
1939
2000
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
["pane", "get", paneId],
|
|
1943
|
-
["pane_not_found", "workspace_not_found"]
|
|
1944
|
-
);
|
|
1945
|
-
if (result === void 0) return void 0;
|
|
1946
|
-
return readPane(result.pane);
|
|
2001
|
+
function isRecord(v) {
|
|
2002
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1947
2003
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
);
|
|
1954
|
-
|
|
1955
|
-
const info = result.process_info;
|
|
1956
|
-
const pid = isRecord(info) ? info.shell_pid : void 0;
|
|
1957
|
-
if (typeof pid !== "number") throw new PaneNotFoundError(paneId);
|
|
1958
|
-
const { readlink } = await import("fs/promises");
|
|
2004
|
+
function asString(v) {
|
|
2005
|
+
return typeof v === "string" ? v : void 0;
|
|
2006
|
+
}
|
|
2007
|
+
function parseErrorEnvelope(stderr) {
|
|
2008
|
+
const trimmed = stderr.trim();
|
|
2009
|
+
if (trimmed.length === 0) return void 0;
|
|
2010
|
+
let parsed;
|
|
1959
2011
|
try {
|
|
1960
|
-
|
|
1961
|
-
} catch
|
|
1962
|
-
|
|
1963
|
-
["pane", "process-info", "--pane", paneId],
|
|
1964
|
-
`cannot resolve the TTY for herdr pane ${paneId} (shell pid ${pid}) via /proc: ${String(err)}`,
|
|
1965
|
-
"",
|
|
1966
|
-
1
|
|
1967
|
-
);
|
|
2012
|
+
parsed = JSON.parse(trimmed);
|
|
2013
|
+
} catch {
|
|
2014
|
+
return void 0;
|
|
1968
2015
|
}
|
|
2016
|
+
if (!isRecord(parsed)) return void 0;
|
|
2017
|
+
const err = parsed.error;
|
|
2018
|
+
if (!isRecord(err)) return void 0;
|
|
2019
|
+
const code = asString(err.code);
|
|
2020
|
+
const message = asString(err.message);
|
|
2021
|
+
if (code === void 0) return void 0;
|
|
2022
|
+
return { code, message: message ?? code };
|
|
1969
2023
|
}
|
|
1970
|
-
async function
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
async function getPaneTitle2(paneId) {
|
|
1975
|
-
if (!isValidPaneId2(paneId)) return void 0;
|
|
1976
|
-
const pane = await getPane(paneId).catch(() => void 0);
|
|
1977
|
-
if (pane === void 0) return void 0;
|
|
1978
|
-
return pane.title.length > 0 ? pane.title : void 0;
|
|
1979
|
-
}
|
|
1980
|
-
async function currentAgentName2() {
|
|
1981
|
-
const paneId = process.env.HERDR_PANE_ID;
|
|
1982
|
-
if (paneId === void 0 || !isValidPaneId2(paneId)) return void 0;
|
|
1983
|
-
const title = await getPaneTitle2(paneId);
|
|
1984
|
-
if (title === void 0) return void 0;
|
|
1985
|
-
const idx = title.indexOf(" \xB7 ");
|
|
1986
|
-
return idx === -1 ? title.trim() : title.slice(0, idx).trim();
|
|
1987
|
-
}
|
|
1988
|
-
async function currentSessionName2() {
|
|
1989
|
-
const workspaceId = process.env.HERDR_WORKSPACE_ID;
|
|
1990
|
-
if (workspaceId === void 0 || !WORKSPACE_ID_RE.test(workspaceId)) return void 0;
|
|
1991
|
-
for (const ws of await listWorkspaces()) {
|
|
1992
|
-
if (ws.workspaceId === workspaceId) return ws.label;
|
|
2024
|
+
async function herdr(args) {
|
|
2025
|
+
const result = await currentExecutor2(args);
|
|
2026
|
+
if (result.exitCode === 2) {
|
|
2027
|
+
throw new HerdrSyntaxError([...args], result.stderr || result.stdout);
|
|
1993
2028
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
throw new HerdrCommandOverrideError(
|
|
2002
|
-
opts.cli,
|
|
2003
|
-
opts.command,
|
|
2004
|
-
// Name the env var only when that is actually where the override
|
|
2005
|
-
// came from; "unset MU_PI_COMMAND" is useless advice to someone who
|
|
2006
|
-
// typed --command.
|
|
2007
|
-
opts.commandSource === "env" ? envVarNameForCli(opts.cli) : void 0
|
|
2029
|
+
if (result.exitCode !== 0) {
|
|
2030
|
+
throw new HerdrError(
|
|
2031
|
+
[...args],
|
|
2032
|
+
result.stderr,
|
|
2033
|
+
result.stdout,
|
|
2034
|
+
result.exitCode,
|
|
2035
|
+
parseErrorEnvelope(result.stderr)?.code
|
|
2008
2036
|
);
|
|
2009
2037
|
}
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
throw new HerdrUnsupportedCliError(opts.cli, err.output.trim().split("\n")[0] ?? "");
|
|
2018
|
-
}
|
|
2019
|
-
const busy = err instanceof HerdrError && err.code === "agent_pane_busy";
|
|
2020
|
-
if (!busy || attempt >= PANE_READY_MAX_ATTEMPTS) throw err;
|
|
2021
|
-
await sleep(PANE_READY_RETRY_INTERVAL_MS);
|
|
2022
|
-
}
|
|
2038
|
+
const trimmed = result.stdout.trim();
|
|
2039
|
+
if (trimmed.length === 0) return {};
|
|
2040
|
+
let parsed;
|
|
2041
|
+
try {
|
|
2042
|
+
parsed = JSON.parse(trimmed);
|
|
2043
|
+
} catch {
|
|
2044
|
+
throw new HerdrError([...args], result.stderr, result.stdout, result.exitCode);
|
|
2023
2045
|
}
|
|
2046
|
+
if (!isRecord(parsed)) return {};
|
|
2047
|
+
const inner = parsed.result;
|
|
2048
|
+
return isRecord(inner) ? inner : {};
|
|
2024
2049
|
}
|
|
2025
|
-
function
|
|
2026
|
-
return `MU_${cli.toUpperCase().replace(/-/g, "_")}_COMMAND`;
|
|
2027
|
-
}
|
|
2028
|
-
var PROMPT_STALLED_CODE = "agent_prompt_stalled";
|
|
2029
|
-
var NO_AGENT_CODES = ["agent_not_found", "agent_not_recognized"];
|
|
2030
|
-
function defaultUndeliveredWarning2(warning) {
|
|
2031
|
-
console.warn(`warning: ${warning.message}`);
|
|
2032
|
-
}
|
|
2033
|
-
async function sendToPane2(paneId, text, opts = {}) {
|
|
2034
|
-
assertValidPaneId2(paneId);
|
|
2035
|
-
const wait = opts.readinessMs === 0 ? [] : ["--wait"];
|
|
2050
|
+
async function herdrTolerating(args, tolerate) {
|
|
2036
2051
|
try {
|
|
2037
|
-
await herdr(
|
|
2038
|
-
return;
|
|
2052
|
+
return await herdr(args);
|
|
2039
2053
|
} catch (err) {
|
|
2040
|
-
if (
|
|
2041
|
-
|
|
2042
|
-
const warn2 = opts.onUndelivered ?? defaultUndeliveredWarning2;
|
|
2043
|
-
warn2({
|
|
2044
|
-
paneId,
|
|
2045
|
-
reason: "paste-vanished",
|
|
2046
|
-
message: `send to ${paneId} was submitted but the agent's state never changed (herdr: ${PROMPT_STALLED_CODE}). The agent may NOT have seen it. Read the pane, or re-send.`
|
|
2047
|
-
});
|
|
2048
|
-
return;
|
|
2054
|
+
if (err instanceof HerdrError && err.code !== void 0 && tolerate.includes(err.code)) {
|
|
2055
|
+
return void 0;
|
|
2049
2056
|
}
|
|
2050
|
-
|
|
2057
|
+
throw err;
|
|
2051
2058
|
}
|
|
2052
|
-
await herdr(["pane", "run", paneId, text]);
|
|
2053
2059
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
const
|
|
2057
|
-
if (
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2060
|
+
function readWorkspace(v) {
|
|
2061
|
+
if (!isRecord(v)) return void 0;
|
|
2062
|
+
const workspaceId = asString(v.workspace_id);
|
|
2063
|
+
if (workspaceId === void 0) return void 0;
|
|
2064
|
+
return { workspaceId, label: asString(v.label) ?? "" };
|
|
2065
|
+
}
|
|
2066
|
+
function readTab(v) {
|
|
2067
|
+
if (!isRecord(v)) return void 0;
|
|
2068
|
+
const id = asString(v.tab_id);
|
|
2069
|
+
if (id === void 0) return void 0;
|
|
2070
|
+
return { id, name: asString(v.label) ?? "" };
|
|
2071
|
+
}
|
|
2072
|
+
function readPane(v) {
|
|
2073
|
+
if (!isRecord(v)) return void 0;
|
|
2074
|
+
const paneId = asString(v.pane_id);
|
|
2075
|
+
if (paneId === void 0) return void 0;
|
|
2076
|
+
return {
|
|
2077
|
+
paneId,
|
|
2078
|
+
// herdr's optional `label` is what `pane rename` writes and is
|
|
2079
|
+
// therefore mu's pane-title equivalent.
|
|
2080
|
+
title: asString(v.label) ?? "",
|
|
2081
|
+
// herdr does not report a foreground command on `pane list`; the
|
|
2082
|
+
// authoritative answer costs a `pane process-info` round trip per
|
|
2083
|
+
// pane. Callers that need it use `paneCommand()`.
|
|
2084
|
+
command: "",
|
|
2085
|
+
windowId: asString(v.tab_id)
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
function readArray(result, key) {
|
|
2089
|
+
const v = result[key];
|
|
2090
|
+
return Array.isArray(v) ? v : [];
|
|
2091
|
+
}
|
|
2092
|
+
async function listWorkspaces() {
|
|
2093
|
+
const result = await herdr(["workspace", "list"]);
|
|
2094
|
+
const out = [];
|
|
2095
|
+
for (const raw of readArray(result, "workspaces")) {
|
|
2096
|
+
const ws = readWorkspace(raw);
|
|
2097
|
+
if (ws) out.push(ws);
|
|
2062
2098
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
parseErrorEnvelope(result.stderr)?.code
|
|
2072
|
-
);
|
|
2099
|
+
return out;
|
|
2100
|
+
}
|
|
2101
|
+
async function listSessions2() {
|
|
2102
|
+
return (await listWorkspaces()).map((ws) => ({ name: ws.label }));
|
|
2103
|
+
}
|
|
2104
|
+
async function resolveWorkspaceId(name) {
|
|
2105
|
+
for (const ws of await listWorkspaces()) {
|
|
2106
|
+
if (ws.label === name) return ws.workspaceId;
|
|
2073
2107
|
}
|
|
2074
|
-
return
|
|
2108
|
+
return void 0;
|
|
2075
2109
|
}
|
|
2076
|
-
function
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2110
|
+
async function requireWorkspaceId(name) {
|
|
2111
|
+
const id = await resolveWorkspaceId(name);
|
|
2112
|
+
if (id === void 0) {
|
|
2113
|
+
throw new HerdrError(
|
|
2114
|
+
["workspace", "list"],
|
|
2115
|
+
`{"error":{"code":"workspace_not_found","message":"no herdr workspace labelled ${name}"}}`,
|
|
2116
|
+
"",
|
|
2117
|
+
1,
|
|
2118
|
+
"workspace_not_found"
|
|
2119
|
+
);
|
|
2084
2120
|
}
|
|
2121
|
+
return id;
|
|
2085
2122
|
}
|
|
2086
|
-
async function
|
|
2087
|
-
|
|
2088
|
-
const result = await herdrTolerating(
|
|
2089
|
-
["pane", "get", paneId],
|
|
2090
|
-
["pane_not_found", "workspace_not_found"]
|
|
2091
|
-
);
|
|
2092
|
-
if (result === void 0) return void 0;
|
|
2093
|
-
const pane = result.pane;
|
|
2094
|
-
const status = isRecord(pane) ? asString(pane.agent_status) : void 0;
|
|
2095
|
-
if (status === void 0) return void 0;
|
|
2096
|
-
return mapAgentStatus(status);
|
|
2123
|
+
async function sessionExists2(name) {
|
|
2124
|
+
return await resolveWorkspaceId(name) !== void 0;
|
|
2097
2125
|
}
|
|
2098
|
-
async function
|
|
2099
|
-
|
|
2126
|
+
async function newSession2(name, opts = {}) {
|
|
2127
|
+
rejectCommand("newSession", opts.command);
|
|
2128
|
+
const args = ["workspace", "create", "--label", name, "--no-focus"];
|
|
2129
|
+
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
2130
|
+
appendEnvFlags2(args, opts.env);
|
|
2131
|
+
await herdr(args);
|
|
2100
2132
|
}
|
|
2101
|
-
async function
|
|
2133
|
+
async function newSessionWithPane2(name, opts) {
|
|
2134
|
+
rejectCommand("newSessionWithPane", opts.command);
|
|
2135
|
+
const args = ["workspace", "create", "--label", name, "--no-focus"];
|
|
2136
|
+
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
2137
|
+
appendEnvFlags2(args, opts.env);
|
|
2138
|
+
const result = await herdr(args);
|
|
2139
|
+
return readCreatedPaneId(result, "root_pane", args);
|
|
2102
2140
|
}
|
|
2103
|
-
function
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2141
|
+
async function killSession2(name) {
|
|
2142
|
+
const id = await resolveWorkspaceId(name);
|
|
2143
|
+
if (id === void 0) return;
|
|
2144
|
+
try {
|
|
2145
|
+
await herdrTolerating(["workspace", "close", id], ["workspace_not_found"]);
|
|
2146
|
+
} catch (err) {
|
|
2147
|
+
if (err instanceof HerdrError && err.code === GROUP_CLOSE_REQUIRED_CODE) {
|
|
2148
|
+
throw new HerdrWorkspaceGroupCloseError(name, id, err.message);
|
|
2109
2149
|
}
|
|
2110
|
-
|
|
2150
|
+
throw err;
|
|
2111
2151
|
}
|
|
2112
2152
|
}
|
|
2113
|
-
function
|
|
2114
|
-
if (
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
const pane = readPane(result[key]);
|
|
2119
|
-
if (pane === void 0) {
|
|
2120
|
-
throw new HerdrError(
|
|
2121
|
-
args,
|
|
2122
|
-
`herdr response had no ${key}.pane_id: ${JSON.stringify(result)}`,
|
|
2123
|
-
"",
|
|
2124
|
-
0
|
|
2125
|
-
);
|
|
2153
|
+
async function listWindows2(session) {
|
|
2154
|
+
if (session !== void 0) {
|
|
2155
|
+
const id = await resolveWorkspaceId(session);
|
|
2156
|
+
if (id === void 0) return [];
|
|
2157
|
+
return readTabs(await herdr(["tab", "list", "--workspace", id]));
|
|
2126
2158
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
return
|
|
2159
|
+
const windows = [];
|
|
2160
|
+
for (const ws of await listWorkspaces()) {
|
|
2161
|
+
for (const tab of readTabs(await herdr(["tab", "list", "--workspace", ws.workspaceId]))) {
|
|
2162
|
+
windows.push({ ...tab, sessionName: ws.label });
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
return windows;
|
|
2134
2166
|
}
|
|
2135
|
-
|
|
2136
|
-
const
|
|
2137
|
-
|
|
2138
|
-
|
|
2167
|
+
function readTabs(result) {
|
|
2168
|
+
const out = [];
|
|
2169
|
+
for (const raw of readArray(result, "tabs")) {
|
|
2170
|
+
const tab = readTab(raw);
|
|
2171
|
+
if (tab) out.push(tab);
|
|
2172
|
+
}
|
|
2173
|
+
return out;
|
|
2139
2174
|
}
|
|
2140
|
-
function
|
|
2141
|
-
|
|
2142
|
-
|
|
2175
|
+
async function newWindow2(opts) {
|
|
2176
|
+
rejectCommand("newWindow", opts.command);
|
|
2177
|
+
const args = ["tab", "create"];
|
|
2178
|
+
if (opts.session !== void 0) {
|
|
2179
|
+
args.push("--workspace", await requireWorkspaceId(opts.session));
|
|
2143
2180
|
}
|
|
2144
|
-
|
|
2181
|
+
args.push("--label", opts.name, "--no-focus");
|
|
2182
|
+
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
2183
|
+
appendEnvFlags2(args, opts.env);
|
|
2184
|
+
return readCreatedPaneId(await herdr(args), "root_pane", args);
|
|
2145
2185
|
}
|
|
2146
|
-
function
|
|
2147
|
-
return [{ command: "herdr", args: ["session", "attach", target.session] }];
|
|
2186
|
+
async function selectLayout2(_window, _layout) {
|
|
2148
2187
|
}
|
|
2149
|
-
function
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
}
|
|
2159
|
-
];
|
|
2188
|
+
async function listPanesInSession2(session) {
|
|
2189
|
+
const id = await resolveWorkspaceId(session);
|
|
2190
|
+
if (id === void 0) return [];
|
|
2191
|
+
const result = await herdrTolerating(
|
|
2192
|
+
["pane", "list", "--workspace", id],
|
|
2193
|
+
["workspace_not_found"]
|
|
2194
|
+
);
|
|
2195
|
+
if (result === void 0) return [];
|
|
2196
|
+
return readPanes(result).map((p) => ({ ...p, sessionName: session }));
|
|
2160
2197
|
}
|
|
2161
|
-
async function
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
version = match?.[1] ?? null;
|
|
2198
|
+
async function listPanes2(target) {
|
|
2199
|
+
if (target !== void 0 && target !== "*") {
|
|
2200
|
+
if (TAB_ID_RE.test(target)) {
|
|
2201
|
+
const workspaceId = target.split(":")[0] ?? target;
|
|
2202
|
+
const panes2 = readPanes(await herdr(["pane", "list", "--workspace", workspaceId]));
|
|
2203
|
+
return panes2.filter((p) => p.windowId === target);
|
|
2168
2204
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2205
|
+
if (WORKSPACE_ID_RE.test(target)) {
|
|
2206
|
+
return readPanes(await herdr(["pane", "list", "--workspace", target]));
|
|
2207
|
+
}
|
|
2208
|
+
return listPanesInSession2(target);
|
|
2171
2209
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
{
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2210
|
+
const ambient = process.env.HERDR_WORKSPACE_ID;
|
|
2211
|
+
if (target === void 0 && ambient !== void 0 && WORKSPACE_ID_RE.test(ambient)) {
|
|
2212
|
+
return readPanes(await herdr(["pane", "list", "--workspace", ambient]));
|
|
2213
|
+
}
|
|
2214
|
+
const panes = [];
|
|
2215
|
+
for (const ws of await listWorkspaces()) {
|
|
2216
|
+
for (const pane of readPanes(await herdr(["pane", "list", "--workspace", ws.workspaceId]))) {
|
|
2217
|
+
panes.push({ ...pane, sessionName: ws.label });
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
return panes;
|
|
2183
2221
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
listSessions: listSessions2,
|
|
2190
|
-
sessionExists: sessionExists2,
|
|
2191
|
-
newSession: newSession2,
|
|
2192
|
-
newSessionWithPane: newSessionWithPane2,
|
|
2193
|
-
killSession: killSession2,
|
|
2194
|
-
listWindows: listWindows2,
|
|
2195
|
-
newWindow: newWindow2,
|
|
2196
|
-
selectLayout: selectLayout2,
|
|
2197
|
-
listPanes: listPanes2,
|
|
2198
|
-
listPanesInSession: listPanesInSession2,
|
|
2199
|
-
splitWindow: splitWindow2,
|
|
2200
|
-
killPane: killPane2,
|
|
2201
|
-
paneExists: paneExists2,
|
|
2202
|
-
paneTTY: paneTTY2,
|
|
2203
|
-
setPaneTitle: setPaneTitle2,
|
|
2204
|
-
getPaneTitle: getPaneTitle2,
|
|
2205
|
-
currentAgentName: currentAgentName2,
|
|
2206
|
-
currentSessionName: currentSessionName2,
|
|
2207
|
-
startAgentInPane,
|
|
2208
|
-
sendToPane: sendToPane2,
|
|
2209
|
-
capturePane: capturePane2,
|
|
2210
|
-
paneStatus,
|
|
2211
|
-
enableMuPaneBordersForSession: enableMuPaneBordersForSession2,
|
|
2212
|
-
enableMuPaneBordersForPane: enableMuPaneBordersForPane2,
|
|
2213
|
-
attachHint: attachHint2,
|
|
2214
|
-
attachCommands: attachCommands2,
|
|
2215
|
-
paneNotFoundNextSteps: paneNotFoundNextSteps2,
|
|
2216
|
-
healthCheck: healthCheck2
|
|
2217
|
-
});
|
|
2218
|
-
|
|
2219
|
-
// src/mux/detect.ts
|
|
2220
|
-
var BACKENDS = [tmuxBackend, herdrBackend];
|
|
2221
|
-
function muxByName(name) {
|
|
2222
|
-
for (const backend of BACKENDS) {
|
|
2223
|
-
if (backend.name === name) return backend;
|
|
2222
|
+
function readPanes(result) {
|
|
2223
|
+
const out = [];
|
|
2224
|
+
for (const raw of readArray(result, "panes")) {
|
|
2225
|
+
const pane = readPane(raw);
|
|
2226
|
+
if (pane) out.push(pane);
|
|
2224
2227
|
}
|
|
2225
|
-
|
|
2228
|
+
return out;
|
|
2229
|
+
}
|
|
2230
|
+
async function splitWindow2(opts) {
|
|
2231
|
+
rejectCommand("splitWindow", opts.command);
|
|
2232
|
+
assertValidPaneId2(opts.target);
|
|
2233
|
+
const args = [
|
|
2234
|
+
"pane",
|
|
2235
|
+
"split",
|
|
2236
|
+
opts.target,
|
|
2237
|
+
"--direction",
|
|
2238
|
+
opts.horizontal === false ? "down" : "right",
|
|
2239
|
+
"--no-focus"
|
|
2240
|
+
];
|
|
2241
|
+
if (opts.cwd) args.push("--cwd", opts.cwd);
|
|
2242
|
+
appendEnvFlags2(args, opts.env);
|
|
2243
|
+
return readCreatedPaneId(await herdr(args), "pane", args);
|
|
2244
|
+
}
|
|
2245
|
+
async function killPane2(paneId) {
|
|
2246
|
+
assertValidPaneId2(paneId);
|
|
2247
|
+
await herdrTolerating(["pane", "close", paneId], ["pane_not_found", "workspace_not_found"]);
|
|
2226
2248
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
const
|
|
2230
|
-
|
|
2231
|
-
return muxByName(override);
|
|
2232
|
-
}
|
|
2233
|
-
if (process.env.HERDR_ENV === "1") return herdrBackend;
|
|
2234
|
-
if (process.env.TMUX || process.env.TMUX_PANE) return tmuxBackend;
|
|
2235
|
-
for (const backend of BACKENDS) {
|
|
2236
|
-
if (await backend.available()) return backend;
|
|
2237
|
-
}
|
|
2238
|
-
throw new NoMultiplexerError(KNOWN_NAMES);
|
|
2249
|
+
async function paneExists2(paneId) {
|
|
2250
|
+
if (!isValidPaneId2(paneId)) return false;
|
|
2251
|
+
const pane = await getPane(paneId);
|
|
2252
|
+
return pane !== void 0;
|
|
2239
2253
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2254
|
+
async function getPane(paneId) {
|
|
2255
|
+
const result = await herdrTolerating(
|
|
2256
|
+
["pane", "get", paneId],
|
|
2257
|
+
["pane_not_found", "workspace_not_found"]
|
|
2258
|
+
);
|
|
2259
|
+
if (result === void 0) return void 0;
|
|
2260
|
+
return readPane(result.pane);
|
|
2244
2261
|
}
|
|
2245
|
-
function
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2262
|
+
async function paneTTY2(paneId) {
|
|
2263
|
+
assertValidPaneId2(paneId);
|
|
2264
|
+
const result = await herdrTolerating(
|
|
2265
|
+
["pane", "process-info", "--pane", paneId],
|
|
2266
|
+
["pane_not_found", "workspace_not_found"]
|
|
2267
|
+
);
|
|
2268
|
+
if (result === void 0) throw new PaneNotFoundError(paneId);
|
|
2269
|
+
const info = result.process_info;
|
|
2270
|
+
const pid = isRecord(info) ? info.shell_pid : void 0;
|
|
2271
|
+
if (typeof pid !== "number") throw new PaneNotFoundError(paneId);
|
|
2272
|
+
const { readlink } = await import("fs/promises");
|
|
2273
|
+
try {
|
|
2274
|
+
return await readlink(`/proc/${pid}/fd/0`);
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
throw new HerdrError(
|
|
2277
|
+
["pane", "process-info", "--pane", paneId],
|
|
2278
|
+
`cannot resolve the TTY for herdr pane ${paneId} (shell pid ${pid}) via /proc: ${String(err)}`,
|
|
2279
|
+
"",
|
|
2280
|
+
1
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2249
2283
|
}
|
|
2250
|
-
function
|
|
2251
|
-
|
|
2284
|
+
async function setPaneTitle2(paneId, title) {
|
|
2285
|
+
assertValidPaneId2(paneId);
|
|
2286
|
+
await herdr(["pane", "rename", paneId, title]);
|
|
2252
2287
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
import { setTimeout as delay } from "timers/promises";
|
|
2259
|
-
import { promisify as promisify2 } from "util";
|
|
2260
|
-
|
|
2261
|
-
// src/agents.ts
|
|
2262
|
-
import { rmSync as rmSync3 } from "fs";
|
|
2263
|
-
import { dirname as dirname4 } from "path";
|
|
2264
|
-
|
|
2265
|
-
// src/glyphs.ts
|
|
2266
|
-
var AGENT_STATE_GLYPH = {
|
|
2267
|
-
busy: "\uF04B",
|
|
2268
|
-
// nf-fa-play
|
|
2269
|
-
needs_input: "\uF186",
|
|
2270
|
-
// nf-fa-moon_o
|
|
2271
|
-
needs_permission: "\uF023",
|
|
2272
|
-
// nf-fa-lock
|
|
2273
|
-
unknown: "\uF059"
|
|
2274
|
-
// nf-fa-question_circle
|
|
2275
|
-
};
|
|
2276
|
-
function agentStateGlyph(state) {
|
|
2277
|
-
return AGENT_STATE_GLYPH[state] ?? GLYPH.unknown;
|
|
2288
|
+
async function getPaneTitle2(paneId) {
|
|
2289
|
+
if (!isValidPaneId2(paneId)) return void 0;
|
|
2290
|
+
const pane = await getPane(paneId).catch(() => void 0);
|
|
2291
|
+
if (pane === void 0) return void 0;
|
|
2292
|
+
return pane.title.length > 0 ? pane.title : void 0;
|
|
2278
2293
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
/** Workspace is ≥ WORKSPACE_STALE_THRESHOLD commits behind main. */
|
|
2293
|
-
stale: "\uF017",
|
|
2294
|
-
// nf-fa-clock_o
|
|
2295
|
-
/** Task is blocked by an incoming edge. */
|
|
2296
|
-
blocked: "\uF0C1",
|
|
2297
|
-
// nf-fa-link (chain)
|
|
2298
|
-
/** Task is parked (OPEN/parked): open, but set aside on purpose. */
|
|
2299
|
-
parked: "\uF04C",
|
|
2300
|
-
// nf-fa-pause
|
|
2301
|
-
/** Track whose roots merged (diamond dependency). */
|
|
2302
|
-
merge: "\uF074",
|
|
2303
|
-
// nf-fa-random
|
|
2304
|
-
/** Agent owns more than one task (pane-title multi-count slot). */
|
|
2305
|
-
multi: "\uF055",
|
|
2306
|
-
// nf-fa-plus_circle
|
|
2307
|
-
/** Filter toggle: enabled / disabled. */
|
|
2308
|
-
on: "\uF111",
|
|
2309
|
-
// nf-fa-circle
|
|
2310
|
-
off: "\uF10C",
|
|
2311
|
-
// nf-fa-circle_o
|
|
2312
|
-
/** Status outside the known enum. */
|
|
2313
|
-
unknown: "\uF059"
|
|
2314
|
-
// nf-fa-question_circle
|
|
2315
|
-
};
|
|
2316
|
-
|
|
2317
|
-
// src/logs.ts
|
|
2318
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
2319
|
-
|
|
2320
|
-
// src/hlc.ts
|
|
2321
|
-
var WALL_WIDTH = 15;
|
|
2322
|
-
var COUNTER_WIDTH = 6;
|
|
2323
|
-
var SEP = ".";
|
|
2324
|
-
var MAX_WALL = 10 ** WALL_WIDTH - 1;
|
|
2325
|
-
var MAX_COUNTER = 10 ** COUNTER_WIDTH - 1;
|
|
2326
|
-
var HlcParseError = class extends Error {
|
|
2327
|
-
constructor(value) {
|
|
2328
|
-
super(`Malformed HLC: ${JSON.stringify(value)}`);
|
|
2329
|
-
this.value = value;
|
|
2330
|
-
this.name = "HlcParseError";
|
|
2294
|
+
async function currentAgentName2() {
|
|
2295
|
+
const paneId = process.env.HERDR_PANE_ID;
|
|
2296
|
+
if (paneId === void 0 || !isValidPaneId2(paneId)) return void 0;
|
|
2297
|
+
const title = await getPaneTitle2(paneId);
|
|
2298
|
+
if (title === void 0) return void 0;
|
|
2299
|
+
const idx = title.indexOf(" \xB7 ");
|
|
2300
|
+
return idx === -1 ? title.trim() : title.slice(0, idx).trim();
|
|
2301
|
+
}
|
|
2302
|
+
async function currentSessionName2() {
|
|
2303
|
+
const workspaceId = process.env.HERDR_WORKSPACE_ID;
|
|
2304
|
+
if (workspaceId === void 0 || !WORKSPACE_ID_RE.test(workspaceId)) return void 0;
|
|
2305
|
+
for (const ws of await listWorkspaces()) {
|
|
2306
|
+
if (ws.workspaceId === workspaceId) return ws.label;
|
|
2331
2307
|
}
|
|
2332
|
-
|
|
2333
|
-
}
|
|
2334
|
-
var
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2308
|
+
return void 0;
|
|
2309
|
+
}
|
|
2310
|
+
var PANE_READY_RETRY_INTERVAL_MS = 50;
|
|
2311
|
+
var PANE_READY_MAX_ATTEMPTS = 60;
|
|
2312
|
+
async function startAgentInPane(opts) {
|
|
2313
|
+
assertValidPaneId2(opts.paneId);
|
|
2314
|
+
if (opts.commandSource !== "cli-key") {
|
|
2315
|
+
throw new HerdrCommandOverrideError(
|
|
2316
|
+
opts.cli,
|
|
2317
|
+
opts.command,
|
|
2318
|
+
// Name the env var only when that is actually where the override
|
|
2319
|
+
// came from; "unset MU_PI_COMMAND" is useless advice to someone who
|
|
2320
|
+
// typed --command.
|
|
2321
|
+
opts.commandSource === "env" ? envVarNameForCli(opts.cli) : void 0
|
|
2338
2322
|
);
|
|
2339
|
-
this.field = field;
|
|
2340
|
-
this.value = value;
|
|
2341
|
-
this.name = "HlcOverflowError";
|
|
2342
2323
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2324
|
+
const args = ["agent", "start", opts.name, "--kind", opts.cli, "--pane", opts.paneId];
|
|
2325
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
2326
|
+
try {
|
|
2327
|
+
await herdr(args);
|
|
2328
|
+
return;
|
|
2329
|
+
} catch (err) {
|
|
2330
|
+
if (err instanceof HerdrSyntaxError) {
|
|
2331
|
+
throw new HerdrUnsupportedCliError(opts.cli, err.output.trim().split("\n")[0] ?? "");
|
|
2332
|
+
}
|
|
2333
|
+
const busy = err instanceof HerdrError && err.code === "agent_pane_busy";
|
|
2334
|
+
if (!busy || attempt >= PANE_READY_MAX_ATTEMPTS) throw err;
|
|
2335
|
+
await sleep(PANE_READY_RETRY_INTERVAL_MS);
|
|
2336
|
+
}
|
|
2350
2337
|
}
|
|
2351
|
-
};
|
|
2352
|
-
function formatHlc(hlc) {
|
|
2353
|
-
const { wallMs, counter, machineId: machineId2 } = hlc;
|
|
2354
|
-
if (!Number.isInteger(wallMs) || wallMs < 0) throw new HlcParseError(String(wallMs));
|
|
2355
|
-
if (!Number.isInteger(counter) || counter < 0) throw new HlcParseError(String(counter));
|
|
2356
|
-
if (wallMs > MAX_WALL) throw new HlcOverflowError("wall", wallMs);
|
|
2357
|
-
if (counter > MAX_COUNTER) throw new HlcOverflowError("counter", counter);
|
|
2358
|
-
if (machineId2.length === 0 || machineId2.includes(SEP)) throw new HlcParseError(machineId2);
|
|
2359
|
-
return `${String(wallMs).padStart(WALL_WIDTH, "0")}${SEP}${String(counter).padStart(
|
|
2360
|
-
COUNTER_WIDTH,
|
|
2361
|
-
"0"
|
|
2362
|
-
)}${SEP}${machineId2}`;
|
|
2363
2338
|
}
|
|
2364
|
-
function
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2339
|
+
function envVarNameForCli(cli) {
|
|
2340
|
+
return `MU_${cli.toUpperCase().replace(/-/g, "_")}_COMMAND`;
|
|
2341
|
+
}
|
|
2342
|
+
var PROMPT_STALLED_CODE = "agent_prompt_stalled";
|
|
2343
|
+
var NO_AGENT_CODES = ["agent_not_found", "agent_not_recognized"];
|
|
2344
|
+
function defaultUndeliveredWarning2(warning) {
|
|
2345
|
+
console.warn(`warning: ${warning.message}`);
|
|
2346
|
+
}
|
|
2347
|
+
async function sendToPane2(paneId, text, opts = {}) {
|
|
2348
|
+
assertValidPaneId2(paneId);
|
|
2349
|
+
const wait = opts.readinessMs === 0 ? [] : ["--wait"];
|
|
2350
|
+
try {
|
|
2351
|
+
await herdr(["agent", "prompt", paneId, text, ...wait]);
|
|
2352
|
+
return;
|
|
2353
|
+
} catch (err) {
|
|
2354
|
+
if (!(err instanceof HerdrError) || err.code === void 0) throw err;
|
|
2355
|
+
if (err.code === PROMPT_STALLED_CODE) {
|
|
2356
|
+
const warn2 = opts.onUndelivered ?? defaultUndeliveredWarning2;
|
|
2357
|
+
warn2({
|
|
2358
|
+
paneId,
|
|
2359
|
+
reason: "paste-vanished",
|
|
2360
|
+
message: `send to ${paneId} was submitted but the agent's state never changed (herdr: ${PROMPT_STALLED_CODE}). The agent may NOT have seen it. Read the pane, or re-send.`
|
|
2361
|
+
});
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
if (!NO_AGENT_CODES.includes(err.code)) throw err;
|
|
2365
|
+
}
|
|
2366
|
+
await herdr(["pane", "run", paneId, text]);
|
|
2367
|
+
}
|
|
2368
|
+
async function capturePane2(paneId, opts = {}) {
|
|
2369
|
+
assertValidPaneId2(paneId);
|
|
2370
|
+
const args = ["pane", "read", paneId];
|
|
2371
|
+
if (opts.lines === 0) {
|
|
2372
|
+
args.push("--source", "visible");
|
|
2373
|
+
} else {
|
|
2374
|
+
args.push("--source", "recent-unwrapped");
|
|
2375
|
+
if (opts.lines !== void 0) args.push("--lines", String(opts.lines));
|
|
2376
|
+
}
|
|
2377
|
+
const result = await currentExecutor2(args);
|
|
2378
|
+
if (result.exitCode === 2) throw new HerdrSyntaxError(args, result.stderr || result.stdout);
|
|
2379
|
+
if (result.exitCode !== 0) {
|
|
2380
|
+
throw new HerdrError(
|
|
2381
|
+
args,
|
|
2382
|
+
result.stderr,
|
|
2383
|
+
result.stdout,
|
|
2384
|
+
result.exitCode,
|
|
2385
|
+
parseErrorEnvelope(result.stderr)?.code
|
|
2386
|
+
);
|
|
2370
2387
|
}
|
|
2371
|
-
|
|
2372
|
-
|
|
2388
|
+
return result.stdout;
|
|
2389
|
+
}
|
|
2390
|
+
function mapAgentStatus(agentStatus) {
|
|
2391
|
+
switch (agentStatus) {
|
|
2392
|
+
case "working":
|
|
2393
|
+
return "busy";
|
|
2394
|
+
case "blocked":
|
|
2395
|
+
return "needs_permission";
|
|
2396
|
+
default:
|
|
2397
|
+
return "needs_input";
|
|
2373
2398
|
}
|
|
2374
|
-
if (!/^\d+$/.test(wall) || !/^\d+$/.test(counter)) throw new HlcParseError(value);
|
|
2375
|
-
return { wallMs: Number(wall), counter: Number(counter), machineId: machineId2 };
|
|
2376
2399
|
}
|
|
2377
|
-
function
|
|
2378
|
-
if (
|
|
2379
|
-
|
|
2400
|
+
async function paneStatus(paneId) {
|
|
2401
|
+
if (!isValidPaneId2(paneId)) return void 0;
|
|
2402
|
+
const result = await herdrTolerating(
|
|
2403
|
+
["pane", "get", paneId],
|
|
2404
|
+
["pane_not_found", "workspace_not_found"]
|
|
2405
|
+
);
|
|
2406
|
+
if (result === void 0) return void 0;
|
|
2407
|
+
const pane = result.pane;
|
|
2408
|
+
const status = isRecord(pane) ? asString(pane.agent_status) : void 0;
|
|
2409
|
+
if (status === void 0) return void 0;
|
|
2410
|
+
return mapAgentStatus(status);
|
|
2411
|
+
}
|
|
2412
|
+
async function enableMuPaneBordersForSession2(_session) {
|
|
2380
2413
|
return 0;
|
|
2381
2414
|
}
|
|
2382
|
-
function
|
|
2383
|
-
const row = db.prepare(
|
|
2384
|
-
`UPDATE machine_identity
|
|
2385
|
-
SET last_wall = MAX(last_wall, @now),
|
|
2386
|
-
last_counter = CASE WHEN @now > last_wall THEN 0 ELSE last_counter + 1 END
|
|
2387
|
-
WHERE id = 1
|
|
2388
|
-
RETURNING machine_id, last_wall, last_counter`
|
|
2389
|
-
).get({ now: Math.floor(now) });
|
|
2390
|
-
if (!row) throw new MachineIdentityMissingError();
|
|
2391
|
-
return formatHlc({ wallMs: row.last_wall, counter: row.last_counter, machineId: row.machine_id });
|
|
2415
|
+
async function enableMuPaneBordersForPane2(_paneId) {
|
|
2392
2416
|
}
|
|
2393
|
-
function
|
|
2394
|
-
|
|
2395
|
-
const
|
|
2396
|
-
|
|
2397
|
-
"
|
|
2398
|
-
|
|
2399
|
-
const write = db.prepare(
|
|
2400
|
-
"UPDATE machine_identity SET last_wall = @wall, last_counter = @counter WHERE id = 1"
|
|
2401
|
-
);
|
|
2402
|
-
const advance = db.transaction(() => {
|
|
2403
|
-
const row = read.get();
|
|
2404
|
-
if (!row) throw new MachineIdentityMissingError();
|
|
2405
|
-
const wall = Math.max(row.last_wall, remote.wallMs, wallNow);
|
|
2406
|
-
const localAtMax = row.last_wall === wall;
|
|
2407
|
-
const remoteAtMax = remote.wallMs === wall;
|
|
2408
|
-
let counter;
|
|
2409
|
-
if (!localAtMax && !remoteAtMax) {
|
|
2410
|
-
counter = 0;
|
|
2411
|
-
} else if (localAtMax && remoteAtMax) {
|
|
2412
|
-
counter = Math.max(row.last_counter, remote.counter) + 1;
|
|
2413
|
-
} else if (localAtMax) {
|
|
2414
|
-
counter = row.last_counter + 1;
|
|
2415
|
-
} else {
|
|
2416
|
-
counter = remote.counter + 1;
|
|
2417
|
+
function appendEnvFlags2(args, env) {
|
|
2418
|
+
if (!env) return;
|
|
2419
|
+
for (const [k, v] of Object.entries(env)) {
|
|
2420
|
+
if (k.length === 0) throw new TypeError("herdr env key must be non-empty");
|
|
2421
|
+
if (k.includes("=")) {
|
|
2422
|
+
throw new TypeError(`herdr env key must not contain '=': ${JSON.stringify(k)}`);
|
|
2417
2423
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
return formatHlc({ wallMs: wall, counter, machineId: row.machine_id });
|
|
2421
|
-
});
|
|
2422
|
-
return advance.immediate();
|
|
2424
|
+
args.push("--env", `${k}=${v}`);
|
|
2425
|
+
}
|
|
2423
2426
|
}
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
function isLegacyLogOnlyIntent(intent) {
|
|
2428
|
-
return intent !== null && LEGACY_LOG_ONLY_INTENTS.has(intent);
|
|
2427
|
+
function rejectCommand(method, command) {
|
|
2428
|
+
if (command === void 0 || command.trim().length === 0) return;
|
|
2429
|
+
throw new HerdrNotImplementedError(`${method} with a command`, "mux-herdr-spawn");
|
|
2429
2430
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2431
|
+
function readCreatedPaneId(result, key, args) {
|
|
2432
|
+
const pane = readPane(result[key]);
|
|
2433
|
+
if (pane === void 0) {
|
|
2434
|
+
throw new HerdrError(
|
|
2435
|
+
args,
|
|
2436
|
+
`herdr response had no ${key}.pane_id: ${JSON.stringify(result)}`,
|
|
2437
|
+
"",
|
|
2438
|
+
0
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
assertValidPaneId2(pane.paneId);
|
|
2442
|
+
return pane.paneId;
|
|
2436
2443
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
// src/logs.ts
|
|
2442
|
-
var SELECT_LOG_COLS = `
|
|
2443
|
-
l.seq AS seq,
|
|
2444
|
-
l.key AS workstream,
|
|
2445
|
-
l.actor AS source,
|
|
2446
|
-
l.intent AS intent,
|
|
2447
|
-
l.group_id AS group_id,
|
|
2448
|
-
l.op AS op,
|
|
2449
|
-
l.entity AS kind,
|
|
2450
|
-
l.payload AS payload,
|
|
2451
|
-
l.created_at AS created_at
|
|
2452
|
-
`;
|
|
2453
|
-
var LOG_FROM_JOIN = "FROM ops l";
|
|
2454
|
-
function workstreamScopeSql(column = "l.key") {
|
|
2455
|
-
return `(${column} = ? OR ${column} LIKE ? ESCAPE '\\')`;
|
|
2444
|
+
function isHerdrStatusUsable(stdout) {
|
|
2445
|
+
if (!/^\s*status:\s*running\s*$/m.test(stdout)) return false;
|
|
2446
|
+
if (/^\s*endpoint_compatible:\s*no\s*$/m.test(stdout)) return false;
|
|
2447
|
+
return !/^\s*compatible:\s*no\s*$/m.test(stdout);
|
|
2456
2448
|
}
|
|
2457
|
-
function
|
|
2458
|
-
const
|
|
2459
|
-
return
|
|
2449
|
+
async function herdrAvailable() {
|
|
2450
|
+
const result = await currentExecutor2(["status"]).catch(() => void 0);
|
|
2451
|
+
if (result === void 0 || result.exitCode !== 0) return false;
|
|
2452
|
+
return isHerdrStatusUsable(result.stdout);
|
|
2460
2453
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
AND l.payload LIKE '{"updated_at":%'
|
|
2465
|
-
AND LENGTH(l.payload) < 45
|
|
2466
|
-
)`;
|
|
2467
|
-
var MACHINE_WIDE_KEY = "";
|
|
2468
|
-
var GroupIdAmbiguousError = class extends Error {
|
|
2469
|
-
constructor(prefix, candidates) {
|
|
2470
|
-
super(
|
|
2471
|
-
`group id ${JSON.stringify(prefix)} is ambiguous: matches ${candidates.length} groups (${candidates.slice(0, 4).map((c) => c.slice(0, 12)).join(", ")}${candidates.length > 4 ? ", \u2026" : ""})`
|
|
2472
|
-
);
|
|
2473
|
-
this.prefix = prefix;
|
|
2474
|
-
this.candidates = candidates;
|
|
2475
|
-
this.name = "GroupIdAmbiguousError";
|
|
2476
|
-
}
|
|
2477
|
-
prefix;
|
|
2478
|
-
candidates;
|
|
2479
|
-
errorNextSteps() {
|
|
2480
|
-
return [
|
|
2481
|
-
{ intent: "List recent groups with their ids", command: "mu undo" },
|
|
2482
|
-
{
|
|
2483
|
-
intent: "Retry with more characters",
|
|
2484
|
-
command: `mu log --group ${this.candidates[0] ?? "<full-id>"}`
|
|
2485
|
-
}
|
|
2486
|
-
];
|
|
2454
|
+
function attachHint2(target) {
|
|
2455
|
+
if (target.window !== void 0) {
|
|
2456
|
+
return `herdr session attach ${target.session} # then focus tab ${target.window}`;
|
|
2487
2457
|
}
|
|
2488
|
-
}
|
|
2489
|
-
function groupIdFromPrefix(db, prefix) {
|
|
2490
|
-
if (prefix === "") return null;
|
|
2491
|
-
const rows = db.prepare(
|
|
2492
|
-
`SELECT DISTINCT group_id AS groupId FROM ops
|
|
2493
|
-
WHERE group_id = @exact OR group_id LIKE @prefix || '%' ESCAPE '\\'`
|
|
2494
|
-
).all({ exact: prefix, prefix: prefix.replace(/[\\%_]/g, (c) => `\\${c}`) });
|
|
2495
|
-
const exact = rows.find((r) => r.groupId === prefix);
|
|
2496
|
-
if (exact !== void 0) return exact.groupId;
|
|
2497
|
-
const first = rows[0];
|
|
2498
|
-
if (rows.length === 1 && first !== void 0) return first.groupId;
|
|
2499
|
-
if (rows.length === 0) return null;
|
|
2500
|
-
throw new GroupIdAmbiguousError(
|
|
2501
|
-
prefix,
|
|
2502
|
-
rows.map((r) => r.groupId)
|
|
2503
|
-
);
|
|
2458
|
+
return `herdr session attach ${target.session}`;
|
|
2504
2459
|
}
|
|
2505
|
-
function
|
|
2506
|
-
|
|
2507
|
-
return row?.machine_id ?? "unknown";
|
|
2460
|
+
function attachCommands2(target) {
|
|
2461
|
+
return [{ command: "herdr", args: ["session", "attach", target.session] }];
|
|
2508
2462
|
}
|
|
2509
|
-
function
|
|
2510
|
-
return
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
};
|
|
2463
|
+
function paneNotFoundNextSteps2(paneId) {
|
|
2464
|
+
return [
|
|
2465
|
+
{
|
|
2466
|
+
intent: `Verify the pane id ${paneId} actually exists`,
|
|
2467
|
+
command: `herdr pane get ${paneId}`
|
|
2468
|
+
},
|
|
2469
|
+
{
|
|
2470
|
+
intent: "List panes in the current workspace",
|
|
2471
|
+
command: 'herdr pane list --workspace "$HERDR_WORKSPACE_ID"'
|
|
2472
|
+
}
|
|
2473
|
+
];
|
|
2521
2474
|
}
|
|
2522
|
-
function
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
group,
|
|
2534
|
-
opts.source,
|
|
2535
|
-
opts.intent ?? null,
|
|
2536
|
-
kind,
|
|
2537
|
-
key,
|
|
2538
|
-
opts.payload,
|
|
2539
|
-
createdAt
|
|
2540
|
-
);
|
|
2475
|
+
async function healthCheck2() {
|
|
2476
|
+
let version = null;
|
|
2477
|
+
try {
|
|
2478
|
+
const result = await currentExecutor2(["status"]);
|
|
2479
|
+
if (result.exitCode === 0) {
|
|
2480
|
+
const match = /^\s*version:\s*(\S+)\s*$/m.exec(result.stdout);
|
|
2481
|
+
version = match?.[1] ?? null;
|
|
2482
|
+
}
|
|
2483
|
+
} catch {
|
|
2484
|
+
version = null;
|
|
2485
|
+
}
|
|
2541
2486
|
return {
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2487
|
+
name: "herdr",
|
|
2488
|
+
ok: version !== null,
|
|
2489
|
+
version,
|
|
2490
|
+
env: [
|
|
2491
|
+
{ name: "$HERDR_ENV", value: process.env.HERDR_ENV ?? null },
|
|
2492
|
+
{ name: "$HERDR_WORKSPACE_ID", value: process.env.HERDR_WORKSPACE_ID ?? null },
|
|
2493
|
+
{ name: "$HERDR_PANE_ID", value: process.env.HERDR_PANE_ID ?? null }
|
|
2494
|
+
],
|
|
2495
|
+
remediation: "start the herdr server (herdr status) or install herdr"
|
|
2551
2496
|
};
|
|
2552
2497
|
}
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2498
|
+
var herdrBackend = Object.freeze({
|
|
2499
|
+
name: "herdr",
|
|
2500
|
+
available: herdrAvailable,
|
|
2501
|
+
isValidPaneId: isValidPaneId2,
|
|
2502
|
+
assertValidPaneId: assertValidPaneId2,
|
|
2503
|
+
listSessions: listSessions2,
|
|
2504
|
+
sessionExists: sessionExists2,
|
|
2505
|
+
newSession: newSession2,
|
|
2506
|
+
newSessionWithPane: newSessionWithPane2,
|
|
2507
|
+
killSession: killSession2,
|
|
2508
|
+
listWindows: listWindows2,
|
|
2509
|
+
newWindow: newWindow2,
|
|
2510
|
+
selectLayout: selectLayout2,
|
|
2511
|
+
listPanes: listPanes2,
|
|
2512
|
+
listPanesInSession: listPanesInSession2,
|
|
2513
|
+
splitWindow: splitWindow2,
|
|
2514
|
+
killPane: killPane2,
|
|
2515
|
+
paneExists: paneExists2,
|
|
2516
|
+
paneTTY: paneTTY2,
|
|
2517
|
+
setPaneTitle: setPaneTitle2,
|
|
2518
|
+
getPaneTitle: getPaneTitle2,
|
|
2519
|
+
currentAgentName: currentAgentName2,
|
|
2520
|
+
currentSessionName: currentSessionName2,
|
|
2521
|
+
startAgentInPane,
|
|
2522
|
+
sendToPane: sendToPane2,
|
|
2523
|
+
capturePane: capturePane2,
|
|
2524
|
+
paneStatus,
|
|
2525
|
+
enableMuPaneBordersForSession: enableMuPaneBordersForSession2,
|
|
2526
|
+
enableMuPaneBordersForPane: enableMuPaneBordersForPane2,
|
|
2527
|
+
attachHint: attachHint2,
|
|
2528
|
+
attachCommands: attachCommands2,
|
|
2529
|
+
paneNotFoundNextSteps: paneNotFoundNextSteps2,
|
|
2530
|
+
healthCheck: healthCheck2
|
|
2531
|
+
});
|
|
2532
|
+
|
|
2533
|
+
// src/mux/detect.ts
|
|
2534
|
+
var BACKENDS = [tmuxBackend, herdrBackend];
|
|
2535
|
+
function muxByName(name) {
|
|
2536
|
+
for (const backend of BACKENDS) {
|
|
2537
|
+
if (backend.name === name) return backend;
|
|
2583
2538
|
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2539
|
+
throw new Error(`unknown mux backend: ${name}`);
|
|
2540
|
+
}
|
|
2541
|
+
var KNOWN_NAMES = BACKENDS.map((b) => b.name);
|
|
2542
|
+
async function detectMux() {
|
|
2543
|
+
const override = process.env.MU_MUX;
|
|
2544
|
+
if (override !== void 0 && override.length > 0) {
|
|
2545
|
+
return muxByName(override);
|
|
2589
2546
|
}
|
|
2590
|
-
|
|
2591
|
-
if (
|
|
2592
|
-
|
|
2593
|
-
|
|
2547
|
+
if (process.env.HERDR_ENV === "1") return herdrBackend;
|
|
2548
|
+
if (process.env.TMUX || process.env.TMUX_PANE) return tmuxBackend;
|
|
2549
|
+
for (const backend of BACKENDS) {
|
|
2550
|
+
if (await backend.available()) return backend;
|
|
2594
2551
|
}
|
|
2595
|
-
|
|
2596
|
-
return rows.map(rowFromDb);
|
|
2597
|
-
}
|
|
2598
|
-
function latestSeq(db, workstream) {
|
|
2599
|
-
const row = workstream === void 0 ? db.prepare(`SELECT MAX(seq) AS s FROM ops l WHERE ${TOUCH_OP_FILTER}`).get() : db.prepare(
|
|
2600
|
-
`SELECT MAX(seq) AS s FROM ops l WHERE ${workstreamScopeSql()} AND ${TOUCH_OP_FILTER}`
|
|
2601
|
-
).get(...workstreamScopeParams(workstream));
|
|
2602
|
-
return row.s ?? 0;
|
|
2552
|
+
throw new NoMultiplexerError(KNOWN_NAMES);
|
|
2603
2553
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2554
|
+
var cached;
|
|
2555
|
+
async function activeMux() {
|
|
2556
|
+
if (cached === void 0) cached = await detectMux();
|
|
2557
|
+
return cached;
|
|
2606
2558
|
}
|
|
2607
|
-
function
|
|
2608
|
-
const
|
|
2609
|
-
|
|
2559
|
+
function setMuxForTests(backend) {
|
|
2560
|
+
const previous = cached;
|
|
2561
|
+
cached = backend;
|
|
2562
|
+
return previous;
|
|
2610
2563
|
}
|
|
2611
|
-
function
|
|
2612
|
-
|
|
2613
|
-
`SELECT actor, created_at FROM ops
|
|
2614
|
-
WHERE key = ? AND intent = 'task.claim'
|
|
2615
|
-
ORDER BY seq DESC LIMIT 1`
|
|
2616
|
-
).get(`${workstream}/${localId}`);
|
|
2617
|
-
return row ?? null;
|
|
2564
|
+
function resetMux() {
|
|
2565
|
+
cached = void 0;
|
|
2618
2566
|
}
|
|
2619
|
-
|
|
2620
|
-
|
|
2567
|
+
|
|
2568
|
+
// src/agents/spawn.ts
|
|
2569
|
+
import { execFile as execFile2 } from "child_process";
|
|
2570
|
+
import { mkdirSync as mkdirSync2, rmSync as rmSync4 } from "fs";
|
|
2571
|
+
import { basename, dirname as dirname6 } from "path";
|
|
2572
|
+
import { setTimeout as delay } from "timers/promises";
|
|
2573
|
+
import { promisify as promisify2 } from "util";
|
|
2574
|
+
|
|
2575
|
+
// src/agents.ts
|
|
2576
|
+
import { dirname as dirname5 } from "path";
|
|
2577
|
+
|
|
2578
|
+
// src/glyphs.ts
|
|
2579
|
+
var AGENT_STATE_GLYPH = {
|
|
2580
|
+
busy: "\uF04B",
|
|
2581
|
+
// nf-fa-play
|
|
2582
|
+
needs_input: "\uF186",
|
|
2583
|
+
// nf-fa-moon_o
|
|
2584
|
+
needs_permission: "\uF023",
|
|
2585
|
+
// nf-fa-lock
|
|
2586
|
+
unknown: "\uF059"
|
|
2587
|
+
// nf-fa-question_circle
|
|
2588
|
+
};
|
|
2589
|
+
function agentStateGlyph(state) {
|
|
2590
|
+
return AGENT_STATE_GLYPH[state] ?? GLYPH.unknown;
|
|
2621
2591
|
}
|
|
2592
|
+
var GLYPH = {
|
|
2593
|
+
/** Healthy, clean, passing, closed-successfully. */
|
|
2594
|
+
ok: "\uF058",
|
|
2595
|
+
// nf-fa-check_circle
|
|
2596
|
+
/** Failed check. */
|
|
2597
|
+
fail: "\uF057",
|
|
2598
|
+
// nf-fa-times_circle
|
|
2599
|
+
/** Needs attention but not broken (idle-but-assigned, stale warning). */
|
|
2600
|
+
warn: "\uF071",
|
|
2601
|
+
// nf-fa-warning
|
|
2602
|
+
/** Workspace has uncommitted edits. */
|
|
2603
|
+
dirty: "\uF005",
|
|
2604
|
+
// nf-fa-star
|
|
2605
|
+
/** Workspace is ≥ WORKSPACE_STALE_THRESHOLD commits behind main. */
|
|
2606
|
+
stale: "\uF017",
|
|
2607
|
+
// nf-fa-clock_o
|
|
2608
|
+
/** Task is blocked by an incoming edge. */
|
|
2609
|
+
blocked: "\uF0C1",
|
|
2610
|
+
// nf-fa-link (chain)
|
|
2611
|
+
/** Task is parked (OPEN/parked): open, but set aside on purpose. */
|
|
2612
|
+
parked: "\uF04C",
|
|
2613
|
+
// nf-fa-pause
|
|
2614
|
+
/** Track whose roots merged (diamond dependency). */
|
|
2615
|
+
merge: "\uF074",
|
|
2616
|
+
// nf-fa-random
|
|
2617
|
+
/** Agent owns more than one task (pane-title multi-count slot). */
|
|
2618
|
+
multi: "\uF055",
|
|
2619
|
+
// nf-fa-plus_circle
|
|
2620
|
+
/** Filter toggle: enabled / disabled. */
|
|
2621
|
+
on: "\uF111",
|
|
2622
|
+
// nf-fa-circle
|
|
2623
|
+
off: "\uF10C",
|
|
2624
|
+
// nf-fa-circle_o
|
|
2625
|
+
/** Status outside the known enum. */
|
|
2626
|
+
unknown: "\uF059"
|
|
2627
|
+
// nf-fa-question_circle
|
|
2628
|
+
};
|
|
2622
2629
|
|
|
2623
2630
|
// src/op-context.ts
|
|
2624
2631
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -2856,7 +2863,7 @@ function workspacesRoot(workstream) {
|
|
|
2856
2863
|
}
|
|
2857
2864
|
|
|
2858
2865
|
// src/workspace/crud.ts
|
|
2859
|
-
import { existsSync as existsSync6, rmSync as
|
|
2866
|
+
import { existsSync as existsSync6, rmSync as rmSync3 } from "fs";
|
|
2860
2867
|
import { homedir as homedir2 } from "os";
|
|
2861
2868
|
import { resolve as resolve3 } from "path";
|
|
2862
2869
|
|
|
@@ -2866,7 +2873,7 @@ import { resolve as resolve2 } from "path";
|
|
|
2866
2873
|
|
|
2867
2874
|
// src/vcs/helpers.ts
|
|
2868
2875
|
import { execFile } from "child_process";
|
|
2869
|
-
import { existsSync, rmSync } from "fs";
|
|
2876
|
+
import { existsSync, rmSync as rmSync2 } from "fs";
|
|
2870
2877
|
import { mkdir } from "fs/promises";
|
|
2871
2878
|
import { dirname as dirname3 } from "path";
|
|
2872
2879
|
import { promisify } from "util";
|
|
@@ -2972,7 +2979,7 @@ async function ensureParent(path) {
|
|
|
2972
2979
|
}
|
|
2973
2980
|
function rmDirSync(path) {
|
|
2974
2981
|
if (!existsSync(path)) return false;
|
|
2975
|
-
|
|
2982
|
+
rmSync2(path, { recursive: true, force: true });
|
|
2976
2983
|
return true;
|
|
2977
2984
|
}
|
|
2978
2985
|
async function run(bin, args, cwd) {
|
|
@@ -3797,7 +3804,7 @@ async function createWorkspace(db, opts) {
|
|
|
3797
3804
|
created = await backend.createWorkspace(createOpts);
|
|
3798
3805
|
} catch (err) {
|
|
3799
3806
|
try {
|
|
3800
|
-
|
|
3807
|
+
rmSync3(path, { recursive: true, force: true });
|
|
3801
3808
|
} catch {
|
|
3802
3809
|
}
|
|
3803
3810
|
throw err;
|
|
@@ -4438,8 +4445,17 @@ var CycleError = class extends Error {
|
|
|
4438
4445
|
];
|
|
4439
4446
|
}
|
|
4440
4447
|
};
|
|
4448
|
+
function stallWaitHint(owner, ws, runs) {
|
|
4449
|
+
if (runs === void 0 || runs < 1) return [];
|
|
4450
|
+
return [
|
|
4451
|
+
{
|
|
4452
|
+
intent: "Read the worker's last answer (its final text, at once)",
|
|
4453
|
+
command: `mu agent wait ${owner} --after-runs ${runs - 1} --json -w ${ws}`
|
|
4454
|
+
}
|
|
4455
|
+
];
|
|
4456
|
+
}
|
|
4441
4457
|
var StallDetectedDuringWaitError = class extends Error {
|
|
4442
|
-
constructor(taskName, owner, workstream, ageSecs, ownerExpectsCtl = false) {
|
|
4458
|
+
constructor(taskName, owner, workstream, ageSecs, ownerExpectsCtl = false, ownerRuns) {
|
|
4443
4459
|
const ownerBit = owner !== null ? owner : "<unknown>";
|
|
4444
4460
|
super(
|
|
4445
4461
|
`task ${taskName} owned by ${ownerBit} has been needs_input for ${ageSecs}s; exiting per --on-stall exit. Read the pane to see which it is \u2014 finished without closing, waiting on an answer, or sitting at a prompt.`
|
|
@@ -4449,12 +4465,14 @@ var StallDetectedDuringWaitError = class extends Error {
|
|
|
4449
4465
|
this.workstream = workstream;
|
|
4450
4466
|
this.ageSecs = ageSecs;
|
|
4451
4467
|
this.ownerExpectsCtl = ownerExpectsCtl;
|
|
4468
|
+
this.ownerRuns = ownerRuns;
|
|
4452
4469
|
}
|
|
4453
4470
|
taskName;
|
|
4454
4471
|
owner;
|
|
4455
4472
|
workstream;
|
|
4456
4473
|
ageSecs;
|
|
4457
4474
|
ownerExpectsCtl;
|
|
4475
|
+
ownerRuns;
|
|
4458
4476
|
name = "StallDetectedDuringWaitError";
|
|
4459
4477
|
/**
|
|
4460
4478
|
* Diagnose before acting. `mu agent read` comes FIRST because the
|
|
@@ -4469,6 +4487,7 @@ var StallDetectedDuringWaitError = class extends Error {
|
|
|
4469
4487
|
const ws = this.workstream;
|
|
4470
4488
|
const ownerBit = this.owner !== null ? this.owner : "<owner>";
|
|
4471
4489
|
return [
|
|
4490
|
+
...stallWaitHint(ownerBit, ws, this.ownerRuns),
|
|
4472
4491
|
{
|
|
4473
4492
|
intent: "Read the worker's pane to see what it is waiting on",
|
|
4474
4493
|
command: `mu agent read ${ownerBit} -w ${ws} --lines 60`
|
|
@@ -4822,7 +4841,7 @@ function reparentTaskImpl(db, taskLocalId, blockers, scope) {
|
|
|
4822
4841
|
|
|
4823
4842
|
// src/workstream.ts
|
|
4824
4843
|
import { existsSync as existsSync7, readdirSync as readdirSync2, rmdirSync } from "fs";
|
|
4825
|
-
import { join as join5 } from "path";
|
|
4844
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
4826
4845
|
var WORKSTREAM_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
4827
4846
|
var RESERVED_WORKSTREAM_PREFIX = "mu-";
|
|
4828
4847
|
var RESERVED_WORKSTREAM_NAMES = /* @__PURE__ */ new Set(["scratch"]);
|
|
@@ -4936,6 +4955,7 @@ async function teardownWorkstream(db, opts) {
|
|
|
4936
4955
|
const notesBefore = countNotes(db, opts.workstream);
|
|
4937
4956
|
const edgesBefore = countEdges(db, opts.workstream);
|
|
4938
4957
|
const workspacesBefore = listWorkspaces2(db, opts.workstream);
|
|
4958
|
+
const agentNames = listAgentNames(db, opts.workstream);
|
|
4939
4959
|
const mux = await activeMux();
|
|
4940
4960
|
const muxAliveBefore = await mux.sessionExists(muxSession);
|
|
4941
4961
|
if (muxAliveBefore) {
|
|
@@ -4973,6 +4993,14 @@ async function teardownWorkstream(db, opts) {
|
|
|
4973
4993
|
} catch {
|
|
4974
4994
|
}
|
|
4975
4995
|
}
|
|
4996
|
+
for (const name of agentNames) unlinkCtlSocket(db, name, opts.workstream);
|
|
4997
|
+
if (!db.memory) {
|
|
4998
|
+
const sockDir = join5(dirname4(db.name), "sock", opts.workstream);
|
|
4999
|
+
try {
|
|
5000
|
+
if (readdirSync2(sockDir).length === 0) rmdirSync(sockDir);
|
|
5001
|
+
} catch {
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
4976
5004
|
withOpContext(
|
|
4977
5005
|
db,
|
|
4978
5006
|
{ intent: "workstream.teardown", group: "new" },
|
|
@@ -4989,6 +5017,14 @@ async function teardownWorkstream(db, opts) {
|
|
|
4989
5017
|
failedWorkspaces
|
|
4990
5018
|
};
|
|
4991
5019
|
}
|
|
5020
|
+
function listAgentNames(db, workstream) {
|
|
5021
|
+
const rows = db.prepare(
|
|
5022
|
+
`SELECT a.name FROM agents a
|
|
5023
|
+
JOIN workstreams ws ON ws.id = a.workstream_id
|
|
5024
|
+
WHERE ws.name = ?`
|
|
5025
|
+
).all(workstream);
|
|
5026
|
+
return rows.map((r) => r.name);
|
|
5027
|
+
}
|
|
4992
5028
|
function countAgents(db, workstream) {
|
|
4993
5029
|
const row = db.prepare(
|
|
4994
5030
|
`SELECT COUNT(*) AS n FROM agents a
|
|
@@ -5296,12 +5332,32 @@ function recordReasonNote(db, localId, workstream, substate, why, author) {
|
|
|
5296
5332
|
if (why === void 0 || why.trim() === "") return;
|
|
5297
5333
|
const noteOpts = { workstream };
|
|
5298
5334
|
if (author !== void 0 && author !== "") noteOpts.author = author;
|
|
5299
|
-
|
|
5335
|
+
const text = substate === "rejected" || substate === "superseded" ? withTitles(db, why, workstream, localId) : why;
|
|
5336
|
+
insertNote(db, localId, `${substate.toUpperCase()}: ${text}`, noteOpts);
|
|
5337
|
+
}
|
|
5338
|
+
var TASK_ID_TOKEN = /(?<![\w./-])[a-z][a-z0-9_-]{0,63}(?![\w-]|\.\w|:\d)/g;
|
|
5339
|
+
var TASK_ID_WITH_PAREN = new RegExp(`(?:${TASK_ID_TOKEN.source})(\\s*\\()?`, "g");
|
|
5340
|
+
function namedTaskIds(db, text, workstream, self) {
|
|
5341
|
+
const ids = (text.match(TASK_ID_TOKEN) ?? []).filter((t) => t !== self);
|
|
5342
|
+
return [...new Set(ids)].filter((t) => getTask(db, t, workstream) !== void 0);
|
|
5343
|
+
}
|
|
5344
|
+
function withTitles(db, why, workstream, self) {
|
|
5345
|
+
const named = new Set(namedTaskIds(db, why, workstream, self));
|
|
5346
|
+
if (named.size === 0) return why;
|
|
5347
|
+
return why.replace(TASK_ID_WITH_PAREN, (match, paren) => {
|
|
5348
|
+
const id = paren === void 0 ? match : match.slice(0, -paren.length);
|
|
5349
|
+
if (!named.has(id)) return match;
|
|
5350
|
+
const title = getTask(db, id, workstream)?.title ?? "";
|
|
5351
|
+
return paren === void 0 ? `${id} (${title})` : `${id} (${title}; `;
|
|
5352
|
+
});
|
|
5300
5353
|
}
|
|
5301
5354
|
function openTask(db, localId, opts) {
|
|
5302
|
-
return withOpContext(db, { intent: "task.open", group: "new" }, () => {
|
|
5355
|
+
return withOpContext(db, { intent: "task.open", actor: opts.author, group: "new" }, () => {
|
|
5303
5356
|
const before = getTask(db, localId, opts.workstream);
|
|
5304
|
-
const r = setTaskStatus(db, localId, "OPEN",
|
|
5357
|
+
const r = setTaskStatus(db, localId, "OPEN", {
|
|
5358
|
+
workstream: opts.workstream,
|
|
5359
|
+
...opts.evidence !== void 0 ? { evidence: opts.evidence } : {}
|
|
5360
|
+
});
|
|
5305
5361
|
if (r.changed && before) recordEvidenceNote(db, localId, before.workstreamName, "OPEN", opts);
|
|
5306
5362
|
return r;
|
|
5307
5363
|
});
|
|
@@ -5332,7 +5388,7 @@ function parkTask(db, localId, opts) {
|
|
|
5332
5388
|
function acceptTask(db, localId, opts) {
|
|
5333
5389
|
return withOpContext(
|
|
5334
5390
|
db,
|
|
5335
|
-
{ intent: "task.accept", group: "new" },
|
|
5391
|
+
{ intent: "task.accept", actor: opts.author, group: "new" },
|
|
5336
5392
|
() => db.transaction(() => {
|
|
5337
5393
|
const before = getTask(db, localId, opts.workstream);
|
|
5338
5394
|
if (!before) throw new TaskNotFoundError(localId);
|
|
@@ -5354,7 +5410,7 @@ function acceptTask(db, localId, opts) {
|
|
|
5354
5410
|
function unparkTask(db, localId, opts) {
|
|
5355
5411
|
return withOpContext(
|
|
5356
5412
|
db,
|
|
5357
|
-
{ intent: "task.unpark", group: "new" },
|
|
5413
|
+
{ intent: "task.unpark", actor: opts.author, group: "new" },
|
|
5358
5414
|
() => db.transaction(() => {
|
|
5359
5415
|
const before = getTask(db, localId, opts.workstream);
|
|
5360
5416
|
if (!before) throw new TaskNotFoundError(localId);
|
|
@@ -5378,7 +5434,7 @@ function unparkTask(db, localId, opts) {
|
|
|
5378
5434
|
function releaseTask(db, localId, opts) {
|
|
5379
5435
|
return withOpContext(
|
|
5380
5436
|
db,
|
|
5381
|
-
{ intent: "task.release", group: "new" },
|
|
5437
|
+
{ intent: "task.release", actor: opts.author, group: "new" },
|
|
5382
5438
|
() => releaseTaskImpl(db, localId, opts)
|
|
5383
5439
|
);
|
|
5384
5440
|
}
|
|
@@ -5466,7 +5522,10 @@ async function claimTaskImpl(db, localId, opts) {
|
|
|
5466
5522
|
}
|
|
5467
5523
|
const after = getTask(db, localId, opts.workstream);
|
|
5468
5524
|
if (!after) throw new Error(`claimTask: row missing after update: ${localId}`);
|
|
5469
|
-
recordEvidenceNote(db, localId, opts.workstream, "CLAIM",
|
|
5525
|
+
recordEvidenceNote(db, localId, opts.workstream, "CLAIM", {
|
|
5526
|
+
...opts,
|
|
5527
|
+
author: opts.author ?? agentName
|
|
5528
|
+
});
|
|
5470
5529
|
return {
|
|
5471
5530
|
ownerName: agentName,
|
|
5472
5531
|
actorName: agentName,
|
|
@@ -5526,7 +5585,10 @@ async function claimSelf(db, localId, opts) {
|
|
|
5526
5585
|
}
|
|
5527
5586
|
const after = getTask(db, localId, before.workstreamName);
|
|
5528
5587
|
if (!after) throw new Error(`claimTask: row missing after update: ${localId}`);
|
|
5529
|
-
recordEvidenceNote(db, localId, before.workstreamName, "CLAIM",
|
|
5588
|
+
recordEvidenceNote(db, localId, before.workstreamName, "CLAIM", {
|
|
5589
|
+
...opts,
|
|
5590
|
+
author: opts.author ?? actor
|
|
5591
|
+
});
|
|
5530
5592
|
return {
|
|
5531
5593
|
ownerName: null,
|
|
5532
5594
|
actorName: actor,
|
|
@@ -5601,12 +5663,15 @@ async function waitForTasks(db, input, opts) {
|
|
|
5601
5663
|
const ctlBrokenSince = /* @__PURE__ */ new Map();
|
|
5602
5664
|
const stuckLabel = /* @__PURE__ */ new Map();
|
|
5603
5665
|
const ctlOwners = /* @__PURE__ */ new Set();
|
|
5666
|
+
const ownerRuns = /* @__PURE__ */ new Map();
|
|
5604
5667
|
const stuckAgeMs = async (status, owner, workstreamName) => {
|
|
5605
5668
|
if (stuckAfterMs <= 0 || status !== "IN_PROGRESS" || owner === null || opts.readOwnerState === void 0)
|
|
5606
5669
|
return null;
|
|
5607
5670
|
const reading = await opts.readOwnerState({ name: owner, workstreamName });
|
|
5608
5671
|
const ownerKey = `${workstreamName}/${owner}`;
|
|
5609
5672
|
if (reading?.ctl !== void 0) ctlOwners.add(ownerKey);
|
|
5673
|
+
if (reading?.runs !== void 0) ownerRuns.set(ownerKey, reading.runs);
|
|
5674
|
+
else ownerRuns.delete(ownerKey);
|
|
5610
5675
|
if (reading?.alive === true && (reading.ctl === "missing" || reading.ctl === "refused")) {
|
|
5611
5676
|
const firstSeen = ctlBrokenSince.get(ownerKey) ?? Date.now();
|
|
5612
5677
|
ctlBrokenSince.set(ownerKey, firstSeen);
|
|
@@ -5636,10 +5701,13 @@ async function waitForTasks(db, input, opts) {
|
|
|
5636
5701
|
stuckWarned.add(key);
|
|
5637
5702
|
const ageSecs = Math.round(ageMs / 1e3);
|
|
5638
5703
|
const ownerBit = owner ?? "<none>";
|
|
5704
|
+
const runs = ownerRuns.get(`${ref.workstreamName}/${ownerBit}`);
|
|
5639
5705
|
const stateBit = stuckLabel.get(`${ref.workstreamName}/${ownerBit}`) ?? "needs_input";
|
|
5706
|
+
const waitLine = stallWaitHint(ownerBit, ref.workstreamName, runs).map((s) => ` ${s.command}
|
|
5707
|
+
`).join("");
|
|
5640
5708
|
currentStuckWarn(
|
|
5641
5709
|
`\x1B[33mmu task wait: ${key} needs attention \u2014 owner=${ownerBit} has been in ${stateBit} for ${formatStallAge(ageMs)}. It may have finished without closing, be waiting on an answer, or be sitting at a prompt.\x1B[0m
|
|
5642
|
-
mu agent read ${ownerBit} -w ${ref.workstreamName} --lines 60
|
|
5710
|
+
` + waitLine + ` mu agent read ${ownerBit} -w ${ref.workstreamName} --lines 60
|
|
5643
5711
|
`
|
|
5644
5712
|
);
|
|
5645
5713
|
emitEvent(
|
|
@@ -5655,7 +5723,8 @@ async function waitForTasks(db, input, opts) {
|
|
|
5655
5723
|
owner,
|
|
5656
5724
|
ref.workstreamName,
|
|
5657
5725
|
ageSecs,
|
|
5658
|
-
ctlOwners.has(`${ref.workstreamName}/${owner ?? ""}`)
|
|
5726
|
+
ctlOwners.has(`${ref.workstreamName}/${owner ?? ""}`),
|
|
5727
|
+
runs
|
|
5659
5728
|
);
|
|
5660
5729
|
}
|
|
5661
5730
|
}
|
|
@@ -5710,6 +5779,7 @@ async function abortAgent(db, name, opts) {
|
|
|
5710
5779
|
const st = await ctlRequestFor(agent, sock, { op: "status" });
|
|
5711
5780
|
if (!st.ok) throw new Error(`control socket refused status: ${st.error}`);
|
|
5712
5781
|
const before = st.state ?? "idle";
|
|
5782
|
+
const pending = st.pending ?? false;
|
|
5713
5783
|
if (before !== "busy") {
|
|
5714
5784
|
return {
|
|
5715
5785
|
...base,
|
|
@@ -5717,6 +5787,7 @@ async function abortAgent(db, name, opts) {
|
|
|
5717
5787
|
after: before,
|
|
5718
5788
|
settled: true,
|
|
5719
5789
|
aborted: false,
|
|
5790
|
+
pending,
|
|
5720
5791
|
elapsedMs: Date.now() - started
|
|
5721
5792
|
};
|
|
5722
5793
|
}
|
|
@@ -5733,9 +5804,50 @@ async function abortAgent(db, name, opts) {
|
|
|
5733
5804
|
after: w.state ?? "idle",
|
|
5734
5805
|
settled: true,
|
|
5735
5806
|
aborted: true,
|
|
5807
|
+
pending,
|
|
5736
5808
|
elapsedMs: Date.now() - started
|
|
5737
5809
|
};
|
|
5738
5810
|
}
|
|
5811
|
+
async function interruptAgent(db, name, text, opts) {
|
|
5812
|
+
const r = await interruptAgentImpl(db, name, text, opts);
|
|
5813
|
+
recordSend(db, { name, workstreamName: opts.workstream }, "interrupt", text.length, {
|
|
5814
|
+
transport: "ctl",
|
|
5815
|
+
wasBusy: r.wasBusy
|
|
5816
|
+
});
|
|
5817
|
+
return r;
|
|
5818
|
+
}
|
|
5819
|
+
async function interruptAgentImpl(db, name, text, opts) {
|
|
5820
|
+
const agent = getAgent(db, name, opts.workstream);
|
|
5821
|
+
if (!agent) throw new AgentNotFoundError(name, opts.workstream);
|
|
5822
|
+
const sock = opts.socket ?? agentCtlSocket(db, agent);
|
|
5823
|
+
if (!expectsCtl(agent, sock)) throw new AgentAbortNeedsCtlError(name, opts.workstream, agent.cli);
|
|
5824
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_ABORT_TIMEOUT_MS;
|
|
5825
|
+
let r;
|
|
5826
|
+
try {
|
|
5827
|
+
r = await ctlRequest(sock, { op: "interrupt", text, timeoutMs });
|
|
5828
|
+
} catch (e) {
|
|
5829
|
+
if (!(e instanceof CtlUnknownOpError)) throw await ctlFailure(agent, sock, e);
|
|
5830
|
+
const ab = await abortAgent(db, name, { ...opts, socket: sock });
|
|
5831
|
+
const sent = await ctlRequestFor(agent, sock, { op: "send", text });
|
|
5832
|
+
if (!sent.ok) throw new Error(`control socket refused the send: ${sent.error}`);
|
|
5833
|
+
return {
|
|
5834
|
+
wasBusy: ab.aborted,
|
|
5835
|
+
pending: ab.pending,
|
|
5836
|
+
...sent.runs !== void 0 ? { runs: sent.runs } : {},
|
|
5837
|
+
fallback: true
|
|
5838
|
+
};
|
|
5839
|
+
}
|
|
5840
|
+
if (!r.ok) {
|
|
5841
|
+
if (r.error === "timeout") throw new AgentAbortTimeoutError(name, opts.workstream, timeoutMs);
|
|
5842
|
+
throw new Error(`control socket refused the interrupt: ${r.error}`);
|
|
5843
|
+
}
|
|
5844
|
+
return {
|
|
5845
|
+
wasBusy: r.wasBusy ?? false,
|
|
5846
|
+
pending: r.pending ?? false,
|
|
5847
|
+
...r.runs !== void 0 ? { runs: r.runs } : {},
|
|
5848
|
+
fallback: false
|
|
5849
|
+
};
|
|
5850
|
+
}
|
|
5739
5851
|
|
|
5740
5852
|
// src/agents/adopt.ts
|
|
5741
5853
|
var ADOPT_CTL_PROBE_MS = 1e3;
|
|
@@ -6089,13 +6201,6 @@ function deleteAgent(db, name, workstream) {
|
|
|
6089
6201
|
if (deleted) unlinkCtlSocket(db, name, workstream);
|
|
6090
6202
|
return deleted;
|
|
6091
6203
|
}
|
|
6092
|
-
function unlinkCtlSocket(db, name, workstream) {
|
|
6093
|
-
if (db.memory) return;
|
|
6094
|
-
try {
|
|
6095
|
-
rmSync3(ctlSocketPath(workstream, name, dirname4(db.name)), { force: true });
|
|
6096
|
-
} catch {
|
|
6097
|
-
}
|
|
6098
|
-
}
|
|
6099
6204
|
function deleteAgentRow(db, name, workstream) {
|
|
6100
6205
|
return withOpContext(
|
|
6101
6206
|
db,
|
|
@@ -6135,10 +6240,23 @@ function isValidAgentName(name) {
|
|
|
6135
6240
|
async function sendToAgent(db, name, text, opts) {
|
|
6136
6241
|
const agent = getAgent(db, name, opts.workstream);
|
|
6137
6242
|
if (!agent) throw new AgentNotFoundError(name);
|
|
6138
|
-
|
|
6243
|
+
const sent = await sendViaTransport(agent, text, {
|
|
6139
6244
|
...opts,
|
|
6140
6245
|
socket: opts.socket ?? agentCtlSocket(db, agent)
|
|
6141
6246
|
});
|
|
6247
|
+
recordSend(
|
|
6248
|
+
db,
|
|
6249
|
+
agent,
|
|
6250
|
+
opts.fresh ? "fresh" : opts.mode === "steer" ? "steer" : "plain",
|
|
6251
|
+
text.length,
|
|
6252
|
+
{
|
|
6253
|
+
transport: sent.transport,
|
|
6254
|
+
command: sent.command,
|
|
6255
|
+
state: sent.state,
|
|
6256
|
+
since: sent.since
|
|
6257
|
+
}
|
|
6258
|
+
);
|
|
6259
|
+
return sent;
|
|
6142
6260
|
}
|
|
6143
6261
|
async function readAgent(db, name, opts) {
|
|
6144
6262
|
const agent = getAgent(db, name, opts.workstream);
|
|
@@ -6191,7 +6309,7 @@ async function listLiveAgents(db, opts) {
|
|
|
6191
6309
|
...opts.mode !== void 0 ? { mode: opts.mode } : {}
|
|
6192
6310
|
});
|
|
6193
6311
|
const baseAgents = listAgents(db, { workstream: opts.workstream });
|
|
6194
|
-
const readings = await readAgentStates(baseAgents, { stateDir:
|
|
6312
|
+
const readings = await readAgentStates(baseAgents, { stateDir: dirname5(db.name) });
|
|
6195
6313
|
const now = Date.now();
|
|
6196
6314
|
const agents = baseAgents.map((agent) => {
|
|
6197
6315
|
const reading = readings.get(agentKey(agent)) ?? {
|
|
@@ -6456,7 +6574,7 @@ function defaultSpawnCtlMs() {
|
|
|
6456
6574
|
return parsed;
|
|
6457
6575
|
}
|
|
6458
6576
|
var CTL_POLL_INTERVAL_MS = 250;
|
|
6459
|
-
async function awaitCtlHandshake(sock) {
|
|
6577
|
+
async function awaitCtlHandshake(sock, onMiss) {
|
|
6460
6578
|
const budgetMs = defaultSpawnCtlMs();
|
|
6461
6579
|
const deadline = Date.now() + budgetMs;
|
|
6462
6580
|
for (; ; ) {
|
|
@@ -6464,6 +6582,7 @@ async function awaitCtlHandshake(sock) {
|
|
|
6464
6582
|
const probe = await ctlProbe(sock, Math.max(1, Math.min(remaining, CTL_POLL_INTERVAL_MS * 4)));
|
|
6465
6583
|
if (probe.kind === "ok") return "ok";
|
|
6466
6584
|
const last = probeToSpawnCtl(probe.kind);
|
|
6585
|
+
if (onMiss) await onMiss();
|
|
6467
6586
|
const left = deadline - Date.now();
|
|
6468
6587
|
if (left <= 0) return last;
|
|
6469
6588
|
await delay(Math.min(CTL_POLL_INTERVAL_MS, left));
|
|
@@ -6491,8 +6610,8 @@ async function spawnAgent(db, opts) {
|
|
|
6491
6610
|
}
|
|
6492
6611
|
}
|
|
6493
6612
|
const workspacePathStr = opts.workspace ? await prestageWorkspace(db, opts, cli) : void 0;
|
|
6494
|
-
const ctlSocket = ctlSocketPath(opts.workstream, opts.name,
|
|
6495
|
-
mkdirSync2(
|
|
6613
|
+
const ctlSocket = ctlSocketPath(opts.workstream, opts.name, dirname6(db.name));
|
|
6614
|
+
mkdirSync2(dirname6(ctlSocket), { recursive: true, mode: 448 });
|
|
6496
6615
|
rmSync4(ctlSocket, { force: true });
|
|
6497
6616
|
const paneEnv = {
|
|
6498
6617
|
MU_MANAGED_AGENT: "1",
|
|
@@ -6503,6 +6622,8 @@ async function spawnAgent(db, opts) {
|
|
|
6503
6622
|
const hasWorkspace = workspacePathStr !== void 0;
|
|
6504
6623
|
let paneId;
|
|
6505
6624
|
let agent;
|
|
6625
|
+
const wantsCtl = opts.ctl !== false && speaksMuCtl(cli, command) && defaultSpawnCtlMs() !== 0;
|
|
6626
|
+
let ctl;
|
|
6506
6627
|
try {
|
|
6507
6628
|
const created = await withSpawnLock(session, async () => {
|
|
6508
6629
|
const pid = await createOrReusePane({
|
|
@@ -6523,7 +6644,23 @@ async function spawnAgent(db, opts) {
|
|
|
6523
6644
|
agent = created.row;
|
|
6524
6645
|
const startAgent = (await activeMux()).startAgentInPane;
|
|
6525
6646
|
if (startAgent === void 0) {
|
|
6526
|
-
|
|
6647
|
+
if (wantsCtl) {
|
|
6648
|
+
const pid = paneId;
|
|
6649
|
+
const check = defaultSpawnLivenessMs() !== 0;
|
|
6650
|
+
const when = `during the ctl handshake (up to ${defaultSpawnCtlMs()}ms after spawn)`;
|
|
6651
|
+
let lastScrollback;
|
|
6652
|
+
const assertPaneAlive = async () => {
|
|
6653
|
+
const mux = await activeMux();
|
|
6654
|
+
const sb = await mux.capturePane(pid, { lines: 50 }).catch(() => void 0);
|
|
6655
|
+
lastScrollback = sb ?? lastScrollback;
|
|
6656
|
+
if (await mux.paneExists(pid)) return;
|
|
6657
|
+
throw new AgentDiedOnSpawnError(opts.name, pid, lastScrollback, command, when);
|
|
6658
|
+
};
|
|
6659
|
+
ctl = await awaitCtlHandshake(ctlSocket, check ? assertPaneAlive : void 0);
|
|
6660
|
+
if (check) await checkSpawnHealth(pid, opts.name, command, when);
|
|
6661
|
+
} else {
|
|
6662
|
+
await awaitSpawnLiveness(paneId, opts.name, command);
|
|
6663
|
+
}
|
|
6527
6664
|
} else {
|
|
6528
6665
|
await startAgent({ paneId, name: opts.name, cli, command, commandSource });
|
|
6529
6666
|
}
|
|
@@ -6532,14 +6669,13 @@ async function spawnAgent(db, opts) {
|
|
|
6532
6669
|
if (hasWorkspace) attachOrphanCleanupHint(err, opts.name, opts.workstream);
|
|
6533
6670
|
throw err;
|
|
6534
6671
|
}
|
|
6535
|
-
|
|
6672
|
+
ctl ??= wantsCtl ? await awaitCtlHandshake(ctlSocket) : "skipped";
|
|
6536
6673
|
emitEvent(
|
|
6537
6674
|
db,
|
|
6538
6675
|
opts.workstream,
|
|
6539
6676
|
"agent.spawn",
|
|
6540
6677
|
`agent spawn ${opts.name} (cli=${cli}, role=${opts.role ?? "full-access"}, pane=${paneId})`
|
|
6541
6678
|
);
|
|
6542
|
-
await refreshAgentTitle(db, opts.name, opts.workstream);
|
|
6543
6679
|
maybeWarnNonConventionalAgentName(opts.name);
|
|
6544
6680
|
return { ...agent, ctl, ctlSocket };
|
|
6545
6681
|
}
|
|
@@ -6667,15 +6803,18 @@ async function awaitSpawnLiveness(paneId, agentName, command) {
|
|
|
6667
6803
|
const ms = defaultSpawnLivenessMs();
|
|
6668
6804
|
if (ms === 0) return;
|
|
6669
6805
|
await sleep(ms);
|
|
6806
|
+
await checkSpawnHealth(paneId, agentName, command);
|
|
6807
|
+
}
|
|
6808
|
+
async function checkSpawnHealth(paneId, agentName, command, when) {
|
|
6670
6809
|
const mux = await activeMux();
|
|
6671
6810
|
const scrollback = await mux.capturePane(paneId, { lines: 50 }).catch(() => void 0);
|
|
6672
6811
|
if (!await mux.paneExists(paneId)) {
|
|
6673
|
-
throw new AgentDiedOnSpawnError(agentName, paneId, scrollback, command);
|
|
6812
|
+
throw new AgentDiedOnSpawnError(agentName, paneId, scrollback, command, when);
|
|
6674
6813
|
}
|
|
6675
6814
|
if (scrollback !== void 0) {
|
|
6676
6815
|
const matchedLine = detectSpawnStartupError(scrollback);
|
|
6677
6816
|
if (matchedLine !== void 0) {
|
|
6678
|
-
throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback);
|
|
6817
|
+
throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback, when);
|
|
6679
6818
|
}
|
|
6680
6819
|
}
|
|
6681
6820
|
}
|
|
@@ -6830,7 +6969,7 @@ var AgentNotInWorkstreamError = class extends Error {
|
|
|
6830
6969
|
}
|
|
6831
6970
|
};
|
|
6832
6971
|
var AgentDiedOnSpawnError = class extends Error {
|
|
6833
|
-
constructor(agentName, paneId, scrollback, command) {
|
|
6972
|
+
constructor(agentName, paneId, scrollback, command, when = `within ${defaultSpawnLivenessMs()}ms of spawn`) {
|
|
6834
6973
|
const tail = scrollback?.trim();
|
|
6835
6974
|
const detail = tail ? `
|
|
6836
6975
|
|
|
@@ -6839,9 +6978,7 @@ ${tail}
|
|
|
6839
6978
|
--- end scrollback ---` : "";
|
|
6840
6979
|
const remote = /^\s*ssh\s/.test(command ?? "");
|
|
6841
6980
|
const cause = remote ? 'The command is an ssh hop, so the likely causes are remote: the remote binary is missing, or its env (PATH, provider keys) is absent because ssh runs a non-interactive shell that skips ~/.zshrc / ~/.bashrc (wrap it: $SHELL -ilc "pi --approve"); or sshd refused the socket forward (ExitOnForwardFailure=yes kills the connection; check AllowStreamLocalForwarding).' : "Most common cause: the spawned CLI exited immediately (e.g. a wrapper CLI blocking on its instance lock; set MU_<UPPER_CLI>_COMMAND to a non-blocking variant to bypass).";
|
|
6842
|
-
super(
|
|
6843
|
-
`agent ${agentName} died within ${defaultSpawnLivenessMs()}ms of spawn (pane ${paneId}). ${cause}${detail}`
|
|
6844
|
-
);
|
|
6981
|
+
super(`agent ${agentName} died ${when} (pane ${paneId}). ${cause}${detail}`);
|
|
6845
6982
|
this.agentName = agentName;
|
|
6846
6983
|
this.paneId = paneId;
|
|
6847
6984
|
this.scrollback = scrollback;
|
|
@@ -6900,9 +7037,9 @@ ${tail}
|
|
|
6900
7037
|
}
|
|
6901
7038
|
};
|
|
6902
7039
|
var AgentSpawnStartupError = class extends Error {
|
|
6903
|
-
constructor(agentName, paneId, matchedLine, scrollback) {
|
|
7040
|
+
constructor(agentName, paneId, matchedLine, scrollback, when = `within ${defaultSpawnLivenessMs()}ms of spawn`) {
|
|
6904
7041
|
super(
|
|
6905
|
-
`agent ${agentName} reported a startup error
|
|
7042
|
+
`agent ${agentName} reported a startup error ${when} (pane ${paneId}). The pane is alive but the spawned CLI parked at an error prompt instead of becoming a working agent.
|
|
6906
7043
|
|
|
6907
7044
|
Matched line: ${matchedLine.trim()}
|
|
6908
7045
|
|
|
@@ -6988,7 +7125,14 @@ var AgentCtlUnreachableError = class extends Error {
|
|
|
6988
7125
|
kind;
|
|
6989
7126
|
name = "AgentCtlUnreachableError";
|
|
6990
7127
|
errorNextSteps() {
|
|
7128
|
+
const trust = this.kind === "missing" ? [
|
|
7129
|
+
{
|
|
7130
|
+
intent: "Just spawned? pi may sit at its project trust prompt: answer /trust in the pane, or respawn with --command 'pi --approve'",
|
|
7131
|
+
command: `mu agent read ${this.agentName} -w ${this.workstream}`
|
|
7132
|
+
}
|
|
7133
|
+
] : [];
|
|
6991
7134
|
return [
|
|
7135
|
+
...trust,
|
|
6992
7136
|
{ intent: "Check whether the mu pi extension is linked", command: "mu doctor" },
|
|
6993
7137
|
{
|
|
6994
7138
|
intent: "Stop a runaway tool without the socket",
|
|
@@ -7071,7 +7215,7 @@ var AgentFreshNeedsCtlError = class extends Error {
|
|
|
7071
7215
|
}
|
|
7072
7216
|
};
|
|
7073
7217
|
var AgentExtensionOutdatedError = class extends Error {
|
|
7074
|
-
constructor(agentName, workstream, op, extVersion) {
|
|
7218
|
+
constructor(agentName, workstream, op, extVersion, ops) {
|
|
7075
7219
|
super(
|
|
7076
7220
|
`agent ${agentName}'s mu pi extension (${extVersion ?? "version unknown"}) predates op ${op}: its pi loaded an older mu build; reload or respawn it`
|
|
7077
7221
|
);
|
|
@@ -7079,18 +7223,21 @@ var AgentExtensionOutdatedError = class extends Error {
|
|
|
7079
7223
|
this.workstream = workstream;
|
|
7080
7224
|
this.op = op;
|
|
7081
7225
|
this.extVersion = extVersion;
|
|
7226
|
+
this.ops = ops;
|
|
7082
7227
|
}
|
|
7083
7228
|
agentName;
|
|
7084
7229
|
workstream;
|
|
7085
7230
|
op;
|
|
7086
7231
|
extVersion;
|
|
7232
|
+
ops;
|
|
7087
7233
|
name = "AgentExtensionOutdatedError";
|
|
7088
7234
|
errorNextSteps() {
|
|
7089
7235
|
const w = `-w ${this.workstream}`;
|
|
7236
|
+
const via = this.ops?.includes("command") ? "" : " --via mux";
|
|
7090
7237
|
return [
|
|
7091
7238
|
{
|
|
7092
|
-
intent: "Reload pi's extensions
|
|
7093
|
-
command: `mu agent send ${this.agentName} '/reload'
|
|
7239
|
+
intent: "Reload pi's extensions (keeps the session and context)",
|
|
7240
|
+
command: `mu agent send ${this.agentName} '/reload'${via} ${w}`
|
|
7094
7241
|
},
|
|
7095
7242
|
{
|
|
7096
7243
|
intent: "Or respawn the agent (loses its context)",
|
|
@@ -7148,7 +7295,7 @@ var AgentSlashCommandUnsupportedError = class extends Error {
|
|
|
7148
7295
|
|
|
7149
7296
|
// src/agents/transport.ts
|
|
7150
7297
|
function agentCtlSocket(db, agent) {
|
|
7151
|
-
return ctlSocketPath(agent.workstreamName, agent.name,
|
|
7298
|
+
return ctlSocketPath(agent.workstreamName, agent.name, dirname7(db.name));
|
|
7152
7299
|
}
|
|
7153
7300
|
function expectsCtl(agent, sock = ctlSocketPath(agent.workstreamName, agent.name)) {
|
|
7154
7301
|
return speaksMuCtl(agent.cli, resolveCliCommand(agent.cli)) || existsSync8(sock);
|
|
@@ -7176,22 +7323,34 @@ async function extensionOutdated(agent, sock, e) {
|
|
|
7176
7323
|
if (hello.ok) extVersion = hello.extVersion;
|
|
7177
7324
|
} catch {
|
|
7178
7325
|
}
|
|
7179
|
-
return new AgentExtensionOutdatedError(agent.name, agent.workstreamName, e.op, extVersion);
|
|
7326
|
+
return new AgentExtensionOutdatedError(agent.name, agent.workstreamName, e.op, extVersion, e.ops);
|
|
7180
7327
|
}
|
|
7181
7328
|
async function ctlRequestFor(agent, sock, req) {
|
|
7182
7329
|
try {
|
|
7183
7330
|
return await ctlRequest(sock, req);
|
|
7184
7331
|
} catch (e) {
|
|
7185
|
-
|
|
7186
|
-
if (e instanceof CtlUnknownOpError) throw await extensionOutdated(agent, sock, e);
|
|
7187
|
-
throw new AgentCtlUnreachableError(
|
|
7188
|
-
agent.name,
|
|
7189
|
-
agent.workstreamName,
|
|
7190
|
-
sock,
|
|
7191
|
-
errCode(e) === "ENOENT" ? "missing" : "refused"
|
|
7192
|
-
);
|
|
7332
|
+
throw await ctlFailure(agent, sock, e);
|
|
7193
7333
|
}
|
|
7194
7334
|
}
|
|
7335
|
+
async function ctlFailure(agent, sock, e) {
|
|
7336
|
+
if (e instanceof CtlVersionError) return e;
|
|
7337
|
+
if (e instanceof CtlUnknownOpError) return extensionOutdated(agent, sock, e);
|
|
7338
|
+
return new AgentCtlUnreachableError(
|
|
7339
|
+
agent.name,
|
|
7340
|
+
agent.workstreamName,
|
|
7341
|
+
sock,
|
|
7342
|
+
errCode(e) === "ENOENT" ? "missing" : "refused"
|
|
7343
|
+
);
|
|
7344
|
+
}
|
|
7345
|
+
function recordSend(db, agent, mode, bytes, fields2) {
|
|
7346
|
+
const detail = Object.entries({ mode, ...fields2 }).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${k === "since" ? new Date(Number(v)).toISOString() : v}`).join(" ");
|
|
7347
|
+
emitEvent(
|
|
7348
|
+
db,
|
|
7349
|
+
agent.workstreamName,
|
|
7350
|
+
"agent.send",
|
|
7351
|
+
`agent send ${agent.name} (${detail}, ${bytes} bytes)`
|
|
7352
|
+
);
|
|
7353
|
+
}
|
|
7195
7354
|
async function sendViaTransport(agent, text, opts) {
|
|
7196
7355
|
const sock = opts.socket;
|
|
7197
7356
|
if (opts.fresh && (opts.via === "mux" || !expectsCtl(agent, sock))) {
|
|
@@ -7225,7 +7384,12 @@ async function sendViaTransport(agent, text, opts) {
|
|
|
7225
7384
|
}
|
|
7226
7385
|
throw new Error(`control socket refused the send: ${reply.error}`);
|
|
7227
7386
|
}
|
|
7228
|
-
return
|
|
7387
|
+
return {
|
|
7388
|
+
transport,
|
|
7389
|
+
...reply.state !== void 0 ? { state: reply.state } : {},
|
|
7390
|
+
...reply.runs !== void 0 ? { runs: reply.runs } : {},
|
|
7391
|
+
...reply.since !== void 0 ? { since: reply.since } : {}
|
|
7392
|
+
};
|
|
7229
7393
|
}
|
|
7230
7394
|
async function runSessionCommand(agent, sock, cmd, force) {
|
|
7231
7395
|
const reply = await ctlRequestFor(agent, sock, {
|
|
@@ -7427,7 +7591,8 @@ async function readAgentStates(agents, opts = {}) {
|
|
|
7427
7591
|
since: probe.status.since,
|
|
7428
7592
|
alive: true,
|
|
7429
7593
|
ctl: "ok",
|
|
7430
|
-
pending: probe.status.pending
|
|
7594
|
+
pending: probe.status.pending,
|
|
7595
|
+
runs: probe.status.runs
|
|
7431
7596
|
});
|
|
7432
7597
|
} else {
|
|
7433
7598
|
failed.set(agentKey(agent), probe?.kind === "missing" ? "missing" : "refused");
|
|
@@ -8078,7 +8243,7 @@ function formatTreeNodeLabel(t, statusFn, opts = {}) {
|
|
|
8078
8243
|
|
|
8079
8244
|
// src/doctor-summary.ts
|
|
8080
8245
|
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync13, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
|
|
8081
|
-
import { delimiter as delimiter2, dirname as
|
|
8246
|
+
import { delimiter as delimiter2, dirname as dirname10, join as join11, parse } from "path";
|
|
8082
8247
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8083
8248
|
|
|
8084
8249
|
// src/drift.ts
|
|
@@ -8088,7 +8253,7 @@ import { join as join9 } from "path";
|
|
|
8088
8253
|
|
|
8089
8254
|
// src/rebuild.ts
|
|
8090
8255
|
import { existsSync as existsSync10, mkdirSync as mkdirSync3, statSync } from "fs";
|
|
8091
|
-
import { dirname as
|
|
8256
|
+
import { dirname as dirname8 } from "path";
|
|
8092
8257
|
var RebuildTargetExistsError = class extends Error {
|
|
8093
8258
|
constructor(path) {
|
|
8094
8259
|
super(`rebuild target already exists: ${path}`);
|
|
@@ -8151,7 +8316,7 @@ function rebuildInto(source, opts) {
|
|
|
8151
8316
|
if (existsSync10(targetPath) && opts.force !== true) {
|
|
8152
8317
|
throw new RebuildTargetExistsError(targetPath);
|
|
8153
8318
|
}
|
|
8154
|
-
mkdirSync3(
|
|
8319
|
+
mkdirSync3(dirname8(targetPath), { recursive: true });
|
|
8155
8320
|
const identity = source.prepare(
|
|
8156
8321
|
"SELECT machine_id, hostname, created_at, last_wall, last_counter FROM machine_identity WHERE id = 1"
|
|
8157
8322
|
).get();
|
|
@@ -8673,7 +8838,7 @@ import {
|
|
|
8673
8838
|
writeFileSync
|
|
8674
8839
|
} from "fs";
|
|
8675
8840
|
import { homedir as homedir4 } from "os";
|
|
8676
|
-
import { dirname as
|
|
8841
|
+
import { dirname as dirname9, join as join10, resolve as resolve5 } from "path";
|
|
8677
8842
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
8678
8843
|
var MU_SHIM_MARKER = "// mu:shim";
|
|
8679
8844
|
var LinkConflictError = class extends Error {
|
|
@@ -8703,7 +8868,7 @@ function resolveHome(opts) {
|
|
|
8703
8868
|
return opts?.home ?? process.env.MU_PI_HOME ?? homedir4();
|
|
8704
8869
|
}
|
|
8705
8870
|
function packageRoot() {
|
|
8706
|
-
return resolve5(
|
|
8871
|
+
return resolve5(dirname9(fileURLToPath(import.meta.url)), "..");
|
|
8707
8872
|
}
|
|
8708
8873
|
function extensionEntry() {
|
|
8709
8874
|
return process.env.MU_EXTENSION_ENTRY ?? join10(packageRoot(), "dist", "extension", "mu-pi.js");
|
|
@@ -8739,7 +8904,7 @@ function linkPi(opts) {
|
|
|
8739
8904
|
replacedCopy = !readFileSync(path, "utf8").includes(MU_SHIM_MARKER);
|
|
8740
8905
|
} catch {
|
|
8741
8906
|
}
|
|
8742
|
-
mkdirSync4(
|
|
8907
|
+
mkdirSync4(dirname9(path), { recursive: true });
|
|
8743
8908
|
writeFileSync(path, opts?.copy === true ? readFileSync(entry, "utf8") : shim(entry));
|
|
8744
8909
|
return { path, replacedCopy };
|
|
8745
8910
|
}
|
|
@@ -8750,7 +8915,7 @@ function linkSkill(opts) {
|
|
|
8750
8915
|
const st = lstatOrUndefined(path);
|
|
8751
8916
|
if (st) {
|
|
8752
8917
|
if (!st.isSymbolicLink()) throw new LinkConflictError(path, "not-a-symlink");
|
|
8753
|
-
const current = resolve5(
|
|
8918
|
+
const current = resolve5(dirname9(path), readlinkSync(path));
|
|
8754
8919
|
if (current === target) return { path, target };
|
|
8755
8920
|
if (existsSync12(current) && opts?.force !== true) {
|
|
8756
8921
|
throw new LinkConflictError(path, "foreign-symlink", current);
|
|
@@ -8758,7 +8923,7 @@ function linkSkill(opts) {
|
|
|
8758
8923
|
previous = current;
|
|
8759
8924
|
rmSync6(path);
|
|
8760
8925
|
}
|
|
8761
|
-
mkdirSync4(
|
|
8926
|
+
mkdirSync4(dirname9(path), { recursive: true });
|
|
8762
8927
|
symlinkSync(target, path, "dir");
|
|
8763
8928
|
return previous === void 0 ? { path, target } : { path, target, previous };
|
|
8764
8929
|
}
|
|
@@ -8792,7 +8957,7 @@ function inspectSkill(path) {
|
|
|
8792
8957
|
try {
|
|
8793
8958
|
target = realpathSync(path);
|
|
8794
8959
|
} catch {
|
|
8795
|
-
return { path, state: "dangling", target: resolve5(
|
|
8960
|
+
return { path, state: "dangling", target: resolve5(dirname9(path), readlinkSync(path)) };
|
|
8796
8961
|
}
|
|
8797
8962
|
let skillMd;
|
|
8798
8963
|
try {
|
|
@@ -8971,7 +9136,7 @@ function resolveExecutable(name) {
|
|
|
8971
9136
|
return null;
|
|
8972
9137
|
}
|
|
8973
9138
|
function murmurVersion(executable) {
|
|
8974
|
-
let dir =
|
|
9139
|
+
let dir = dirname10(executable);
|
|
8975
9140
|
const root = parse(dir).root;
|
|
8976
9141
|
while (dir !== root) {
|
|
8977
9142
|
const packagePath = join11(dir, "package.json");
|
|
@@ -8984,7 +9149,7 @@ function murmurVersion(executable) {
|
|
|
8984
9149
|
} catch {
|
|
8985
9150
|
}
|
|
8986
9151
|
}
|
|
8987
|
-
dir =
|
|
9152
|
+
dir = dirname10(dir);
|
|
8988
9153
|
}
|
|
8989
9154
|
return null;
|
|
8990
9155
|
}
|
|
@@ -9020,7 +9185,7 @@ function murmurDoctorCheck(agentStateSource, opts = {}) {
|
|
|
9020
9185
|
return {
|
|
9021
9186
|
name: "murmur",
|
|
9022
9187
|
status: "warn",
|
|
9023
|
-
detail: `murmur ${version} is older than 1.0.0; @murmur_pane_since missing, idle/stall timing unknown`
|
|
9188
|
+
detail: opts.muExtOk === true ? `murmur ${version} is older than 1.0.0; idle/stall timing unknown for non-pi CLIs (pi agents use ctl)` : `murmur ${version} is older than 1.0.0; @murmur_pane_since missing, idle/stall timing unknown`
|
|
9024
9189
|
};
|
|
9025
9190
|
}
|
|
9026
9191
|
return {
|
|
@@ -9129,11 +9294,14 @@ function remediationParagraph(check) {
|
|
|
9129
9294
|
return [
|
|
9130
9295
|
"mu talks to pi agents over a per-agent control socket served by",
|
|
9131
9296
|
"the mu pi extension. `missing`: no socket (extension not loaded,",
|
|
9132
|
-
"
|
|
9133
|
-
"
|
|
9297
|
+
"the agent predates `mu link pi`, or a new pi still waits at its",
|
|
9298
|
+
"project trust prompt: answer /trust in the pane, or spawn with",
|
|
9299
|
+
"--command 'pi --approve'). `refused`: socket present but not",
|
|
9300
|
+
"answering. `version`: protocol mismatch; upgrade mu or pi.",
|
|
9134
9301
|
"Run `mu link pi`, then respawn the agent. `extension X older than",
|
|
9135
|
-
"installed Y`: the agent's pi loaded an older mu
|
|
9136
|
-
"
|
|
9302
|
+
"installed Y` or `lacks ops`: the agent's pi loaded an older mu",
|
|
9303
|
+
"build; reload it with `mu agent send <a> '/reload'` (add",
|
|
9304
|
+
"`--via mux` when the extension lacks op command) or respawn it."
|
|
9137
9305
|
];
|
|
9138
9306
|
case "agents":
|
|
9139
9307
|
return [
|
|
@@ -9292,6 +9460,7 @@ var VERBS = {
|
|
|
9292
9460
|
"agent.close": "agent close",
|
|
9293
9461
|
"agent.adopt": "agent adopt",
|
|
9294
9462
|
"agent.kick": "agent kick",
|
|
9463
|
+
"agent.send": "agent send",
|
|
9295
9464
|
// Payload prose reads "agent stalled ..."; keep the verb matching it
|
|
9296
9465
|
// so the emitted text is not printed with a second, different verb.
|
|
9297
9466
|
"agent.stall": "agent stalled",
|
|
@@ -9414,6 +9583,7 @@ function renderKnown(row, intent) {
|
|
|
9414
9583
|
case "agent.close":
|
|
9415
9584
|
case "agent.adopt":
|
|
9416
9585
|
case "agent.kick":
|
|
9586
|
+
case "agent.send":
|
|
9417
9587
|
case "agent.stall":
|
|
9418
9588
|
case "workspace.create":
|
|
9419
9589
|
case "workspace.free":
|
|
@@ -11167,6 +11337,7 @@ export {
|
|
|
11167
11337
|
insertAgent,
|
|
11168
11338
|
inspectLinks,
|
|
11169
11339
|
installCapture,
|
|
11340
|
+
interruptAgent,
|
|
11170
11341
|
isKickSignal,
|
|
11171
11342
|
isPathInside,
|
|
11172
11343
|
isTaskStatus,
|