@mu-crew/mu 4.1.0 → 4.2.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/{chunk-4ULY3ZKO.js → chunk-3ZLI6YTV.js} +8 -4
- package/dist/chunk-3ZLI6YTV.js.map +1 -0
- package/dist/{chunk-SS7NQSSZ.js → chunk-RVWNQLFE.js} +2 -2
- package/dist/{chunk-UCZPJX7K.js → chunk-SFV5RPZW.js} +1518 -411
- package/dist/chunk-SFV5RPZW.js.map +1 -0
- package/dist/{chunk-7M634JMD.js → chunk-UL2QE43S.js} +20 -20
- package/dist/chunk-UL2QE43S.js.map +1 -0
- package/dist/{cli-TV3N47AZ.js → cli-72EJ3CKW.js} +152 -33
- package/dist/cli-72EJ3CKW.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{errors-GRONKWFS.js → errors-LY4OZVQU.js} +8 -2
- package/dist/extension/mu-pi.js +1 -1
- package/dist/index.d.ts +2554 -2277
- package/dist/index.js +58 -4
- package/dist/index.js.map +1 -1
- package/dist/{tui-FUBQ326D.js → tui-3ZPLSLDW.js} +13 -14
- package/dist/tui-3ZPLSLDW.js.map +1 -0
- package/docs/ARCHITECTURE.md +15 -8
- package/docs/VOCABULARY.md +14 -6
- package/docs/guide/remote.md +113 -4
- package/docs/guide/sql.md +2 -1
- package/docs/reference/env.md +1 -0
- package/package.json +1 -1
- package/skills/mu/recipes/remote-workers.md +7 -6
- package/dist/chunk-4ULY3ZKO.js.map +0 -1
- package/dist/chunk-7M634JMD.js.map +0 -1
- package/dist/chunk-UCZPJX7K.js.map +0 -1
- package/dist/cli-TV3N47AZ.js.map +0 -1
- package/dist/tui-FUBQ326D.js.map +0 -1
- /package/dist/{chunk-SS7NQSSZ.js.map → chunk-RVWNQLFE.js.map} +0 -0
- /package/dist/{errors-GRONKWFS.js.map → errors-LY4OZVQU.js.map} +0 -0
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
steerStallHandler,
|
|
42
42
|
waitForEvents,
|
|
43
43
|
waitWithActions
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-3ZLI6YTV.js";
|
|
45
45
|
import {
|
|
46
46
|
FRONTIER_MAX_DEPTH,
|
|
47
47
|
HumanNeededInvalidError,
|
|
@@ -101,7 +101,7 @@ import {
|
|
|
101
101
|
taskRecordCut,
|
|
102
102
|
taskToOutput,
|
|
103
103
|
workstreamIdOf
|
|
104
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-RVWNQLFE.js";
|
|
105
105
|
import {
|
|
106
106
|
AgentAbortNeedsCtlError,
|
|
107
107
|
AgentAbortTimeoutError,
|
|
@@ -127,6 +127,11 @@ import {
|
|
|
127
127
|
CTL_OPS,
|
|
128
128
|
CTL_SOCK_ENV,
|
|
129
129
|
CURRENT_SCHEMA_VERSION,
|
|
130
|
+
CarrierJobKeptError,
|
|
131
|
+
CarrierJobUnresolvedError,
|
|
132
|
+
CarrierJobUnstoppedError,
|
|
133
|
+
CarrierStopFailedError,
|
|
134
|
+
CarrierStopRefusedError,
|
|
130
135
|
ClaimerNotRegisteredError,
|
|
131
136
|
CloseNeedsEvidenceError,
|
|
132
137
|
CrossWorkstreamEdgeError,
|
|
@@ -149,6 +154,7 @@ import {
|
|
|
149
154
|
HostChannelBusyError,
|
|
150
155
|
HostCheckFailedError,
|
|
151
156
|
HostExistsError,
|
|
157
|
+
HostHandbackError,
|
|
152
158
|
HostInUseError,
|
|
153
159
|
HostNotFoundError,
|
|
154
160
|
HostProfileError,
|
|
@@ -260,8 +266,10 @@ import {
|
|
|
260
266
|
gitToplevel,
|
|
261
267
|
groupIdFromPrefix,
|
|
262
268
|
headSha,
|
|
269
|
+
hostMuleStarter,
|
|
263
270
|
hostStarter,
|
|
264
271
|
interruptAgent,
|
|
272
|
+
isAgentListArgv,
|
|
265
273
|
isCommandGate,
|
|
266
274
|
isJsonMode,
|
|
267
275
|
isScratchWorkstream,
|
|
@@ -273,6 +281,7 @@ import {
|
|
|
273
281
|
lastClaimActor,
|
|
274
282
|
latestSeq,
|
|
275
283
|
listAgents,
|
|
284
|
+
listCarrierJobs,
|
|
276
285
|
listDelegates,
|
|
277
286
|
listEmptyWorkstreams,
|
|
278
287
|
listForgetCandidates,
|
|
@@ -349,7 +358,7 @@ import {
|
|
|
349
358
|
workstreamRevision,
|
|
350
359
|
worktreePath,
|
|
351
360
|
worktreesRoot
|
|
352
|
-
} from "./chunk-
|
|
361
|
+
} from "./chunk-SFV5RPZW.js";
|
|
353
362
|
import {
|
|
354
363
|
defaultStateDir
|
|
355
364
|
} from "./chunk-PZ5GOCWH.js";
|
|
@@ -3900,7 +3909,10 @@ function classifyError(err) {
|
|
|
3900
3909
|
if (err instanceof NameAmbiguousError || // A host profile that exists, is at its channel cap, or still holds channels.
|
|
3901
3910
|
err instanceof HostExistsError || err instanceof HostChannelBusyError || // A remote worktree close refuses to remove (dirty, uncollected HEAD, moved).
|
|
3902
3911
|
err instanceof RemoteCloseRefusedError || // A recovery record that is ambiguous, claimed, changed or pinned to another profile.
|
|
3903
|
-
err instanceof RemoteRecoveryRefusedError ||
|
|
3912
|
+
err instanceof RemoteRecoveryRefusedError || // A recorded job handle whose stop template changed, or whose profile is gone: nothing ran.
|
|
3913
|
+
err instanceof CarrierStopRefusedError || // A respawn of a name whose older incarnation's job was never stopped.
|
|
3914
|
+
err instanceof CarrierJobUnstoppedError || // A lease close with no handle it can stop, or a reservation it had to keep.
|
|
3915
|
+
err instanceof CarrierJobUnresolvedError || err instanceof CarrierJobKeptError || err instanceof HostInUseError || err instanceof AgentExistsError || err instanceof AgentNameExhaustedError || err instanceof TaskExistsError || err instanceof BackupTargetExistsError || err instanceof TaskAutoRefusedError || err instanceof TaskBriefMissingError || err instanceof DelegateCallConflictError || err instanceof DelegateStateError || err instanceof TaskAlreadyOwnedError || err instanceof TaskHasOpenBlockersError || // Substate refusals (ts_4): a park from the wrong phase, a claim on a
|
|
3904
3916
|
// parked task, a pair outside TASK_SUBSTATES.
|
|
3905
3917
|
err instanceof TaskParkStateError || err instanceof TaskParkedError || err instanceof HumanTaskError || err instanceof TaskInTriageError || err instanceof InvalidSubstateError || err instanceof TaskNotInWorkstreamError || err instanceof AgentNotInWorkstreamError || err instanceof AgentSendUnsupportedError || err instanceof CycleError || err instanceof CrossWorkstreamEdgeError || err instanceof WorktreeDirtyError || err instanceof WorktreeBranchExistsError || err instanceof WorktreeHeldError || err instanceof WorktreePathNotEmptyError || err instanceof WorktreeVcsRequiredError || err instanceof ClaimerNotRegisteredError || err instanceof SchemaTooOldError || err instanceof SchemaTooNewError || err instanceof SqliteTooOldError || err instanceof SyncSourceTooNewError || err instanceof DbInsideSyncDirError || err instanceof SyncDirForeignError || err instanceof WorkstreamExistsError || err instanceof WorkstreamRevisionMismatchError || err instanceof RevisionMismatchError || err instanceof WorkstreamHasAgentsError || err instanceof WorkstreamPanesLiveError || err instanceof WorkstreamSessionLiveError || // Rebuild refuses to overwrite an existing target or to write onto
|
|
3906
3918
|
// the source DB: both are name/state collisions, not usage errors.
|
|
@@ -3959,9 +3971,13 @@ function classifyError(err) {
|
|
|
3959
3971
|
if (err instanceof AgentAbortTimeoutError) {
|
|
3960
3972
|
return { label: "timeout", exitCode: 5 };
|
|
3961
3973
|
}
|
|
3962
|
-
if (err instanceof RemotePrepareError || err instanceof RemoteCollectError
|
|
3974
|
+
if (err instanceof RemotePrepareError || err instanceof RemoteCollectError || // The stop template ran and did not prove the job stopped: the host's answer, substrate.
|
|
3975
|
+
err instanceof CarrierStopFailedError) {
|
|
3963
3976
|
return { label: "remote host", exitCode: 5 };
|
|
3964
3977
|
}
|
|
3978
|
+
if (err instanceof HostHandbackError) {
|
|
3979
|
+
return { label: "human needed", exitCode: 5 };
|
|
3980
|
+
}
|
|
3965
3981
|
if (err instanceof HostCheckFailedError) {
|
|
3966
3982
|
return { label: "host check", exitCode: 5 };
|
|
3967
3983
|
}
|
|
@@ -4052,7 +4068,7 @@ function emitError(err) {
|
|
|
4052
4068
|
agentId: err.agentId
|
|
4053
4069
|
});
|
|
4054
4070
|
}
|
|
4055
|
-
const humanNoMux = err instanceof NoMultiplexerError && isStateVerbArgv(process.argv) && !process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
4071
|
+
const humanNoMux = err instanceof NoMultiplexerError && (isStateVerbArgv(process.argv) || isAgentListArgv(process.argv)) && !process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
4056
4072
|
if (isJsonMode() && !humanNoMux) {
|
|
4057
4073
|
const extra = {};
|
|
4058
4074
|
if (usageCmd) extra.usage = usageLines(usageCmd);
|
|
@@ -4073,7 +4089,8 @@ function emitError(err) {
|
|
|
4073
4089
|
error: errClass,
|
|
4074
4090
|
message: cleanMessage,
|
|
4075
4091
|
exit: exitCode,
|
|
4076
|
-
...Object.keys(extra).length > 0 ? { extra } : {}
|
|
4092
|
+
...Object.keys(extra).length > 0 ? { extra } : {},
|
|
4093
|
+
...err instanceof HostHandbackError ? { data: { handback: true, host: err.host, template: err.field, detail: err.detail } } : {}
|
|
4077
4094
|
});
|
|
4078
4095
|
return exitCode;
|
|
4079
4096
|
}
|
|
@@ -4179,12 +4196,20 @@ function findCommandForArgv(root, argv) {
|
|
|
4179
4196
|
}
|
|
4180
4197
|
|
|
4181
4198
|
// src/cli/agent-close.ts
|
|
4199
|
+
function carrierJobWorkstreams(db, name) {
|
|
4200
|
+
return listCarrierJobs(db).filter((j) => j.name === name).map((j) => j.workstream);
|
|
4201
|
+
}
|
|
4202
|
+
var hasCarrierJob = (db, ws, name) => carrierJobWorkstreams(db, name).includes(ws);
|
|
4182
4203
|
async function closeScope(db, name, explicit) {
|
|
4183
4204
|
if (explicit !== void 0 || injectedWorkstream() !== void 0) {
|
|
4184
4205
|
return resolveWorkstream(explicit);
|
|
4185
4206
|
}
|
|
4186
4207
|
const matches = [
|
|
4187
|
-
.../* @__PURE__ */ new Set([
|
|
4208
|
+
.../* @__PURE__ */ new Set([
|
|
4209
|
+
...findEntityWorkstreams(db, "agent", name),
|
|
4210
|
+
...recoveryWorkstreams(db, name),
|
|
4211
|
+
...carrierJobWorkstreams(db, name)
|
|
4212
|
+
])
|
|
4188
4213
|
].sort();
|
|
4189
4214
|
if (matches.length >= 2) throw new NameAmbiguousError(name, matches, "agent");
|
|
4190
4215
|
const only = matches[0];
|
|
@@ -4193,19 +4218,21 @@ async function closeScope(db, name, explicit) {
|
|
|
4193
4218
|
async function cmdClose(db, rawName, opts = {}) {
|
|
4194
4219
|
const name = applyQualifiedRef(rawName, opts);
|
|
4195
4220
|
const ws = await closeScope(db, name, opts.workstream);
|
|
4196
|
-
if (opts.recovery === void 0 && recoveriesFor(db, ws, name).length === 0) {
|
|
4221
|
+
if (opts.recovery === void 0 && recoveriesFor(db, ws, name).length === 0 && !hasCarrierJob(db, ws, name)) {
|
|
4197
4222
|
assertAgentInWorkstream(db, name, opts.workstream ?? ws);
|
|
4198
4223
|
}
|
|
4199
4224
|
const discard = opts.discard === true;
|
|
4200
4225
|
const keepBranch = opts.keepBranch === true;
|
|
4201
4226
|
const recoveryId = opts.recovery;
|
|
4202
4227
|
const force = opts.force === true;
|
|
4228
|
+
const abandonJob = opts.abandonJob === true;
|
|
4203
4229
|
if (opts.if === void 0) {
|
|
4204
4230
|
const p = await previewClose(db, name, {
|
|
4205
4231
|
workstream: ws,
|
|
4206
4232
|
discard,
|
|
4207
4233
|
keepBranch,
|
|
4208
|
-
...recoveryId === void 0 ? {} : { recoveryId }
|
|
4234
|
+
...recoveryId === void 0 ? {} : { recoveryId },
|
|
4235
|
+
...abandonJob ? { abandonJob: true } : {}
|
|
4209
4236
|
});
|
|
4210
4237
|
if (p.kind !== "none" && !p.refusesDirty && p.recovery?.refusal == null) {
|
|
4211
4238
|
setNextOutcome({
|
|
@@ -4220,7 +4247,8 @@ async function cmdClose(db, rawName, opts = {}) {
|
|
|
4220
4247
|
...p.recovery === null ? [] : ["--recovery", String(p.recovery.id)],
|
|
4221
4248
|
...discard ? ["--discard"] : [],
|
|
4222
4249
|
...keepBranch ? ["--keep-branch"] : [],
|
|
4223
|
-
...force ? ["--force"] : []
|
|
4250
|
+
...force ? ["--force"] : [],
|
|
4251
|
+
...abandonJob ? ["--abandon-job"] : []
|
|
4224
4252
|
],
|
|
4225
4253
|
revision: p.revision
|
|
4226
4254
|
});
|
|
@@ -4238,19 +4266,29 @@ async function cmdClose(db, rawName, opts = {}) {
|
|
|
4238
4266
|
...discard ? { discard: true } : {},
|
|
4239
4267
|
...keepBranch ? { keepBranch: true } : {},
|
|
4240
4268
|
...force ? { force: true } : {},
|
|
4269
|
+
...abandonJob ? { abandonJob: true } : {},
|
|
4241
4270
|
...recoveryId === void 0 ? {} : { recoveryId }
|
|
4242
4271
|
});
|
|
4243
4272
|
if (opts.json) {
|
|
4244
4273
|
emitJson({ agentName: name, ...result });
|
|
4245
4274
|
return;
|
|
4246
4275
|
}
|
|
4247
|
-
|
|
4276
|
+
const jobs = result.carrierJobs ?? [];
|
|
4277
|
+
if (!result.killedPane && !result.deletedRow && result.worktree === null && jobs.length === 0) {
|
|
4248
4278
|
console.log(pc.dim(`no agent named ${name} (already closed?)`));
|
|
4249
4279
|
return;
|
|
4250
4280
|
}
|
|
4251
4281
|
console.log(
|
|
4252
|
-
result.recovery
|
|
4282
|
+
result.recovery !== void 0 ? `Closed the remote worktree ${pc.bold(name)} left (recovery ${result.recovery.id})` : !result.killedPane && result.worktree === null ? `Closed the lease job ${pc.bold(name)} left` : `Closed ${pc.bold(name)}`
|
|
4253
4283
|
);
|
|
4284
|
+
for (const j of jobs) {
|
|
4285
|
+
const what = j.job === null ? "lease reservation" : `job ${j.job}`;
|
|
4286
|
+
console.log(
|
|
4287
|
+
pc.dim(
|
|
4288
|
+
j.action === "stopped" ? ` ${what} on ${j.host} stopped` : j.action === "abandon" ? ` ${what} on ${j.host} abandoned without a stop: it may still run there` : ` ${what} on ${j.host} released (its carrier was never launched)`
|
|
4289
|
+
)
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4254
4292
|
if (result.worktree !== null) {
|
|
4255
4293
|
const w = result.worktree;
|
|
4256
4294
|
console.log(
|
|
@@ -4261,11 +4299,28 @@ async function cmdClose(db, rawName, opts = {}) {
|
|
|
4261
4299
|
for (const warn of w.warnings ?? []) console.error(pc.yellow(`warning: ${warn}`));
|
|
4262
4300
|
}
|
|
4263
4301
|
}
|
|
4302
|
+
function printCarrierJob(p) {
|
|
4303
|
+
const j = p.carrierJob;
|
|
4304
|
+
if (j === null) return;
|
|
4305
|
+
const what = j.job !== null ? `job ${j.job}` : `a lease reservation with no job handle (launch ${j.launch}, pane ${j.paneId ?? "none"}${j.reason === null ? "" : `, ${j.reason}`})`;
|
|
4306
|
+
console.log(
|
|
4307
|
+
p.abandonJob ? pc.yellow(
|
|
4308
|
+
` ${what} on ${j.host}: --abandon-job drops it WITHOUT a stop (the job may keep running; the job file is left)`
|
|
4309
|
+
) : j.job !== null ? ` ${what} on ${j.host}: stopped (its stop template) before anything else; a failed stop keeps everything` : ` ${what} on ${j.host}: released only if its carrier was never launched; otherwise the close refuses (then --abandon-job)`
|
|
4310
|
+
);
|
|
4311
|
+
}
|
|
4264
4312
|
function printClosePreview(p) {
|
|
4265
4313
|
if (p.kind === "none") {
|
|
4266
4314
|
console.log(pc.dim(`no agent named ${p.agentName} (already closed?)`));
|
|
4267
4315
|
return;
|
|
4268
4316
|
}
|
|
4317
|
+
if (p.kind === "lease-job") {
|
|
4318
|
+
console.log(
|
|
4319
|
+
`${pc.bold(p.agentName)} has no agent row, only a lease job reservation (rev ${p.revision}): no pane is killed, no task changes`
|
|
4320
|
+
);
|
|
4321
|
+
printCarrierJob(p);
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4269
4324
|
if (p.kind === "recovery" && p.recovery !== null && p.remote !== null) {
|
|
4270
4325
|
const head = p.remote.collectedHead;
|
|
4271
4326
|
console.log(
|
|
@@ -4274,6 +4329,7 @@ function printClosePreview(p) {
|
|
|
4274
4329
|
console.log(
|
|
4275
4330
|
` remote worktree ${p.remote.path} on ${p.remote.host}: removed only if clean and at ${p.remote.base.slice(0, 12)}${head === null ? "" : ` or ${head.slice(0, 12)} (collected)`}; otherwise it refuses${p.keepBranch ? " (--keep-branch: unmerged commits go on a remote branch first; dirty still refuses)" : ""}`
|
|
4276
4331
|
);
|
|
4332
|
+
printCarrierJob(p);
|
|
4277
4333
|
if (p.recovery.refusal !== null)
|
|
4278
4334
|
console.log(pc.yellow(` would refuse: ${p.recovery.refusal}`));
|
|
4279
4335
|
return;
|
|
@@ -4281,6 +4337,7 @@ function printClosePreview(p) {
|
|
|
4281
4337
|
console.log(
|
|
4282
4338
|
p.kind === "agent" ? `Close ${pc.bold(p.agentName)}? pane ${p.paneId} will be killed and its row removed (rev ${p.revision})` : `${pc.bold(p.agentName)} has no agent row, only a registered worktree (rev ${p.revision})`
|
|
4283
4339
|
);
|
|
4340
|
+
printCarrierJob(p);
|
|
4284
4341
|
for (const t of p.ownedTasks) {
|
|
4285
4342
|
console.log(` owns ${t.ref} (${t.status}${t.reverts ? ": goes back to OPEN" : ""})`);
|
|
4286
4343
|
}
|
|
@@ -4315,6 +4372,9 @@ function wireCloseCommand(agent) {
|
|
|
4315
4372
|
).option(
|
|
4316
4373
|
"--force",
|
|
4317
4374
|
"close past what you vouch for: a pane mu cannot ask, or a worktree a process writes in or no scan can check (a cwd or reader there only warns); never overrides a live pane or uncommitted work"
|
|
4375
|
+
).option(
|
|
4376
|
+
"--abandon-job",
|
|
4377
|
+
"a lease job whose host is gone for good: drop its handle WITHOUT stopping it (logged with host and handle; the job may keep running). --force never skips the stop"
|
|
4318
4378
|
).option("--if <rev>", "apply the preview with this revision", parseNonNegativeInt).option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(name) {
|
|
4319
4379
|
const opts = this.opts();
|
|
4320
4380
|
return handle((db) => cmdClose(db, name, opts), this)();
|
|
@@ -4653,7 +4713,7 @@ async function parseWaitRef(db, raw, scope, legacy = {}) {
|
|
|
4653
4713
|
return { kind, workstream, id: parsed.name };
|
|
4654
4714
|
}
|
|
4655
4715
|
if (getAgent(db, parsed.name, workstream) === void 0) {
|
|
4656
|
-
const { AgentNotFoundError: AgentNotFoundError2 } = await import("./errors-
|
|
4716
|
+
const { AgentNotFoundError: AgentNotFoundError2 } = await import("./errors-LY4OZVQU.js");
|
|
4657
4717
|
throw new AgentNotFoundError2(parsed.name);
|
|
4658
4718
|
}
|
|
4659
4719
|
return {
|
|
@@ -7916,8 +7976,11 @@ function wireAgentCommands(program2) {
|
|
|
7916
7976
|
const opts = this.opts();
|
|
7917
7977
|
return handle((db) => cmdRead(db, name, opts), this)();
|
|
7918
7978
|
});
|
|
7919
|
-
agent.command("list").description(
|
|
7979
|
+
agent.command("list").description(
|
|
7980
|
+
"List agents in the current workstream (reconciled with the active mux). One JSON document by default; --table for the human table"
|
|
7981
|
+
).option(...WORKSTREAM_OPT).option(...JSON_OPT).option("--table", "print the human table instead of the JSON document").action(function() {
|
|
7920
7982
|
const opts = this.opts();
|
|
7983
|
+
opts.json = opts.json === true || opts.table !== true;
|
|
7921
7984
|
return handle((db) => cmdList(db, opts), this)();
|
|
7922
7985
|
});
|
|
7923
7986
|
agent.command("show <name>").description("Show an agent: registry row + recent scrollback (last N lines, default 20)").option("-n, --lines <n>", "how many scrollback lines to show (default 20)", parseLines).option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(name) {
|
|
@@ -8041,7 +8104,7 @@ function openDashboardOrHelp(program2) {
|
|
|
8041
8104
|
(syncDb) => withShutdown(syncDb, async (shutdown) => {
|
|
8042
8105
|
const key = "NODE_ENV";
|
|
8043
8106
|
process.env[key] ??= "production";
|
|
8044
|
-
const { runDashboard } = await import("./tui-
|
|
8107
|
+
const { runDashboard } = await import("./tui-3ZPLSLDW.js");
|
|
8045
8108
|
const first = program2.opts().workstream?.[0];
|
|
8046
8109
|
await runDashboard({ dbPath: defaultDbPath(), syncDb, shutdown, initialWorkstream: first });
|
|
8047
8110
|
}),
|
|
@@ -10166,6 +10229,31 @@ async function ctlSocketsDoctorCheck(agents, opts = {}) {
|
|
|
10166
10229
|
}
|
|
10167
10230
|
return { check, agents: reports };
|
|
10168
10231
|
}
|
|
10232
|
+
function leaseJobHints(db) {
|
|
10233
|
+
const agentIds = new Set(
|
|
10234
|
+
db.prepare("SELECT id FROM agents").all().map((r) => r.id)
|
|
10235
|
+
);
|
|
10236
|
+
const named = db.prepare(
|
|
10237
|
+
`SELECT 1 FROM agents a JOIN workstreams w ON w.id = a.workstream_id
|
|
10238
|
+
WHERE w.name = ? AND a.name = ?`
|
|
10239
|
+
);
|
|
10240
|
+
return listCarrierJobs(db).filter(
|
|
10241
|
+
(j) => j.agentId === null ? named.get(j.workstream, j.name) === void 0 : !agentIds.has(j.agentId)
|
|
10242
|
+
).map((j) => ({
|
|
10243
|
+
workstream: j.workstream,
|
|
10244
|
+
name: j.name,
|
|
10245
|
+
host: j.host,
|
|
10246
|
+
job: j.job,
|
|
10247
|
+
launch: j.launch,
|
|
10248
|
+
agentId: j.agentId,
|
|
10249
|
+
reason: j.reason,
|
|
10250
|
+
close: `mu agent close ${j.name} -w ${j.workstream}`
|
|
10251
|
+
}));
|
|
10252
|
+
}
|
|
10253
|
+
function leaseJobHintLine(h) {
|
|
10254
|
+
const what = h.job !== null ? `job ${h.job}` : `no job handle (launch ${h.launch}${h.reason === null ? "" : `, ${h.reason}`})`;
|
|
10255
|
+
return `${h.workstream}/${h.name} on ${h.host}: ${what}, agent ${h.agentId === null ? "never bound" : "gone"}: ${h.close}`;
|
|
10256
|
+
}
|
|
10169
10257
|
|
|
10170
10258
|
// src/dormant.ts
|
|
10171
10259
|
var FINISHED_IDLE_DAYS = 14;
|
|
@@ -10483,6 +10571,11 @@ state (workstream=${ws})`));
|
|
|
10483
10571
|
console.log("");
|
|
10484
10572
|
for (const line2 of oldDelegates.remediation) console.log(` ${line2}`);
|
|
10485
10573
|
}
|
|
10574
|
+
const leaseJobs = leaseJobHints(db);
|
|
10575
|
+
console.log(
|
|
10576
|
+
` ${"lease-job".padEnd(16)} : ${leaseJobs.length === 0 ? `${pc.green("ok")} ${pc.dim("no lease job reservation outlives its agent")}` : `${pc.yellow("hint")} ${pc.dim(`${leaseJobs.length} lease job reservation(s) whose agent is gone; each close stops its job (a host gone for good: --abandon-job)`)}`}`
|
|
10577
|
+
);
|
|
10578
|
+
for (const h of leaseJobs) console.log(pc.dim(` ${leaseJobHintLine(h)}`));
|
|
10486
10579
|
console.log(pc.bold("\ndisk"));
|
|
10487
10580
|
const disk = checkDiskRecon(db);
|
|
10488
10581
|
if (printHazards(disk)) sawHazard = true;
|
|
@@ -10695,6 +10788,7 @@ async function cmdDoctorJson(db, opts = {}) {
|
|
|
10695
10788
|
workstreams: findDormantWorkstreams(db, { currentWorkstream }),
|
|
10696
10789
|
thresholds: { finishedIdleDays: FINISHED_IDLE_DAYS, abandonedIdleDays: ABANDONED_IDLE_DAYS }
|
|
10697
10790
|
},
|
|
10791
|
+
leaseJobs: leaseJobHints(db),
|
|
10698
10792
|
oldDelegates: (() => {
|
|
10699
10793
|
const h = checkOldDelegates(db);
|
|
10700
10794
|
return {
|
|
@@ -10808,7 +10902,9 @@ function cmdHostAdd(db, name, opts) {
|
|
|
10808
10902
|
exec: opts.exec,
|
|
10809
10903
|
fetch: opts.fetch,
|
|
10810
10904
|
repo: opts.repo ?? null,
|
|
10811
|
-
max_sessions: opts.maxSessions ?? null
|
|
10905
|
+
max_sessions: opts.maxSessions ?? null,
|
|
10906
|
+
transport: opts.transport ?? "session",
|
|
10907
|
+
stop: opts.stop ?? null
|
|
10812
10908
|
});
|
|
10813
10909
|
if (isJsonMode()) {
|
|
10814
10910
|
emitJson({ host });
|
|
@@ -10828,9 +10924,15 @@ function cmdHostList(db) {
|
|
|
10828
10924
|
}
|
|
10829
10925
|
const held = /* @__PURE__ */ new Map();
|
|
10830
10926
|
for (const c of listHostChannels(db)) held.set(c.host, (held.get(c.host) ?? 0) + 1);
|
|
10831
|
-
const t = muTable({ head: ["HOST", "DEFAULT REPO", "CHANNELS"] });
|
|
10927
|
+
const t = muTable({ head: ["HOST", "TRANSPORT", "STOP", "DEFAULT REPO", "CHANNELS"] });
|
|
10832
10928
|
for (const h of hosts) {
|
|
10833
|
-
t.push([
|
|
10929
|
+
t.push([
|
|
10930
|
+
h.name,
|
|
10931
|
+
h.transport,
|
|
10932
|
+
h.stop ?? "-",
|
|
10933
|
+
h.repo ?? "-",
|
|
10934
|
+
`${held.get(h.name) ?? 0}/${h.max_sessions ?? "\u221E"}`
|
|
10935
|
+
]);
|
|
10834
10936
|
}
|
|
10835
10937
|
console.log(t.toString());
|
|
10836
10938
|
}
|
|
@@ -10842,9 +10944,10 @@ function cmdHostShow(db, name) {
|
|
|
10842
10944
|
return;
|
|
10843
10945
|
}
|
|
10844
10946
|
console.log(
|
|
10845
|
-
`${pc.bold(host.name)} default repo ${host.repo ?? "-"} max-sessions ${host.max_sessions ?? "none"}`
|
|
10947
|
+
`${pc.bold(host.name)} transport ${host.transport} default repo ${host.repo ?? "-"} max-sessions ${host.max_sessions ?? "none"}`
|
|
10846
10948
|
);
|
|
10847
10949
|
for (const f of ["shell", "exec", "fetch"]) console.log(` ${f.padEnd(6)} ${host[f]}`);
|
|
10950
|
+
if (host.stop !== null) console.log(` ${"stop".padEnd(6)} ${host.stop}`);
|
|
10848
10951
|
console.log(
|
|
10849
10952
|
channels.length === 0 ? " channels: none held" : ` channels: ${channels.map(channelLine).join("; ")}`
|
|
10850
10953
|
);
|
|
@@ -10858,8 +10961,11 @@ function cmdHostRm(db, name) {
|
|
|
10858
10961
|
console.log(removed ? `removed host ${name}` : `no host profile named ${name}`);
|
|
10859
10962
|
}
|
|
10860
10963
|
function cmdHostInit(name, opts) {
|
|
10861
|
-
if (opts.ssh
|
|
10862
|
-
throw new UsageError("host init
|
|
10964
|
+
if (opts.ssh === true && opts.mule === true) {
|
|
10965
|
+
throw new UsageError("host init takes one starter kind: --ssh or --mule");
|
|
10966
|
+
}
|
|
10967
|
+
if (opts.ssh !== true && opts.mule !== true) {
|
|
10968
|
+
throw new UsageError("host init needs a starter kind: --ssh or --mule");
|
|
10863
10969
|
}
|
|
10864
10970
|
if (!isValidHostName(name)) {
|
|
10865
10971
|
throw new UsageError(
|
|
@@ -10867,20 +10973,24 @@ function cmdHostInit(name, opts) {
|
|
|
10867
10973
|
{ showHelp: false }
|
|
10868
10974
|
);
|
|
10869
10975
|
}
|
|
10870
|
-
const
|
|
10976
|
+
const lease = opts.mule === true;
|
|
10977
|
+
const s = lease ? hostMuleStarter(name) : hostStarter(name);
|
|
10871
10978
|
const command2 = [
|
|
10872
|
-
`mu host add ${name}`,
|
|
10979
|
+
`mu host add ${name}${lease ? " --transport lease" : ""}`,
|
|
10873
10980
|
`--shell ${shellQuote(s.shell)}`,
|
|
10874
10981
|
`--exec ${shellQuote(s.exec)}`,
|
|
10982
|
+
...s.stop === void 0 ? [] : [`--stop ${shellQuote(s.stop)}`],
|
|
10875
10983
|
`--fetch ${shellQuote(s.fetch)}`,
|
|
10876
|
-
"--max-sessions 1"
|
|
10984
|
+
...lease ? [] : ["--max-sessions 1"]
|
|
10877
10985
|
].join(" \\\n ");
|
|
10878
10986
|
if (isJsonMode()) {
|
|
10879
|
-
emitJson({ host: name, ...s, command: command2 });
|
|
10987
|
+
emitJson({ host: name, ...lease ? { transport: "lease" } : {}, ...s, command: command2 });
|
|
10880
10988
|
return;
|
|
10881
10989
|
}
|
|
10882
10990
|
console.log(`# Starter for ${name}. Edit it, then run it: mu runs none of this.`);
|
|
10883
|
-
console.log(
|
|
10991
|
+
console.log(
|
|
10992
|
+
lease ? `# ${name} is mule's host; a lease worker holds no ssh session (docs/guide/remote.md).` : `# ${name} is the ssh target; ~/.ssh/config decides the rest.`
|
|
10993
|
+
);
|
|
10884
10994
|
console.log(command2);
|
|
10885
10995
|
}
|
|
10886
10996
|
async function cmdHostCheck(db, name, opts) {
|
|
@@ -10898,6 +11008,7 @@ async function cmdHostCheck(db, name, opts) {
|
|
|
10898
11008
|
const mark = s.ok === true ? pc.green("ok ") : s.ok === false ? pc.red("FAIL") : pc.dim("skip");
|
|
10899
11009
|
console.log(`${mark} ${s.step.padEnd(9)} ${s.detail}`);
|
|
10900
11010
|
}
|
|
11011
|
+
for (const w of report.warnings) console.log(`${pc.yellow("warn")} ${w}`);
|
|
10901
11012
|
if (!report.ok) throw failedError(report);
|
|
10902
11013
|
}
|
|
10903
11014
|
function failedError(report) {
|
|
@@ -10911,10 +11022,16 @@ function wireHostCommands(program2) {
|
|
|
10911
11022
|
"Host profiles: command templates for a remote host. mu never connects and knows no ssh flags; it fills your templates and runs them with /bin/sh -c"
|
|
10912
11023
|
);
|
|
10913
11024
|
host.command("add <name>").description(
|
|
10914
|
-
"Store a machine-local host profile
|
|
11025
|
+
"Store a machine-local host profile: --shell opens the pane, --exec runs one remote command, --fetch brings commits home. --transport lease adds --stop and {job_file}/{job}; grammar in docs/guide/remote.md."
|
|
10915
11026
|
).requiredOption("--shell <template>", "pane command template (opens the remote shell)").requiredOption("--exec <template>", "template that runs one remote command ({cmd})").requiredOption("--fetch <template>", "local template that fetches commits home ({cwd})").option(
|
|
10916
11027
|
"--repo <path>",
|
|
10917
11028
|
"default remote repo for `agent spawn --host` without --root (a profile names a machine, not a repo)"
|
|
11029
|
+
).option(
|
|
11030
|
+
"--transport <kind>",
|
|
11031
|
+
"session (default: the pane holds an ssh) or lease (the pane is a carrier owning a detached remote job; needs --stop and {job_file} in --shell)"
|
|
11032
|
+
).option(
|
|
11033
|
+
"--stop <template>",
|
|
11034
|
+
"lease only: ends a job given its handle ({job}, required; {cwd} is `.`). Must be idempotent: exit 0 for a job already ended"
|
|
10918
11035
|
).option(
|
|
10919
11036
|
"--max-sessions <n>",
|
|
10920
11037
|
"cap on channels mu holds to this host at once (panes, --exec, --fetch)",
|
|
@@ -10926,20 +11043,22 @@ function wireHostCommands(program2) {
|
|
|
10926
11043
|
host.command("list").description("List host profiles with channels held / cap").option(...JSON_OPT).action(function() {
|
|
10927
11044
|
return handle(async (db) => cmdHostList(db), this)();
|
|
10928
11045
|
});
|
|
10929
|
-
host.command("show <name>").description(
|
|
11046
|
+
host.command("show <name>").description(
|
|
11047
|
+
"Show a profile's transport, templates and the channels it holds, with their holders"
|
|
11048
|
+
).option(...JSON_OPT).action(function(name) {
|
|
10930
11049
|
return handle(async (db) => cmdHostShow(db, name), this)();
|
|
10931
11050
|
});
|
|
10932
11051
|
host.command("rm <name>").description("Remove a profile. Refuses while it holds a channel (close those agents first)").option(...JSON_OPT).action(function(name) {
|
|
10933
11052
|
return handle(async (db) => cmdHostRm(db, name), this)();
|
|
10934
11053
|
});
|
|
10935
11054
|
host.command("init <name>").description(
|
|
10936
|
-
"Print a starter `mu host add` for ssh (the socket forward, `$SHELL -ilc`, identity env inside the command) for
|
|
10937
|
-
).option("--ssh", "print the ssh starter").option(...JSON_OPT).action(function(name) {
|
|
11055
|
+
"Print a starter `mu host add` for you to edit: --ssh (the socket forward, `$SHELL -ilc`, identity env inside the command) or --mule (a lease profile for a session-capped host). Prints only: mu never runs it and never connects."
|
|
11056
|
+
).option("--ssh", "print the ssh starter").option("--mule", "print the lease starter for a session-capped host (a mule carrier)").option(...JSON_OPT).action(function(name) {
|
|
10938
11057
|
const opts = this.opts();
|
|
10939
11058
|
return handle(async () => cmdHostInit(name, opts), this)();
|
|
10940
11059
|
});
|
|
10941
11060
|
host.command("check <name>").description(
|
|
10942
|
-
"Run the profile's --exec under one channel: host answers,
|
|
11061
|
+
"Run the profile's --exec under one channel: host answers, repo is git, pi runs, `pi list` names mu. A lease profile's templates are checked too (stop expanded, not run). --agent probes its control socket and job. Exit 5 on failure."
|
|
10943
11062
|
).option("--agent <name>", "also probe this agent's control socket (the forward rides its pane)").option("--root <path>", "the remote repo the repo step checks (default: the profile's)").option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(name) {
|
|
10944
11063
|
const opts = this.optsWithGlobals();
|
|
10945
11064
|
return handle((db) => cmdHostCheck(db, name, opts), this)();
|
|
@@ -12468,4 +12587,4 @@ export {
|
|
|
12468
12587
|
truncate,
|
|
12469
12588
|
truncateFront
|
|
12470
12589
|
};
|
|
12471
|
-
//# sourceMappingURL=cli-
|
|
12590
|
+
//# sourceMappingURL=cli-72EJ3CKW.js.map
|