@mu-crew/mu 3.6.1 → 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 +217 -120
- 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 +27 -4
- package/dist/index.js +136 -58
- 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 +6 -3
- 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, {
|
|
@@ -5300,7 +5335,9 @@ async function readAgentStates(agents, opts = {}) {
|
|
|
5300
5335
|
source: "ctl",
|
|
5301
5336
|
since: probe.status.since,
|
|
5302
5337
|
alive: true,
|
|
5303
|
-
ctl: "ok"
|
|
5338
|
+
ctl: "ok",
|
|
5339
|
+
pending: probe.status.pending,
|
|
5340
|
+
runs: probe.status.runs
|
|
5304
5341
|
});
|
|
5305
5342
|
} else {
|
|
5306
5343
|
failed.set(agentKey(agent), probe?.kind === "missing" ? "missing" : "refused");
|
|
@@ -5856,6 +5893,15 @@ var init_id = __esm({
|
|
|
5856
5893
|
});
|
|
5857
5894
|
|
|
5858
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
|
+
}
|
|
5859
5905
|
var TaskNotFoundError, TaskIdInvalidError, TaskExistsError, TaskNotInWorkstreamError, TaskAlreadyOwnedError, ClaimerNotRegisteredError, TaskClaimStaleWorkspaceError, CycleError, ReaperDetectedDuringWaitError, StallDetectedDuringWaitError, CrossWorkstreamEdgeError, SubstateReasonRequiredError, TaskParkStateError, TaskInTriageError, TaskParkedError, InvalidSubstateError;
|
|
5860
5906
|
var init_errors2 = __esm({
|
|
5861
5907
|
"src/tasks/errors.ts"() {
|
|
@@ -6078,7 +6124,7 @@ var init_errors2 = __esm({
|
|
|
6078
6124
|
}
|
|
6079
6125
|
};
|
|
6080
6126
|
StallDetectedDuringWaitError = class extends Error {
|
|
6081
|
-
constructor(taskName, owner, workstream, ageSecs, ownerExpectsCtl = false) {
|
|
6127
|
+
constructor(taskName, owner, workstream, ageSecs, ownerExpectsCtl = false, ownerRuns) {
|
|
6082
6128
|
const ownerBit = owner !== null ? owner : "<unknown>";
|
|
6083
6129
|
super(
|
|
6084
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.`
|
|
@@ -6088,12 +6134,14 @@ var init_errors2 = __esm({
|
|
|
6088
6134
|
this.workstream = workstream;
|
|
6089
6135
|
this.ageSecs = ageSecs;
|
|
6090
6136
|
this.ownerExpectsCtl = ownerExpectsCtl;
|
|
6137
|
+
this.ownerRuns = ownerRuns;
|
|
6091
6138
|
}
|
|
6092
6139
|
taskName;
|
|
6093
6140
|
owner;
|
|
6094
6141
|
workstream;
|
|
6095
6142
|
ageSecs;
|
|
6096
6143
|
ownerExpectsCtl;
|
|
6144
|
+
ownerRuns;
|
|
6097
6145
|
name = "StallDetectedDuringWaitError";
|
|
6098
6146
|
/**
|
|
6099
6147
|
* Diagnose before acting. `mu agent read` comes FIRST because the
|
|
@@ -6108,6 +6156,7 @@ var init_errors2 = __esm({
|
|
|
6108
6156
|
const ws = this.workstream;
|
|
6109
6157
|
const ownerBit = this.owner !== null ? this.owner : "<owner>";
|
|
6110
6158
|
return [
|
|
6159
|
+
...stallWaitHint(ownerBit, ws, this.ownerRuns),
|
|
6111
6160
|
{
|
|
6112
6161
|
intent: "Read the worker's pane to see what it is waiting on",
|
|
6113
6162
|
command: `mu agent read ${ownerBit} -w ${ws} --lines 60`
|
|
@@ -6459,7 +6508,7 @@ var init_edges = __esm({
|
|
|
6459
6508
|
|
|
6460
6509
|
// src/workstream.ts
|
|
6461
6510
|
import { existsSync as existsSync9, readdirSync as readdirSync2, rmdirSync } from "fs";
|
|
6462
|
-
import { join as join8 } from "path";
|
|
6511
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
6463
6512
|
async function listMuxSessions() {
|
|
6464
6513
|
try {
|
|
6465
6514
|
return await (await activeMux()).listSessions();
|
|
@@ -6613,6 +6662,7 @@ async function teardownWorkstream(db, opts) {
|
|
|
6613
6662
|
const notesBefore = countNotes(db, opts.workstream);
|
|
6614
6663
|
const edgesBefore = countEdges(db, opts.workstream);
|
|
6615
6664
|
const workspacesBefore = listWorkspaces2(db, opts.workstream);
|
|
6665
|
+
const agentNames = listAgentNames(db, opts.workstream);
|
|
6616
6666
|
const mux = await activeMux();
|
|
6617
6667
|
const muxAliveBefore = await mux.sessionExists(muxSession);
|
|
6618
6668
|
if (muxAliveBefore) {
|
|
@@ -6650,6 +6700,14 @@ async function teardownWorkstream(db, opts) {
|
|
|
6650
6700
|
} catch {
|
|
6651
6701
|
}
|
|
6652
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
|
+
}
|
|
6653
6711
|
withOpContext(
|
|
6654
6712
|
db,
|
|
6655
6713
|
{ intent: "workstream.teardown", group: "new" },
|
|
@@ -6666,6 +6724,14 @@ async function teardownWorkstream(db, opts) {
|
|
|
6666
6724
|
failedWorkspaces
|
|
6667
6725
|
};
|
|
6668
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
|
+
}
|
|
6669
6735
|
function countAgents(db, workstream) {
|
|
6670
6736
|
const row2 = db.prepare(
|
|
6671
6737
|
`SELECT COUNT(*) AS n FROM agents a
|
|
@@ -6706,6 +6772,7 @@ var WORKSTREAM_NAME_RE, RESERVED_WORKSTREAM_PREFIX, RESERVED_WORKSTREAM_NAMES, W
|
|
|
6706
6772
|
var init_workstream = __esm({
|
|
6707
6773
|
"src/workstream.ts"() {
|
|
6708
6774
|
"use strict";
|
|
6775
|
+
init_path();
|
|
6709
6776
|
init_db();
|
|
6710
6777
|
init_legacy_ops();
|
|
6711
6778
|
init_mux2();
|
|
@@ -7347,6 +7414,7 @@ async function waitForTasks(db, input, opts) {
|
|
|
7347
7414
|
const timeoutMs = opts.timeoutMs ?? 6e5;
|
|
7348
7415
|
const pollMs = opts.pollMs ?? 1e3;
|
|
7349
7416
|
const stuckAfterMs = opts.stuckAfterMs ?? DEFAULT_STUCK_AFTER_MS;
|
|
7417
|
+
const ctlStuckAfterMs = opts.ctlStuckAfterMs ?? DEFAULT_CTL_STUCK_AFTER_MS;
|
|
7350
7418
|
const onStall = opts.onStall ?? "warn";
|
|
7351
7419
|
const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : Number.POSITIVE_INFINITY;
|
|
7352
7420
|
for (const ref of refs) {
|
|
@@ -7358,12 +7426,15 @@ async function waitForTasks(db, input, opts) {
|
|
|
7358
7426
|
const ctlBrokenSince = /* @__PURE__ */ new Map();
|
|
7359
7427
|
const stuckLabel = /* @__PURE__ */ new Map();
|
|
7360
7428
|
const ctlOwners = /* @__PURE__ */ new Set();
|
|
7429
|
+
const ownerRuns = /* @__PURE__ */ new Map();
|
|
7361
7430
|
const stuckAgeMs = async (status, owner, workstreamName) => {
|
|
7362
7431
|
if (stuckAfterMs <= 0 || status !== "IN_PROGRESS" || owner === null || opts.readOwnerState === void 0)
|
|
7363
7432
|
return null;
|
|
7364
7433
|
const reading = await opts.readOwnerState({ name: owner, workstreamName });
|
|
7365
7434
|
const ownerKey = `${workstreamName}/${owner}`;
|
|
7366
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);
|
|
7367
7438
|
if (reading?.alive === true && (reading.ctl === "missing" || reading.ctl === "refused")) {
|
|
7368
7439
|
const firstSeen = ctlBrokenSince.get(ownerKey) ?? Date.now();
|
|
7369
7440
|
ctlBrokenSince.set(ownerKey, firstSeen);
|
|
@@ -7374,8 +7445,10 @@ async function waitForTasks(db, input, opts) {
|
|
|
7374
7445
|
ctlBrokenSince.delete(ownerKey);
|
|
7375
7446
|
stuckLabel.delete(ownerKey);
|
|
7376
7447
|
if (reading?.state !== "needs_input" || reading.since === null) return null;
|
|
7448
|
+
const viaCtl = reading.source === "ctl" && reading.ctl === "ok";
|
|
7449
|
+
if (viaCtl && reading.pending === true) return null;
|
|
7377
7450
|
const ageMs2 = Date.now() - reading.since;
|
|
7378
|
-
return ageMs2 >= stuckAfterMs ? ageMs2 : null;
|
|
7451
|
+
return ageMs2 >= (viaCtl ? ctlStuckAfterMs : stuckAfterMs) ? ageMs2 : null;
|
|
7379
7452
|
};
|
|
7380
7453
|
const snapshot2 = async () => {
|
|
7381
7454
|
const refStates = [];
|
|
@@ -7391,10 +7464,13 @@ async function waitForTasks(db, input, opts) {
|
|
|
7391
7464
|
stuckWarned.add(key);
|
|
7392
7465
|
const ageSecs = Math.round(ageMs2 / 1e3);
|
|
7393
7466
|
const ownerBit = owner ?? "<none>";
|
|
7467
|
+
const runs = ownerRuns.get(`${ref.workstreamName}/${ownerBit}`);
|
|
7394
7468
|
const stateBit = stuckLabel.get(`${ref.workstreamName}/${ownerBit}`) ?? "needs_input";
|
|
7469
|
+
const waitLine = stallWaitHint(ownerBit, ref.workstreamName, runs).map((s) => ` ${s.command}
|
|
7470
|
+
`).join("");
|
|
7395
7471
|
currentStuckWarn(
|
|
7396
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
|
|
7397
|
-
mu agent read ${ownerBit} -w ${ref.workstreamName} --lines 60
|
|
7473
|
+
` + waitLine + ` mu agent read ${ownerBit} -w ${ref.workstreamName} --lines 60
|
|
7398
7474
|
`
|
|
7399
7475
|
);
|
|
7400
7476
|
emitEvent(
|
|
@@ -7410,7 +7486,8 @@ async function waitForTasks(db, input, opts) {
|
|
|
7410
7486
|
owner,
|
|
7411
7487
|
ref.workstreamName,
|
|
7412
7488
|
ageSecs,
|
|
7413
|
-
ctlOwners.has(`${ref.workstreamName}/${owner ?? ""}`)
|
|
7489
|
+
ctlOwners.has(`${ref.workstreamName}/${owner ?? ""}`),
|
|
7490
|
+
runs
|
|
7414
7491
|
);
|
|
7415
7492
|
}
|
|
7416
7493
|
}
|
|
@@ -7451,7 +7528,7 @@ async function waitForTasks(db, input, opts) {
|
|
|
7451
7528
|
if (isDone(snap)) return snap;
|
|
7452
7529
|
}
|
|
7453
7530
|
}
|
|
7454
|
-
var currentWaitSleep, pollCount, DEFAULT_STUCK_AFTER_MS, defaultStuckWarn, currentStuckWarn;
|
|
7531
|
+
var currentWaitSleep, pollCount, DEFAULT_STUCK_AFTER_MS, DEFAULT_CTL_STUCK_AFTER_MS, defaultStuckWarn, currentStuckWarn;
|
|
7455
7532
|
var init_wait = __esm({
|
|
7456
7533
|
"src/tasks/wait.ts"() {
|
|
7457
7534
|
"use strict";
|
|
@@ -7462,6 +7539,7 @@ var init_wait = __esm({
|
|
|
7462
7539
|
currentWaitSleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
7463
7540
|
pollCount = 0;
|
|
7464
7541
|
DEFAULT_STUCK_AFTER_MS = 12e4;
|
|
7542
|
+
DEFAULT_CTL_STUCK_AFTER_MS = 5e3;
|
|
7465
7543
|
defaultStuckWarn = (msg) => {
|
|
7466
7544
|
process.stderr.write(msg);
|
|
7467
7545
|
};
|
|
@@ -7925,8 +8003,7 @@ var init_wait2 = __esm({
|
|
|
7925
8003
|
});
|
|
7926
8004
|
|
|
7927
8005
|
// src/agents.ts
|
|
7928
|
-
import {
|
|
7929
|
-
import { dirname as dirname6 } from "path";
|
|
8006
|
+
import { dirname as dirname7 } from "path";
|
|
7930
8007
|
function idleThresholdMs() {
|
|
7931
8008
|
const env = process.env.MU_IDLE_THRESHOLD_MS;
|
|
7932
8009
|
if (env === void 0 || env === "") return DEFAULT_IDLE_THRESHOLD_MS;
|
|
@@ -8045,13 +8122,6 @@ function deleteAgent(db, name, workstream) {
|
|
|
8045
8122
|
if (deleted) unlinkCtlSocket(db, name, workstream);
|
|
8046
8123
|
return deleted;
|
|
8047
8124
|
}
|
|
8048
|
-
function unlinkCtlSocket(db, name, workstream) {
|
|
8049
|
-
if (db.memory) return;
|
|
8050
|
-
try {
|
|
8051
|
-
rmSync4(ctlSocketPath(workstream, name, dirname6(db.name)), { force: true });
|
|
8052
|
-
} catch {
|
|
8053
|
-
}
|
|
8054
|
-
}
|
|
8055
8125
|
function deleteAgentRow(db, name, workstream) {
|
|
8056
8126
|
return withOpContext(
|
|
8057
8127
|
db,
|
|
@@ -8146,7 +8216,7 @@ async function listLiveAgents(db, opts) {
|
|
|
8146
8216
|
...opts.mode !== void 0 ? { mode: opts.mode } : {}
|
|
8147
8217
|
});
|
|
8148
8218
|
const baseAgents = listAgents(db, { workstream: opts.workstream });
|
|
8149
|
-
const readings = await readAgentStates(baseAgents, { stateDir:
|
|
8219
|
+
const readings = await readAgentStates(baseAgents, { stateDir: dirname7(db.name) });
|
|
8150
8220
|
const now = Date.now();
|
|
8151
8221
|
const agents = baseAgents.map((agent) => {
|
|
8152
8222
|
const reading = readings.get(agentKey(agent)) ?? {
|
|
@@ -8703,7 +8773,7 @@ var init_apply = __esm({
|
|
|
8703
8773
|
|
|
8704
8774
|
// src/rebuild.ts
|
|
8705
8775
|
import { existsSync as existsSync10, mkdirSync as mkdirSync3, statSync } from "fs";
|
|
8706
|
-
import { dirname as
|
|
8776
|
+
import { dirname as dirname9 } from "path";
|
|
8707
8777
|
function isProjectable(entity, intent) {
|
|
8708
8778
|
if (isLegacyLogOnlyIntent(intent)) return false;
|
|
8709
8779
|
if (LOG_ONLY_ENTITIES.has(entity)) return false;
|
|
@@ -8734,7 +8804,7 @@ function rebuildInto(source, opts) {
|
|
|
8734
8804
|
if (existsSync10(targetPath) && opts.force !== true) {
|
|
8735
8805
|
throw new RebuildTargetExistsError(targetPath);
|
|
8736
8806
|
}
|
|
8737
|
-
mkdirSync3(
|
|
8807
|
+
mkdirSync3(dirname9(targetPath), { recursive: true });
|
|
8738
8808
|
const identity = source.prepare(
|
|
8739
8809
|
"SELECT machine_id, hostname, created_at, last_wall, last_counter FROM machine_identity WHERE id = 1"
|
|
8740
8810
|
).get();
|
|
@@ -9316,13 +9386,13 @@ import {
|
|
|
9316
9386
|
writeFileSync
|
|
9317
9387
|
} from "fs";
|
|
9318
9388
|
import { homedir as homedir4 } from "os";
|
|
9319
|
-
import { dirname as
|
|
9389
|
+
import { dirname as dirname10, join as join10, resolve as resolve5 } from "path";
|
|
9320
9390
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
9321
9391
|
function resolveHome(opts) {
|
|
9322
9392
|
return opts?.home ?? process.env.MU_PI_HOME ?? homedir4();
|
|
9323
9393
|
}
|
|
9324
9394
|
function packageRoot() {
|
|
9325
|
-
return resolve5(
|
|
9395
|
+
return resolve5(dirname10(fileURLToPath(import.meta.url)), "..");
|
|
9326
9396
|
}
|
|
9327
9397
|
function extensionEntry() {
|
|
9328
9398
|
return process.env.MU_EXTENSION_ENTRY ?? join10(packageRoot(), "dist", "extension", "mu-pi.js");
|
|
@@ -9358,7 +9428,7 @@ function linkPi(opts) {
|
|
|
9358
9428
|
replacedCopy = !readFileSync(path2, "utf8").includes(MU_SHIM_MARKER);
|
|
9359
9429
|
} catch {
|
|
9360
9430
|
}
|
|
9361
|
-
mkdirSync4(
|
|
9431
|
+
mkdirSync4(dirname10(path2), { recursive: true });
|
|
9362
9432
|
writeFileSync(path2, opts?.copy === true ? readFileSync(entry, "utf8") : shim(entry));
|
|
9363
9433
|
return { path: path2, replacedCopy };
|
|
9364
9434
|
}
|
|
@@ -9369,7 +9439,7 @@ function linkSkill(opts) {
|
|
|
9369
9439
|
const st = lstatOrUndefined(path2);
|
|
9370
9440
|
if (st) {
|
|
9371
9441
|
if (!st.isSymbolicLink()) throw new LinkConflictError(path2, "not-a-symlink");
|
|
9372
|
-
const current = resolve5(
|
|
9442
|
+
const current = resolve5(dirname10(path2), readlinkSync(path2));
|
|
9373
9443
|
if (current === target) return { path: path2, target };
|
|
9374
9444
|
if (existsSync12(current) && opts?.force !== true) {
|
|
9375
9445
|
throw new LinkConflictError(path2, "foreign-symlink", current);
|
|
@@ -9377,7 +9447,7 @@ function linkSkill(opts) {
|
|
|
9377
9447
|
previous = current;
|
|
9378
9448
|
rmSync6(path2);
|
|
9379
9449
|
}
|
|
9380
|
-
mkdirSync4(
|
|
9450
|
+
mkdirSync4(dirname10(path2), { recursive: true });
|
|
9381
9451
|
symlinkSync(target, path2, "dir");
|
|
9382
9452
|
return previous === void 0 ? { path: path2, target } : { path: path2, target, previous };
|
|
9383
9453
|
}
|
|
@@ -9411,7 +9481,7 @@ function inspectSkill(path2) {
|
|
|
9411
9481
|
try {
|
|
9412
9482
|
target = realpathSync(path2);
|
|
9413
9483
|
} catch {
|
|
9414
|
-
return { path: path2, state: "dangling", target: resolve5(
|
|
9484
|
+
return { path: path2, state: "dangling", target: resolve5(dirname10(path2), readlinkSync(path2)) };
|
|
9415
9485
|
}
|
|
9416
9486
|
let skillMd;
|
|
9417
9487
|
try {
|
|
@@ -9462,7 +9532,7 @@ var init_link = __esm({
|
|
|
9462
9532
|
|
|
9463
9533
|
// src/doctor-summary.ts
|
|
9464
9534
|
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync13, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
|
|
9465
|
-
import { delimiter as delimiter2, dirname as
|
|
9535
|
+
import { delimiter as delimiter2, dirname as dirname11, join as join11, parse } from "path";
|
|
9466
9536
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9467
9537
|
function loadDoctorSummary(db, snapshot2, agentStateSource = "murmur") {
|
|
9468
9538
|
const checks = [];
|
|
@@ -9623,7 +9693,7 @@ function resolveExecutable(name) {
|
|
|
9623
9693
|
return null;
|
|
9624
9694
|
}
|
|
9625
9695
|
function murmurVersion(executable) {
|
|
9626
|
-
let dir =
|
|
9696
|
+
let dir = dirname11(executable);
|
|
9627
9697
|
const root = parse(dir).root;
|
|
9628
9698
|
while (dir !== root) {
|
|
9629
9699
|
const packagePath = join11(dir, "package.json");
|
|
@@ -9636,7 +9706,7 @@ function murmurVersion(executable) {
|
|
|
9636
9706
|
} catch {
|
|
9637
9707
|
}
|
|
9638
9708
|
}
|
|
9639
|
-
dir =
|
|
9709
|
+
dir = dirname11(dir);
|
|
9640
9710
|
}
|
|
9641
9711
|
return null;
|
|
9642
9712
|
}
|
|
@@ -9671,7 +9741,7 @@ function murmurDoctorCheck(agentStateSource, opts = {}) {
|
|
|
9671
9741
|
return {
|
|
9672
9742
|
name: "murmur",
|
|
9673
9743
|
status: "warn",
|
|
9674
|
-
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`
|
|
9675
9745
|
};
|
|
9676
9746
|
}
|
|
9677
9747
|
return {
|
|
@@ -9725,7 +9795,7 @@ function skillDoctorCheck(opts) {
|
|
|
9725
9795
|
}
|
|
9726
9796
|
function installedMuVersion() {
|
|
9727
9797
|
try {
|
|
9728
|
-
const pkgPath = join11(
|
|
9798
|
+
const pkgPath = join11(dirname11(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
9729
9799
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
9730
9800
|
const v = typeof pkg === "object" && pkg !== null ? pkg.version : null;
|
|
9731
9801
|
return typeof v === "string" ? v : null;
|
|
@@ -9769,7 +9839,10 @@ async function ctlSocketsDoctorCheck(agents, opts = {}) {
|
|
|
9769
9839
|
socket,
|
|
9770
9840
|
probe: probe.kind,
|
|
9771
9841
|
...probe.kind === "ok" && probe.extVersion !== void 0 ? { extVersion: probe.extVersion } : {},
|
|
9772
|
-
...older || missingOps.length > 0 ? {
|
|
9842
|
+
...older || missingOps.length > 0 ? {
|
|
9843
|
+
outdated: true,
|
|
9844
|
+
reloadVia: probe.kind === "ok" && probe.ops?.includes("command") ? "ctl" : "mux"
|
|
9845
|
+
} : {},
|
|
9773
9846
|
...missingOps.length > 0 ? { missingOps } : {}
|
|
9774
9847
|
};
|
|
9775
9848
|
})
|
|
@@ -9855,11 +9928,14 @@ function remediationParagraph(check) {
|
|
|
9855
9928
|
return [
|
|
9856
9929
|
"mu talks to pi agents over a per-agent control socket served by",
|
|
9857
9930
|
"the mu pi extension. `missing`: no socket (extension not loaded,",
|
|
9858
|
-
"
|
|
9859
|
-
"
|
|
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.",
|
|
9860
9935
|
"Run `mu link pi`, then respawn the agent. `extension X older than",
|
|
9861
|
-
"installed Y`: the agent's pi loaded an older mu
|
|
9862
|
-
"
|
|
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."
|
|
9863
9939
|
];
|
|
9864
9940
|
case "agents":
|
|
9865
9941
|
return [
|
|
@@ -18603,14 +18679,14 @@ var init_tui = __esm({
|
|
|
18603
18679
|
// src/cli.ts
|
|
18604
18680
|
init_agents();
|
|
18605
18681
|
import { readFileSync as readFileSync4, realpathSync as realpathSync4 } from "fs";
|
|
18606
|
-
import { dirname as
|
|
18682
|
+
import { dirname as dirname17, join as join14 } from "path";
|
|
18607
18683
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
|
|
18608
18684
|
import { Command, InvalidArgumentError } from "commander";
|
|
18609
18685
|
|
|
18610
18686
|
// src/cli/agents.ts
|
|
18611
18687
|
init_agent_state();
|
|
18612
18688
|
init_agents();
|
|
18613
|
-
import { dirname as
|
|
18689
|
+
import { dirname as dirname13 } from "path";
|
|
18614
18690
|
init_client();
|
|
18615
18691
|
init_mux2();
|
|
18616
18692
|
init_output();
|
|
@@ -18710,7 +18786,7 @@ function formatStaleWorkspaceWarning(staleness) {
|
|
|
18710
18786
|
|
|
18711
18787
|
// src/cli/agents-remote.ts
|
|
18712
18788
|
init_agents();
|
|
18713
|
-
import { dirname as
|
|
18789
|
+
import { dirname as dirname8 } from "path";
|
|
18714
18790
|
init_path();
|
|
18715
18791
|
init_output();
|
|
18716
18792
|
init_workstream();
|
|
@@ -18726,7 +18802,7 @@ function buildRemoteEnv(db, agent, workstream, remoteSock) {
|
|
|
18726
18802
|
if (!isValidWorkstreamName(workstream)) {
|
|
18727
18803
|
throw new UsageError(`invalid workstream name ${JSON.stringify(workstream)}`);
|
|
18728
18804
|
}
|
|
18729
|
-
const localSock = ctlSocketPath(workstream, agent,
|
|
18805
|
+
const localSock = ctlSocketPath(workstream, agent, dirname8(db.name));
|
|
18730
18806
|
const remote = remoteSock ?? remoteCtlSocketPath(workstream, agent, process.getuid?.() ?? 0);
|
|
18731
18807
|
if (!remote.startsWith("/") || !SAFE_WORD.test(remote)) {
|
|
18732
18808
|
throw new UsageError(
|
|
@@ -19205,12 +19281,13 @@ init_tasks();
|
|
|
19205
19281
|
init_agent_state();
|
|
19206
19282
|
init_errors();
|
|
19207
19283
|
init_agents();
|
|
19208
|
-
import { dirname as
|
|
19284
|
+
import { dirname as dirname12 } from "path";
|
|
19209
19285
|
init_db();
|
|
19210
19286
|
init_glyphs();
|
|
19211
19287
|
init_output();
|
|
19212
19288
|
init_reconcile();
|
|
19213
19289
|
init_state();
|
|
19290
|
+
init_errors2();
|
|
19214
19291
|
init_tasks();
|
|
19215
19292
|
init_vcs2();
|
|
19216
19293
|
init_workspace();
|
|
@@ -19419,6 +19496,7 @@ async function cmdTaskWait(db, ids, opts) {
|
|
|
19419
19496
|
const timeoutMs = opts.timeout !== void 0 ? opts.timeout * 1e3 : 6e5;
|
|
19420
19497
|
const stuckAfterMs = opts.stuckAfter !== void 0 ? opts.stuckAfter * 1e3 : DEFAULT_STUCK_AFTER_MS;
|
|
19421
19498
|
const sdkOpts = { timeoutMs, stuckAfterMs };
|
|
19499
|
+
if (opts.stuckAfter !== void 0) sdkOpts.ctlStuckAfterMs = stuckAfterMs;
|
|
19422
19500
|
if (statusOpt !== void 0) sdkOpts.status = statusOpt;
|
|
19423
19501
|
if (wantAny) sdkOpts.any = true;
|
|
19424
19502
|
let ownerReadings = /* @__PURE__ */ new Map();
|
|
@@ -19440,7 +19518,7 @@ async function cmdTaskWait(db, ids, opts) {
|
|
|
19440
19518
|
const agent = getAgent(db, owner, ref.workstreamName);
|
|
19441
19519
|
return agent === void 0 ? [] : [agent];
|
|
19442
19520
|
});
|
|
19443
|
-
ownerReadings = await readAgentStates(owners, { stateDir:
|
|
19521
|
+
ownerReadings = await readAgentStates(owners, { stateDir: dirname12(db.name) });
|
|
19444
19522
|
if (!reaperExitEnabled) return;
|
|
19445
19523
|
for (const ref of refs) {
|
|
19446
19524
|
const key = qualifiedId(ref);
|
|
@@ -19492,6 +19570,10 @@ async function cmdTaskWait(db, ids, opts) {
|
|
|
19492
19570
|
}
|
|
19493
19571
|
for (const t of unmetRefs) {
|
|
19494
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));
|
|
19495
19577
|
nextSteps3.push({
|
|
19496
19578
|
intent: `Read ${t.owner}'s pane \u2014 ${qualifiedId(t)} is IN_PROGRESS but its owner needs attention`,
|
|
19497
19579
|
command: `mu agent read ${t.owner} -w ${t.workstreamName} --lines 60`
|
|
@@ -20184,7 +20266,7 @@ function wireTaskCommands(program) {
|
|
|
20184
20266
|
"alias for --any that ALSO prints the firing ref's qualified id to stdout and populates `firing` in --json (which --any leaves null). Use to drive a single-shot dispatch loop: `closed=$(mu task wait a b --first --json | jq -r .firing.qualifiedId)`."
|
|
20185
20267
|
).option("--timeout <seconds>", "max seconds to wait (0 = forever, default 600)", parseLines).option(
|
|
20186
20268
|
"--stuck-after <seconds>",
|
|
20187
|
-
"the TRIGGER: mark an IN_PROGRESS task as needing attention when its owner has been in needs_input for >= N seconds since their last status change (0 = disable
|
|
20269
|
+
"the TRIGGER: mark an IN_PROGRESS task as needing attention when its owner has been in needs_input for >= N seconds since their last status change (0 = disable; default 120, or 5 for a pi owner read over its control socket, whose idle is exact and comes after the close nudge; an explicit value applies to every owner). needs_input has several causes \u2014 the worker may have finished without closing, be waiting on an answer, or be sitting at a prompt \u2014 so the warning names the observation and points at `mu agent read <owner>`, which is the next move in every case. The default ACTION is `exit` (exit 7); see --on-stall warn to keep polling instead.",
|
|
20188
20270
|
parseLines
|
|
20189
20271
|
).option(
|
|
20190
20272
|
"--on-stall <action>",
|
|
@@ -20299,8 +20381,6 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20299
20381
|
});
|
|
20300
20382
|
let undelivered;
|
|
20301
20383
|
const agentRow = getAgent(db, name, ws);
|
|
20302
|
-
const plainCtl = agentRow !== void 0 && !opts.fresh && !opts.steer && parseSessionCommand(text2) === void 0 && (via ?? chooseTransport(agentRow, agentCtlSocket(db, agentRow))) === "ctl";
|
|
20303
|
-
const before = plainCtl && agentRow !== void 0 ? await ctlProbe(agentCtlSocket(db, agentRow)) : void 0;
|
|
20304
20384
|
const sent = await sendToAgent(db, name, text2, {
|
|
20305
20385
|
workstream: ws,
|
|
20306
20386
|
...opts.steer ? { mode: "steer" } : {},
|
|
@@ -20312,11 +20392,15 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20312
20392
|
}
|
|
20313
20393
|
});
|
|
20314
20394
|
const nextSteps3 = [
|
|
20315
|
-
|
|
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}` },
|
|
20316
20399
|
{ intent: "Watch live events", command: `mu log -w ${ws} --tail` }
|
|
20317
20400
|
];
|
|
20318
|
-
|
|
20319
|
-
|
|
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 }));
|
|
20320
20404
|
}
|
|
20321
20405
|
if (stalenessCheck.warned && stalenessCheck.nextStep !== null) {
|
|
20322
20406
|
nextSteps3.push(stalenessCheck.nextStep);
|
|
@@ -20335,6 +20419,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20335
20419
|
fresh: opts.fresh === true,
|
|
20336
20420
|
...sent.command !== void 0 ? { command: sent.command } : {},
|
|
20337
20421
|
...sent.state !== void 0 ? { state: sent.state } : {},
|
|
20422
|
+
...sent.runs !== void 0 ? { runs: sent.runs } : {},
|
|
20338
20423
|
delivered: undelivered === void 0,
|
|
20339
20424
|
...undelivered !== void 0 ? { undelivered: { reason: undelivered.reason, message: undelivered.message } } : {},
|
|
20340
20425
|
staleness: stalenessCheck.staleness,
|
|
@@ -20345,7 +20430,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20345
20430
|
if (undelivered !== void 0) {
|
|
20346
20431
|
console.error(pc.yellow(`warning: ${undelivered.message}`));
|
|
20347
20432
|
} else {
|
|
20348
|
-
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 ?? "?"}`;
|
|
20349
20434
|
console.log(pc.dim(`sent ${text2.length} bytes to ${name} (${how})`));
|
|
20350
20435
|
}
|
|
20351
20436
|
printNextSteps(nextSteps3);
|
|
@@ -20408,7 +20493,7 @@ async function cmdAgentShow(db, rawName, opts) {
|
|
|
20408
20493
|
} catch {
|
|
20409
20494
|
scrollback = "";
|
|
20410
20495
|
}
|
|
20411
|
-
const reading = (await readAgentStates([agent], { stateDir:
|
|
20496
|
+
const reading = (await readAgentStates([agent], { stateDir: dirname13(db.name) })).get(
|
|
20412
20497
|
agentKey(agent)
|
|
20413
20498
|
);
|
|
20414
20499
|
const displayed = {
|
|
@@ -20443,7 +20528,7 @@ async function cmdAgentShow(db, rawName, opts) {
|
|
|
20443
20528
|
}
|
|
20444
20529
|
async function cmdMe(db, opts = {}) {
|
|
20445
20530
|
const self = resolveSelf(db);
|
|
20446
|
-
const reading = (await readAgentStates([self], { stateDir:
|
|
20531
|
+
const reading = (await readAgentStates([self], { stateDir: dirname13(db.name) })).get(
|
|
20447
20532
|
agentKey(self)
|
|
20448
20533
|
);
|
|
20449
20534
|
const displayed = {
|
|
@@ -20636,7 +20721,7 @@ async function cmdAbort(db, rawName, opts = {}) {
|
|
|
20636
20721
|
intent: "Steer it with the next instruction",
|
|
20637
20722
|
command: `mu agent send ${name} '...' -w ${ws}`
|
|
20638
20723
|
},
|
|
20639
|
-
{ 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}` }]
|
|
20640
20725
|
];
|
|
20641
20726
|
if (opts.json) {
|
|
20642
20727
|
emitJson({ ...result, nextSteps: nextSteps3 });
|
|
@@ -20661,12 +20746,22 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20661
20746
|
return { name, workstreamName };
|
|
20662
20747
|
})
|
|
20663
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
|
+
}
|
|
20664
20759
|
const readStatuses = async (pending2) => {
|
|
20665
20760
|
const agents = pending2.flatMap((ref) => {
|
|
20666
20761
|
const agent = getAgent(db, ref.name, ref.workstreamName);
|
|
20667
20762
|
return agent === void 0 ? [] : [agent];
|
|
20668
20763
|
});
|
|
20669
|
-
const readings = await readAgentStates(agents, { stateDir:
|
|
20764
|
+
const readings = await readAgentStates(agents, { stateDir: dirname13(db.name) });
|
|
20670
20765
|
const snapshots = /* @__PURE__ */ new Map();
|
|
20671
20766
|
for (const ref of pending2) {
|
|
20672
20767
|
const agent = getAgent(db, ref.name, ref.workstreamName);
|
|
@@ -20683,9 +20778,15 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20683
20778
|
if (agent === void 0) return void 0;
|
|
20684
20779
|
const sock = agentCtlSocket(db, agent);
|
|
20685
20780
|
if (!expectsCtl(agent, sock)) return void 0;
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
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(
|
|
20689
20790
|
(reply) => reply.ok && reply.state !== void 0 ? {
|
|
20690
20791
|
status: ctlRuntimeState(reply.state),
|
|
20691
20792
|
...reply.lastText !== void 0 ? { lastText: reply.lastText } : {},
|
|
@@ -20700,7 +20801,7 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20700
20801
|
return again.kind === "ok" ? { status: "unknown", unknownReason: "ctl wait interrupted" } : { status: null };
|
|
20701
20802
|
}
|
|
20702
20803
|
);
|
|
20703
|
-
return { initial
|
|
20804
|
+
return { initial, settled };
|
|
20704
20805
|
};
|
|
20705
20806
|
const timeoutMs = (opts.timeout ?? 600) * 1e3;
|
|
20706
20807
|
const result = await waitForAgents(db, refs, {
|
|
@@ -20723,6 +20824,12 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20723
20824
|
const fired = result.agents.filter((a) => a.fired);
|
|
20724
20825
|
const firing = wantFirstShape && fired.length > 0 ? fired[0] : null;
|
|
20725
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
|
+
] : [];
|
|
20726
20833
|
if (opts.json) {
|
|
20727
20834
|
emitJson({
|
|
20728
20835
|
agents: result.agents.map((a) => ({
|
|
@@ -20732,12 +20839,7 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20732
20839
|
timedOut: result.timedOut,
|
|
20733
20840
|
...firing ? { firing: { workstream: firing.workstreamName, name: firing.name } } : {},
|
|
20734
20841
|
...dead.length > 0 ? { dead: dead.map(qualified) } : {},
|
|
20735
|
-
nextSteps:
|
|
20736
|
-
{
|
|
20737
|
-
intent: "Read the finished agent",
|
|
20738
|
-
command: `mu agent read ${firing.name} -w ${firing.workstreamName}`
|
|
20739
|
-
}
|
|
20740
|
-
] : []
|
|
20842
|
+
nextSteps: readHint
|
|
20741
20843
|
});
|
|
20742
20844
|
} else if (result.timedOut) {
|
|
20743
20845
|
console.log(
|
|
@@ -20749,12 +20851,7 @@ async function cmdAgentWait(db, rawNames, opts) {
|
|
|
20749
20851
|
console.log(pc.red(`Agent pane(s) died: ${dead.map(qualified).join(", ")}`));
|
|
20750
20852
|
} else if (firing) {
|
|
20751
20853
|
console.log(`${pc.bold(qualified(firing))} finished`);
|
|
20752
|
-
printNextSteps(
|
|
20753
|
-
{
|
|
20754
|
-
intent: "Read the finished agent",
|
|
20755
|
-
command: `mu agent read ${firing.name} -w ${firing.workstreamName}`
|
|
20756
|
-
}
|
|
20757
|
-
]);
|
|
20854
|
+
printNextSteps(readHint);
|
|
20758
20855
|
} else {
|
|
20759
20856
|
console.log(`All ${result.agents.length} agent(s) finished`);
|
|
20760
20857
|
}
|
|
@@ -20849,7 +20946,11 @@ function wireAgentCommands(program) {
|
|
|
20849
20946
|
"--timeout <seconds>",
|
|
20850
20947
|
"max seconds to wait (0 = forever, default 600)",
|
|
20851
20948
|
parseNonNegativeInt
|
|
20852
|
-
).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) {
|
|
20853
20954
|
const opts = this.optsWithGlobals();
|
|
20854
20955
|
return handle(
|
|
20855
20956
|
(db) => cmdAgentWait(db, names, {
|
|
@@ -20899,7 +21000,7 @@ function wireSelfCommands(program) {
|
|
|
20899
21000
|
|
|
20900
21001
|
// src/cli/db.ts
|
|
20901
21002
|
import { existsSync as existsSync16, mkdirSync as mkdirSync6, statSync as statSync5 } from "fs";
|
|
20902
|
-
import { dirname as
|
|
21003
|
+
import { dirname as dirname14, resolve as resolve7 } from "path";
|
|
20903
21004
|
|
|
20904
21005
|
// src/compact.ts
|
|
20905
21006
|
init_legacy_ops();
|
|
@@ -21991,7 +22092,7 @@ function cmdDbBackup(db, file, opts = {}) {
|
|
|
21991
22092
|
if (existsSync16(target)) {
|
|
21992
22093
|
throw new UsageError(`${target} already exists: mu db backup never overwrites`);
|
|
21993
22094
|
}
|
|
21994
|
-
mkdirSync6(
|
|
22095
|
+
mkdirSync6(dirname14(target), { recursive: true });
|
|
21995
22096
|
db.prepare("VACUUM INTO ?").run(target);
|
|
21996
22097
|
const nextSteps3 = [
|
|
21997
22098
|
{
|
|
@@ -22135,7 +22236,7 @@ function wireDbCommands(program) {
|
|
|
22135
22236
|
|
|
22136
22237
|
// src/cli/doctor.ts
|
|
22137
22238
|
init_agents();
|
|
22138
|
-
import { dirname as
|
|
22239
|
+
import { dirname as dirname15 } from "path";
|
|
22139
22240
|
init_db();
|
|
22140
22241
|
init_doctor_summary();
|
|
22141
22242
|
|
|
@@ -22301,7 +22402,7 @@ async function cmdDoctor(db, opts = {}) {
|
|
|
22301
22402
|
printCheck("mu skill", skillDoctorCheck());
|
|
22302
22403
|
printCheck(
|
|
22303
22404
|
"ctl",
|
|
22304
|
-
(await ctlSocketsDoctorCheck(listAgents(db), { stateDir:
|
|
22405
|
+
(await ctlSocketsDoctorCheck(listAgents(db), { stateDir: dirname15(db.name) })).check
|
|
22305
22406
|
);
|
|
22306
22407
|
console.log(pc.bold("\ndb"));
|
|
22307
22408
|
console.log(` path : ${pc.dim(defaultDbPath())}`);
|
|
@@ -22499,7 +22600,7 @@ function opsSizeHint(db) {
|
|
|
22499
22600
|
async function cmdDoctorJson(db, opts = {}) {
|
|
22500
22601
|
const health = await muxHealth();
|
|
22501
22602
|
const muExt = ctlExtensionDoctorCheck();
|
|
22502
|
-
const ctlSockets = await ctlSocketsDoctorCheck(listAgents(db), { stateDir:
|
|
22603
|
+
const ctlSockets = await ctlSocketsDoctorCheck(listAgents(db), { stateDir: dirname15(db.name) });
|
|
22503
22604
|
const env = {
|
|
22504
22605
|
// `mux` is the backend-agnostic key. `tmux` is kept as an alias for
|
|
22505
22606
|
// back-compat with scripts that grew around the pre-MuxBackend
|
|
@@ -23203,7 +23304,6 @@ function wireSqlCommand(program) {
|
|
|
23203
23304
|
}
|
|
23204
23305
|
|
|
23205
23306
|
// src/cli/state.ts
|
|
23206
|
-
init_agents();
|
|
23207
23307
|
init_db();
|
|
23208
23308
|
init_glyphs();
|
|
23209
23309
|
init_output();
|
|
@@ -23218,7 +23318,7 @@ import path from "path";
|
|
|
23218
23318
|
// src/project-root.ts
|
|
23219
23319
|
import { execFile as execFile3 } from "child_process";
|
|
23220
23320
|
import { realpath } from "fs/promises";
|
|
23221
|
-
import { dirname as
|
|
23321
|
+
import { dirname as dirname16, resolve as resolve8 } from "path";
|
|
23222
23322
|
import { promisify as promisify3 } from "util";
|
|
23223
23323
|
var exec2 = promisify3(execFile3);
|
|
23224
23324
|
async function run2(bin, args, cwd) {
|
|
@@ -23240,7 +23340,7 @@ async function gitProjectRoot(workspacePath2) {
|
|
|
23240
23340
|
workspacePath2
|
|
23241
23341
|
);
|
|
23242
23342
|
if (commonDir.length === 0) return null;
|
|
23243
|
-
return realpathOrResolve(
|
|
23343
|
+
return realpathOrResolve(dirname16(commonDir));
|
|
23244
23344
|
} catch {
|
|
23245
23345
|
return null;
|
|
23246
23346
|
}
|
|
@@ -23260,7 +23360,7 @@ async function workspaceProjectRoot(workspacePath2, backend) {
|
|
|
23260
23360
|
if (backend === "sl") return slProjectRoot(workspacePath2);
|
|
23261
23361
|
const gitRoot = await gitProjectRoot(workspacePath2);
|
|
23262
23362
|
if (gitRoot !== null) return gitRoot;
|
|
23263
|
-
return realpathOrResolve(
|
|
23363
|
+
return realpathOrResolve(dirname16(workspacePath2));
|
|
23264
23364
|
}
|
|
23265
23365
|
|
|
23266
23366
|
// src/cli/tui-launch-focus.ts
|
|
@@ -23449,6 +23549,13 @@ async function cmdState(db, opts) {
|
|
|
23449
23549
|
renderFullMode(perWs);
|
|
23450
23550
|
}
|
|
23451
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
|
+
}
|
|
23452
23559
|
function renderFullMode(perWs) {
|
|
23453
23560
|
perWs.forEach((d, i) => {
|
|
23454
23561
|
if (i > 0) console.log("");
|
|
@@ -23469,18 +23576,8 @@ function renderFullCard(d) {
|
|
|
23469
23576
|
);
|
|
23470
23577
|
}
|
|
23471
23578
|
}
|
|
23472
|
-
|
|
23473
|
-
|
|
23474
|
-
const lingering = view.agents.filter((a) => isLingeringScratchAgent(a.updatedAt, threshold));
|
|
23475
|
-
if (lingering.length > 0) {
|
|
23476
|
-
const names = lingering.map((a) => a.name).join(", ");
|
|
23477
|
-
console.log(
|
|
23478
|
-
pc.yellow(
|
|
23479
|
-
`\u26A0 ${lingering.length} idle scratch agent(s): ${names}. Close when done: mu agent close ${lingering[0]?.name ?? "<name>"} -w scratch`
|
|
23480
|
-
)
|
|
23481
|
-
);
|
|
23482
|
-
}
|
|
23483
|
-
}
|
|
23579
|
+
const nudge = scratchIdleNudge(workstreamName, view.agents);
|
|
23580
|
+
if (nudge !== null) console.log(pc.yellow(nudge));
|
|
23484
23581
|
const total = d.taskCount;
|
|
23485
23582
|
const open = ready.length + inProgress.length + blocked.length + d.triage.length;
|
|
23486
23583
|
if (total >= BIG_WORKSTREAM_TASKS && open * 10 < total) {
|
|
@@ -24753,7 +24850,7 @@ async function cmdBareTui(db, program, requestedWorkstreams) {
|
|
|
24753
24850
|
}
|
|
24754
24851
|
function readPackageVersion() {
|
|
24755
24852
|
try {
|
|
24756
|
-
const here =
|
|
24853
|
+
const here = dirname17(fileURLToPath3(import.meta.url));
|
|
24757
24854
|
const pkgPath = join14(here, "..", "package.json");
|
|
24758
24855
|
const raw = readFileSync4(pkgPath, "utf8");
|
|
24759
24856
|
const parsed = JSON.parse(raw);
|