@mu-crew/mu 3.2.1 → 3.4.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/AGENTS.md +10 -5
- package/README.md +83 -22
- package/dist/cli.js +294 -98
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +346 -85
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +68 -13
- package/dist/index.js +159 -71
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +1 -0
- package/docs/VISION.md +3 -1
- package/docs/VOCABULARY.md +6 -1
- package/docs/architecture/control-socket.md +17 -3
- package/docs/architecture/dag.md +3 -3
- package/docs/architecture/mux.md +3 -4
- package/docs/guide/README.md +5 -0
- package/docs/guide/delegate.md +30 -3
- package/docs/guide/dispatch.md +11 -4
- package/docs/guide/getting-started.md +3 -0
- package/docs/guide/recovery.md +3 -1
- package/docs/guide/remote.md +1 -1
- package/docs/history/CHANGELOG-pre-3.md +1 -1
- package/docs/reference/env.md +2 -1
- package/docs/reference/naming.md +18 -0
- package/docs/specs/2026-09-29-task-substates.md +1 -1
- package/docs/specs/2026-10-03-keep-driving-nudge.md +142 -0
- package/package.json +2 -1
- package/prompts/mu-debug.md +8 -0
- package/prompts/mu-refute.md +8 -0
- package/prompts/mu-research.md +8 -0
- package/prompts/mu-review.md +8 -0
- package/prompts/mu-rules-audit.md +8 -0
- package/prompts/mu-sweep.md +8 -0
- package/prompts/mu-tournament.md +9 -0
- package/prompts/mu-until.md +8 -0
- package/prompts/ultrathink.md +10 -0
- package/skills/mu/SKILL.md +124 -145
- package/skills/mu/recipes/adversarial-review.md +91 -0
- package/skills/mu/recipes/backlog-triage.md +43 -0
- package/skills/mu/recipes/brief.md +53 -0
- package/skills/mu/recipes/codemode-driver.md +102 -0
- package/skills/mu/recipes/deep-research.md +67 -0
- package/skills/mu/recipes/fan-out.md +40 -0
- package/skills/mu/recipes/findings.md +103 -0
- package/skills/mu/recipes/hypothesis-panel.md +45 -0
- package/skills/mu/recipes/long-run.md +20 -0
- package/skills/mu/recipes/loop-until-done.md +42 -0
- package/skills/mu/recipes/orchestrator-loop.md +98 -0
- package/skills/mu/recipes/plan.md +59 -0
- package/skills/mu/recipes/recovery.md +28 -0
- package/skills/mu/recipes/refute.md +51 -0
- package/skills/mu/{REMOTE_WORKERS.md → recipes/remote-workers.md} +7 -2
- package/skills/mu/recipes/review-panel.md +62 -0
- package/skills/mu/recipes/rules-audit.md +83 -0
- package/skills/mu/recipes/tasks-or-calls.md +67 -0
- package/skills/mu/recipes/tournament.md +40 -0
- package/skills/mu/recipes/ultrathink.md +82 -0
- package/skills/mu/recipes/watcher.md +19 -0
- package/skills/mu/recipes/waves.md +20 -0
- package/skills/mu/recipes/worker.md +26 -0
package/dist/cli.js
CHANGED
|
@@ -14,18 +14,20 @@ function encode(msg) {
|
|
|
14
14
|
return `${JSON.stringify(msg)}
|
|
15
15
|
`;
|
|
16
16
|
}
|
|
17
|
-
var CTL_PROTOCOL_VERSION, CTL_OPS, UNKNOWN_OP_PREFIX, LineDecoder;
|
|
17
|
+
var CTL_PROTOCOL_VERSION, CTL_COMMANDS, CTL_OPS, UNKNOWN_OP_PREFIX, LineDecoder;
|
|
18
18
|
var init_protocol = __esm({
|
|
19
19
|
"src/ctl/protocol.ts"() {
|
|
20
20
|
"use strict";
|
|
21
21
|
CTL_PROTOCOL_VERSION = 1;
|
|
22
|
+
CTL_COMMANDS = ["new", "reload", "compact"];
|
|
22
23
|
CTL_OPS = [
|
|
23
24
|
"hello",
|
|
24
25
|
"status",
|
|
25
26
|
"send",
|
|
26
27
|
"wait",
|
|
27
28
|
"abort",
|
|
28
|
-
"fresh"
|
|
29
|
+
"fresh",
|
|
30
|
+
"command"
|
|
29
31
|
];
|
|
30
32
|
UNKNOWN_OP_PREFIX = "unknown op: ";
|
|
31
33
|
LineDecoder = class {
|
|
@@ -44,7 +46,7 @@ var init_protocol = __esm({
|
|
|
44
46
|
import { createConnection } from "net";
|
|
45
47
|
function clientTimeout(req, override) {
|
|
46
48
|
if (override !== void 0) return override;
|
|
47
|
-
if (req.op === "fresh") return CTL_FRESH_TIMEOUT_MS;
|
|
49
|
+
if (req.op === "fresh" || req.op === "command") return CTL_FRESH_TIMEOUT_MS;
|
|
48
50
|
if (req.op !== "wait") return CTL_DEFAULT_TIMEOUT_MS;
|
|
49
51
|
return req.timeoutMs === void 0 ? void 0 : req.timeoutMs + CTL_WAIT_SLACK_MS;
|
|
50
52
|
}
|
|
@@ -391,7 +393,7 @@ var init_status = __esm({
|
|
|
391
393
|
TASK_STATUSES = ["OPEN", "IN_PROGRESS", "CLOSED"];
|
|
392
394
|
TASK_STATUS_LIST = TASK_STATUSES.join(" | ");
|
|
393
395
|
TASK_SUBSTATES = {
|
|
394
|
-
OPEN: ["todo", "parked"],
|
|
396
|
+
OPEN: ["todo", "parked", "triage"],
|
|
395
397
|
IN_PROGRESS: ["active"],
|
|
396
398
|
CLOSED: ["done", "rejected", "wontfix", "duplicate", "superseded"]
|
|
397
399
|
};
|
|
@@ -605,7 +607,7 @@ CREATE VIEW ready AS
|
|
|
605
607
|
SELECT t.*
|
|
606
608
|
FROM tasks t
|
|
607
609
|
WHERE t.status = 'OPEN'
|
|
608
|
-
AND t.substate
|
|
610
|
+
AND t.substate NOT IN ('parked', 'triage')
|
|
609
611
|
AND NOT EXISTS (
|
|
610
612
|
SELECT 1
|
|
611
613
|
FROM task_edges e
|
|
@@ -1289,27 +1291,6 @@ async function awaitPaneQuiescence(paneId, budgetMs) {
|
|
|
1289
1291
|
await currentSleep(Math.min(SEND_READINESS_POLL_MS, remaining));
|
|
1290
1292
|
}
|
|
1291
1293
|
}
|
|
1292
|
-
async function awaitNewSession(paneId, baseline, budgetMs) {
|
|
1293
|
-
if (budgetMs <= 0) return true;
|
|
1294
|
-
const deadline = Date.now() + budgetMs;
|
|
1295
|
-
let previous = baseline;
|
|
1296
|
-
let stable = 0;
|
|
1297
|
-
for (; ; ) {
|
|
1298
|
-
const scrollback = await capturePane(paneId, { lines: 50 }).catch(() => void 0);
|
|
1299
|
-
if (scrollback !== void 0 && scrollback !== baseline) {
|
|
1300
|
-
if (scrollback === previous) {
|
|
1301
|
-
stable++;
|
|
1302
|
-
if (stable >= QUIESCENCE_CONFIRMATIONS) return true;
|
|
1303
|
-
} else {
|
|
1304
|
-
stable = 0;
|
|
1305
|
-
}
|
|
1306
|
-
previous = scrollback;
|
|
1307
|
-
}
|
|
1308
|
-
const remaining = deadline - Date.now();
|
|
1309
|
-
if (remaining <= 0) return false;
|
|
1310
|
-
await currentSleep(Math.min(SEND_READINESS_POLL_MS, remaining));
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
1294
|
function pasteProbe(text2) {
|
|
1314
1295
|
const firstLine = (text2.split("\n")[0] ?? "").trim();
|
|
1315
1296
|
return firstLine.slice(0, 24);
|
|
@@ -1337,7 +1318,6 @@ async function sendToPane(paneId, text2, opts = {}) {
|
|
|
1337
1318
|
assertValidPaneId(paneId);
|
|
1338
1319
|
const readinessMs = opts.readinessMs ?? defaultSendReadinessMs();
|
|
1339
1320
|
const quiesced = await awaitPaneQuiescence(paneId, readinessMs);
|
|
1340
|
-
const newSessionBaseline = readinessMs > 0 && text2.trim() === "/new" ? await capturePane(paneId, { lines: 50 }).catch(() => void 0) : void 0;
|
|
1341
1321
|
const copyResult = await currentExecutor(["copy-mode", "-q", "-t", paneId]);
|
|
1342
1322
|
if (copyResult.exitCode !== 0 && /can't find pane|no current target/i.test(copyResult.stderr)) {
|
|
1343
1323
|
throw new TmuxError(
|
|
@@ -1360,15 +1340,6 @@ async function sendToPane(paneId, text2, opts = {}) {
|
|
|
1360
1340
|
if (delay2 > 0) await currentSleep(delay2);
|
|
1361
1341
|
await tmux(["send-keys", "-t", paneId, "Enter"]);
|
|
1362
1342
|
if (readinessMs <= 0) return;
|
|
1363
|
-
if (newSessionBaseline !== void 0 && !await awaitNewSession(paneId, newSessionBaseline, readinessMs)) {
|
|
1364
|
-
const warn3 = opts.onUndelivered ?? defaultUndeliveredWarning;
|
|
1365
|
-
warn3({
|
|
1366
|
-
paneId,
|
|
1367
|
-
reason: "transition-unconfirmed",
|
|
1368
|
-
message: `send to ${paneId} submitted /new, but the new session did not become ready within ${readinessMs}ms. Wait for the pane to settle before sending more work.`
|
|
1369
|
-
});
|
|
1370
|
-
return;
|
|
1371
|
-
}
|
|
1372
1343
|
const probe = pasteProbe(text2);
|
|
1373
1344
|
if (probe.length === 0) return;
|
|
1374
1345
|
for (let attempt = 0; attempt <= SUBMIT_RETRIES; attempt++) {
|
|
@@ -4401,7 +4372,7 @@ async function spawnAgent(db, opts) {
|
|
|
4401
4372
|
agent = created.row;
|
|
4402
4373
|
const startAgent = (await activeMux()).startAgentInPane;
|
|
4403
4374
|
if (startAgent === void 0) {
|
|
4404
|
-
await awaitSpawnLiveness(paneId, opts.name,
|
|
4375
|
+
await awaitSpawnLiveness(paneId, opts.name, command);
|
|
4405
4376
|
} else {
|
|
4406
4377
|
await startAgent({ paneId, name: opts.name, cli, command, commandSource });
|
|
4407
4378
|
}
|
|
@@ -4510,7 +4481,7 @@ function detectSpawnStartupError(scrollback) {
|
|
|
4510
4481
|
}
|
|
4511
4482
|
return void 0;
|
|
4512
4483
|
}
|
|
4513
|
-
async function awaitSpawnLiveness(paneId, agentName,
|
|
4484
|
+
async function awaitSpawnLiveness(paneId, agentName, command) {
|
|
4514
4485
|
const ms = defaultSpawnLivenessMs();
|
|
4515
4486
|
if (ms === 0) return;
|
|
4516
4487
|
await sleep(ms);
|
|
@@ -4525,30 +4496,6 @@ async function awaitSpawnLiveness(paneId, agentName, workstreamName, command) {
|
|
|
4525
4496
|
throw new AgentSpawnStartupError(agentName, paneId, matchedLine, scrollback);
|
|
4526
4497
|
}
|
|
4527
4498
|
}
|
|
4528
|
-
await awaitSpawnReadiness(paneId, agentName, workstreamName, command);
|
|
4529
|
-
}
|
|
4530
|
-
function defaultSpawnReadinessMs() {
|
|
4531
|
-
const raw = process.env.MU_SPAWN_READINESS_MS;
|
|
4532
|
-
if (raw === void 0) return 1e4;
|
|
4533
|
-
const parsed = Number.parseInt(raw, 10);
|
|
4534
|
-
if (Number.isNaN(parsed) || parsed < 0) return 1e4;
|
|
4535
|
-
return parsed;
|
|
4536
|
-
}
|
|
4537
|
-
async function awaitSpawnReadiness(paneId, agentName, workstreamName, command) {
|
|
4538
|
-
const budgetMs = defaultSpawnReadinessMs();
|
|
4539
|
-
if (budgetMs === 0 || !murmurAvailable()) return;
|
|
4540
|
-
const mux = await activeMux();
|
|
4541
|
-
const deadline = Date.now() + budgetMs;
|
|
4542
|
-
const agent = { name: agentName, workstreamName, paneId };
|
|
4543
|
-
while (Date.now() < deadline) {
|
|
4544
|
-
if (!await mux.paneExists(paneId)) {
|
|
4545
|
-
const scrollback = await mux.capturePane(paneId, { lines: 50 }).catch(() => void 0);
|
|
4546
|
-
throw new AgentDiedOnSpawnError(agentName, paneId, scrollback, command);
|
|
4547
|
-
}
|
|
4548
|
-
const reading = (await readAgentStates([agent])).get(agentKey(agent));
|
|
4549
|
-
if (reading?.source === "murmur") return;
|
|
4550
|
-
await sleep(Math.min(READINESS_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
4551
|
-
}
|
|
4552
4499
|
}
|
|
4553
4500
|
async function createOrReusePane(opts) {
|
|
4554
4501
|
const mux = await activeMux();
|
|
@@ -4594,11 +4541,10 @@ async function firstPaneInWindow(mux, session, windowId) {
|
|
|
4594
4541
|
}
|
|
4595
4542
|
return pane.paneId;
|
|
4596
4543
|
}
|
|
4597
|
-
var AGENT_NAME_CONVENTION_RE, execFileP, activeCommandResolver, CTL_POLL_INTERVAL_MS, STARTUP_ERROR_PATTERNS, STARTUP_ERROR_TAIL_LINES
|
|
4544
|
+
var AGENT_NAME_CONVENTION_RE, execFileP, activeCommandResolver, CTL_POLL_INTERVAL_MS, STARTUP_ERROR_PATTERNS, STARTUP_ERROR_TAIL_LINES;
|
|
4598
4545
|
var init_spawn = __esm({
|
|
4599
4546
|
"src/agents/spawn.ts"() {
|
|
4600
4547
|
"use strict";
|
|
4601
|
-
init_agent_state();
|
|
4602
4548
|
init_agents();
|
|
4603
4549
|
init_client();
|
|
4604
4550
|
init_path();
|
|
@@ -4644,12 +4590,11 @@ var init_spawn = __esm({
|
|
|
4644
4590
|
/^(?:.*: )?(?:command not found|No such file or directory)(?::\s*\S+)?$/i
|
|
4645
4591
|
];
|
|
4646
4592
|
STARTUP_ERROR_TAIL_LINES = 30;
|
|
4647
|
-
READINESS_POLL_INTERVAL_MS = 250;
|
|
4648
4593
|
}
|
|
4649
4594
|
});
|
|
4650
4595
|
|
|
4651
4596
|
// src/agents/errors.ts
|
|
4652
|
-
var AgentSpawnCliNotFoundError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, AgentDiedOnSpawnError, AgentSpawnStartupError, WorkspacePreservedError, AgentCtlUnreachableError, AgentAbortNeedsCtlError, AgentAbortTimeoutError, AgentFreshNeedsCtlError, AgentExtensionOutdatedError, AgentBusyError;
|
|
4597
|
+
var AgentSpawnCliNotFoundError, AgentExistsError, AgentNotFoundError, AgentNotInWorkstreamError, AgentDiedOnSpawnError, AgentSpawnStartupError, WorkspacePreservedError, AgentCtlUnreachableError, AgentAbortNeedsCtlError, AgentAbortTimeoutError, AgentFreshNeedsCtlError, AgentExtensionOutdatedError, AgentBusyError, AgentSlashCommandUnsupportedError;
|
|
4653
4598
|
var init_errors = __esm({
|
|
4654
4599
|
"src/agents/errors.ts"() {
|
|
4655
4600
|
"use strict";
|
|
@@ -5029,23 +4974,47 @@ ${scrollback.trim()}
|
|
|
5029
4974
|
}
|
|
5030
4975
|
};
|
|
5031
4976
|
AgentBusyError = class extends Error {
|
|
5032
|
-
constructor(agentName, workstream) {
|
|
5033
|
-
super(`agent ${agentName} is busy: --fresh would abandon its running turn`);
|
|
4977
|
+
constructor(agentName, workstream, command) {
|
|
4978
|
+
super(`agent ${agentName} is busy: ${command ?? "--fresh"} would abandon its running turn`);
|
|
5034
4979
|
this.agentName = agentName;
|
|
5035
4980
|
this.workstream = workstream;
|
|
4981
|
+
this.command = command;
|
|
5036
4982
|
}
|
|
5037
4983
|
agentName;
|
|
5038
4984
|
workstream;
|
|
4985
|
+
command;
|
|
5039
4986
|
name = "AgentBusyError";
|
|
5040
4987
|
errorNextSteps() {
|
|
4988
|
+
const retry = this.command === void 0 ? `mu agent send ${this.agentName} --fresh --force '...' -w ${this.workstream}` : `mu agent send ${this.agentName} '${this.command}' --force -w ${this.workstream}`;
|
|
5041
4989
|
return [
|
|
5042
4990
|
{
|
|
5043
4991
|
intent: "Stop the turn first, then retry",
|
|
5044
4992
|
command: `mu agent abort ${this.agentName} -w ${this.workstream}`
|
|
5045
4993
|
},
|
|
4994
|
+
{ intent: "Or abandon the turn in one step", command: retry }
|
|
4995
|
+
];
|
|
4996
|
+
}
|
|
4997
|
+
};
|
|
4998
|
+
AgentSlashCommandUnsupportedError = class extends Error {
|
|
4999
|
+
constructor(agentName, workstream, text2, supported) {
|
|
5000
|
+
super(
|
|
5001
|
+
`agent ${agentName} runs pi: over the control socket mu runs only ${supported.join(", ")}; ${JSON.stringify(text2.split("\n")[0])} is not one of them. Pass --via mux to type it into the pane`
|
|
5002
|
+
);
|
|
5003
|
+
this.agentName = agentName;
|
|
5004
|
+
this.workstream = workstream;
|
|
5005
|
+
this.text = text2;
|
|
5006
|
+
this.supported = supported;
|
|
5007
|
+
}
|
|
5008
|
+
agentName;
|
|
5009
|
+
workstream;
|
|
5010
|
+
text;
|
|
5011
|
+
supported;
|
|
5012
|
+
name = "AgentSlashCommandUnsupportedError";
|
|
5013
|
+
errorNextSteps() {
|
|
5014
|
+
return [
|
|
5046
5015
|
{
|
|
5047
|
-
intent: "
|
|
5048
|
-
command: `mu agent send ${this.agentName}
|
|
5016
|
+
intent: "Type it into the pane on purpose (tmux paste; unconfirmed)",
|
|
5017
|
+
command: `mu agent send ${this.agentName} '${this.text.split("\n")[0]}' --via mux -w ${this.workstream}`
|
|
5049
5018
|
}
|
|
5050
5019
|
];
|
|
5051
5020
|
}
|
|
@@ -5062,10 +5031,21 @@ function agentCtlSocket(db, agent) {
|
|
|
5062
5031
|
function expectsCtl(agent, sock = ctlSocketPath(agent.workstreamName, agent.name)) {
|
|
5063
5032
|
return speaksMuCtl(agent.cli, resolveCliCommand(agent.cli)) || existsSync7(sock);
|
|
5064
5033
|
}
|
|
5065
|
-
function chooseTransport(agent,
|
|
5066
|
-
if (text2.startsWith("/")) return "mux";
|
|
5034
|
+
function chooseTransport(agent, sock) {
|
|
5067
5035
|
return expectsCtl(agent, sock) ? "ctl" : "mux";
|
|
5068
5036
|
}
|
|
5037
|
+
function parseSessionCommand(text2) {
|
|
5038
|
+
const t = text2.trim();
|
|
5039
|
+
if (t === "/new") return { name: "new" };
|
|
5040
|
+
if (t === "/reload") return { name: "reload" };
|
|
5041
|
+
const m = /^\/compact(?:\s+([\s\S]*))?$/.exec(t);
|
|
5042
|
+
if (!m) return void 0;
|
|
5043
|
+
const instructions = m[1]?.trim();
|
|
5044
|
+
return instructions ? { name: "compact", instructions } : { name: "compact" };
|
|
5045
|
+
}
|
|
5046
|
+
function isSlashCommand(text2) {
|
|
5047
|
+
return /^\/[A-Za-z][\w:-]*(?:\s|$)/.test(text2.trimStart());
|
|
5048
|
+
}
|
|
5069
5049
|
async function extensionOutdated(agent, sock, e) {
|
|
5070
5050
|
let extVersion;
|
|
5071
5051
|
try {
|
|
@@ -5094,11 +5074,23 @@ async function sendViaTransport(agent, text2, opts) {
|
|
|
5094
5074
|
if (opts.fresh && (opts.via === "mux" || !expectsCtl(agent, sock))) {
|
|
5095
5075
|
throw new AgentFreshNeedsCtlError(agent.name, agent.workstreamName, agent.cli);
|
|
5096
5076
|
}
|
|
5097
|
-
const transport = opts.fresh ? "ctl" : opts.via ?? chooseTransport(agent,
|
|
5077
|
+
const transport = opts.fresh ? "ctl" : opts.via ?? chooseTransport(agent, sock);
|
|
5098
5078
|
if (transport === "mux") {
|
|
5099
5079
|
await (await activeMux()).sendToPane(agent.paneId, text2, opts);
|
|
5100
5080
|
return { transport };
|
|
5101
5081
|
}
|
|
5082
|
+
if (!opts.fresh) {
|
|
5083
|
+
const cmd = parseSessionCommand(text2);
|
|
5084
|
+
if (cmd) return runSessionCommand(agent, sock, cmd, opts.force === true);
|
|
5085
|
+
if (isSlashCommand(text2)) {
|
|
5086
|
+
throw new AgentSlashCommandUnsupportedError(
|
|
5087
|
+
agent.name,
|
|
5088
|
+
agent.workstreamName,
|
|
5089
|
+
text2,
|
|
5090
|
+
CTL_SLASH_COMMANDS
|
|
5091
|
+
);
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5102
5094
|
const reply = await ctlRequestFor(
|
|
5103
5095
|
agent,
|
|
5104
5096
|
sock,
|
|
@@ -5112,14 +5104,36 @@ async function sendViaTransport(agent, text2, opts) {
|
|
|
5112
5104
|
}
|
|
5113
5105
|
return reply.state === void 0 ? { transport } : { transport, state: reply.state };
|
|
5114
5106
|
}
|
|
5107
|
+
async function runSessionCommand(agent, sock, cmd, force) {
|
|
5108
|
+
const reply = await ctlRequestFor(agent, sock, {
|
|
5109
|
+
op: "command",
|
|
5110
|
+
name: cmd.name,
|
|
5111
|
+
...cmd.instructions !== void 0 ? { instructions: cmd.instructions } : {},
|
|
5112
|
+
...force ? { force: true } : {}
|
|
5113
|
+
});
|
|
5114
|
+
if (!reply.ok) {
|
|
5115
|
+
if (reply.error === "busy") {
|
|
5116
|
+
throw new AgentBusyError(agent.name, agent.workstreamName, `/${cmd.name}`);
|
|
5117
|
+
}
|
|
5118
|
+
throw new Error(`pi refused /${cmd.name}: ${reply.error}`);
|
|
5119
|
+
}
|
|
5120
|
+
return {
|
|
5121
|
+
transport: "ctl",
|
|
5122
|
+
command: cmd.name,
|
|
5123
|
+
...reply.state !== void 0 ? { state: reply.state } : {}
|
|
5124
|
+
};
|
|
5125
|
+
}
|
|
5126
|
+
var CTL_SLASH_COMMANDS;
|
|
5115
5127
|
var init_transport = __esm({
|
|
5116
5128
|
"src/agents/transport.ts"() {
|
|
5117
5129
|
"use strict";
|
|
5118
5130
|
init_client();
|
|
5119
5131
|
init_path();
|
|
5132
|
+
init_protocol();
|
|
5120
5133
|
init_mux2();
|
|
5121
5134
|
init_errors();
|
|
5122
5135
|
init_spawn();
|
|
5136
|
+
CTL_SLASH_COMMANDS = CTL_COMMANDS.map((n) => `/${n}`);
|
|
5123
5137
|
}
|
|
5124
5138
|
});
|
|
5125
5139
|
|
|
@@ -5836,7 +5850,7 @@ var init_id = __esm({
|
|
|
5836
5850
|
});
|
|
5837
5851
|
|
|
5838
5852
|
// src/tasks/errors.ts
|
|
5839
|
-
var TaskNotFoundError, TaskIdInvalidError, TaskExistsError, TaskNotInWorkstreamError, TaskAlreadyOwnedError, ClaimerNotRegisteredError, TaskClaimStaleWorkspaceError, CycleError, ReaperDetectedDuringWaitError, StallDetectedDuringWaitError, CrossWorkstreamEdgeError, SubstateReasonRequiredError, TaskParkStateError, TaskParkedError, InvalidSubstateError;
|
|
5853
|
+
var TaskNotFoundError, TaskIdInvalidError, TaskExistsError, TaskNotInWorkstreamError, TaskAlreadyOwnedError, ClaimerNotRegisteredError, TaskClaimStaleWorkspaceError, CycleError, ReaperDetectedDuringWaitError, StallDetectedDuringWaitError, CrossWorkstreamEdgeError, SubstateReasonRequiredError, TaskParkStateError, TaskInTriageError, TaskParkedError, InvalidSubstateError;
|
|
5840
5854
|
var init_errors2 = __esm({
|
|
5841
5855
|
"src/tasks/errors.ts"() {
|
|
5842
5856
|
"use strict";
|
|
@@ -6184,6 +6198,30 @@ var init_errors2 = __esm({
|
|
|
6184
6198
|
];
|
|
6185
6199
|
}
|
|
6186
6200
|
};
|
|
6201
|
+
TaskInTriageError = class extends Error {
|
|
6202
|
+
constructor(taskId, workstream) {
|
|
6203
|
+
super(`task ${taskId} is in triage (OPEN/triage); accept it or claim with --force`);
|
|
6204
|
+
this.taskId = taskId;
|
|
6205
|
+
this.workstream = workstream;
|
|
6206
|
+
}
|
|
6207
|
+
taskId;
|
|
6208
|
+
workstream;
|
|
6209
|
+
name = "TaskInTriageError";
|
|
6210
|
+
errorNextSteps() {
|
|
6211
|
+
const ws = this.workstream;
|
|
6212
|
+
return [
|
|
6213
|
+
{
|
|
6214
|
+
intent: "Accept it as work, then claim",
|
|
6215
|
+
command: `mu task accept ${this.taskId} -w ${ws}`
|
|
6216
|
+
},
|
|
6217
|
+
{
|
|
6218
|
+
intent: "Decline it",
|
|
6219
|
+
command: `mu task close ${this.taskId} --as rejected --why "<reason>" -w ${ws}`
|
|
6220
|
+
},
|
|
6221
|
+
{ intent: "Claim it anyway", command: `mu task claim ${this.taskId} --force -w ${ws}` }
|
|
6222
|
+
];
|
|
6223
|
+
}
|
|
6224
|
+
};
|
|
6187
6225
|
TaskParkedError = class extends Error {
|
|
6188
6226
|
constructor(taskId, workstream) {
|
|
6189
6227
|
super(`task ${taskId} is parked (OPEN/parked); unpark it or claim with --force`);
|
|
@@ -6746,8 +6784,17 @@ function addTaskImpl(db, opts) {
|
|
|
6746
6784
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6747
6785
|
const insertResult = db.prepare(
|
|
6748
6786
|
`INSERT INTO tasks (workstream_id, local_id, title, status, substate, impact, effort_days, created_at, updated_at)
|
|
6749
|
-
VALUES (?, ?, ?, 'OPEN',
|
|
6750
|
-
).run(
|
|
6787
|
+
VALUES (?, ?, ?, 'OPEN', ?, ?, ?, ?, ?)`
|
|
6788
|
+
).run(
|
|
6789
|
+
wsId,
|
|
6790
|
+
opts.localId,
|
|
6791
|
+
opts.title,
|
|
6792
|
+
opts.triage === true ? "triage" : "todo",
|
|
6793
|
+
opts.impact,
|
|
6794
|
+
opts.effortDays,
|
|
6795
|
+
now,
|
|
6796
|
+
now
|
|
6797
|
+
);
|
|
6751
6798
|
const newTaskId = Number(insertResult.lastInsertRowid);
|
|
6752
6799
|
if (opts.blockedBy && opts.blockedBy.length > 0) {
|
|
6753
6800
|
const blockerLookupSameWs = db.prepare(
|
|
@@ -7035,6 +7082,28 @@ function parkTask(db, localId, opts) {
|
|
|
7035
7082
|
})()
|
|
7036
7083
|
);
|
|
7037
7084
|
}
|
|
7085
|
+
function acceptTask(db, localId, opts) {
|
|
7086
|
+
return withOpContext(
|
|
7087
|
+
db,
|
|
7088
|
+
{ intent: "task.accept", group: "new" },
|
|
7089
|
+
() => db.transaction(() => {
|
|
7090
|
+
const before = getTask(db, localId, opts.workstream);
|
|
7091
|
+
if (!before) throw new TaskNotFoundError(localId);
|
|
7092
|
+
if (before.status !== "OPEN" || before.substate !== "triage") {
|
|
7093
|
+
return {
|
|
7094
|
+
previousStatus: before.status,
|
|
7095
|
+
status: before.status,
|
|
7096
|
+
previousSubstate: before.substate,
|
|
7097
|
+
substate: before.substate,
|
|
7098
|
+
changed: false
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
const r = setTaskStatus(db, localId, "OPEN", { workstream: opts.workstream });
|
|
7102
|
+
recordEvidenceNote(db, localId, before.workstreamName, "ACCEPT", opts);
|
|
7103
|
+
return r;
|
|
7104
|
+
})()
|
|
7105
|
+
);
|
|
7106
|
+
}
|
|
7038
7107
|
function unparkTask(db, localId, opts) {
|
|
7039
7108
|
return withOpContext(
|
|
7040
7109
|
db,
|
|
@@ -7177,6 +7246,9 @@ function assertNotParked(task, opts) {
|
|
|
7177
7246
|
if (task.status === "OPEN" && task.substate === "parked" && opts.force !== true) {
|
|
7178
7247
|
throw new TaskParkedError(task.name, task.workstreamName);
|
|
7179
7248
|
}
|
|
7249
|
+
if (task.status === "OPEN" && task.substate === "triage" && opts.force !== true) {
|
|
7250
|
+
throw new TaskInTriageError(task.name, task.workstreamName);
|
|
7251
|
+
}
|
|
7180
7252
|
}
|
|
7181
7253
|
async function resolveActorIdentity() {
|
|
7182
7254
|
const worker = await resolveWorkerIdentity();
|
|
@@ -9597,6 +9669,15 @@ function murmurDoctorCheck(agentStateSource, opts = {}) {
|
|
|
9597
9669
|
detail: version === null ? "agent state from murmur" : `agent state from murmur ${version}`
|
|
9598
9670
|
};
|
|
9599
9671
|
}
|
|
9672
|
+
function extFeatures(env) {
|
|
9673
|
+
const features = [
|
|
9674
|
+
["mu_delegate", "MU_DELEGATE"],
|
|
9675
|
+
["nudge", "MU_NUDGE"]
|
|
9676
|
+
];
|
|
9677
|
+
const on = ["ctl", ...features.filter(([, v]) => env[v] !== "0").map(([f]) => f)];
|
|
9678
|
+
const off = features.filter(([, v]) => env[v] === "0").map(([f, v]) => `${f} disabled by ${v}=0`);
|
|
9679
|
+
return `(${on.join(", ")}${off.length ? `; ${off.join("; ")}` : ""})`;
|
|
9680
|
+
}
|
|
9600
9681
|
function ctlExtensionDoctorCheck(opts) {
|
|
9601
9682
|
const { extension } = inspectLinks(opts);
|
|
9602
9683
|
switch (extension.state) {
|
|
@@ -9604,7 +9685,7 @@ function ctlExtensionDoctorCheck(opts) {
|
|
|
9604
9685
|
return {
|
|
9605
9686
|
name: "mu ext",
|
|
9606
9687
|
status: "ok",
|
|
9607
|
-
detail: process.env
|
|
9688
|
+
detail: extFeatures(process.env)
|
|
9608
9689
|
};
|
|
9609
9690
|
case "stale-copy":
|
|
9610
9691
|
return { name: "mu ext", status: "warn", detail: `stale copy: ${RELINK}` };
|
|
@@ -10060,6 +10141,10 @@ async function loadWorkstreamSnapshotFast(db, workstream, opts = {}) {
|
|
|
10060
10141
|
blocked: listBlocked(db, workstream),
|
|
10061
10142
|
recentClosed: listRecentClosed(db, workstream),
|
|
10062
10143
|
parkedCount: listTasks(db, workstream, { status: "OPEN", substate: "parked" }).length,
|
|
10144
|
+
triage: listTasks(db, workstream, { status: "OPEN", substate: "triage" }),
|
|
10145
|
+
taskCount: db.prepare(
|
|
10146
|
+
"SELECT count(*) AS n FROM tasks t JOIN workstreams w ON w.id = t.workstream_id WHERE w.name = ?"
|
|
10147
|
+
).get(workstream).n,
|
|
10063
10148
|
allTasks: opts.withAllTasks === true ? listTasks(db, workstream) : [],
|
|
10064
10149
|
workspaces: listWorkspaces2(db, workstream),
|
|
10065
10150
|
workspaceOrphans: listWorkspaceOrphans(db, workstream),
|
|
@@ -10133,6 +10218,8 @@ function minimalSnapshot(workstream) {
|
|
|
10133
10218
|
blocked: [],
|
|
10134
10219
|
recentClosed: [],
|
|
10135
10220
|
parkedCount: 0,
|
|
10221
|
+
triage: [],
|
|
10222
|
+
taskCount: 0,
|
|
10136
10223
|
allTasks: [],
|
|
10137
10224
|
workspaces: [],
|
|
10138
10225
|
workspaceOrphans: [],
|
|
@@ -11301,7 +11388,7 @@ function colorPair(pair) {
|
|
|
11301
11388
|
const text2 = formatPair(pair);
|
|
11302
11389
|
switch (pair.status) {
|
|
11303
11390
|
case "OPEN":
|
|
11304
|
-
return pair.substate === "parked" ? pc.dim(text2) : pc.cyan(text2);
|
|
11391
|
+
return pair.substate === "parked" || pair.substate === "triage" ? pc.dim(text2) : pc.cyan(text2);
|
|
11305
11392
|
case "IN_PROGRESS":
|
|
11306
11393
|
return pc.yellow(text2);
|
|
11307
11394
|
case "CLOSED":
|
|
@@ -11321,7 +11408,7 @@ function colorStatus(status) {
|
|
|
11321
11408
|
function inkColorForPair(pair) {
|
|
11322
11409
|
switch (pair.status) {
|
|
11323
11410
|
case "OPEN":
|
|
11324
|
-
return pair.substate === "parked" ? "gray" : "cyan";
|
|
11411
|
+
return pair.substate === "parked" || pair.substate === "triage" ? "gray" : "cyan";
|
|
11325
11412
|
case "IN_PROGRESS":
|
|
11326
11413
|
return "yellow";
|
|
11327
11414
|
case "CLOSED":
|
|
@@ -16514,6 +16601,7 @@ function yankCommandForTask2(t, ws) {
|
|
|
16514
16601
|
switch (t.status) {
|
|
16515
16602
|
case "OPEN":
|
|
16516
16603
|
if (t.substate === "parked") return `mu task unpark ${t.name} -w ${ws}`;
|
|
16604
|
+
if (t.substate === "triage") return `mu task accept ${t.name} -w ${ws}`;
|
|
16517
16605
|
return t.ownerName === null ? `mu task claim ${t.name} -w ${ws}` : `mu task release ${t.name} -w ${ws}`;
|
|
16518
16606
|
case "IN_PROGRESS":
|
|
16519
16607
|
return `mu task close ${t.name} -w ${ws} --evidence "..."`;
|
|
@@ -17573,6 +17661,7 @@ function snapshotKey(s) {
|
|
|
17573
17661
|
blocked: s.blocked.map(taskKey2),
|
|
17574
17662
|
recentClosed: s.recentClosed.map(taskKey2),
|
|
17575
17663
|
parkedCount: s.parkedCount,
|
|
17664
|
+
triage: s.triage.map(taskKey2),
|
|
17576
17665
|
allTasks: s.allTasks.map(taskKey2),
|
|
17577
17666
|
commitsBackend: s.commitsBackend ?? null,
|
|
17578
17667
|
recentCommits: s.recentCommits.map((c) => [c.sha, c.subject, c.author, c.relTime]),
|
|
@@ -18746,7 +18835,8 @@ async function cmdTaskAdd(db, localId, opts) {
|
|
|
18746
18835
|
title: opts.title,
|
|
18747
18836
|
impact: opts.impact,
|
|
18748
18837
|
effortDays: opts.effortDays,
|
|
18749
|
-
...hasBlockers ? { blockedBy } : {}
|
|
18838
|
+
...hasBlockers ? { blockedBy } : {},
|
|
18839
|
+
...opts.triage === true ? { triage: true } : {}
|
|
18750
18840
|
});
|
|
18751
18841
|
const createdNote = opts.note !== void 0 ? addNote(db, createdTask.name, unescapeNoteText(opts.note), {
|
|
18752
18842
|
author: initialNoteAuthor,
|
|
@@ -18770,7 +18860,7 @@ async function cmdTaskAdd(db, localId, opts) {
|
|
|
18770
18860
|
intent: "Add a blocker",
|
|
18771
18861
|
command: `mu task block ${task.name} --by <other-id> -w ${workstream}`
|
|
18772
18862
|
},
|
|
18773
|
-
{
|
|
18863
|
+
opts.triage === true ? { intent: "Accept it as work", command: `mu task accept ${task.name} -w ${workstream}` } : {
|
|
18774
18864
|
intent: "Claim and start",
|
|
18775
18865
|
command: `mu task claim ${task.name} -w ${workstream} --self (or --for <worker>)`
|
|
18776
18866
|
}
|
|
@@ -18893,6 +18983,12 @@ async function cmdTaskShow(db, rawId, opts = {}) {
|
|
|
18893
18983
|
if (task.substate === "parked") {
|
|
18894
18984
|
console.log("");
|
|
18895
18985
|
printNextSteps([{ intent: "Return it to next", command: `mu task unpark ${ref}` }]);
|
|
18986
|
+
} else if (task.substate === "triage") {
|
|
18987
|
+
console.log("");
|
|
18988
|
+
printNextSteps([
|
|
18989
|
+
{ intent: "Accept it as work", command: `mu task accept ${ref}` },
|
|
18990
|
+
{ intent: "Decline it", command: `mu task close ${ref} --as rejected --why "<reason>"` }
|
|
18991
|
+
]);
|
|
18896
18992
|
} else if (task.status === "CLOSED" && task.substate !== "done") {
|
|
18897
18993
|
console.log("");
|
|
18898
18994
|
printNextSteps([{ intent: "Reopen it", command: `mu task open ${ref}` }]);
|
|
@@ -19017,6 +19113,17 @@ async function cmdTaskList(db, opts) {
|
|
|
19017
19113
|
const basis = relTimeBasisForSort(sortKey);
|
|
19018
19114
|
if (basis !== null) tableOpts.relTimeBasis = basis;
|
|
19019
19115
|
console.log(formatTaskListTable(tasks, tableOpts));
|
|
19116
|
+
const first = tasks.find((t) => t.status === "OPEN" && t.substate === "triage");
|
|
19117
|
+
if (first !== void 0) {
|
|
19118
|
+
printNextSteps([
|
|
19119
|
+
{ intent: "Accept as work", command: `mu task accept ${first.name} -w ${workstream}` },
|
|
19120
|
+
{
|
|
19121
|
+
intent: "Decline",
|
|
19122
|
+
command: `mu task close ${first.name} --as rejected|duplicate --why "<reason>" -w ${workstream}`
|
|
19123
|
+
},
|
|
19124
|
+
{ intent: "Read the evidence", command: `mu task notes ${first.name} -w ${workstream}` }
|
|
19125
|
+
]);
|
|
19126
|
+
}
|
|
19020
19127
|
}
|
|
19021
19128
|
async function cmdTaskNext(db, opts) {
|
|
19022
19129
|
const workstream = await resolveWorkstream(opts.workstream);
|
|
@@ -19037,16 +19144,26 @@ async function cmdTaskNext(db, opts) {
|
|
|
19037
19144
|
}
|
|
19038
19145
|
if (tasks.length === 0) {
|
|
19039
19146
|
const parked = listTasks(db, workstream, { substate: "parked" }).length;
|
|
19040
|
-
|
|
19147
|
+
const triage = listTasks(db, workstream, { substate: "triage" }).length;
|
|
19148
|
+
if (parked === 0 && triage === 0) {
|
|
19041
19149
|
console.log(pc.dim("(no ready tasks)"));
|
|
19042
19150
|
return;
|
|
19043
19151
|
}
|
|
19044
|
-
|
|
19152
|
+
const held = [parked > 0 ? `${parked} parked` : "", triage > 0 ? `${triage} in triage` : ""].filter(Boolean).join(", ");
|
|
19153
|
+
console.log(pc.dim(`(no ready tasks; ${held})`));
|
|
19045
19154
|
printNextSteps([
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19155
|
+
...triage > 0 ? [
|
|
19156
|
+
{
|
|
19157
|
+
intent: "Triage findings",
|
|
19158
|
+
command: `mu task list --substate triage -w ${workstream}`
|
|
19159
|
+
}
|
|
19160
|
+
] : [],
|
|
19161
|
+
...parked > 0 ? [
|
|
19162
|
+
{
|
|
19163
|
+
intent: "List parked tasks",
|
|
19164
|
+
command: `mu task list --substate parked -w ${workstream}`
|
|
19165
|
+
}
|
|
19166
|
+
] : []
|
|
19050
19167
|
]);
|
|
19051
19168
|
return;
|
|
19052
19169
|
}
|
|
@@ -19615,7 +19732,14 @@ async function cmdTaskClose(db, rawId, opts = {}) {
|
|
|
19615
19732
|
const taskRow = getTask(db, localId, ws);
|
|
19616
19733
|
const r = closeTask(db, localId, sdkOpts);
|
|
19617
19734
|
if ("skipped" in r) {
|
|
19735
|
+
const inTriage = r.blockingIds.filter((id) => getTask(db, id, ws)?.substate === "triage");
|
|
19618
19736
|
const blockingNextSteps = [
|
|
19737
|
+
...inTriage.length > 0 ? [
|
|
19738
|
+
{
|
|
19739
|
+
intent: `Triage ${inTriage.length} blocking finding(s) (accept, or close --as rejected|duplicate)`,
|
|
19740
|
+
command: `mu task list --substate triage -w ${ws}`
|
|
19741
|
+
}
|
|
19742
|
+
] : [],
|
|
19619
19743
|
{
|
|
19620
19744
|
intent: "Watch the remaining blockers (returns when one closes)",
|
|
19621
19745
|
command: `mu task wait ${r.blockingIds.join(" ")} -w ${ws} --first --any --on-stall exit`
|
|
@@ -19748,6 +19872,32 @@ async function cmdTaskPark(db, rawId, opts) {
|
|
|
19748
19872
|
}
|
|
19749
19873
|
printNextSteps(nextSteps3);
|
|
19750
19874
|
}
|
|
19875
|
+
async function cmdTaskAccept(db, rawId, opts = {}) {
|
|
19876
|
+
const { name: localId } = await resolveEntityRef(db, rawId, opts, "task");
|
|
19877
|
+
assertTaskInWorkstream(db, localId, opts.workstream);
|
|
19878
|
+
const ws = await resolveWorkstream(opts.workstream);
|
|
19879
|
+
const sdkOpts = { workstream: ws };
|
|
19880
|
+
if (opts.evidence !== void 0) sdkOpts.evidence = opts.evidence;
|
|
19881
|
+
const r = acceptTask(db, localId, sdkOpts);
|
|
19882
|
+
const nextSteps3 = [
|
|
19883
|
+
{
|
|
19884
|
+
intent: "Claim it",
|
|
19885
|
+
command: `mu task claim ${localId} -w ${ws} (--self / --for <worker>)`
|
|
19886
|
+
},
|
|
19887
|
+
{ intent: "Triage the next one", command: `mu task list --substate triage -w ${ws}` }
|
|
19888
|
+
];
|
|
19889
|
+
if (opts.json) {
|
|
19890
|
+
emitJson({ taskName: localId, ...r, nextSteps: nextSteps3 });
|
|
19891
|
+
return;
|
|
19892
|
+
}
|
|
19893
|
+
if (!r.changed) {
|
|
19894
|
+
const pair = formatPair({ status: r.status, substate: r.substate });
|
|
19895
|
+
console.log(pc.dim(`${localId} is ${pair}, not in triage (no-op)`));
|
|
19896
|
+
} else {
|
|
19897
|
+
console.log(`Accepted ${pc.bold(localId)} ${pc.dim(`(${transition(r)})`)}`);
|
|
19898
|
+
}
|
|
19899
|
+
printNextSteps(nextSteps3);
|
|
19900
|
+
}
|
|
19751
19901
|
async function cmdTaskUnpark(db, rawId, opts = {}) {
|
|
19752
19902
|
const { name: localId } = await resolveEntityRef(db, rawId, opts, "task");
|
|
19753
19903
|
assertTaskInWorkstream(db, localId, opts.workstream);
|
|
@@ -19826,7 +19976,10 @@ function wireTaskCommands(program) {
|
|
|
19826
19976
|
).requiredOption("-t, --title <title>", "task title").requiredOption("-i, --impact <n>", "impact 1..100", parseImpact).requiredOption("-e, --effort-days <days>", "effort in days (>0)", parsePositiveNumber).option(
|
|
19827
19977
|
"-b, --blocked-by <ids...>",
|
|
19828
19978
|
"task ids that block this one (repeat or comma-separate; or both)"
|
|
19829
|
-
).option("--note <text>", "append an initial note after creating the task").option("--note-author <name>", "author label for --note (default: current actor)").option(
|
|
19979
|
+
).option("--note <text>", "append an initial note after creating the task").option("--note-author <name>", "author label for --note (default: current actor)").option(
|
|
19980
|
+
"--triage",
|
|
19981
|
+
"start as OPEN/triage: a proposal (e.g. a review finding) kept out of next/claim until `mu task accept`"
|
|
19982
|
+
).option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(id) {
|
|
19830
19983
|
const opts = this.opts();
|
|
19831
19984
|
return handle((db) => cmdTaskAdd(db, id, opts), this)();
|
|
19832
19985
|
});
|
|
@@ -19931,6 +20084,12 @@ function wireTaskCommands(program) {
|
|
|
19931
20084
|
const opts = this.opts();
|
|
19932
20085
|
return handle((db) => cmdTaskPark(db, id, opts), this)();
|
|
19933
20086
|
});
|
|
20087
|
+
task.command("accept <id>").description(
|
|
20088
|
+
"Accept an OPEN/triage task (a proposed finding) as work: OPEN/todo, back in `next`. No-op on any other state. Decline with `close --as rejected|duplicate --why`."
|
|
20089
|
+
).option(...WORKSTREAM_OPT).option(...EVIDENCE_OPT).option(...JSON_OPT).action(function(id) {
|
|
20090
|
+
const opts = this.opts();
|
|
20091
|
+
return handle((db) => cmdTaskAccept(db, id, opts), this)();
|
|
20092
|
+
});
|
|
19934
20093
|
task.command("unpark <id>").description("Return a parked task to OPEN (back in `next`). No-op on any other state.").option(...WORKSTREAM_OPT).option(...EVIDENCE_OPT).option(...JSON_OPT).action(function(id) {
|
|
19935
20094
|
const opts = this.opts();
|
|
19936
20095
|
return handle((db) => cmdTaskUnpark(db, id, opts), this)();
|
|
@@ -19955,7 +20114,7 @@ function wireTaskCommands(program) {
|
|
|
19955
20114
|
).option(
|
|
19956
20115
|
"--actor <name>",
|
|
19957
20116
|
"override the actor name used for the log (only valid with --self; defaults to pane title or $USER)"
|
|
19958
|
-
).option("--force", "claim even when the task is parked (OPEN/parked)").option(
|
|
20117
|
+
).option("--force", "claim even when the task is parked or in triage (OPEN/parked, OPEN/triage)").option(
|
|
19959
20118
|
"--strict-staleness",
|
|
19960
20119
|
"refuse --for dispatch when the target agent's workspace is stale (default: warn and proceed)"
|
|
19961
20120
|
).option(...WORKSTREAM_OPT).option(...EVIDENCE_OPT).option(...JSON_OPT).action(function(taskId) {
|
|
@@ -20117,7 +20276,9 @@ function parseVia(raw) {
|
|
|
20117
20276
|
}
|
|
20118
20277
|
async function cmdSend(db, rawName, text2, opts = {}) {
|
|
20119
20278
|
const via = parseVia(opts.via);
|
|
20120
|
-
if (opts.force && !opts.fresh
|
|
20279
|
+
if (opts.force && !opts.fresh && parseSessionCommand(text2) === void 0) {
|
|
20280
|
+
throw new UsageError("--force only applies with --fresh or a /new, /reload, /compact");
|
|
20281
|
+
}
|
|
20121
20282
|
if (opts.fresh && opts.steer) throw new UsageError("--fresh and --steer are mutually exclusive");
|
|
20122
20283
|
const { name } = await resolveEntityRef(db, rawName, opts, "agent");
|
|
20123
20284
|
assertAgentInWorkstream(db, name, opts.workstream);
|
|
@@ -20127,7 +20288,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20127
20288
|
});
|
|
20128
20289
|
let undelivered;
|
|
20129
20290
|
const agentRow = getAgent(db, name, ws);
|
|
20130
|
-
const plainCtl = agentRow !== void 0 && !opts.fresh && !opts.steer && (via ?? chooseTransport(agentRow,
|
|
20291
|
+
const plainCtl = agentRow !== void 0 && !opts.fresh && !opts.steer && parseSessionCommand(text2) === void 0 && (via ?? chooseTransport(agentRow, agentCtlSocket(db, agentRow))) === "ctl";
|
|
20131
20292
|
const before = plainCtl && agentRow !== void 0 ? await ctlProbe(agentCtlSocket(db, agentRow)) : void 0;
|
|
20132
20293
|
const sent = await sendToAgent(db, name, text2, {
|
|
20133
20294
|
workstream: ws,
|
|
@@ -20161,6 +20322,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20161
20322
|
sentBytes: text2.length,
|
|
20162
20323
|
transport: sent.transport,
|
|
20163
20324
|
fresh: opts.fresh === true,
|
|
20325
|
+
...sent.command !== void 0 ? { command: sent.command } : {},
|
|
20164
20326
|
...sent.state !== void 0 ? { state: sent.state } : {},
|
|
20165
20327
|
delivered: undelivered === void 0,
|
|
20166
20328
|
...undelivered !== void 0 ? { undelivered: { reason: undelivered.reason, message: undelivered.message } } : {},
|
|
@@ -20172,7 +20334,7 @@ async function cmdSend(db, rawName, text2, opts = {}) {
|
|
|
20172
20334
|
if (undelivered !== void 0) {
|
|
20173
20335
|
console.error(pc.yellow(`warning: ${undelivered.message}`));
|
|
20174
20336
|
} else {
|
|
20175
|
-
const how = sent.transport === "mux" ? "via mux paste" : opts.fresh ? "via ctl, fresh session" : `via ctl, pi ${sent.state ?? "?"}`;
|
|
20337
|
+
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 ?? "?"}`;
|
|
20176
20338
|
console.log(pc.dim(`sent ${text2.length} bytes to ${name} (${how})`));
|
|
20177
20339
|
}
|
|
20178
20340
|
printNextSteps(nextSteps3);
|
|
@@ -20613,14 +20775,17 @@ function wireAgentCommands(program) {
|
|
|
20613
20775
|
return handle((db) => cmdSpawn(db, name, opts), this)();
|
|
20614
20776
|
});
|
|
20615
20777
|
agent.command("send <name> <text>").description(
|
|
20616
|
-
"Send text to an agent. pi agents: through the control socket (fails loud if it does not answer; no paste fallback)
|
|
20778
|
+
"Send text to an agent. pi agents: through the control socket (fails loud if it does not answer; no paste fallback); '/new', '/reload' and '/compact [instructions]' run inside pi over the socket (refused while busy unless --force), any other slash command is refused (--via mux types it into the pane). Non-pi CLIs: pasted into the pane (atomic on herdr; bracketed-paste on tmux). --fresh (pi only): start a new session and send the text into it as one operation; returns once the prompt's run has started, so an immediate next send cannot be lost. Use it instead of sending '/new' then the prompt"
|
|
20617
20779
|
).option(
|
|
20618
20780
|
"--strict-staleness",
|
|
20619
20781
|
"refuse send when the target agent's workspace is stale (default: warn and proceed)"
|
|
20620
20782
|
).option("--steer", "if pi is busy, interrupt the current run (default: queue as a follow-up)").option("--via <transport>", "force the transport: ctl or mux (mux = paste into the pane)").option(
|
|
20621
20783
|
"--fresh",
|
|
20622
20784
|
"pi only: new session + this prompt, one operation (refused with exit 4 while pi is busy)"
|
|
20623
|
-
).option(
|
|
20785
|
+
).option(
|
|
20786
|
+
"--force",
|
|
20787
|
+
"with --fresh or a pi /new, /reload, /compact: abandon a running turn instead of refusing"
|
|
20788
|
+
).option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(name, text2) {
|
|
20624
20789
|
const opts = this.opts();
|
|
20625
20790
|
return handle((db) => cmdSend(db, name, text2, opts), this)();
|
|
20626
20791
|
});
|
|
@@ -21348,7 +21513,8 @@ var NameAmbiguousError = class extends Error {
|
|
|
21348
21513
|
function classifyError(err) {
|
|
21349
21514
|
if (err instanceof UsageError || // abort on a non-pi agent: the operator picked the wrong verb (kick).
|
|
21350
21515
|
err instanceof AgentAbortNeedsCtlError || // send --fresh on an agent with no control socket: wrong recipe.
|
|
21351
|
-
err instanceof AgentFreshNeedsCtlError ||
|
|
21516
|
+
err instanceof AgentFreshNeedsCtlError || // an unsupported slash command to a pi agent: --via mux is the recipe.
|
|
21517
|
+
err instanceof AgentSlashCommandUnsupportedError || err instanceof WorkstreamNameInvalidError || err instanceof WorkstreamNameReservedError || // The operator asked the ACTIVE backend for something it cannot do:
|
|
21352
21518
|
// a `--cli` the mux does not recognise as an agent kind, or a
|
|
21353
21519
|
// `--command` / `MU_<CLI>_COMMAND` override it cannot honour. The
|
|
21354
21520
|
// substrate is healthy and answered precisely, so this is the usage
|
|
@@ -21365,7 +21531,7 @@ function classifyError(err) {
|
|
|
21365
21531
|
}
|
|
21366
21532
|
if (err instanceof NameAmbiguousError || err instanceof AgentExistsError || err instanceof TaskExistsError || err instanceof TaskAlreadyOwnedError || // Substate refusals (ts_4): a missing --why, a park from the wrong
|
|
21367
21533
|
// phase, a claim on a parked task, a pair outside TASK_SUBSTATES.
|
|
21368
|
-
err instanceof SubstateReasonRequiredError || err instanceof TaskParkStateError || err instanceof TaskParkedError || err instanceof InvalidSubstateError || err instanceof TaskClaimStaleWorkspaceError || err instanceof TaskNotInWorkstreamError || err instanceof AgentNotInWorkstreamError || err instanceof CycleError || err instanceof CrossWorkstreamEdgeError || err instanceof WorkspaceExistsError || err instanceof WorkspacePathNotEmptyError || err instanceof WorkspacePreservedError || err instanceof HomeDirAsProjectRootError || err instanceof WorkspaceVcsRequiredError || err instanceof WorkspaceDirtyError || err instanceof ClaimerNotRegisteredError || err instanceof SchemaTooOldError || err instanceof SchemaTooNewError || err instanceof TaskIdInvalidError || err instanceof WorkstreamExistsError || // Rebuild refuses to overwrite an existing target or to write onto
|
|
21534
|
+
err instanceof SubstateReasonRequiredError || err instanceof TaskParkStateError || err instanceof TaskParkedError || err instanceof TaskInTriageError || err instanceof InvalidSubstateError || err instanceof TaskClaimStaleWorkspaceError || err instanceof TaskNotInWorkstreamError || err instanceof AgentNotInWorkstreamError || err instanceof CycleError || err instanceof CrossWorkstreamEdgeError || err instanceof WorkspaceExistsError || err instanceof WorkspacePathNotEmptyError || err instanceof WorkspacePreservedError || err instanceof HomeDirAsProjectRootError || err instanceof WorkspaceVcsRequiredError || err instanceof WorkspaceDirtyError || err instanceof ClaimerNotRegisteredError || err instanceof SchemaTooOldError || err instanceof SchemaTooNewError || err instanceof TaskIdInvalidError || err instanceof WorkstreamExistsError || // Rebuild refuses to overwrite an existing target or to write onto
|
|
21369
21535
|
// the source DB: both are name/state collisions, not usage errors.
|
|
21370
21536
|
err instanceof RebuildTargetExistsError || err instanceof RebuildTargetIsSourceError || // An abbreviated group id matching several groups: the operator must
|
|
21371
21537
|
// disambiguate. Guessing would be catastrophic for `mu undo`.
|
|
@@ -22990,6 +23156,7 @@ function fullJsonShape(d) {
|
|
|
22990
23156
|
orphans: d.view.orphans,
|
|
22991
23157
|
tracks: d.tracks,
|
|
22992
23158
|
ready: withRoiAll(d.ready),
|
|
23159
|
+
triage: withRoiAll(d.triage),
|
|
22993
23160
|
inProgress: withRoiAll(d.inProgress),
|
|
22994
23161
|
blocked: withRoiAll(d.blocked),
|
|
22995
23162
|
recentClosed: withRoiAll(d.recentClosed),
|
|
@@ -23062,6 +23229,7 @@ async function cmdState(db, opts) {
|
|
|
23062
23229
|
}
|
|
23063
23230
|
renderFullMode(perWs);
|
|
23064
23231
|
}
|
|
23232
|
+
var BIG_WORKSTREAM_TASKS = 300;
|
|
23065
23233
|
function renderFullMode(perWs) {
|
|
23066
23234
|
perWs.forEach((d, i) => {
|
|
23067
23235
|
if (i > 0) console.log("");
|
|
@@ -23094,6 +23262,15 @@ function renderFullCard(d) {
|
|
|
23094
23262
|
);
|
|
23095
23263
|
}
|
|
23096
23264
|
}
|
|
23265
|
+
const total = d.taskCount;
|
|
23266
|
+
const open = ready.length + inProgress.length + blocked.length + d.triage.length;
|
|
23267
|
+
if (total >= BIG_WORKSTREAM_TASKS && open * 10 < total) {
|
|
23268
|
+
console.log(
|
|
23269
|
+
pc.dim(
|
|
23270
|
+
`hint: ${total} tasks, ${open} open; start the next effort in a new <project>-<purpose> workstream`
|
|
23271
|
+
)
|
|
23272
|
+
);
|
|
23273
|
+
}
|
|
23097
23274
|
console.log("");
|
|
23098
23275
|
console.log(pc.bold(`Tracks (${tracks.length})`));
|
|
23099
23276
|
console.log(formatTracks(tracks));
|
|
@@ -23101,6 +23278,13 @@ function renderFullCard(d) {
|
|
|
23101
23278
|
console.log(pc.bold(`Ready (${ready.length})`));
|
|
23102
23279
|
console.log(ready.length === 0 ? pc.dim(" (none)") : formatTaskListTable(ready));
|
|
23103
23280
|
console.log("");
|
|
23281
|
+
if (d.triage.length > 0) {
|
|
23282
|
+
console.log(
|
|
23283
|
+
`${pc.bold(`Triage (${d.triage.length})`)} ${pc.dim(`accept: mu task accept <id> -w ${workstreamName} \xB7 decline: mu task close <id> --as rejected|duplicate --why ...`)}`
|
|
23284
|
+
);
|
|
23285
|
+
console.log(formatTaskListTable(d.triage));
|
|
23286
|
+
console.log("");
|
|
23287
|
+
}
|
|
23104
23288
|
console.log(pc.bold(`In progress (${inProgress.length})`));
|
|
23105
23289
|
console.log(inProgress.length === 0 ? pc.dim(" (none)") : formatTaskListTable(inProgress));
|
|
23106
23290
|
console.log("");
|
|
@@ -23706,6 +23890,7 @@ function wireWorkspaceCommands(program) {
|
|
|
23706
23890
|
}
|
|
23707
23891
|
|
|
23708
23892
|
// src/cli/workstream.ts
|
|
23893
|
+
import { basename as basename2 } from "path";
|
|
23709
23894
|
init_mux2();
|
|
23710
23895
|
init_output();
|
|
23711
23896
|
init_workstream();
|
|
@@ -23734,6 +23919,7 @@ async function cmdInit(db, name, opts = {}) {
|
|
|
23734
23919
|
await mux.enableMuPaneBordersForSession(sessionName).catch(() => {
|
|
23735
23920
|
});
|
|
23736
23921
|
const created = !sessionAlready || dbCreated;
|
|
23922
|
+
const nameHint = name.includes("-") ? void 0 : `name workstreams <project>-<purpose> (e.g. ${projectGuess() ?? "repo"}-${name}); one per effort`;
|
|
23737
23923
|
const nextSteps3 = [
|
|
23738
23924
|
{ intent: "Attach the session", command: mux.attachHint({ session: sessionName }) },
|
|
23739
23925
|
{
|
|
@@ -23751,6 +23937,7 @@ async function cmdInit(db, name, opts = {}) {
|
|
|
23751
23937
|
muxSessionAlreadyExisted: sessionAlready,
|
|
23752
23938
|
dbRowAlreadyExisted: !dbCreated,
|
|
23753
23939
|
muWindowRepaired,
|
|
23940
|
+
...nameHint && created ? { hint: nameHint } : {},
|
|
23754
23941
|
nextSteps: nextSteps3
|
|
23755
23942
|
});
|
|
23756
23943
|
return;
|
|
@@ -23766,8 +23953,13 @@ async function cmdInit(db, name, opts = {}) {
|
|
|
23766
23953
|
return;
|
|
23767
23954
|
}
|
|
23768
23955
|
console.log(`Created workstream ${pc.bold(name)} (mux session ${pc.bold(sessionName)})`);
|
|
23956
|
+
if (nameHint && created) console.log(pc.dim(`hint: ${nameHint}`));
|
|
23769
23957
|
printNextSteps(nextSteps3);
|
|
23770
23958
|
}
|
|
23959
|
+
function projectGuess() {
|
|
23960
|
+
const base = basename2(process.cwd()).toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "");
|
|
23961
|
+
return base === "" ? void 0 : base;
|
|
23962
|
+
}
|
|
23771
23963
|
async function cmdWorkstreamList(db, opts = {}) {
|
|
23772
23964
|
if (opts.tornDown === true) {
|
|
23773
23965
|
const gone = listTornDownWorkstreams(db);
|
|
@@ -24023,8 +24215,12 @@ function withPositionalWorkstream(opts, name) {
|
|
|
24023
24215
|
return { ...opts, workstream: name };
|
|
24024
24216
|
}
|
|
24025
24217
|
function wireWorkstreamCommands(program) {
|
|
24026
|
-
const workstream = program.command("workstream").description(
|
|
24027
|
-
|
|
24218
|
+
const workstream = program.command("workstream").description(
|
|
24219
|
+
"Workstream-level commands. Name them <project>-<purpose> (hail-auth), one per effort; tear down when it ships. Reviews nobody will track belong in scratch delegates, not a workstream."
|
|
24220
|
+
);
|
|
24221
|
+
workstream.command("init <name>").description(
|
|
24222
|
+
"Create the workstream's mux session and register it in the DB. Name it <project>-<purpose>."
|
|
24223
|
+
).option(...JSON_OPT).action(function(name) {
|
|
24028
24224
|
const opts = this.opts();
|
|
24029
24225
|
return handle((db) => cmdInit(db, name, opts), this)();
|
|
24030
24226
|
});
|