@mu-crew/mu 3.6.2 → 3.7.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 +208 -117
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +37 -10
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +20 -4
- package/dist/index.js +130 -57
- package/dist/index.js.map +1 -1
- package/docs/architecture/control-socket.md +2 -2
- package/docs/architecture/mux.md +4 -1
- package/docs/guide/delegate.md +7 -3
- package/docs/guide/dispatch.md +5 -2
- package/docs/guide/getting-started.md +1 -1
- package/docs/guide/recovery.md +11 -6
- package/docs/guide/upgrade.md +4 -4
- package/docs/reference/env.md +1 -1
- package/package.json +1 -1
- package/skills/mu/SKILL.md +6 -3
- package/skills/mu/recipes/codemode-driver.md +2 -2
- package/skills/mu/recipes/orchestrator-loop.md +10 -6
- package/skills/mu/recipes/remote-workers.md +23 -6
- package/skills/mu/recipes/tasks-or-calls.md +6 -2
package/dist/cli.js
CHANGED
|
@@ -861,6 +861,7 @@ ${GOALS_VIEW_SQL}
|
|
|
861
861
|
|
|
862
862
|
// src/ctl/path.ts
|
|
863
863
|
import { createHash } from "crypto";
|
|
864
|
+
import { rmSync } from "fs";
|
|
864
865
|
import { dirname as dirname2, join as join2 } from "path";
|
|
865
866
|
function sockPathUnder(dir, workstream, agent) {
|
|
866
867
|
const readable = join2(dir, workstream, `${agent}.sock`);
|
|
@@ -873,6 +874,13 @@ function ctlSocketPath(workstream, agent, stateDir) {
|
|
|
873
874
|
const base = stateDir ?? (dbPath ? dirname2(dbPath) : defaultStateDir());
|
|
874
875
|
return sockPathUnder(join2(base, "sock"), workstream, agent);
|
|
875
876
|
}
|
|
877
|
+
function unlinkCtlSocket(db, name, workstream) {
|
|
878
|
+
if (db.memory) return;
|
|
879
|
+
try {
|
|
880
|
+
rmSync(ctlSocketPath(workstream, name, dirname2(db.name)), { force: true });
|
|
881
|
+
} catch {
|
|
882
|
+
}
|
|
883
|
+
}
|
|
876
884
|
function remoteCtlSocketPath(workstream, agent, uid) {
|
|
877
885
|
return sockPathUnder(`/tmp/mu-${uid}`, workstream, agent);
|
|
878
886
|
}
|
|
@@ -2698,12 +2706,6 @@ var init_tmux2 = __esm({
|
|
|
2698
2706
|
function isWorkspaceStale(behind) {
|
|
2699
2707
|
return behind !== null && behind !== void 0 && behind >= WORKSPACE_STALE_THRESHOLD;
|
|
2700
2708
|
}
|
|
2701
|
-
function isLingeringScratchAgent(updatedAt, thresholdMs, now = Date.now()) {
|
|
2702
|
-
if (thresholdMs <= 0) return false;
|
|
2703
|
-
const updated = Date.parse(updatedAt);
|
|
2704
|
-
if (!Number.isFinite(updated)) return false;
|
|
2705
|
-
return now - updated >= thresholdMs;
|
|
2706
|
-
}
|
|
2707
2709
|
var WORKSPACE_STALE_THRESHOLD;
|
|
2708
2710
|
var init_staleness = __esm({
|
|
2709
2711
|
"src/staleness.ts"() {
|
|
@@ -2935,7 +2937,7 @@ var init_types2 = __esm({
|
|
|
2935
2937
|
|
|
2936
2938
|
// src/vcs/helpers.ts
|
|
2937
2939
|
import { execFile } from "child_process";
|
|
2938
|
-
import { existsSync, rmSync } from "fs";
|
|
2940
|
+
import { existsSync, rmSync as rmSync2 } from "fs";
|
|
2939
2941
|
import { mkdir } from "fs/promises";
|
|
2940
2942
|
import { dirname as dirname3 } from "path";
|
|
2941
2943
|
import { promisify } from "util";
|
|
@@ -2959,7 +2961,7 @@ async function ensureParent(path2) {
|
|
|
2959
2961
|
}
|
|
2960
2962
|
function rmDirSync(path2) {
|
|
2961
2963
|
if (!existsSync(path2)) return false;
|
|
2962
|
-
|
|
2964
|
+
rmSync2(path2, { recursive: true, force: true });
|
|
2963
2965
|
return true;
|
|
2964
2966
|
}
|
|
2965
2967
|
async function run(bin, args, cwd) {
|
|
@@ -3826,7 +3828,7 @@ var init_vcs2 = __esm({
|
|
|
3826
3828
|
});
|
|
3827
3829
|
|
|
3828
3830
|
// src/workspace/crud.ts
|
|
3829
|
-
import { existsSync as existsSync6, rmSync as
|
|
3831
|
+
import { existsSync as existsSync6, rmSync as rmSync3 } from "fs";
|
|
3830
3832
|
import { homedir as homedir2 } from "os";
|
|
3831
3833
|
import { resolve as resolve3 } from "path";
|
|
3832
3834
|
async function createWorkspace(db, opts) {
|
|
@@ -3852,7 +3854,7 @@ async function createWorkspace(db, opts) {
|
|
|
3852
3854
|
created = await backend.createWorkspace(createOpts);
|
|
3853
3855
|
} catch (err) {
|
|
3854
3856
|
try {
|
|
3855
|
-
|
|
3857
|
+
rmSync3(path2, { recursive: true, force: true });
|
|
3856
3858
|
} catch {
|
|
3857
3859
|
}
|
|
3858
3860
|
throw err;
|
|
@@ -4241,7 +4243,7 @@ var init_spawn_lock = __esm({
|
|
|
4241
4243
|
|
|
4242
4244
|
// src/agents/spawn.ts
|
|
4243
4245
|
import { execFile as execFile2 } from "child_process";
|
|
4244
|
-
import { mkdirSync as mkdirSync2, rmSync as
|
|
4246
|
+
import { mkdirSync as mkdirSync2, rmSync as rmSync4 } from "fs";
|
|
4245
4247
|
import { basename, dirname as dirname4 } from "path";
|
|
4246
4248
|
import { setTimeout as delay } from "timers/promises";
|
|
4247
4249
|
import { promisify as promisify2 } from "util";
|
|
@@ -4310,7 +4312,7 @@ function defaultSpawnCtlMs() {
|
|
|
4310
4312
|
if (Number.isNaN(parsed) || parsed < 0) return 3e4;
|
|
4311
4313
|
return parsed;
|
|
4312
4314
|
}
|
|
4313
|
-
async function awaitCtlHandshake(sock) {
|
|
4315
|
+
async function awaitCtlHandshake(sock, onMiss) {
|
|
4314
4316
|
const budgetMs = defaultSpawnCtlMs();
|
|
4315
4317
|
const deadline = Date.now() + budgetMs;
|
|
4316
4318
|
for (; ; ) {
|
|
@@ -4318,6 +4320,7 @@ async function awaitCtlHandshake(sock) {
|
|
|
4318
4320
|
const probe = await ctlProbe(sock, Math.max(1, Math.min(remaining, CTL_POLL_INTERVAL_MS * 4)));
|
|
4319
4321
|
if (probe.kind === "ok") return "ok";
|
|
4320
4322
|
const last = probeToSpawnCtl(probe.kind);
|
|
4323
|
+
if (onMiss) await onMiss();
|
|
4321
4324
|
const left = deadline - Date.now();
|
|
4322
4325
|
if (left <= 0) return last;
|
|
4323
4326
|
await delay(Math.min(CTL_POLL_INTERVAL_MS, left));
|
|
@@ -4347,7 +4350,7 @@ async function spawnAgent(db, opts) {
|
|
|
4347
4350
|
const workspacePathStr = opts.workspace ? await prestageWorkspace(db, opts, cli) : void 0;
|
|
4348
4351
|
const ctlSocket = ctlSocketPath(opts.workstream, opts.name, dirname4(db.name));
|
|
4349
4352
|
mkdirSync2(dirname4(ctlSocket), { recursive: true, mode: 448 });
|
|
4350
|
-
|
|
4353
|
+
rmSync4(ctlSocket, { force: true });
|
|
4351
4354
|
const paneEnv = {
|
|
4352
4355
|
MU_MANAGED_AGENT: "1",
|
|
4353
4356
|
MU_AGENT_NAME: opts.name,
|
|
@@ -4357,6 +4360,8 @@ async function spawnAgent(db, opts) {
|
|
|
4357
4360
|
const hasWorkspace = workspacePathStr !== void 0;
|
|
4358
4361
|
let paneId;
|
|
4359
4362
|
let agent;
|
|
4363
|
+
const wantsCtl = opts.ctl !== false && speaksMuCtl(cli, command) && defaultSpawnCtlMs() !== 0;
|
|
4364
|
+
let ctl;
|
|
4360
4365
|
try {
|
|
4361
4366
|
const created = await withSpawnLock(session, async () => {
|
|
4362
4367
|
const pid = await createOrReusePane({
|
|
@@ -4377,7 +4382,23 @@ async function spawnAgent(db, opts) {
|
|
|
4377
4382
|
agent = created.row;
|
|
4378
4383
|
const startAgent = (await activeMux()).startAgentInPane;
|
|
4379
4384
|
if (startAgent === void 0) {
|
|
4380
|
-
|
|
4385
|
+
if (wantsCtl) {
|
|
4386
|
+
const pid = paneId;
|
|
4387
|
+
const check = defaultSpawnLivenessMs() !== 0;
|
|
4388
|
+
const when = `during the ctl handshake (up to ${defaultSpawnCtlMs()}ms after spawn)`;
|
|
4389
|
+
let lastScrollback;
|
|
4390
|
+
const assertPaneAlive = async () => {
|
|
4391
|
+
const mux = await activeMux();
|
|
4392
|
+
const sb = await mux.capturePane(pid, { lines: 50 }).catch(() => void 0);
|
|
4393
|
+
lastScrollback = sb ?? lastScrollback;
|
|
4394
|
+
if (await mux.paneExists(pid)) return;
|
|
4395
|
+
throw new AgentDiedOnSpawnError(opts.name, pid, lastScrollback, command, when);
|
|
4396
|
+
};
|
|
4397
|
+
ctl = await awaitCtlHandshake(ctlSocket, check ? assertPaneAlive : void 0);
|
|
4398
|
+
if (check) await checkSpawnHealth(pid, opts.name, command, when);
|
|
4399
|
+
} else {
|
|
4400
|
+
await awaitSpawnLiveness(paneId, opts.name, command);
|
|
4401
|
+
}
|
|
4381
4402
|
} else {
|
|
4382
4403
|
await startAgent({ paneId, name: opts.name, cli, command, commandSource });
|
|
4383
4404
|
}
|
|
@@ -4386,14 +4407,13 @@ async function spawnAgent(db, opts) {
|
|
|
4386
4407
|
if (hasWorkspace) attachOrphanCleanupHint(err, opts.name, opts.workstream);
|
|
4387
4408
|
throw err;
|
|
4388
4409
|
}
|
|
4389
|
-
|
|
4410
|
+
ctl ??= wantsCtl ? await awaitCtlHandshake(ctlSocket) : "skipped";
|
|
4390
4411
|
emitEvent(
|
|
4391
4412
|
db,
|
|
4392
4413
|
opts.workstream,
|
|
4393
4414
|
"agent.spawn",
|
|
4394
4415
|
`agent spawn ${opts.name} (cli=${cli}, role=${opts.role ?? "full-access"}, pane=${paneId})`
|
|
4395
4416
|
);
|
|
4396
|
-
await refreshAgentTitle(db, opts.name, opts.workstream);
|
|
4397
4417
|
maybeWarnNonConventionalAgentName(opts.name);
|
|
4398
4418
|
return { ...agent, ctl, ctlSocket };
|
|
4399
4419
|
}
|
|
@@ -4490,15 +4510,18 @@ async function awaitSpawnLiveness(paneId, agentName, command) {
|
|
|
4490
4510
|
const ms = defaultSpawnLivenessMs();
|
|
4491
4511
|
if (ms === 0) return;
|
|
4492
4512
|
await sleep(ms);
|
|
4513
|
+
await checkSpawnHealth(paneId, agentName, command);
|
|
4514
|
+
}
|
|
4515
|
+
async function checkSpawnHealth(paneId, agentName, command, when) {
|
|
4493
4516
|
const mux = await activeMux();
|
|
4494
4517
|
const scrollback = await mux.capturePane(paneId, { lines: 50 }).catch(() => void 0);
|
|
4495
4518
|
if (!await mux.paneExists(paneId)) {
|
|
4496
|
-
throw new AgentDiedOnSpawnError(agentName, paneId, scrollback, command);
|
|
4519
|
+
throw new AgentDiedOnSpawnError(agentName, paneId, scrollback, command, when);
|
|
4497
4520
|
}
|
|
4498
4521
|
if (scrollback !== void 0) {
|
|
4499
4522
|
const matchedLine = detectSpawnStartupError(scrollback);
|
|
4500
4523
|
if (matchedLine !== void 0) {
|
|
4501
|
-
throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback);
|
|
4524
|
+
throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback, when);
|
|
4502
4525
|
}
|
|
4503
4526
|
}
|
|
4504
4527
|
}
|
|
@@ -4709,7 +4732,7 @@ var init_errors = __esm({
|
|
|
4709
4732
|
}
|
|
4710
4733
|
};
|
|
4711
4734
|
AgentDiedOnSpawnError = class extends Error {
|
|
4712
|
-
constructor(agentName, paneId, scrollback, command) {
|
|
4735
|
+
constructor(agentName, paneId, scrollback, command, when = `within ${defaultSpawnLivenessMs()}ms of spawn`) {
|
|
4713
4736
|
const tail = scrollback?.trim();
|
|
4714
4737
|
const detail = tail ? `
|
|
4715
4738
|
|
|
@@ -4718,9 +4741,7 @@ ${tail}
|
|
|
4718
4741
|
--- end scrollback ---` : "";
|
|
4719
4742
|
const remote = /^\s*ssh\s/.test(command ?? "");
|
|
4720
4743
|
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).";
|
|
4721
|
-
super(
|
|
4722
|
-
`agent ${agentName} died within ${defaultSpawnLivenessMs()}ms of spawn (pane ${paneId}). ${cause}${detail}`
|
|
4723
|
-
);
|
|
4744
|
+
super(`agent ${agentName} died ${when} (pane ${paneId}). ${cause}${detail}`);
|
|
4724
4745
|
this.agentName = agentName;
|
|
4725
4746
|
this.paneId = paneId;
|
|
4726
4747
|
this.scrollback = scrollback;
|
|
@@ -4779,9 +4800,9 @@ ${tail}
|
|
|
4779
4800
|
}
|
|
4780
4801
|
};
|
|
4781
4802
|
AgentSpawnStartupError = class extends Error {
|
|
4782
|
-
constructor(agentName, paneId, matchedLine, scrollback) {
|
|
4803
|
+
constructor(agentName, paneId, matchedLine, scrollback, when = `within ${defaultSpawnLivenessMs()}ms of spawn`) {
|
|
4783
4804
|
super(
|
|
4784
|
-
`agent ${agentName} reported a startup error
|
|
4805
|
+
`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.
|
|
4785
4806
|
|
|
4786
4807
|
Matched line: ${matchedLine.trim()}
|
|
4787
4808
|
|
|
@@ -4867,7 +4888,14 @@ ${scrollback.trim()}
|
|
|
4867
4888
|
kind;
|
|
4868
4889
|
name = "AgentCtlUnreachableError";
|
|
4869
4890
|
errorNextSteps() {
|
|
4891
|
+
const trust = this.kind === "missing" ? [
|
|
4892
|
+
{
|
|
4893
|
+
intent: "Just spawned? pi may sit at its project trust prompt: answer /trust in the pane, or respawn with --command 'pi --approve'",
|
|
4894
|
+
command: `mu agent read ${this.agentName} -w ${this.workstream}`
|
|
4895
|
+
}
|
|
4896
|
+
] : [];
|
|
4870
4897
|
return [
|
|
4898
|
+
...trust,
|
|
4871
4899
|
{ intent: "Check whether the mu pi extension is linked", command: "mu doctor" },
|
|
4872
4900
|
{
|
|
4873
4901
|
intent: "Stop a runaway tool without the socket",
|
|
@@ -4950,7 +4978,7 @@ ${scrollback.trim()}
|
|
|
4950
4978
|
}
|
|
4951
4979
|
};
|
|
4952
4980
|
AgentExtensionOutdatedError = class extends Error {
|
|
4953
|
-
constructor(agentName, workstream, op, extVersion) {
|
|
4981
|
+
constructor(agentName, workstream, op, extVersion, ops) {
|
|
4954
4982
|
super(
|
|
4955
4983
|
`agent ${agentName}'s mu pi extension (${extVersion ?? "version unknown"}) predates op ${op}: its pi loaded an older mu build; reload or respawn it`
|
|
4956
4984
|
);
|
|
@@ -4958,18 +4986,21 @@ ${scrollback.trim()}
|
|
|
4958
4986
|
this.workstream = workstream;
|
|
4959
4987
|
this.op = op;
|
|
4960
4988
|
this.extVersion = extVersion;
|
|
4989
|
+
this.ops = ops;
|
|
4961
4990
|
}
|
|
4962
4991
|
agentName;
|
|
4963
4992
|
workstream;
|
|
4964
4993
|
op;
|
|
4965
4994
|
extVersion;
|
|
4995
|
+
ops;
|
|
4966
4996
|
name = "AgentExtensionOutdatedError";
|
|
4967
4997
|
errorNextSteps() {
|
|
4968
4998
|
const w = `-w ${this.workstream}`;
|
|
4999
|
+
const via = this.ops?.includes("command") ? "" : " --via mux";
|
|
4969
5000
|
return [
|
|
4970
5001
|
{
|
|
4971
|
-
intent: "Reload pi's extensions
|
|
4972
|
-
command: `mu agent send ${this.agentName} '/reload'
|
|
5002
|
+
intent: "Reload pi's extensions (keeps the session and context)",
|
|
5003
|
+
command: `mu agent send ${this.agentName} '/reload'${via} ${w}`
|
|
4973
5004
|
},
|
|
4974
5005
|
{
|
|
4975
5006
|
intent: "Or respawn the agent (loses its context)",
|
|
@@ -5058,7 +5089,7 @@ async function extensionOutdated(agent, sock, e) {
|
|
|
5058
5089
|
if (hello.ok) extVersion = hello.extVersion;
|
|
5059
5090
|
} catch {
|
|
5060
5091
|
}
|
|
5061
|
-
return new AgentExtensionOutdatedError(agent.name, agent.workstreamName, e.op, extVersion);
|
|
5092
|
+
return new AgentExtensionOutdatedError(agent.name, agent.workstreamName, e.op, extVersion, e.ops);
|
|
5062
5093
|
}
|
|
5063
5094
|
async function ctlRequestFor(agent, sock, req) {
|
|
5064
5095
|
try {
|
|
@@ -5107,7 +5138,11 @@ async function sendViaTransport(agent, text2, opts) {
|
|
|
5107
5138
|
}
|
|
5108
5139
|
throw new Error(`control socket refused the send: ${reply.error}`);
|
|
5109
5140
|
}
|
|
5110
|
-
return
|
|
5141
|
+
return {
|
|
5142
|
+
transport,
|
|
5143
|
+
...reply.state !== void 0 ? { state: reply.state } : {},
|
|
5144
|
+
...reply.runs !== void 0 ? { runs: reply.runs } : {}
|
|
5145
|
+
};
|
|
5111
5146
|
}
|
|
5112
5147
|
async function runSessionCommand(agent, sock, cmd, force) {
|
|
5113
5148
|
const reply = await ctlRequestFor(agent, sock, {
|
|
@@ -5301,7 +5336,8 @@ async function readAgentStates(agents, opts = {}) {
|
|
|
5301
5336
|
since: probe.status.since,
|
|
5302
5337
|
alive: true,
|
|
5303
5338
|
ctl: "ok",
|
|
5304
|
-
pending: probe.status.pending
|
|
5339
|
+
pending: probe.status.pending,
|
|
5340
|
+
runs: probe.status.runs
|
|
5305
5341
|
});
|
|
5306
5342
|
} else {
|
|
5307
5343
|
failed.set(agentKey(agent), probe?.kind === "missing" ? "missing" : "refused");
|
|
@@ -5857,6 +5893,15 @@ var init_id = __esm({
|
|
|
5857
5893
|
});
|
|
5858
5894
|
|
|
5859
5895
|
// src/tasks/errors.ts
|
|
5896
|
+
function stallWaitHint(owner, ws, runs) {
|
|
5897
|
+
if (runs === void 0 || runs < 1) return [];
|
|
5898
|
+
return [
|
|
5899
|
+
{
|
|
5900
|
+
intent: "Read the worker's last answer (its final text, at once)",
|
|
5901
|
+
command: `mu agent wait ${owner} --after-runs ${runs - 1} --json -w ${ws}`
|
|
5902
|
+
}
|
|
5903
|
+
];
|
|
5904
|
+
}
|
|
5860
5905
|
var TaskNotFoundError, TaskIdInvalidError, TaskExistsError, TaskNotInWorkstreamError, TaskAlreadyOwnedError, ClaimerNotRegisteredError, TaskClaimStaleWorkspaceError, CycleError, ReaperDetectedDuringWaitError, StallDetectedDuringWaitError, CrossWorkstreamEdgeError, SubstateReasonRequiredError, TaskParkStateError, TaskInTriageError, TaskParkedError, InvalidSubstateError;
|
|
5861
5906
|
var init_errors2 = __esm({
|
|
5862
5907
|
"src/tasks/errors.ts"() {
|
|
@@ -6079,7 +6124,7 @@ var init_errors2 = __esm({
|
|
|
6079
6124
|
}
|
|
6080
6125
|
};
|
|
6081
6126
|
StallDetectedDuringWaitError = class extends Error {
|
|
6082
|
-
constructor(taskName, owner, workstream, ageSecs, ownerExpectsCtl = false) {
|
|
6127
|
+
constructor(taskName, owner, workstream, ageSecs, ownerExpectsCtl = false, ownerRuns) {
|
|
6083
6128
|
const ownerBit = owner !== null ? owner : "<unknown>";
|
|
6084
6129
|
super(
|
|
6085
6130
|
`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.`
|
|
@@ -6089,12 +6134,14 @@ var init_errors2 = __esm({
|
|
|
6089
6134
|
this.workstream = workstream;
|
|
6090
6135
|
this.ageSecs = ageSecs;
|
|
6091
6136
|
this.ownerExpectsCtl = ownerExpectsCtl;
|
|
6137
|
+
this.ownerRuns = ownerRuns;
|
|
6092
6138
|
}
|
|
6093
6139
|
taskName;
|
|
6094
6140
|
owner;
|
|
6095
6141
|
workstream;
|
|
6096
6142
|
ageSecs;
|
|
6097
6143
|
ownerExpectsCtl;
|
|
6144
|
+
ownerRuns;
|
|
6098
6145
|
name = "StallDetectedDuringWaitError";
|
|
6099
6146
|
/**
|
|
6100
6147
|
* Diagnose before acting. `mu agent read` comes FIRST because the
|
|
@@ -6109,6 +6156,7 @@ var init_errors2 = __esm({
|
|
|
6109
6156
|
const ws = this.workstream;
|
|
6110
6157
|
const ownerBit = this.owner !== null ? this.owner : "<owner>";
|
|
6111
6158
|
return [
|
|
6159
|
+
...stallWaitHint(ownerBit, ws, this.ownerRuns),
|
|
6112
6160
|
{
|
|
6113
6161
|
intent: "Read the worker's pane to see what it is waiting on",
|
|
6114
6162
|
command: `mu agent read ${ownerBit} -w ${ws} --lines 60`
|
|
@@ -6460,7 +6508,7 @@ var init_edges = __esm({
|
|
|
6460
6508
|
|
|
6461
6509
|
// src/workstream.ts
|
|
6462
6510
|
import { existsSync as existsSync9, readdirSync as readdirSync2, rmdirSync } from "fs";
|
|
6463
|
-
import { join as join8 } from "path";
|
|
6511
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
6464
6512
|
async function listMuxSessions() {
|
|
6465
6513
|
try {
|
|
6466
6514
|
return await (await activeMux()).listSessions();
|
|
@@ -6614,6 +6662,7 @@ async function teardownWorkstream(db, opts) {
|
|
|
6614
6662
|
const notesBefore = countNotes(db, opts.workstream);
|
|
6615
6663
|
const edgesBefore = countEdges(db, opts.workstream);
|
|
6616
6664
|
const workspacesBefore = listWorkspaces2(db, opts.workstream);
|
|
6665
|
+
const agentNames = listAgentNames(db, opts.workstream);
|
|
6617
6666
|
const mux = await activeMux();
|
|
6618
6667
|
const muxAliveBefore = await mux.sessionExists(muxSession);
|
|
6619
6668
|
if (muxAliveBefore) {
|
|
@@ -6651,6 +6700,14 @@ async function teardownWorkstream(db, opts) {
|
|
|
6651
6700
|
} catch {
|
|
6652
6701
|
}
|
|
6653
6702
|
}
|
|
6703
|
+
for (const name of agentNames) unlinkCtlSocket(db, name, opts.workstream);
|
|
6704
|
+
if (!db.memory) {
|
|
6705
|
+
const sockDir = join8(dirname6(db.name), "sock", opts.workstream);
|
|
6706
|
+
try {
|
|
6707
|
+
if (readdirSync2(sockDir).length === 0) rmdirSync(sockDir);
|
|
6708
|
+
} catch {
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6654
6711
|
withOpContext(
|
|
6655
6712
|
db,
|
|
6656
6713
|
{ intent: "workstream.teardown", group: "new" },
|
|
@@ -6667,6 +6724,14 @@ async function teardownWorkstream(db, opts) {
|
|
|
6667
6724
|
failedWorkspaces
|
|
6668
6725
|
};
|
|
6669
6726
|
}
|
|
6727
|
+
function listAgentNames(db, workstream) {
|
|
6728
|
+
const rows = db.prepare(
|
|
6729
|
+
`SELECT a.name FROM agents a
|
|
6730
|
+
JOIN workstreams ws ON ws.id = a.workstream_id
|
|
6731
|
+
WHERE ws.name = ?`
|
|
6732
|
+
).all(workstream);
|
|
6733
|
+
return rows.map((r) => r.name);
|
|
6734
|
+
}
|
|
6670
6735
|
function countAgents(db, workstream) {
|
|
6671
6736
|
const row2 = db.prepare(
|
|
6672
6737
|
`SELECT COUNT(*) AS n FROM agents a
|
|
@@ -6707,6 +6772,7 @@ var WORKSTREAM_NAME_RE, RESERVED_WORKSTREAM_PREFIX, RESERVED_WORKSTREAM_NAMES, W
|
|
|
6707
6772
|
var init_workstream = __esm({
|
|
6708
6773
|
"src/workstream.ts"() {
|
|
6709
6774
|
"use strict";
|
|
6775
|
+
init_path();
|
|
6710
6776
|
init_db();
|
|
6711
6777
|
init_legacy_ops();
|
|
6712
6778
|
init_mux2();
|
|
@@ -7360,12 +7426,15 @@ async function waitForTasks(db, input, opts) {
|
|
|
7360
7426
|
const ctlBrokenSince = /* @__PURE__ */ new Map();
|
|
7361
7427
|
const stuckLabel = /* @__PURE__ */ new Map();
|
|
7362
7428
|
const ctlOwners = /* @__PURE__ */ new Set();
|
|
7429
|
+
const ownerRuns = /* @__PURE__ */ new Map();
|
|
7363
7430
|
const stuckAgeMs = async (status, owner, workstreamName) => {
|
|
7364
7431
|
if (stuckAfterMs <= 0 || status !== "IN_PROGRESS" || owner === null || opts.readOwnerState === void 0)
|
|
7365
7432
|
return null;
|
|
7366
7433
|
const reading = await opts.readOwnerState({ name: owner, workstreamName });
|
|
7367
7434
|
const ownerKey = `${workstreamName}/${owner}`;
|
|
7368
7435
|
if (reading?.ctl !== void 0) ctlOwners.add(ownerKey);
|
|
7436
|
+
if (reading?.runs !== void 0) ownerRuns.set(ownerKey, reading.runs);
|
|
7437
|
+
else ownerRuns.delete(ownerKey);
|
|
7369
7438
|
if (reading?.alive === true && (reading.ctl === "missing" || reading.ctl === "refused")) {
|
|
7370
7439
|
const firstSeen = ctlBrokenSince.get(ownerKey) ?? Date.now();
|
|
7371
7440
|
ctlBrokenSince.set(ownerKey, firstSeen);
|
|
@@ -7395,10 +7464,13 @@ async function waitForTasks(db, input, opts) {
|
|
|
7395
7464
|
stuckWarned.add(key);
|
|
7396
7465
|
const ageSecs = Math.round(ageMs2 / 1e3);
|
|
7397
7466
|
const ownerBit = owner ?? "<none>";
|
|
7467
|
+
const runs = ownerRuns.get(`${ref.workstreamName}/${ownerBit}`);
|
|
7398
7468
|
const stateBit = stuckLabel.get(`${ref.workstreamName}/${ownerBit}`) ?? "needs_input";
|
|
7469
|
+
const waitLine = stallWaitHint(ownerBit, ref.workstreamName, runs).map((s) => ` ${s.command}
|
|
7470
|
+
`).join("");
|
|
7399
7471
|
currentStuckWarn(
|
|
7400
7472
|
`\x1B[33mmu task wait: ${key} needs attention \u2014 owner=${ownerBit} has been in ${stateBit} for ${formatStallAge(ageMs2)}. It may have finished without closing, be waiting on an answer, or be sitting at a prompt.\x1B[0m
|
|
7401
|
-
mu agent read ${ownerBit} -w ${ref.workstreamName} --lines 60
|
|
7473
|
+
` + waitLine + ` mu agent read ${ownerBit} -w ${ref.workstreamName} --lines 60
|
|
7402
7474
|
`
|
|
7403
7475
|
);
|
|
7404
7476
|
emitEvent(
|
|
@@ -7414,7 +7486,8 @@ async function waitForTasks(db, input, opts) {
|
|
|
7414
7486
|
owner,
|
|
7415
7487
|
ref.workstreamName,
|
|
7416
7488
|
ageSecs,
|
|
7417
|
-
ctlOwners.has(`${ref.workstreamName}/${owner ?? ""}`)
|
|
7489
|
+
ctlOwners.has(`${ref.workstreamName}/${owner ?? ""}`),
|
|
7490
|
+
runs
|
|
7418
7491
|
);
|
|
7419
7492
|
}
|
|
7420
7493
|
}
|
|
@@ -7930,8 +8003,7 @@ var init_wait2 = __esm({
|
|
|
7930
8003
|
});
|
|
7931
8004
|
|
|
7932
8005
|
// src/agents.ts
|
|
7933
|
-
import {
|
|
7934
|
-
import { dirname as dirname6 } from "path";
|
|
8006
|
+
import { dirname as dirname7 } from "path";
|
|
7935
8007
|
function idleThresholdMs() {
|
|
7936
8008
|
const env = process.env.MU_IDLE_THRESHOLD_MS;
|
|
7937
8009
|
if (env === void 0 || env === "") return DEFAULT_IDLE_THRESHOLD_MS;
|
|
@@ -8050,13 +8122,6 @@ function deleteAgent(db, name, workstream) {
|
|
|
8050
8122
|
if (deleted) unlinkCtlSocket(db, name, workstream);
|
|
8051
8123
|
return deleted;
|
|
8052
8124
|
}
|
|
8053
|
-
function unlinkCtlSocket(db, name, workstream) {
|
|
8054
|
-
if (db.memory) return;
|
|
8055
|
-
try {
|
|
8056
|
-
rmSync4(ctlSocketPath(workstream, name, dirname6(db.name)), { force: true });
|
|
8057
|
-
} catch {
|
|
8058
|
-
}
|
|
8059
|
-
}
|
|
8060
8125
|
function deleteAgentRow(db, name, workstream) {
|
|
8061
8126
|
return withOpContext(
|
|
8062
8127
|
db,
|
|
@@ -8151,7 +8216,7 @@ async function listLiveAgents(db, opts) {
|
|
|
8151
8216
|
...opts.mode !== void 0 ? { mode: opts.mode } : {}
|
|
8152
8217
|
});
|
|
8153
8218
|
const baseAgents = listAgents(db, { workstream: opts.workstream });
|
|
8154
|
-
const readings = await readAgentStates(baseAgents, { stateDir:
|
|
8219
|
+
const readings = await readAgentStates(baseAgents, { stateDir: dirname7(db.name) });
|
|
8155
8220
|
const now = Date.now();
|
|
8156
8221
|
const agents = baseAgents.map((agent) => {
|
|
8157
8222
|
const reading = readings.get(agentKey(agent)) ?? {
|
|
@@ -8708,7 +8773,7 @@ var init_apply = __esm({
|
|
|
8708
8773
|
|
|
8709
8774
|
// src/rebuild.ts
|
|
8710
8775
|
import { existsSync as existsSync10, mkdirSync as mkdirSync3, statSync } from "fs";
|
|
8711
|
-
import { dirname as
|
|
8776
|
+
import { dirname as dirname9 } from "path";
|
|
8712
8777
|
function isProjectable(entity, intent) {
|
|
8713
8778
|
if (isLegacyLogOnlyIntent(intent)) return false;
|
|
8714
8779
|
if (LOG_ONLY_ENTITIES.has(entity)) return false;
|
|
@@ -8739,7 +8804,7 @@ function rebuildInto(source, opts) {
|
|
|
8739
8804
|
if (existsSync10(targetPath) && opts.force !== true) {
|
|
8740
8805
|
throw new RebuildTargetExistsError(targetPath);
|
|
8741
8806
|
}
|
|
8742
|
-
mkdirSync3(
|
|
8807
|
+
mkdirSync3(dirname9(targetPath), { recursive: true });
|
|
8743
8808
|
const identity = source.prepare(
|
|
8744
8809
|
"SELECT machine_id, hostname, created_at, last_wall, last_counter FROM machine_identity WHERE id = 1"
|
|
8745
8810
|
).get();
|
|
@@ -9321,13 +9386,13 @@ import {
|
|
|
9321
9386
|
writeFileSync
|
|
9322
9387
|
} from "fs";
|
|
9323
9388
|
import { homedir as homedir4 } from "os";
|
|
9324
|
-
import { dirname as
|
|
9389
|
+
import { dirname as dirname10, join as join10, resolve as resolve5 } from "path";
|
|
9325
9390
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
9326
9391
|
function resolveHome(opts) {
|
|
9327
9392
|
return opts?.home ?? process.env.MU_PI_HOME ?? homedir4();
|
|
9328
9393
|
}
|
|
9329
9394
|
function packageRoot() {
|
|
9330
|
-
return resolve5(
|
|
9395
|
+
return resolve5(dirname10(fileURLToPath(import.meta.url)), "..");
|
|
9331
9396
|
}
|
|
9332
9397
|
function extensionEntry() {
|
|
9333
9398
|
return process.env.MU_EXTENSION_ENTRY ?? join10(packageRoot(), "dist", "extension", "mu-pi.js");
|
|
@@ -9363,7 +9428,7 @@ function linkPi(opts) {
|
|
|
9363
9428
|
replacedCopy = !readFileSync(path2, "utf8").includes(MU_SHIM_MARKER);
|
|
9364
9429
|
} catch {
|
|
9365
9430
|
}
|
|
9366
|
-
mkdirSync4(
|
|
9431
|
+
mkdirSync4(dirname10(path2), { recursive: true });
|
|
9367
9432
|
writeFileSync(path2, opts?.copy === true ? readFileSync(entry, "utf8") : shim(entry));
|
|
9368
9433
|
return { path: path2, replacedCopy };
|
|
9369
9434
|
}
|
|
@@ -9374,7 +9439,7 @@ function linkSkill(opts) {
|
|
|
9374
9439
|
const st = lstatOrUndefined(path2);
|
|
9375
9440
|
if (st) {
|
|
9376
9441
|
if (!st.isSymbolicLink()) throw new LinkConflictError(path2, "not-a-symlink");
|
|
9377
|
-
const current = resolve5(
|
|
9442
|
+
const current = resolve5(dirname10(path2), readlinkSync(path2));
|
|
9378
9443
|
if (current === target) return { path: path2, target };
|
|
9379
9444
|
if (existsSync12(current) && opts?.force !== true) {
|
|
9380
9445
|
throw new LinkConflictError(path2, "foreign-symlink", current);
|
|
@@ -9382,7 +9447,7 @@ function linkSkill(opts) {
|
|
|
9382
9447
|
previous = current;
|
|
9383
9448
|
rmSync6(path2);
|
|
9384
9449
|
}
|
|
9385
|
-
mkdirSync4(
|
|
9450
|
+
mkdirSync4(dirname10(path2), { recursive: true });
|
|
9386
9451
|
symlinkSync(target, path2, "dir");
|
|
9387
9452
|
return previous === void 0 ? { path: path2, target } : { path: path2, target, previous };
|
|
9388
9453
|
}
|
|
@@ -9416,7 +9481,7 @@ function inspectSkill(path2) {
|
|
|
9416
9481
|
try {
|
|
9417
9482
|
target = realpathSync(path2);
|
|
9418
9483
|
} catch {
|
|
9419
|
-
return { path: path2, state: "dangling", target: resolve5(
|
|
9484
|
+
return { path: path2, state: "dangling", target: resolve5(dirname10(path2), readlinkSync(path2)) };
|
|
9420
9485
|
}
|
|
9421
9486
|
let skillMd;
|
|
9422
9487
|
try {
|
|
@@ -9467,7 +9532,7 @@ var init_link = __esm({
|
|
|
9467
9532
|
|
|
9468
9533
|
// src/doctor-summary.ts
|
|
9469
9534
|
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync13, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
|
|
9470
|
-
import { delimiter as delimiter2, dirname as
|
|
9535
|
+
import { delimiter as delimiter2, dirname as dirname11, join as join11, parse } from "path";
|
|
9471
9536
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9472
9537
|
function loadDoctorSummary(db, snapshot2, agentStateSource = "murmur") {
|
|
9473
9538
|
const checks = [];
|
|
@@ -9628,7 +9693,7 @@ function resolveExecutable(name) {
|
|
|
9628
9693
|
return null;
|
|
9629
9694
|
}
|
|
9630
9695
|
function murmurVersion(executable) {
|
|
9631
|
-
let dir =
|
|
9696
|
+
let dir = dirname11(executable);
|
|
9632
9697
|
const root = parse(dir).root;
|
|
9633
9698
|
while (dir !== root) {
|
|
9634
9699
|
const packagePath = join11(dir, "package.json");
|
|
@@ -9641,7 +9706,7 @@ function murmurVersion(executable) {
|
|
|
9641
9706
|
} catch {
|
|
9642
9707
|
}
|
|
9643
9708
|
}
|
|
9644
|
-
dir =
|
|
9709
|
+
dir = dirname11(dir);
|
|
9645
9710
|
}
|
|
9646
9711
|
return null;
|
|
9647
9712
|
}
|
|
@@ -9676,7 +9741,7 @@ function murmurDoctorCheck(agentStateSource, opts = {}) {
|
|
|
9676
9741
|
return {
|
|
9677
9742
|
name: "murmur",
|
|
9678
9743
|
status: "warn",
|
|
9679
|
-
detail: `murmur ${version} is older than 1.0.0; @murmur_pane_since missing, idle/stall timing unknown`
|
|
9744
|
+
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`
|
|
9680
9745
|
};
|
|
9681
9746
|
}
|
|
9682
9747
|
return {
|
|
@@ -9730,7 +9795,7 @@ function skillDoctorCheck(opts) {
|
|
|
9730
9795
|
}
|
|
9731
9796
|
function installedMuVersion() {
|
|
9732
9797
|
try {
|
|
9733
|
-
const pkgPath = join11(
|
|
9798
|
+
const pkgPath = join11(dirname11(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
9734
9799
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
9735
9800
|
const v = typeof pkg === "object" && pkg !== null ? pkg.version : null;
|
|
9736
9801
|
return typeof v === "string" ? v : null;
|
|
@@ -9774,7 +9839,10 @@ async function ctlSocketsDoctorCheck(agents, opts = {}) {
|
|
|
9774
9839
|
socket,
|
|
9775
9840
|
probe: probe.kind,
|
|
9776
9841
|
...probe.kind === "ok" && probe.extVersion !== void 0 ? { extVersion: probe.extVersion } : {},
|
|
9777
|
-
...older || missingOps.length > 0 ? {
|
|
9842
|
+
...older || missingOps.length > 0 ? {
|
|
9843
|
+
outdated: true,
|
|
9844
|
+
reloadVia: probe.kind === "ok" && probe.ops?.includes("command") ? "ctl" : "mux"
|
|
9845
|
+
} : {},
|
|
9778
9846
|
...missingOps.length > 0 ? { missingOps } : {}
|
|
9779
9847
|
};
|
|
9780
9848
|
})
|
|
@@ -9860,11 +9928,14 @@ function remediationParagraph(check) {
|
|
|
9860
9928
|
return [
|
|
9861
9929
|
"mu talks to pi agents over a per-agent control socket served by",
|
|
9862
9930
|
"the mu pi extension. `missing`: no socket (extension not loaded,",
|
|
9863
|
-
"
|
|
9864
|
-
"
|
|
9931
|
+
"the agent predates `mu link pi`, or a new pi still waits at its",
|
|
9932
|
+
"project trust prompt: answer /trust in the pane, or spawn with",
|
|
9933
|
+
"--command 'pi --approve'). `refused`: socket present but not",
|
|
9934
|
+
"answering. `version`: protocol mismatch; upgrade mu or pi.",
|
|
9865
9935
|
"Run `mu link pi`, then respawn the agent. `extension X older than",
|
|
9866
|
-
"installed Y`: the agent's pi loaded an older mu
|
|
9867
|
-
"
|
|
9936
|
+
"installed Y` or `lacks ops`: the agent's pi loaded an older mu",
|
|
9937
|
+
"build; reload it with `mu agent send <a> '/reload'` (add",
|
|
9938
|
+
"`--via mux` when the extension lacks op command) or respawn it."
|
|
9868
9939
|
];
|
|
9869
9940
|
case "agents":
|
|
9870
9941
|
return [
|
|
@@ -18608,14 +18679,14 @@ var init_tui = __esm({
|
|
|
18608
18679
|
// src/cli.ts
|
|
18609
18680
|
init_agents();
|
|
18610
18681
|
import { readFileSync as readFileSync4, realpathSync as realpathSync4 } from "fs";
|
|
18611
|
-
import { dirname as
|
|
18682
|
+
import { dirname as dirname17, join as join14 } from "path";
|
|
18612
18683
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
|
|
18613
18684
|
import { Command, InvalidArgumentError } from "commander";
|
|
18614
18685
|
|
|
18615
18686
|
// src/cli/agents.ts
|
|
18616
18687
|
init_agent_state();
|
|
18617
18688
|
init_agents();
|
|
18618
|
-
import { dirname as
|
|
18689
|
+
import { dirname as dirname13 } from "path";
|
|
18619
18690
|
init_client();
|
|
18620
18691
|
init_mux2();
|
|
18621
18692
|
init_output();
|
|
@@ -18715,7 +18786,7 @@ function formatStaleWorkspaceWarning(staleness) {
|
|
|
18715
18786
|
|
|
18716
18787
|
// src/cli/agents-remote.ts
|
|
18717
18788
|
init_agents();
|
|
18718
|
-
import { dirname as
|
|
18789
|
+
import { dirname as dirname8 } from "path";
|
|
18719
18790
|
init_path();
|
|
18720
18791
|
init_output();
|
|
18721
18792
|
init_workstream();
|
|
@@ -18731,7 +18802,7 @@ function buildRemoteEnv(db, agent, workstream, remoteSock) {
|
|
|
18731
18802
|
if (!isValidWorkstreamName(workstream)) {
|
|
18732
18803
|
throw new UsageError(`invalid workstream name ${JSON.stringify(workstream)}`);
|
|
18733
18804
|
}
|
|
18734
|
-
const localSock = ctlSocketPath(workstream, agent,
|
|
18805
|
+
const localSock = ctlSocketPath(workstream, agent, dirname8(db.name));
|
|
18735
18806
|
const remote = remoteSock ?? remoteCtlSocketPath(workstream, agent, process.getuid?.() ?? 0);
|
|
18736
18807
|
if (!remote.startsWith("/") || !SAFE_WORD.test(remote)) {
|
|
18737
18808
|
throw new UsageError(
|
|
@@ -19210,12 +19281,13 @@ init_tasks();
|
|
|
19210
19281
|
init_agent_state();
|
|
19211
19282
|
init_errors();
|
|
19212
19283
|
init_agents();
|
|
19213
|
-
import { dirname as
|
|
19284
|
+
import { dirname as dirname12 } from "path";
|
|
19214
19285
|
init_db();
|
|
19215
19286
|
init_glyphs();
|
|
19216
19287
|
init_output();
|
|
19217
19288
|
init_reconcile();
|
|
19218
19289
|
init_state();
|
|
19290
|
+
init_errors2();
|
|
19219
19291
|
init_tasks();
|
|
19220
19292
|
init_vcs2();
|
|
19221
19293
|
init_workspace();
|
|
@@ -19446,7 +19518,7 @@ async function cmdTaskWait(db, ids, opts) {
|
|
|
19446
19518
|
const agent = getAgent(db, owner, ref.workstreamName);
|
|
19447
19519
|
return agent === void 0 ? [] : [agent];
|
|
19448
19520
|
});
|
|
19449
|
-
ownerReadings = await readAgentStates(owners, { stateDir:
|
|
19521
|
+
ownerReadings = await readAgentStates(owners, { stateDir: dirname12(db.name) });
|
|
19450
19522
|
if (!reaperExitEnabled) return;
|
|
19451
19523
|
for (const ref of refs) {
|
|
19452
19524
|
const key = qualifiedId(ref);
|
|
@@ -19498,6 +19570,10 @@ async function cmdTaskWait(db, ids, opts) {
|
|
|
19498
19570
|
}
|
|
19499
19571
|
for (const t of unmetRefs) {
|
|
19500
19572
|
if (t.stuck && t.owner !== null) {
|
|
19573
|
+
const runs = ownerReadings.get(
|
|
19574
|
+
agentKey({ name: t.owner, workstreamName: t.workstreamName })
|
|
19575
|
+
)?.runs;
|
|
19576
|
+
nextSteps3.push(...stallWaitHint(t.owner, t.workstreamName, runs));
|
|
19501
19577
|
nextSteps3.push({
|
|
19502
19578
|
intent: `Read ${t.owner}'s pane \u2014 ${qualifiedId(t)} is IN_PROGRESS but its owner needs attention`,
|
|
19503
19579
|
command: `mu agent read ${t.owner} -w ${t.workstreamName} --lines 60`
|
|
@@ -20305,8 +20381,6 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20305
20381
|
});
|
|
20306
20382
|
let undelivered;
|
|
20307
20383
|
const agentRow = getAgent(db, name, ws);
|
|
20308
|
-
const plainCtl = agentRow !== void 0 && !opts.fresh && !opts.steer && parseSessionCommand(text2) === void 0 && (via ?? chooseTransport(agentRow, agentCtlSocket(db, agentRow))) === "ctl";
|
|
20309
|
-
const before = plainCtl && agentRow !== void 0 ? await ctlProbe(agentCtlSocket(db, agentRow)) : void 0;
|
|
20310
20384
|
const sent = await sendToAgent(db, name, text2, {
|
|
20311
20385
|
workstream: ws,
|
|
20312
20386
|
...opts.steer ? { mode: "steer" } : {},
|
|
@@ -20318,11 +20392,15 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20318
20392
|
}
|
|
20319
20393
|
});
|
|
20320
20394
|
const nextSteps3 = [
|
|
20321
|
-
|
|
20395
|
+
sent.transport === "ctl" && sent.runs !== void 0 ? {
|
|
20396
|
+
intent: "Wait for the response (returns its final text)",
|
|
20397
|
+
command: `mu agent wait ${name} --after-runs ${sent.runs} --json -w ${ws}`
|
|
20398
|
+
} : { intent: "Read response", command: `mu agent read ${name} -n 50 -w ${ws}` },
|
|
20322
20399
|
{ intent: "Watch live events", command: `mu log -w ${ws} --tail` }
|
|
20323
20400
|
];
|
|
20324
|
-
|
|
20325
|
-
|
|
20401
|
+
const plainCtl = sent.transport === "ctl" && !opts.fresh && !opts.steer && sent.command === void 0;
|
|
20402
|
+
if (agentRow !== void 0 && plainCtl && sent.state !== void 0 && sent.runs !== void 0) {
|
|
20403
|
+
nextSteps3.push(...plainSendHints(agentRow, { state: sent.state, runs: sent.runs }));
|
|
20326
20404
|
}
|
|
20327
20405
|
if (stalenessCheck.warned && stalenessCheck.nextStep !== null) {
|
|
20328
20406
|
nextSteps3.push(stalenessCheck.nextStep);
|
|
@@ -20341,6 +20419,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20341
20419
|
fresh: opts.fresh === true,
|
|
20342
20420
|
...sent.command !== void 0 ? { command: sent.command } : {},
|
|
20343
20421
|
...sent.state !== void 0 ? { state: sent.state } : {},
|
|
20422
|
+
...sent.runs !== void 0 ? { runs: sent.runs } : {},
|
|
20344
20423
|
delivered: undelivered === void 0,
|
|
20345
20424
|
...undelivered !== void 0 ? { undelivered: { reason: undelivered.reason, message: undelivered.message } } : {},
|
|
20346
20425
|
staleness: stalenessCheck.staleness,
|
|
@@ -20351,7 +20430,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20351
20430
|
if (undelivered !== void 0) {
|
|
20352
20431
|
console.error(pc.yellow(`warning: ${undelivered.message}`));
|
|
20353
20432
|
} else {
|
|
20354
|
-
const how = sent.transport === "mux" ? "via mux paste" : opts.fresh ? "via ctl, fresh session" : sent.command !== void 0 ? `via ctl, pi ran /${sent.command}` : `via ctl, pi ${sent.state ?? "?"}`;
|
|
20433
|
+
const how = sent.transport === "mux" ? "via mux paste" : opts.fresh ? "via ctl, fresh session" : sent.command !== void 0 ? `via ctl, pi ran /${sent.command}` : sent.runs !== void 0 ? `via ctl, pi was ${sent.state ?? "?"}, runs ${sent.runs}` : `via ctl, pi ${sent.state ?? "?"}`;
|
|
20355
20434
|
console.log(pc.dim(`sent ${text2.length} bytes to ${name} (${how})`));
|
|
20356
20435
|
}
|
|
20357
20436
|
printNextSteps(nextSteps3);
|
|
@@ -20414,7 +20493,7 @@ async function cmdAgentShow(db, rawName, opts) {
|
|
|
20414
20493
|
} catch {
|
|
20415
20494
|
scrollback = "";
|
|
20416
20495
|
}
|
|
20417
|
-
const reading = (await readAgentStates([agent], { stateDir:
|
|
20496
|
+
const reading = (await readAgentStates([agent], { stateDir: dirname13(db.name) })).get(
|
|
20418
20497
|
agentKey(agent)
|
|
20419
20498
|
);
|
|
20420
20499
|
const displayed = {
|
|
@@ -20449,7 +20528,7 @@ async function cmdAgentShow(db, rawName, opts) {
|
|
|
20449
20528
|
}
|
|
20450
20529
|
async function cmdMe(db, opts = {}) {
|
|
20451
20530
|
const self = resolveSelf(db);
|
|
20452
|
-
const reading = (await readAgentStates([self], { stateDir:
|
|
20531
|
+
const reading = (await readAgentStates([self], { stateDir: dirname13(db.name) })).get(
|
|
20453
20532
|
agentKey(self)
|
|
20454
20533
|
);
|
|
20455
20534
|
const displayed = {
|
|
@@ -20642,7 +20721,7 @@ async function cmdAbort(db, rawName, opts = {}) {
|
|
|
20642
20721
|
intent: "Steer it with the next instruction",
|
|
20643
20722
|
command: `mu agent send ${name} '...' -w ${ws}`
|
|
20644
20723
|
},
|
|
20645
|
-
{ intent: "Read the pane", command: `mu agent read ${name} -n 30 -w ${ws}` }
|
|
20724
|
+
...result.after === "idle" ? [] : [{ intent: "Read the pane", command: `mu agent read ${name} -n 30 -w ${ws}` }]
|
|
20646
20725
|
];
|
|
20647
20726
|
if (opts.json) {
|
|
20648
20727
|
emitJson({ ...result, nextSteps: nextSteps3 });
|
|
@@ -20667,12 +20746,22 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20667
20746
|
return { name, workstreamName };
|
|
20668
20747
|
})
|
|
20669
20748
|
);
|
|
20749
|
+
if (opts.afterRuns !== void 0) {
|
|
20750
|
+
for (const ref of refs) {
|
|
20751
|
+
const agent = getAgent(db, ref.name, ref.workstreamName);
|
|
20752
|
+
if (agent !== void 0 && !expectsCtl(agent, agentCtlSocket(db, agent))) {
|
|
20753
|
+
throw new UsageError(
|
|
20754
|
+
`--after-runs needs a pi agent (control socket); ${ref.workstreamName}/${ref.name} runs ${agent.cli}. Drop --after-runs to wait on its pane state.`
|
|
20755
|
+
);
|
|
20756
|
+
}
|
|
20757
|
+
}
|
|
20758
|
+
}
|
|
20670
20759
|
const readStatuses = async (pending2) => {
|
|
20671
20760
|
const agents = pending2.flatMap((ref) => {
|
|
20672
20761
|
const agent = getAgent(db, ref.name, ref.workstreamName);
|
|
20673
20762
|
return agent === void 0 ? [] : [agent];
|
|
20674
20763
|
});
|
|
20675
|
-
const readings = await readAgentStates(agents, { stateDir:
|
|
20764
|
+
const readings = await readAgentStates(agents, { stateDir: dirname13(db.name) });
|
|
20676
20765
|
const snapshots = /* @__PURE__ */ new Map();
|
|
20677
20766
|
for (const ref of pending2) {
|
|
20678
20767
|
const agent = getAgent(db, ref.name, ref.workstreamName);
|
|
@@ -20689,9 +20778,15 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20689
20778
|
if (agent === void 0) return void 0;
|
|
20690
20779
|
const sock = agentCtlSocket(db, agent);
|
|
20691
20780
|
if (!expectsCtl(agent, sock)) return void 0;
|
|
20692
|
-
|
|
20693
|
-
|
|
20694
|
-
|
|
20781
|
+
let initial = { status: "busy" };
|
|
20782
|
+
let afterRuns = opts.afterRuns;
|
|
20783
|
+
if (afterRuns === void 0) {
|
|
20784
|
+
const probe = await ctlProbe(sock);
|
|
20785
|
+
if (probe.kind !== "ok") return void 0;
|
|
20786
|
+
afterRuns = probe.status.runs;
|
|
20787
|
+
initial = { status: ctlRuntimeState(probe.status.state) };
|
|
20788
|
+
}
|
|
20789
|
+
const settled = ctlRequest(sock, { op: "wait", afterRuns }, { signal }).then(
|
|
20695
20790
|
(reply) => reply.ok && reply.state !== void 0 ? {
|
|
20696
20791
|
status: ctlRuntimeState(reply.state),
|
|
20697
20792
|
...reply.lastText !== void 0 ? { lastText: reply.lastText } : {},
|
|
@@ -20706,7 +20801,7 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20706
20801
|
return again.kind === "ok" ? { status: "unknown", unknownReason: "ctl wait interrupted" } : { status: null };
|
|
20707
20802
|
}
|
|
20708
20803
|
);
|
|
20709
|
-
return { initial
|
|
20804
|
+
return { initial, settled };
|
|
20710
20805
|
};
|
|
20711
20806
|
const timeoutMs = (opts.timeout ?? 600) * 1e3;
|
|
20712
20807
|
const result = await waitForAgents(db, refs, {
|
|
@@ -20729,6 +20824,12 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20729
20824
|
const fired = result.agents.filter((a) => a.fired);
|
|
20730
20825
|
const firing = wantFirstShape && fired.length > 0 ? fired[0] : null;
|
|
20731
20826
|
const qualified = (a) => `${a.workstreamName}/${a.name}`;
|
|
20827
|
+
const readHint = firing && (!firing.lastText || firing.lastError !== void 0) ? [
|
|
20828
|
+
{
|
|
20829
|
+
intent: "Read the finished agent",
|
|
20830
|
+
command: `mu agent read ${firing.name} -w ${firing.workstreamName}`
|
|
20831
|
+
}
|
|
20832
|
+
] : [];
|
|
20732
20833
|
if (opts.json) {
|
|
20733
20834
|
emitJson({
|
|
20734
20835
|
agents: result.agents.map((a) => ({
|
|
@@ -20738,12 +20839,7 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20738
20839
|
timedOut: result.timedOut,
|
|
20739
20840
|
...firing ? { firing: { workstream: firing.workstreamName, name: firing.name } } : {},
|
|
20740
20841
|
...dead.length > 0 ? { dead: dead.map(qualified) } : {},
|
|
20741
|
-
nextSteps:
|
|
20742
|
-
{
|
|
20743
|
-
intent: "Read the finished agent",
|
|
20744
|
-
command: `mu agent read ${firing.name} -w ${firing.workstreamName}`
|
|
20745
|
-
}
|
|
20746
|
-
] : []
|
|
20842
|
+
nextSteps: readHint
|
|
20747
20843
|
});
|
|
20748
20844
|
} else if (result.timedOut) {
|
|
20749
20845
|
console.log(
|
|
@@ -20755,12 +20851,7 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20755
20851
|
console.log(pc.red(`Agent pane(s) died: ${dead.map(qualified).join(", ")}`));
|
|
20756
20852
|
} else if (firing) {
|
|
20757
20853
|
console.log(`${pc.bold(qualified(firing))} finished`);
|
|
20758
|
-
printNextSteps(
|
|
20759
|
-
{
|
|
20760
|
-
intent: "Read the finished agent",
|
|
20761
|
-
command: `mu agent read ${firing.name} -w ${firing.workstreamName}`
|
|
20762
|
-
}
|
|
20763
|
-
]);
|
|
20854
|
+
printNextSteps(readHint);
|
|
20764
20855
|
} else {
|
|
20765
20856
|
console.log(`All ${result.agents.length} agent(s) finished`);
|
|
20766
20857
|
}
|
|
@@ -20855,7 +20946,11 @@ function wireAgentCommands(program) {
|
|
|
20855
20946
|
"--timeout <seconds>",
|
|
20856
20947
|
"max seconds to wait (0 = forever, default 600)",
|
|
20857
20948
|
parseNonNegativeInt
|
|
20858
|
-
).option(
|
|
20949
|
+
).option(
|
|
20950
|
+
"--after-runs <n>",
|
|
20951
|
+
"pi agents only: fire on the first run settled past N (the `runs` that `mu agent send --json` printed), even one that settled before this wait started",
|
|
20952
|
+
parseNonNegativeInt
|
|
20953
|
+
).option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(names) {
|
|
20859
20954
|
const opts = this.optsWithGlobals();
|
|
20860
20955
|
return handle(
|
|
20861
20956
|
(db) => cmdAgentWait(db, names, {
|
|
@@ -20905,7 +21000,7 @@ function wireSelfCommands(program) {
|
|
|
20905
21000
|
|
|
20906
21001
|
// src/cli/db.ts
|
|
20907
21002
|
import { existsSync as existsSync16, mkdirSync as mkdirSync6, statSync as statSync5 } from "fs";
|
|
20908
|
-
import { dirname as
|
|
21003
|
+
import { dirname as dirname14, resolve as resolve7 } from "path";
|
|
20909
21004
|
|
|
20910
21005
|
// src/compact.ts
|
|
20911
21006
|
init_legacy_ops();
|
|
@@ -21997,7 +22092,7 @@ function cmdDbBackup(db, file, opts = {}) {
|
|
|
21997
22092
|
if (existsSync16(target)) {
|
|
21998
22093
|
throw new UsageError(`${target} already exists: mu db backup never overwrites`);
|
|
21999
22094
|
}
|
|
22000
|
-
mkdirSync6(
|
|
22095
|
+
mkdirSync6(dirname14(target), { recursive: true });
|
|
22001
22096
|
db.prepare("VACUUM INTO ?").run(target);
|
|
22002
22097
|
const nextSteps3 = [
|
|
22003
22098
|
{
|
|
@@ -22141,7 +22236,7 @@ function wireDbCommands(program) {
|
|
|
22141
22236
|
|
|
22142
22237
|
// src/cli/doctor.ts
|
|
22143
22238
|
init_agents();
|
|
22144
|
-
import { dirname as
|
|
22239
|
+
import { dirname as dirname15 } from "path";
|
|
22145
22240
|
init_db();
|
|
22146
22241
|
init_doctor_summary();
|
|
22147
22242
|
|
|
@@ -22307,7 +22402,7 @@ async function cmdDoctor(db, opts = {}) {
|
|
|
22307
22402
|
printCheck("mu skill", skillDoctorCheck());
|
|
22308
22403
|
printCheck(
|
|
22309
22404
|
"ctl",
|
|
22310
|
-
(await ctlSocketsDoctorCheck(listAgents(db), { stateDir:
|
|
22405
|
+
(await ctlSocketsDoctorCheck(listAgents(db), { stateDir: dirname15(db.name) })).check
|
|
22311
22406
|
);
|
|
22312
22407
|
console.log(pc.bold("\ndb"));
|
|
22313
22408
|
console.log(` path : ${pc.dim(defaultDbPath())}`);
|
|
@@ -22505,7 +22600,7 @@ function opsSizeHint(db) {
|
|
|
22505
22600
|
async function cmdDoctorJson(db, opts = {}) {
|
|
22506
22601
|
const health = await muxHealth();
|
|
22507
22602
|
const muExt = ctlExtensionDoctorCheck();
|
|
22508
|
-
const ctlSockets = await ctlSocketsDoctorCheck(listAgents(db), { stateDir:
|
|
22603
|
+
const ctlSockets = await ctlSocketsDoctorCheck(listAgents(db), { stateDir: dirname15(db.name) });
|
|
22509
22604
|
const env = {
|
|
22510
22605
|
// `mux` is the backend-agnostic key. `tmux` is kept as an alias for
|
|
22511
22606
|
// back-compat with scripts that grew around the pre-MuxBackend
|
|
@@ -23209,7 +23304,6 @@ function wireSqlCommand(program) {
|
|
|
23209
23304
|
}
|
|
23210
23305
|
|
|
23211
23306
|
// src/cli/state.ts
|
|
23212
|
-
init_agents();
|
|
23213
23307
|
init_db();
|
|
23214
23308
|
init_glyphs();
|
|
23215
23309
|
init_output();
|
|
@@ -23224,7 +23318,7 @@ import path from "path";
|
|
|
23224
23318
|
// src/project-root.ts
|
|
23225
23319
|
import { execFile as execFile3 } from "child_process";
|
|
23226
23320
|
import { realpath } from "fs/promises";
|
|
23227
|
-
import { dirname as
|
|
23321
|
+
import { dirname as dirname16, resolve as resolve8 } from "path";
|
|
23228
23322
|
import { promisify as promisify3 } from "util";
|
|
23229
23323
|
var exec2 = promisify3(execFile3);
|
|
23230
23324
|
async function run2(bin, args, cwd) {
|
|
@@ -23246,7 +23340,7 @@ async function gitProjectRoot(workspacePath2) {
|
|
|
23246
23340
|
workspacePath2
|
|
23247
23341
|
);
|
|
23248
23342
|
if (commonDir.length === 0) return null;
|
|
23249
|
-
return realpathOrResolve(
|
|
23343
|
+
return realpathOrResolve(dirname16(commonDir));
|
|
23250
23344
|
} catch {
|
|
23251
23345
|
return null;
|
|
23252
23346
|
}
|
|
@@ -23266,7 +23360,7 @@ async function workspaceProjectRoot(workspacePath2, backend) {
|
|
|
23266
23360
|
if (backend === "sl") return slProjectRoot(workspacePath2);
|
|
23267
23361
|
const gitRoot = await gitProjectRoot(workspacePath2);
|
|
23268
23362
|
if (gitRoot !== null) return gitRoot;
|
|
23269
|
-
return realpathOrResolve(
|
|
23363
|
+
return realpathOrResolve(dirname16(workspacePath2));
|
|
23270
23364
|
}
|
|
23271
23365
|
|
|
23272
23366
|
// src/cli/tui-launch-focus.ts
|
|
@@ -23455,6 +23549,13 @@ async function cmdState(db, opts) {
|
|
|
23455
23549
|
renderFullMode(perWs);
|
|
23456
23550
|
}
|
|
23457
23551
|
var BIG_WORKSTREAM_TASKS = 300;
|
|
23552
|
+
function scratchIdleNudge(workstreamName, agents) {
|
|
23553
|
+
if (!isScratchWorkstream(workstreamName)) return null;
|
|
23554
|
+
const lingering = agents.filter((a) => a.idle === true);
|
|
23555
|
+
if (lingering.length === 0) return null;
|
|
23556
|
+
const names = lingering.map((a) => a.name).join(", ");
|
|
23557
|
+
return `\u26A0 ${lingering.length} idle scratch agent(s): ${names}. Close when done: mu agent close ${lingering[0]?.name ?? "<name>"} -w scratch`;
|
|
23558
|
+
}
|
|
23458
23559
|
function renderFullMode(perWs) {
|
|
23459
23560
|
perWs.forEach((d, i) => {
|
|
23460
23561
|
if (i > 0) console.log("");
|
|
@@ -23475,18 +23576,8 @@ function renderFullCard(d) {
|
|
|
23475
23576
|
);
|
|
23476
23577
|
}
|
|
23477
23578
|
}
|
|
23478
|
-
|
|
23479
|
-
|
|
23480
|
-
const lingering = view.agents.filter((a) => isLingeringScratchAgent(a.updatedAt, threshold));
|
|
23481
|
-
if (lingering.length > 0) {
|
|
23482
|
-
const names = lingering.map((a) => a.name).join(", ");
|
|
23483
|
-
console.log(
|
|
23484
|
-
pc.yellow(
|
|
23485
|
-
`\u26A0 ${lingering.length} idle scratch agent(s): ${names}. Close when done: mu agent close ${lingering[0]?.name ?? "<name>"} -w scratch`
|
|
23486
|
-
)
|
|
23487
|
-
);
|
|
23488
|
-
}
|
|
23489
|
-
}
|
|
23579
|
+
const nudge = scratchIdleNudge(workstreamName, view.agents);
|
|
23580
|
+
if (nudge !== null) console.log(pc.yellow(nudge));
|
|
23490
23581
|
const total = d.taskCount;
|
|
23491
23582
|
const open = ready.length + inProgress.length + blocked.length + d.triage.length;
|
|
23492
23583
|
if (total >= BIG_WORKSTREAM_TASKS && open * 10 < total) {
|
|
@@ -24759,7 +24850,7 @@ async function cmdBareTui(db, program, requestedWorkstreams) {
|
|
|
24759
24850
|
}
|
|
24760
24851
|
function readPackageVersion() {
|
|
24761
24852
|
try {
|
|
24762
|
-
const here =
|
|
24853
|
+
const here = dirname17(fileURLToPath3(import.meta.url));
|
|
24763
24854
|
const pkgPath = join14(here, "..", "package.json");
|
|
24764
24855
|
const raw = readFileSync4(pkgPath, "utf8");
|
|
24765
24856
|
const parsed = JSON.parse(raw);
|