cyberlegion 1.9.0 → 2.0.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.plugin/pins.json +1 -1
- package/CHANGELOG.md +28 -0
- package/dist/cli.mjs +408 -151
- package/dist/index.d.mts +38 -48
- package/dist/index.mjs +336 -82
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/readme.md +1 -1
- package/skills/dispatch-governance/SKILL.md +16 -3
- package/skills/dispatch-governance/scripts/cyberlegion.mjs +58 -58
- package/skills/init-cyberlegion/scripts/cyberlegion.mjs +58 -58
- package/skills/legate/SKILL.md +1 -1
- package/skills/legate/scripts/cyberlegion.mjs +58 -58
- package/skills/manage-inbox/SKILL.md +1 -1
- package/skills/manage-inbox/scripts/cyberlegion.mjs +58 -58
- package/skills/relay-governance/SKILL.md +1 -1
- package/skills/relay-governance/scripts/cyberlegion.mjs +58 -58
- package/skills/subagent-backend-governance/SKILL.md +1 -1
- package/skills/subagent-backend-governance/scripts/cyberlegion.mjs +58 -58
package/dist/cli.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { stripVTControlCharacters } from "node:util";
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
10
10
|
import { lstat, readFile, readdir, readlink, realpath, unlink, writeFile } from "node:fs/promises";
|
|
11
|
-
|
|
11
|
+
// ../../node_modules/.pnpm/commander@15.1.0/node_modules/commander/lib/error.js
|
|
12
12
|
/**
|
|
13
13
|
* CommanderError class
|
|
14
14
|
*/
|
|
@@ -42,8 +42,7 @@ var InvalidArgumentError = class extends CommanderError {
|
|
|
42
42
|
this.name = this.constructor.name;
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
|
-
|
|
46
|
-
//#region ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/argument.js
|
|
45
|
+
// ../../node_modules/.pnpm/commander@15.1.0/node_modules/commander/lib/argument.js
|
|
47
46
|
var Argument = class {
|
|
48
47
|
/**
|
|
49
48
|
* Initialize a new command argument with the given name and description.
|
|
@@ -161,8 +160,7 @@ function humanReadableArgName(arg) {
|
|
|
161
160
|
const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
|
|
162
161
|
return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
|
|
163
162
|
}
|
|
164
|
-
|
|
165
|
-
//#region ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/help.js
|
|
163
|
+
// ../../node_modules/.pnpm/commander@15.1.0/node_modules/commander/lib/help.js
|
|
166
164
|
/**
|
|
167
165
|
* TypeScript import types for JSDoc, used by Visual Studio Code IntelliSense and `npm run typescript-checkJS`
|
|
168
166
|
* https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types
|
|
@@ -230,8 +228,8 @@ var Help = class {
|
|
|
230
228
|
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
|
|
231
229
|
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
|
|
232
230
|
if (!removeShort && !removeLong) visibleOptions.push(helpOption);
|
|
233
|
-
else if (helpOption.long && !removeLong) visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
|
|
234
|
-
else if (helpOption.short && !removeShort) visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
|
|
231
|
+
else if (helpOption.long && !removeLong) visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description).helpGroup(helpOption.helpGroupHeading));
|
|
232
|
+
else if (helpOption.short && !removeShort) visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description).helpGroup(helpOption.helpGroupHeading));
|
|
235
233
|
}
|
|
236
234
|
if (this.sortOptions) visibleOptions.sort(this.compareOptions);
|
|
237
235
|
return visibleOptions;
|
|
@@ -636,8 +634,7 @@ var Help = class {
|
|
|
636
634
|
return wrappedLines.join("\n");
|
|
637
635
|
}
|
|
638
636
|
};
|
|
639
|
-
|
|
640
|
-
//#region ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/option.js
|
|
637
|
+
// ../../node_modules/.pnpm/commander@15.1.0/node_modules/commander/lib/option.js
|
|
641
638
|
var Option = class {
|
|
642
639
|
/**
|
|
643
640
|
* Initialize a new `Option` with the given `flags` and `description`.
|
|
@@ -650,7 +647,7 @@ var Option = class {
|
|
|
650
647
|
this.description = description || "";
|
|
651
648
|
this.required = flags.includes("<");
|
|
652
649
|
this.optional = flags.includes("[");
|
|
653
|
-
this.variadic = /\
|
|
650
|
+
this.variadic = /\p{ID_Continue}\.\.\.[>\]]$/u.test(flags);
|
|
654
651
|
this.mandatory = false;
|
|
655
652
|
const optionFlags = splitOptionFlags(flags);
|
|
656
653
|
this.short = optionFlags.shortFlag;
|
|
@@ -934,8 +931,7 @@ function splitOptionFlags(flags) {
|
|
|
934
931
|
longFlag
|
|
935
932
|
};
|
|
936
933
|
}
|
|
937
|
-
|
|
938
|
-
//#region ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/suggestSimilar.js
|
|
934
|
+
// ../../node_modules/.pnpm/commander@15.1.0/node_modules/commander/lib/suggestSimilar.js
|
|
939
935
|
const maxDistance = 3;
|
|
940
936
|
function editDistance(a, b) {
|
|
941
937
|
if (Math.abs(a.length - b.length) > maxDistance) return Math.max(a.length, b.length);
|
|
@@ -986,8 +982,7 @@ function suggestSimilar(word, candidates) {
|
|
|
986
982
|
if (similar.length === 1) return `\n(Did you mean ${similar[0]}?)`;
|
|
987
983
|
return "";
|
|
988
984
|
}
|
|
989
|
-
|
|
990
|
-
//#region ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/command.js
|
|
985
|
+
// ../../node_modules/.pnpm/commander@15.1.0/node_modules/commander/lib/command.js
|
|
991
986
|
var Command = class Command extends EventEmitter {
|
|
992
987
|
/**
|
|
993
988
|
* Initialize a new `Command`.
|
|
@@ -1737,7 +1732,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1737
1732
|
getOptionValueSourceWithGlobals(key) {
|
|
1738
1733
|
let source;
|
|
1739
1734
|
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1740
|
-
if (cmd.
|
|
1735
|
+
if (Object.hasOwn(cmd._optionValueSources, key)) source = cmd.getOptionValueSource(key);
|
|
1741
1736
|
});
|
|
1742
1737
|
return source;
|
|
1743
1738
|
}
|
|
@@ -2246,7 +2241,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2246
2241
|
return arg.length > 1 && arg[0] === "-";
|
|
2247
2242
|
}
|
|
2248
2243
|
const negativeNumberArg = (arg) => {
|
|
2249
|
-
if (!/^-(\d+|\d*\.\d+)(
|
|
2244
|
+
if (!/^-(\d+|\d*\.\d+)([eE][+-]?\d+)?$/.test(arg)) return false;
|
|
2250
2245
|
return !this._getCommandAndAncestors().some((cmd) => cmd.options.map((opt) => opt.short).some((short) => /^-\d$/.test(short)));
|
|
2251
2246
|
};
|
|
2252
2247
|
let activeVariadicOption = null;
|
|
@@ -2646,7 +2641,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2646
2641
|
* (This does not override a group set directly on the subcommand using .helpGroup().)
|
|
2647
2642
|
*
|
|
2648
2643
|
* @example
|
|
2649
|
-
* program.commandsGroup('Development Commands:);
|
|
2644
|
+
* program.commandsGroup('Development Commands:');
|
|
2650
2645
|
* program.command('watch')...
|
|
2651
2646
|
* program.command('lint')...
|
|
2652
2647
|
* ...
|
|
@@ -2815,7 +2810,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2815
2810
|
* Pass in false to disable the built-in help option.
|
|
2816
2811
|
*
|
|
2817
2812
|
* @example
|
|
2818
|
-
* program.helpOption('-?, --help' 'show help'); // customise
|
|
2813
|
+
* program.helpOption('-?, --help', 'show help'); // customise
|
|
2819
2814
|
* program.helpOption(false); // disable
|
|
2820
2815
|
*
|
|
2821
2816
|
* @param {(string | boolean)} flags
|
|
@@ -2937,12 +2932,12 @@ function incrementNodeInspectorPort(args) {
|
|
|
2937
2932
|
let debugHost = "127.0.0.1";
|
|
2938
2933
|
let debugPort = "9229";
|
|
2939
2934
|
let match;
|
|
2940
|
-
if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) debugOption = match[1];
|
|
2941
|
-
else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
|
|
2935
|
+
if ((match = arg.match(/^(--inspect(-brk|-wait)?)$/)) !== null) debugOption = match[1];
|
|
2936
|
+
else if ((match = arg.match(/^(--inspect(-brk|-port|-wait)?)=(\[[^\]]+\]|[^:]+)$/)) !== null) {
|
|
2942
2937
|
debugOption = match[1];
|
|
2943
2938
|
if (/^\d+$/.test(match[3])) debugPort = match[3];
|
|
2944
2939
|
else debugHost = match[3];
|
|
2945
|
-
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
|
|
2940
|
+
} else if ((match = arg.match(/^(--inspect(-brk|-port|-wait)?)=(\[[^\]]+\]|[^:]+):(\d+)$/)) !== null) {
|
|
2946
2941
|
debugOption = match[1];
|
|
2947
2942
|
debugHost = match[3];
|
|
2948
2943
|
debugPort = match[4];
|
|
@@ -2962,8 +2957,7 @@ function useColor() {
|
|
|
2962
2957
|
if (process$1.env.FORCE_COLOR || process$1.env.CLICOLOR_FORCE !== void 0) return true;
|
|
2963
2958
|
}
|
|
2964
2959
|
new Command();
|
|
2965
|
-
|
|
2966
|
-
//#region ../../node_modules/.pnpm/cyber-mux@0.12.0/node_modules/cyber-mux/dist/exec-Bmdcg_T7.mjs
|
|
2960
|
+
// ../../node_modules/.pnpm/cyber-mux@0.12.0/node_modules/cyber-mux/dist/exec-Bmdcg_T7.mjs
|
|
2967
2961
|
const nodeExec$1 = (cmd, args) => {
|
|
2968
2962
|
try {
|
|
2969
2963
|
const out = execFileSync(cmd, args, {
|
|
@@ -2990,8 +2984,7 @@ const nodeExec$1 = (cmd, args) => {
|
|
|
2990
2984
|
function withReason(exec, message) {
|
|
2991
2985
|
return exec.lastError ? `${message} — ${exec.lastError}` : message;
|
|
2992
2986
|
}
|
|
2993
|
-
|
|
2994
|
-
//#region ../../node_modules/.pnpm/cyber-mux@0.12.0/node_modules/cyber-mux/dist/backend-D3VSsev4.mjs
|
|
2987
|
+
// ../../node_modules/.pnpm/cyber-mux@0.12.0/node_modules/cyber-mux/dist/backend-D3VSsev4.mjs
|
|
2995
2988
|
const KNOWN_MUX = [
|
|
2996
2989
|
"tmux",
|
|
2997
2990
|
"rmux",
|
|
@@ -9138,8 +9131,7 @@ function callerPane(adapter, env) {
|
|
|
9138
9131
|
const self = currentPane(env);
|
|
9139
9132
|
return self && self.mux === adapter.name ? { id: self.pane } : void 0;
|
|
9140
9133
|
}
|
|
9141
|
-
|
|
9142
|
-
//#region src/admin.ts
|
|
9134
|
+
// src/admin.ts
|
|
9143
9135
|
/**
|
|
9144
9136
|
* Merge one store's registry + mailboxes + briefs into another (skip agent ids already present at
|
|
9145
9137
|
* the destination). Used by `admin migrate` when moving state between an old project-local root
|
|
@@ -9173,8 +9165,7 @@ function migrateStore(from, to) {
|
|
|
9173
9165
|
briefs
|
|
9174
9166
|
};
|
|
9175
9167
|
}
|
|
9176
|
-
|
|
9177
|
-
//#region ../../node_modules/.pnpm/cyber-agentkit@2.0.0/node_modules/cyber-agentkit/dist/worktrees.js
|
|
9168
|
+
// ../../node_modules/.pnpm/cyber-agentkit@2.2.0/node_modules/cyber-agentkit/dist/worktrees.js
|
|
9178
9169
|
/**
|
|
9179
9170
|
* Whether a checkout has uncommitted changes, tracked or untracked, or `undefined` when git could not
|
|
9180
9171
|
* say. `undefined` is not `false`: a caller deciding whether a worktree is safe to reuse must not read
|
|
@@ -10247,8 +10238,7 @@ async function exists(path) {
|
|
|
10247
10238
|
return false;
|
|
10248
10239
|
}
|
|
10249
10240
|
}
|
|
10250
|
-
|
|
10251
|
-
//#region ../../node_modules/.pnpm/cyber-mux@0.12.0/node_modules/cyber-mux/dist/worktree.mjs
|
|
10241
|
+
// ../../node_modules/.pnpm/cyber-mux@0.12.0/node_modules/cyber-mux/dist/worktree.mjs
|
|
10252
10242
|
/**
|
|
10253
10243
|
* This module's own refusals and failures — plain cyber-mux prose, never a dependency's raw words.
|
|
10254
10244
|
* `reportWorktreeFailure` (`cli.ts`) forwards a `WorktreeGitError`'s message onto stdout verbatim
|
|
@@ -10309,8 +10299,7 @@ function resolvePrimaryRoot(exec) {
|
|
|
10309
10299
|
function assertDistinctFromPrimary(worktreeRoot, primaryRoot) {
|
|
10310
10300
|
if (resolve(worktreeRoot) === resolve(primaryRoot)) throw new WorktreeGitError("refusing to run in the primary checkout — spawn a worktree distinct from the primary checkout");
|
|
10311
10301
|
}
|
|
10312
|
-
|
|
10313
|
-
//#region src/paths.ts
|
|
10302
|
+
// src/paths.ts
|
|
10314
10303
|
/** The tracked marker file that makes a hub root initialized (see ensureMarker). */
|
|
10315
10304
|
const MARKER_FILE = "config.json";
|
|
10316
10305
|
/** Where `spawn` stamps that marker inside a worktree it creates, relative to the worktree root. */
|
|
@@ -10406,8 +10395,7 @@ const paths = {
|
|
|
10406
10395
|
function sanitizePane(pane) {
|
|
10407
10396
|
return pane.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
10408
10397
|
}
|
|
10409
|
-
|
|
10410
|
-
//#region src/mux-env.ts
|
|
10398
|
+
// src/mux-env.ts
|
|
10411
10399
|
/**
|
|
10412
10400
|
* Transitional env-normalization seam (mux.feature: "a pane carrying only the legacy fast-path vars
|
|
10413
10401
|
* is still honored"). cyber-mux's own fast-path reads `$CYBER_MUX`/`$CYBER_MUX_PANE` — its
|
|
@@ -10436,8 +10424,7 @@ function normalizeMuxEnv(env) {
|
|
|
10436
10424
|
CYBER_MUX_PANE: env.CYBERLEGION_MUX_PANE
|
|
10437
10425
|
};
|
|
10438
10426
|
}
|
|
10439
|
-
|
|
10440
|
-
//#region src/identity.ts
|
|
10427
|
+
// src/identity.ts
|
|
10441
10428
|
const realExec = nodeExec$1;
|
|
10442
10429
|
/** `currentPane`, narrowed to a backend the registry can actually store a locator under. A caller
|
|
10443
10430
|
* inside a pane-carrying-but-unstorable multiplexer (wezterm/zellij) is simply unpaned here — it can
|
|
@@ -10450,6 +10437,133 @@ function storablePane(env) {
|
|
|
10450
10437
|
pane: cur.pane
|
|
10451
10438
|
} : void 0;
|
|
10452
10439
|
}
|
|
10440
|
+
/** The calling session's storable pane, resolved the way `register` resolves it: herdr's answer
|
|
10441
|
+
* when herdr gives a trustworthy one, `$HERDR_PANE_ID` otherwise (#154, #268). */
|
|
10442
|
+
function callingStorablePane(ctx) {
|
|
10443
|
+
return selfPane(storablePane(ctx.env ?? process.env), ctx.exec ?? realExec);
|
|
10444
|
+
}
|
|
10445
|
+
/**
|
|
10446
|
+
* This session's own pane as the multiplexer sees it now. The env names the pane the shell was
|
|
10447
|
+
* launched in, and for herdr that goes stale: herdr gives a pane a new id when it moves to another
|
|
10448
|
+
* tab or workspace, while the shell keeps its launch-time `$HERDR_PANE_ID`, and herdr ids recur on a
|
|
10449
|
+
* hub, so the stale id can be another unit's pane (#154). herdr's `pane current` resolves the
|
|
10450
|
+
* calling pane itself, so its answer wins on a herdr that answers it truly (0.8.2 on). When herdr
|
|
10451
|
+
* gives no answer, or an older herdr disagrees with the env, the env id stands. tmux pane
|
|
10452
|
+
* ids last as long as the pane, so tmux needs no second opinion.
|
|
10453
|
+
*/
|
|
10454
|
+
function selfPane(cur, exec) {
|
|
10455
|
+
if (cur?.mux !== "herdr") return cur;
|
|
10456
|
+
const live = herdrCallingPane(exec);
|
|
10457
|
+
if (!live) return cur;
|
|
10458
|
+
if (live.pane === cur.pane) return {
|
|
10459
|
+
...cur,
|
|
10460
|
+
...live.terminal ? { terminal: live.terminal } : {}
|
|
10461
|
+
};
|
|
10462
|
+
if (!herdrReportsCallingPane(exec)) return cur;
|
|
10463
|
+
return {
|
|
10464
|
+
...cur,
|
|
10465
|
+
pane: live.pane,
|
|
10466
|
+
...live.terminal ? { terminal: live.terminal } : {}
|
|
10467
|
+
};
|
|
10468
|
+
}
|
|
10469
|
+
/** herdr 0.8.2 fixed `pane current` to answer the calling pane; before it, the command answered
|
|
10470
|
+
* another client's focused pane (herdr #2297), which would misattribute exactly as a stale env id
|
|
10471
|
+
* does. A version that cannot be read is treated as too old. */
|
|
10472
|
+
function herdrReportsCallingPane(exec) {
|
|
10473
|
+
const m = exec("herdr", ["--version"])?.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
10474
|
+
if (!m) return false;
|
|
10475
|
+
const [major, minor, patch] = m.slice(1).map(Number);
|
|
10476
|
+
return major > 0 || minor > 8 || minor === 8 && patch >= 2;
|
|
10477
|
+
}
|
|
10478
|
+
function herdrCallingPane(exec) {
|
|
10479
|
+
return herdrPaneAnswer(exec("herdr", ["pane", "current"]));
|
|
10480
|
+
}
|
|
10481
|
+
/** Parse a herdr `pane current` / `pane get` answer into its pane id and terminal id. */
|
|
10482
|
+
function herdrPaneAnswer(out) {
|
|
10483
|
+
if (!out) return void 0;
|
|
10484
|
+
try {
|
|
10485
|
+
const pane = JSON.parse(out)?.result?.pane;
|
|
10486
|
+
const id = pane?.pane_id;
|
|
10487
|
+
if (typeof id !== "string" || !id) return void 0;
|
|
10488
|
+
const terminal = pane.terminal_id;
|
|
10489
|
+
return {
|
|
10490
|
+
pane: id,
|
|
10491
|
+
...typeof terminal === "string" && terminal ? { terminal } : {}
|
|
10492
|
+
};
|
|
10493
|
+
} catch {
|
|
10494
|
+
return;
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
/**
|
|
10498
|
+
* The herdr terminal id behind a pane id — what a unit's herdr pane record keys on besides the pane
|
|
10499
|
+
* id. herdr gives a pane a new id when it moves to another workspace and keeps its terminal id
|
|
10500
|
+
* (#272; herdr 0.9.5: `pane move wRD:p2 --tab wRE:t1` answered `wRE:p2` on the same `terminal_id`).
|
|
10501
|
+
* Undefined when herdr does not answer or reports no terminal id.
|
|
10502
|
+
*/
|
|
10503
|
+
function herdrTerminalOf(exec, paneId) {
|
|
10504
|
+
return herdrPaneAnswer(exec("herdr", [
|
|
10505
|
+
"pane",
|
|
10506
|
+
"get",
|
|
10507
|
+
paneId
|
|
10508
|
+
]))?.terminal;
|
|
10509
|
+
}
|
|
10510
|
+
/** The pane locator for a pane a spawn or restart just opened: tagged with its multiplexer so the
|
|
10511
|
+
* unit's own `prune` runs the right liveness check, and for herdr carrying the terminal id a move
|
|
10512
|
+
* keeps. `null` for a backend a record cannot carry. */
|
|
10513
|
+
function openedPaneLocator(exec, mux, id) {
|
|
10514
|
+
if (mux === "tmux") return {
|
|
10515
|
+
mux,
|
|
10516
|
+
id
|
|
10517
|
+
};
|
|
10518
|
+
if (mux !== "herdr") return null;
|
|
10519
|
+
const terminal = herdrTerminalOf(exec, id);
|
|
10520
|
+
return {
|
|
10521
|
+
mux,
|
|
10522
|
+
id,
|
|
10523
|
+
...terminal ? { terminal } : {}
|
|
10524
|
+
};
|
|
10525
|
+
}
|
|
10526
|
+
/** herdr's live panes keyed by terminal id → current pane id. Empty when herdr gives no list, which
|
|
10527
|
+
* says nothing about any pane. */
|
|
10528
|
+
function herdrTerminals(exec) {
|
|
10529
|
+
const map = /* @__PURE__ */ new Map();
|
|
10530
|
+
const out = exec("herdr", ["pane", "list"]);
|
|
10531
|
+
if (!out) return map;
|
|
10532
|
+
try {
|
|
10533
|
+
const panes = JSON.parse(out)?.result?.panes;
|
|
10534
|
+
if (!Array.isArray(panes)) return map;
|
|
10535
|
+
for (const p of panes) if (typeof p?.terminal_id === "string" && typeof p?.pane_id === "string") map.set(p.terminal_id, p.pane_id);
|
|
10536
|
+
} catch {
|
|
10537
|
+
return map;
|
|
10538
|
+
}
|
|
10539
|
+
return map;
|
|
10540
|
+
}
|
|
10541
|
+
/**
|
|
10542
|
+
* Where a herdr record that carries its terminal id lives now: the pane id herdr lists for that
|
|
10543
|
+
* terminal (`{ id }`), `'gone'` when herdr answered with a list that lacks the terminal, or
|
|
10544
|
+
* `undefined` when herdr gave no list. Not called for a record without a terminal id, which keeps
|
|
10545
|
+
* being judged by its pane id.
|
|
10546
|
+
*/
|
|
10547
|
+
function locateTerminal(exec, terminal, cache = {}) {
|
|
10548
|
+
cache.terminals ??= herdrTerminals(exec);
|
|
10549
|
+
if (cache.terminals.size === 0) return void 0;
|
|
10550
|
+
const id = cache.terminals.get(terminal);
|
|
10551
|
+
return id ? { id } : "gone";
|
|
10552
|
+
}
|
|
10553
|
+
/** Point a record whose herdr pane moved at the pane id it has now, moving its pane pointer with it.
|
|
10554
|
+
* The old pointer is dropped only while it still names this record, since herdr hands old ids out
|
|
10555
|
+
* again. */
|
|
10556
|
+
function followMove(ctx, rec, id) {
|
|
10557
|
+
const pane = rec.pane;
|
|
10558
|
+
if (!pane || pane.id === id) return;
|
|
10559
|
+
if (ctx.store.resolvePaneId(pane.id) === rec.id) ctx.store.removePaneIndex(pane.id);
|
|
10560
|
+
rec.pane = {
|
|
10561
|
+
...pane,
|
|
10562
|
+
id
|
|
10563
|
+
};
|
|
10564
|
+
saveAgent(ctx.store, rec);
|
|
10565
|
+
ctx.store.putPaneIndex(id, rec.id);
|
|
10566
|
+
}
|
|
10453
10567
|
const nowIso = (ctx) => new Date(ctx.now?.() ?? Date.now()).toISOString();
|
|
10454
10568
|
function randomId() {
|
|
10455
10569
|
return randomBytes(8).toString("hex");
|
|
@@ -10495,10 +10609,33 @@ function detectHarness(explicit, ctx) {
|
|
|
10495
10609
|
* explicit via the env var.
|
|
10496
10610
|
*/
|
|
10497
10611
|
function resolveSelfId(ctx) {
|
|
10612
|
+
return resolveSelf(ctx).id;
|
|
10613
|
+
}
|
|
10614
|
+
function resolveSelf(ctx) {
|
|
10498
10615
|
const env = ctx.env ?? process.env;
|
|
10499
|
-
const cur = currentPane(normalizeMuxEnv(env));
|
|
10500
|
-
if (cur) return
|
|
10501
|
-
|
|
10616
|
+
const cur = selfPane(currentPane(normalizeMuxEnv(env)), ctx.exec ?? realExec);
|
|
10617
|
+
if (cur) return {
|
|
10618
|
+
id: selfIdAt(ctx.store, cur),
|
|
10619
|
+
cur
|
|
10620
|
+
};
|
|
10621
|
+
return {
|
|
10622
|
+
id: env.CYBERLEGION_AGENT_ID || void 0,
|
|
10623
|
+
cur
|
|
10624
|
+
};
|
|
10625
|
+
}
|
|
10626
|
+
/**
|
|
10627
|
+
* The unit a pane belongs to. The pane pointer answers first. When herdr named the calling pane's
|
|
10628
|
+
* terminal, a pointer whose record carries a different terminal is stale (the id was handed to
|
|
10629
|
+
* this pane after the unit's own pane moved away), and a missing pointer may mean this pane moved:
|
|
10630
|
+
* either way the record bearing this terminal is the caller (#272). A record without a terminal id
|
|
10631
|
+
* is trusted by its pointer as before.
|
|
10632
|
+
*/
|
|
10633
|
+
function selfIdAt(store, cur) {
|
|
10634
|
+
const indexed = store.resolvePaneId(cur.pane);
|
|
10635
|
+
if (!cur.terminal) return indexed;
|
|
10636
|
+
const terminal = indexed ? loadAgent(store, indexed)?.pane?.terminal : void 0;
|
|
10637
|
+
if (indexed && (!terminal || terminal === cur.terminal)) return indexed;
|
|
10638
|
+
return listAgents(store).find((a) => a.pane?.mux === "herdr" && a.pane.terminal === cur.terminal)?.id;
|
|
10502
10639
|
}
|
|
10503
10640
|
/** Register (or idempotently refresh) this session's identity. */
|
|
10504
10641
|
function register(ctx, input) {
|
|
@@ -10510,14 +10647,14 @@ function register(ctx, input) {
|
|
|
10510
10647
|
const existing = existingId ? loadAgent(ctx.store, existingId) : void 0;
|
|
10511
10648
|
const id = existing?.id ?? existingId ?? randomId();
|
|
10512
10649
|
const ts = nowIso(ctx);
|
|
10513
|
-
const cur = storablePane(env);
|
|
10514
10650
|
const exec = ctx.exec ?? realExec;
|
|
10651
|
+
const cur = selfPane(storablePane(env), exec);
|
|
10515
10652
|
const rec = {
|
|
10516
10653
|
id,
|
|
10517
10654
|
handle: input.handle ?? existing?.handle ?? id.slice(0, 6),
|
|
10518
10655
|
harness,
|
|
10519
10656
|
cwd: process.cwd(),
|
|
10520
|
-
|
|
10657
|
+
...checkoutFor(ctx.store, exec, id, existing),
|
|
10521
10658
|
pane: cur ? paneLocator(cur, exec, env) : null,
|
|
10522
10659
|
status: "active",
|
|
10523
10660
|
createdAt: existing?.createdAt ?? ts,
|
|
@@ -10546,7 +10683,8 @@ function paneLocator(cur, exec, env) {
|
|
|
10546
10683
|
};
|
|
10547
10684
|
return {
|
|
10548
10685
|
mux: cur.mux,
|
|
10549
|
-
id: cur.pane
|
|
10686
|
+
id: cur.pane,
|
|
10687
|
+
...cur.terminal ? { terminal: cur.terminal } : {}
|
|
10550
10688
|
};
|
|
10551
10689
|
}
|
|
10552
10690
|
/** Derive a standing record's stable id from its handle — same slug rule as `sanitizePane`, prefixed
|
|
@@ -10584,24 +10722,71 @@ function writeStanding(ctx, id, input) {
|
|
|
10584
10722
|
saveAgent(ctx.store, rec);
|
|
10585
10723
|
return rec;
|
|
10586
10724
|
}
|
|
10587
|
-
/** The checkout the cwd sits in: its root, its branch, and the repository's primary
|
|
10588
|
-
* same `primaryRoot` spawn records, so a lookup by repository finds a unit in a linked
|
|
10589
|
-
* the repository's name rather than the worktree folder's. */
|
|
10590
|
-
function gitWorktree(exec) {
|
|
10591
|
-
const
|
|
10725
|
+
/** The checkout `dir` (else the cwd) sits in: its root, its branch, and the repository's primary
|
|
10726
|
+
* checkout — the same `primaryRoot` spawn records, so a lookup by repository finds a unit in a linked
|
|
10727
|
+
* worktree by the repository's name rather than the worktree folder's. */
|
|
10728
|
+
function gitWorktree(exec, dir) {
|
|
10729
|
+
const git = dir === void 0 ? exec : (cmd, args) => exec(cmd, cmd === "git" ? [
|
|
10730
|
+
"-C",
|
|
10731
|
+
dir,
|
|
10732
|
+
...args
|
|
10733
|
+
] : args);
|
|
10734
|
+
const root = git("git", ["rev-parse", "--show-toplevel"]);
|
|
10592
10735
|
if (!root) return null;
|
|
10593
|
-
const branch =
|
|
10736
|
+
const branch = git("git", [
|
|
10594
10737
|
"rev-parse",
|
|
10595
10738
|
"--abbrev-ref",
|
|
10596
10739
|
"HEAD"
|
|
10597
10740
|
]) ?? void 0;
|
|
10598
|
-
const primaryRoot = primaryRootHere(
|
|
10741
|
+
const primaryRoot = primaryRootHere(git);
|
|
10599
10742
|
return {
|
|
10600
10743
|
root,
|
|
10601
10744
|
...branch !== void 0 ? { branch } : {},
|
|
10602
10745
|
...primaryRoot !== void 0 ? { primaryRoot } : {}
|
|
10603
10746
|
};
|
|
10604
10747
|
}
|
|
10748
|
+
/** The id of the unit, other than `self`, whose record owns the worktree at `root`. */
|
|
10749
|
+
function ownerOf(store, root, self) {
|
|
10750
|
+
const at = resolve(root);
|
|
10751
|
+
return listAgents(store).find((a) => a.id !== self && a.worktree?.root && resolve(a.worktree.root) === at)?.id;
|
|
10752
|
+
}
|
|
10753
|
+
/** `checkout` as a shared reference, naming the unit that owns it when one does. */
|
|
10754
|
+
function asShared(store, checkout, self) {
|
|
10755
|
+
const owner = ownerOf(store, checkout.root, self);
|
|
10756
|
+
return {
|
|
10757
|
+
...checkout,
|
|
10758
|
+
...owner ? { owner } : {}
|
|
10759
|
+
};
|
|
10760
|
+
}
|
|
10761
|
+
/** The checkout a `--cwd` spawn opens into, as a shared reference — spawn created none, so the unit
|
|
10762
|
+
* owns none, whoever does. `undefined` when `dir` is in no git checkout. */
|
|
10763
|
+
function sharedCheckoutAt(store, exec, dir) {
|
|
10764
|
+
const checkout = gitWorktree(exec, dir);
|
|
10765
|
+
return checkout ? asShared(store, checkout) : void 0;
|
|
10766
|
+
}
|
|
10767
|
+
/**
|
|
10768
|
+
* Which checkout a registering session owns and which it only shares. An existing owned worktree is
|
|
10769
|
+
* kept. Otherwise the cwd's checkout is the session's own — unless another unit's record owns it, or
|
|
10770
|
+
* this unit already records it as shared (a `--cwd` spawn): then it is a shared reference, and the
|
|
10771
|
+
* session owns none, so its close can never remove another unit's worktree or release its lease.
|
|
10772
|
+
*/
|
|
10773
|
+
function checkoutFor(store, exec, id, existing) {
|
|
10774
|
+
const kept = existing?.sharedWorktree ? { sharedWorktree: existing.sharedWorktree } : {};
|
|
10775
|
+
if (existing?.worktree) return {
|
|
10776
|
+
worktree: keptWorktree(existing.worktree, exec),
|
|
10777
|
+
...kept
|
|
10778
|
+
};
|
|
10779
|
+
const here = gitWorktree(exec);
|
|
10780
|
+
if (!here) return {
|
|
10781
|
+
worktree: null,
|
|
10782
|
+
...kept
|
|
10783
|
+
};
|
|
10784
|
+
if (existing?.sharedWorktree || ownerOf(store, here.root, id)) return {
|
|
10785
|
+
worktree: null,
|
|
10786
|
+
sharedWorktree: asShared(store, here, id)
|
|
10787
|
+
};
|
|
10788
|
+
return { worktree: here };
|
|
10789
|
+
}
|
|
10605
10790
|
function primaryRootHere(exec) {
|
|
10606
10791
|
try {
|
|
10607
10792
|
return resolvePrimaryRoot(exec);
|
|
@@ -10808,10 +10993,26 @@ function bumpLastSeen(ctx, id) {
|
|
|
10808
10993
|
rec.lastSeen = nowIso(ctx);
|
|
10809
10994
|
saveAgent(ctx.store, rec);
|
|
10810
10995
|
}
|
|
10811
|
-
/**
|
|
10996
|
+
/**
|
|
10997
|
+
* Refresh this session's own last-seen if it is registered — best-effort, never throws. In a herdr
|
|
10998
|
+
* pane whose calling pane herdr reported, it also heals the record's pane to the id and terminal
|
|
10999
|
+
* herdr gave, so a unit whose pane moved fixes its own record on its next turn (#272).
|
|
11000
|
+
*/
|
|
10812
11001
|
function touch(ctx) {
|
|
10813
|
-
const id =
|
|
10814
|
-
if (id)
|
|
11002
|
+
const { id, cur } = resolveSelf(ctx);
|
|
11003
|
+
if (!id) return;
|
|
11004
|
+
bumpLastSeen(ctx, id);
|
|
11005
|
+
if (cur?.mux !== "herdr" || !cur.terminal) return;
|
|
11006
|
+
const rec = loadAgent(ctx.store, id);
|
|
11007
|
+
if (rec?.pane?.mux !== "herdr") return;
|
|
11008
|
+
if (rec.pane.terminal !== cur.terminal) {
|
|
11009
|
+
rec.pane = {
|
|
11010
|
+
...rec.pane,
|
|
11011
|
+
terminal: cur.terminal
|
|
11012
|
+
};
|
|
11013
|
+
saveAgent(ctx.store, rec);
|
|
11014
|
+
}
|
|
11015
|
+
followMove(ctx, rec, cur.pane);
|
|
10815
11016
|
}
|
|
10816
11017
|
const STALE_MS = 9e5;
|
|
10817
11018
|
/** Standing and service records have no session of their own, so nothing about a pane or a timer
|
|
@@ -10838,7 +11039,9 @@ function isPrunable(rec) {
|
|
|
10838
11039
|
function sessionLive(ctx, rec) {
|
|
10839
11040
|
if (rec.status === "exited" || rec.status === "stopped") return false;
|
|
10840
11041
|
if (!rec.pane) return true;
|
|
10841
|
-
const
|
|
11042
|
+
const exec = ctx.exec ?? realExec;
|
|
11043
|
+
if (rec.pane.mux === "herdr" && rec.pane.terminal) return locateTerminal(exec, rec.pane.terminal) !== "gone";
|
|
11044
|
+
const panes = PANE_ADAPTERS[rec.pane.mux].listPanes(exec);
|
|
10842
11045
|
if (panes.length === 0) return true;
|
|
10843
11046
|
const id = rec.pane.id;
|
|
10844
11047
|
return panes.some((p) => p.id === id);
|
|
@@ -10913,11 +11116,12 @@ function reconcile(ctx, opts) {
|
|
|
10913
11116
|
if (panes.length === 0) return [];
|
|
10914
11117
|
const live = new Set(panes.map((p) => p.id));
|
|
10915
11118
|
const changed = [];
|
|
11119
|
+
const cache = {};
|
|
10916
11120
|
for (const rec of listAgents(ctx.store)) {
|
|
10917
11121
|
if (!isPrunable(rec)) continue;
|
|
10918
11122
|
if (!rec.pane) continue;
|
|
10919
11123
|
if (rec.pane.mux !== cur.mux) continue;
|
|
10920
|
-
if (!live.has(rec.pane.id)) {
|
|
11124
|
+
if (rec.pane.mux === "herdr" && rec.pane.terminal ? terminalGone(ctx, rec, rec.pane.terminal, cache) : !live.has(rec.pane.id)) {
|
|
10921
11125
|
rec.status = "exited";
|
|
10922
11126
|
saveAgent(ctx.store, rec);
|
|
10923
11127
|
changed.push(rec);
|
|
@@ -10942,27 +11146,93 @@ function paneGone(exec, pane, lists) {
|
|
|
10942
11146
|
}
|
|
10943
11147
|
return panes.length > 0 && !panes.some((p) => p.id === pane.id);
|
|
10944
11148
|
}
|
|
10945
|
-
/**
|
|
10946
|
-
*
|
|
10947
|
-
*
|
|
10948
|
-
*
|
|
11149
|
+
/**
|
|
11150
|
+
* Whether a herdr record that carries its terminal id is positively gone: herdr listed its panes and
|
|
11151
|
+
* that terminal is not among them. A live terminal under a new pane id is a moved pane, so the record
|
|
11152
|
+
* follows it instead (#272). A pane id that herdr now gives another terminal does not keep the record
|
|
11153
|
+
* alive, since herdr ids recur. No list at all decides nothing.
|
|
11154
|
+
*/
|
|
11155
|
+
function terminalGone(ctx, rec, terminal, cache) {
|
|
11156
|
+
const at = locateTerminal(ctx.exec ?? realExec, terminal, cache);
|
|
11157
|
+
if (at === "gone") return true;
|
|
11158
|
+
if (at) followMove(ctx, rec, at.id);
|
|
11159
|
+
return false;
|
|
11160
|
+
}
|
|
11161
|
+
/**
|
|
11162
|
+
* Mark agents whose session is gone as exited, then delete every exited unit with nothing left to
|
|
11163
|
+
* work from. Reconcile-culls against the current mux's live set first, then judges each remaining
|
|
11164
|
+
* record (covers the other mux and sessions outside any multiplexer pane): a pane-bound record by its
|
|
11165
|
+
* pane alone — a live or unprobeable pane keeps it whatever its last-seen — and a pane-less record by
|
|
11166
|
+
* the staleness timer, its only signal. An exited unit whose worktree is still on disk is kept, since
|
|
11167
|
+
* `unit close` owns removing that worktree; every other exited unit is deleted with `deleteExited`.
|
|
11168
|
+
*/
|
|
10949
11169
|
function prune(ctx) {
|
|
10950
11170
|
const exec = ctx.exec ?? realExec;
|
|
10951
11171
|
const now = ctx.now?.() ?? Date.now();
|
|
10952
|
-
const
|
|
11172
|
+
const marked = reconcile(ctx);
|
|
10953
11173
|
const lists = /* @__PURE__ */ new Map();
|
|
11174
|
+
const cache = {};
|
|
10954
11175
|
for (const rec of listAgents(ctx.store)) {
|
|
10955
11176
|
if (!isPrunable(rec)) continue;
|
|
10956
|
-
if (rec.pane ?
|
|
11177
|
+
if (!rec.pane ? now - new Date(rec.lastSeen).getTime() > STALE_MS : rec.pane.mux === "herdr" && rec.pane.terminal ? terminalGone(ctx, rec, rec.pane.terminal, cache) : paneGone(exec, rec.pane, lists)) {
|
|
10957
11178
|
rec.status = "exited";
|
|
10958
11179
|
saveAgent(ctx.store, rec);
|
|
10959
|
-
|
|
10960
|
-
}
|
|
11180
|
+
marked.push(rec);
|
|
11181
|
+
}
|
|
11182
|
+
}
|
|
11183
|
+
const deleted = deleteExited(ctx);
|
|
11184
|
+
const gone = new Set(deleted.map((r) => r.id));
|
|
11185
|
+
return [...marked.filter((r) => !gone.has(r.id)).map((r) => ({
|
|
11186
|
+
...r,
|
|
11187
|
+
pruned: "exited"
|
|
11188
|
+
})), ...deleted.map((r) => ({
|
|
11189
|
+
...r,
|
|
11190
|
+
pruned: "deleted"
|
|
11191
|
+
}))];
|
|
11192
|
+
}
|
|
11193
|
+
/** Whether an exited record still has a worktree on disk — the one thing it leaves behind that
|
|
11194
|
+
* `unit close` must deal with. A record that never had a worktree has nothing on disk. */
|
|
11195
|
+
function worktreeOnDisk(rec) {
|
|
11196
|
+
if (rec.worktree?.root == null || !existsSync(rec.worktree.root)) return false;
|
|
11197
|
+
return !isPrimaryCheckout(rec.worktree.root, rec.worktree.primaryRoot);
|
|
11198
|
+
}
|
|
11199
|
+
/** Whether a worktree root is its repository's primary checkout — the one test `unit close` and
|
|
11200
|
+
* `unit prune` share for "a seat on the main checkout, which nothing removes". */
|
|
11201
|
+
function isPrimaryCheckout(worktreeRoot, primaryRoot) {
|
|
11202
|
+
return worktreeRoot != null && primaryRoot != null && resolve(worktreeRoot) === resolve(primaryRoot);
|
|
11203
|
+
}
|
|
11204
|
+
/**
|
|
11205
|
+
* Delete every exited unit with no worktree left to close (none on disk, or only the primary checkout): its record, brief, mailbox, and the pane
|
|
11206
|
+
* pointer that still names it. Nothing revives an exited unit (`unit restart` takes only a running or
|
|
11207
|
+
* stopped one), so these records are read by nobody. A pane pointer is dropped only when it still
|
|
11208
|
+
* resolves to the deleted unit, since a multiplexer may have handed the pane id to another unit
|
|
11209
|
+
* since. A worktree that is gone from disk may still be registered with git, and the record is the
|
|
11210
|
+
* last place that names its repository, so that registration is cleared first, best-effort, as
|
|
11211
|
+
* `unit close` does. No pane is torn down: an exited unit's pane is already gone or belongs to
|
|
11212
|
+
* someone else.
|
|
11213
|
+
*/
|
|
11214
|
+
function deleteExited(ctx) {
|
|
11215
|
+
const exec = ctx.exec ?? realExec;
|
|
11216
|
+
const deleted = [];
|
|
11217
|
+
for (const rec of listAgents(ctx.store)) {
|
|
11218
|
+
if (rec.status !== "exited" || isSessionIndependent(rec) || worktreeOnDisk(rec)) continue;
|
|
11219
|
+
const primaryRoot = rec.worktree?.primaryRoot;
|
|
11220
|
+
if (primaryRoot && existsSync(primaryRoot)) exec("git", [
|
|
11221
|
+
"-C",
|
|
11222
|
+
primaryRoot,
|
|
11223
|
+
"worktree",
|
|
11224
|
+
"prune"
|
|
11225
|
+
]);
|
|
11226
|
+
const pane = rec.pane?.id ?? ctx.store.findPaneByAgentId(rec.id);
|
|
11227
|
+
if (pane && ctx.store.resolvePaneId(pane) === rec.id) ctx.store.removePaneIndex(pane);
|
|
11228
|
+
ctx.store.removeAgent(rec.id);
|
|
11229
|
+
ctx.store.removeAgentData(rec.id);
|
|
11230
|
+
ctx.store.removeMailbox(rec.id);
|
|
11231
|
+
deleted.push(rec);
|
|
10961
11232
|
}
|
|
10962
|
-
return
|
|
11233
|
+
return deleted;
|
|
10963
11234
|
}
|
|
10964
|
-
|
|
10965
|
-
//#region src/console/prompt-guard.ts
|
|
11235
|
+
// src/console/prompt-guard.ts
|
|
10966
11236
|
const CODEX_PLACEHOLDERS = /* @__PURE__ */ new Set([
|
|
10967
11237
|
"Ask Codex to do anything",
|
|
10968
11238
|
"Explain this codebase",
|
|
@@ -11120,8 +11390,7 @@ async function clearLine(adapter, exec, target, sleep) {
|
|
|
11120
11390
|
adapter.sendText(exec, target, KILL_LINE);
|
|
11121
11391
|
await sleep(KEY_SETTLE_MS);
|
|
11122
11392
|
}
|
|
11123
|
-
|
|
11124
|
-
//#region src/console/ring.ts
|
|
11393
|
+
// src/console/ring.ts
|
|
11125
11394
|
const DEFAULT_ATTEMPTS = 10;
|
|
11126
11395
|
const DEFAULT_SETTLE_MS = 400;
|
|
11127
11396
|
/** cyber-mux's `isStaged` matches on the same prefix length. */
|
|
@@ -11226,8 +11495,7 @@ async function ringTurn(adapter, exec, target, message, opts = {}) {
|
|
|
11226
11495
|
const why = state === "lost" ? "the text vanished without the harness posting it" : `input still staged after ${attempts} re-submit attempts`;
|
|
11227
11496
|
throw new Error(`nudge failed: peer at pane ${target.id} never took the turn — ${why}`);
|
|
11228
11497
|
}
|
|
11229
|
-
|
|
11230
|
-
//#region src/console/doorbell.ts
|
|
11498
|
+
// src/console/doorbell.ts
|
|
11231
11499
|
/** The doorbell text delivered to a woken recipient; also the standalone `unit nudge` default. */
|
|
11232
11500
|
const DELIVERY_DOORBELL = "You have unread mail — check your inbox.";
|
|
11233
11501
|
/**
|
|
@@ -11426,8 +11694,7 @@ async function wakeSpawn(getAdapter, exec, input, nudgeOpts = SPAWN_NUDGE_OPTS,
|
|
|
11426
11694
|
};
|
|
11427
11695
|
}
|
|
11428
11696
|
}
|
|
11429
|
-
|
|
11430
|
-
//#region src/console/trust.ts
|
|
11697
|
+
// src/console/trust.ts
|
|
11431
11698
|
/**
|
|
11432
11699
|
* Each harness's folder-trust prompt, as it draws it, and the keys that accept it. Read from Claude
|
|
11433
11700
|
* Code 2.1.286, Codex 0.153.4 (and its newer "Trust this folder?" wording), and cursor-agent
|
|
@@ -11537,8 +11804,7 @@ function stuck(where) {
|
|
|
11537
11804
|
message: `${where}, and the prompt is still showing after spawn sent the keys that accept it. A person must answer it.`
|
|
11538
11805
|
};
|
|
11539
11806
|
}
|
|
11540
|
-
|
|
11541
|
-
//#region src/mux-select.ts
|
|
11807
|
+
// src/mux-select.ts
|
|
11542
11808
|
/**
|
|
11543
11809
|
* Backend selection via cyber-mux's two-mode mux probe, normalized through the transitional
|
|
11544
11810
|
* `$CYBERLEGION_MUX*` → `$CYBER_MUX*` env seam (`mux-env.ts`) — tmux/herdr map to their existing
|
|
@@ -11558,8 +11824,7 @@ function selectSessionAdapter(env, exec = realExec) {
|
|
|
11558
11824
|
if (probe.mux === "wezterm" || probe.mux === "zellij") throw new Error(`spawn detected ${probe.mux}, a backend unit/registry cannot store a pane locator under (only tmux and herdr) — refusing before opening anything`);
|
|
11559
11825
|
throw new Error("spawn requires a session backend — run inside tmux ($TMUX) or herdr ($HERDR_ENV=1)");
|
|
11560
11826
|
}
|
|
11561
|
-
|
|
11562
|
-
//#region src/workspace-label.ts
|
|
11827
|
+
// src/workspace-label.ts
|
|
11563
11828
|
/** Leading actions that tear down or revert — the `A2-` class, checked first. */
|
|
11564
11829
|
const TEARDOWN_ACTIONS = /* @__PURE__ */ new Set([
|
|
11565
11830
|
"clean",
|
|
@@ -11702,8 +11967,7 @@ function fitWords(words, budget) {
|
|
|
11702
11967
|
}
|
|
11703
11968
|
return out;
|
|
11704
11969
|
}
|
|
11705
|
-
|
|
11706
|
-
//#region src/worktree-lease.ts
|
|
11970
|
+
// src/worktree-lease.ts
|
|
11707
11971
|
/**
|
|
11708
11972
|
* The two worktree-library calls a unit's lifetime makes: `unit spawn` acquires a worktree, `unit
|
|
11709
11973
|
* close` releases it. Gathered here so a test can stand in for them and drive spawn's routing without
|
|
@@ -11727,8 +11991,7 @@ function leaseHolder(id) {
|
|
|
11727
11991
|
* cyberlegion's own file, not the unit's work, and counting it would keep every slot from reuse in a
|
|
11728
11992
|
* project that does not track it. */
|
|
11729
11993
|
const LEASE_IGNORE = [WORKTREE_MARKER];
|
|
11730
|
-
|
|
11731
|
-
//#region src/session.ts
|
|
11994
|
+
// src/session.ts
|
|
11732
11995
|
/** How each harness's own CLI is launched in the new pane. */
|
|
11733
11996
|
const LAUNCH_MAP = {
|
|
11734
11997
|
claude: "claude",
|
|
@@ -11852,6 +12115,7 @@ async function spawn(ctx, input) {
|
|
|
11852
12115
|
const from = callerPane(sessionAdapter, normalizedEnv);
|
|
11853
12116
|
let cwd;
|
|
11854
12117
|
let worktree;
|
|
12118
|
+
let sharedWorktree;
|
|
11855
12119
|
let base;
|
|
11856
12120
|
let reused;
|
|
11857
12121
|
let target;
|
|
@@ -11860,6 +12124,7 @@ async function spawn(ctx, input) {
|
|
|
11860
12124
|
cwd = resolve(input.cwd);
|
|
11861
12125
|
if (primaryRoot$1 !== void 0) assertDistinctFromPrimary(cwd, primaryRoot$1);
|
|
11862
12126
|
worktree = null;
|
|
12127
|
+
sharedWorktree = sharedCheckoutAt(ctx.store, exec, cwd);
|
|
11863
12128
|
const at = input.at ?? "tab";
|
|
11864
12129
|
target = sessionAdapter.open(exec, {
|
|
11865
12130
|
cwd,
|
|
@@ -11950,10 +12215,8 @@ async function spawn(ctx, input) {
|
|
|
11950
12215
|
harness,
|
|
11951
12216
|
cwd,
|
|
11952
12217
|
worktree,
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
id: target.id
|
|
11956
|
-
} : null,
|
|
12218
|
+
...sharedWorktree ? { sharedWorktree } : {},
|
|
12219
|
+
pane: openedPaneLocator(exec, muxName, target.id),
|
|
11957
12220
|
status: "active",
|
|
11958
12221
|
createdAt: ts,
|
|
11959
12222
|
lastSeen: ts,
|
|
@@ -12275,8 +12538,7 @@ function resolveBrief(input, readStdin = () => readFileSync(0, "utf8")) {
|
|
|
12275
12538
|
if (input.task != null && input.task !== "") return input.task;
|
|
12276
12539
|
return null;
|
|
12277
12540
|
}
|
|
12278
|
-
|
|
12279
|
-
//#region src/agentdef/resolve.ts
|
|
12541
|
+
// src/agentdef/resolve.ts
|
|
12280
12542
|
function parseAgentDefFile(text) {
|
|
12281
12543
|
const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n)?([\s\S]*)$/.exec(text);
|
|
12282
12544
|
if (!m) return {
|
|
@@ -12396,8 +12658,7 @@ function listAgentDefs(input = {}) {
|
|
|
12396
12658
|
}
|
|
12397
12659
|
return defs;
|
|
12398
12660
|
}
|
|
12399
|
-
|
|
12400
|
-
//#region src/agentdef/realize.ts
|
|
12661
|
+
// src/agentdef/realize.ts
|
|
12401
12662
|
const DEFAULT_HARNESS = "claude";
|
|
12402
12663
|
/** POSIX single-quote a value for safe inclusion in a shell command line. */
|
|
12403
12664
|
function shellQuote(value) {
|
|
@@ -12515,8 +12776,7 @@ function resolveSpawnLaunch(input) {
|
|
|
12515
12776
|
...input.searchRoots ? { searchRoots: input.searchRoots } : {}
|
|
12516
12777
|
}), overrides);
|
|
12517
12778
|
}
|
|
12518
|
-
|
|
12519
|
-
//#region src/cli-input.ts
|
|
12779
|
+
// src/cli-input.ts
|
|
12520
12780
|
/**
|
|
12521
12781
|
* Translate `unit spawn`'s options into the spawn input and its wake decision — resolving an
|
|
12522
12782
|
* `--agent`/`--agent-file` def into the harness and composed launch command, with an explicit
|
|
@@ -12564,8 +12824,7 @@ function spawnCommandInput(opts, listCursorModels) {
|
|
|
12564
12824
|
function readCommandOutput(format, result) {
|
|
12565
12825
|
return format === "json" ? JSON.stringify(result, null, 2) : result.output;
|
|
12566
12826
|
}
|
|
12567
|
-
|
|
12568
|
-
//#region src/cursor-models.ts
|
|
12827
|
+
// src/cursor-models.ts
|
|
12569
12828
|
/** The model ids in `cursor-agent models` output: the first token of each line, ANSI colour and list
|
|
12570
12829
|
* bullets stripped. Headings and tips come along too; callers only test a specific id for membership. */
|
|
12571
12830
|
function parseCursorModels(output) {
|
|
@@ -12588,8 +12847,7 @@ function listCursorModels() {
|
|
|
12588
12847
|
return [];
|
|
12589
12848
|
}
|
|
12590
12849
|
}
|
|
12591
|
-
|
|
12592
|
-
//#region src/message.ts
|
|
12850
|
+
// src/message.ts
|
|
12593
12851
|
/** Write one message into the recipient's inbox. Collision-free by <epochMs>-<hex>. */
|
|
12594
12852
|
function send(ctx, input) {
|
|
12595
12853
|
const toId = resolveRecipient(ctx.store, input.to);
|
|
@@ -12686,8 +12944,7 @@ function deleteMessage(ctx, meId, msgId) {
|
|
|
12686
12944
|
function deleteMailbox(store, id) {
|
|
12687
12945
|
store.removeMailbox(id);
|
|
12688
12946
|
}
|
|
12689
|
-
|
|
12690
|
-
//#region src/decommission.ts
|
|
12947
|
+
// src/decommission.ts
|
|
12691
12948
|
/**
|
|
12692
12949
|
* Tear a unit down and reap its registry record — the deterministic inverse of `spawn`. Refuses a
|
|
12693
12950
|
* dirty worktree unless `--force`. Teardown always precedes reap: an already-gone worktree or pane
|
|
@@ -12723,7 +12980,7 @@ async function decommission(ctx, input) {
|
|
|
12723
12980
|
const worktreeExists = worktreeRoot != null && existsSync(worktreeRoot);
|
|
12724
12981
|
const primaryRoot = worktreeExists ? await primaryRootOf(exec, worktreeRoot) : rec.worktree?.primaryRoot;
|
|
12725
12982
|
const held = rec.worktree?.lease;
|
|
12726
|
-
const onPrimary =
|
|
12983
|
+
const onPrimary = isPrimaryCheckout(worktreeRoot, primaryRoot);
|
|
12727
12984
|
if (onPrimary && input.force) throw new Error(`refusing to close "${input.id}" with --force — its worktree is the primary checkout, which close never alters; close it without --force to end its session and reap its record`);
|
|
12728
12985
|
const removesWorktree = worktreeExists && !input.keepWorktree && !held && !onPrimary;
|
|
12729
12986
|
if (removesWorktree && !input.force && isDirty(exec, worktreeRoot)) throw new Error(`unit "${input.id}" has uncommitted changes in its worktree — pass --force to discard them`);
|
|
@@ -12789,10 +13046,9 @@ function isDirty(exec, worktreeRoot) {
|
|
|
12789
13046
|
"--porcelain",
|
|
12790
13047
|
"--untracked-files=all"
|
|
12791
13048
|
]);
|
|
12792
|
-
return !!status && status.split("\n").some((line) => line && line !== `??
|
|
13049
|
+
return !!status && status.split("\n").some((line) => line && line !== `?? .agents/cyberlegion/config.json`);
|
|
12793
13050
|
}
|
|
12794
|
-
|
|
12795
|
-
//#region src/install.ts
|
|
13051
|
+
// src/install.ts
|
|
12796
13052
|
const PIN_TOKEN = /^[0-9A-Za-z][0-9A-Za-z._+-]*$/;
|
|
12797
13053
|
function validatePin(pin) {
|
|
12798
13054
|
if (!PIN_TOKEN.test(pin)) throw new Error(`invalid --pin "${pin}" — expected a version or dist-tag token like 0.2.0 or latest (no spaces, ranges, or shell metacharacters)`);
|
|
@@ -12945,8 +13201,7 @@ function removeCursor(settings, event, canonical) {
|
|
|
12945
13201
|
else delete hooks[event];
|
|
12946
13202
|
return true;
|
|
12947
13203
|
}
|
|
12948
|
-
|
|
12949
|
-
//#region src/output.ts
|
|
13204
|
+
// src/output.ts
|
|
12950
13205
|
function stringifyCell(v) {
|
|
12951
13206
|
if (v == null) return "";
|
|
12952
13207
|
const s = String(v);
|
|
@@ -12979,8 +13234,7 @@ function fail(msg) {
|
|
|
12979
13234
|
console.error(JSON.stringify({ error: msg }));
|
|
12980
13235
|
process.exit(1);
|
|
12981
13236
|
}
|
|
12982
|
-
|
|
12983
|
-
//#region src/participants.ts
|
|
13237
|
+
// src/participants.ts
|
|
12984
13238
|
const UNIT_PREFIX = "cyberlegion:unit/";
|
|
12985
13239
|
/** This hub's own participant key. One per hub root, so reconciling one hub (a `--space`, say)
|
|
12986
13240
|
* never retires the units another hub registered in the same cynapse store. */
|
|
@@ -13144,8 +13398,7 @@ async function syncCynapse(hub, env = process.env, load = importCynapse) {
|
|
|
13144
13398
|
cyn?.close();
|
|
13145
13399
|
}
|
|
13146
13400
|
}
|
|
13147
|
-
|
|
13148
|
-
//#region src/permission.ts
|
|
13401
|
+
// src/permission.ts
|
|
13149
13402
|
const CLI_RULE = "Bash(cyberlegion *)";
|
|
13150
13403
|
const COVERING = /^Bash(?:\((?:\*|cyberlegion(?: \*|:\*|\*))\))?$/;
|
|
13151
13404
|
/** Claude Code's user settings file: `$CLAUDE_CONFIG_DIR/settings.json`, else `~/.claude/settings.json`. */
|
|
@@ -13196,8 +13449,7 @@ function addPermissionRule(file) {
|
|
|
13196
13449
|
writeFileSync(file, `${JSON.stringify(settings, null, 2)}\n`);
|
|
13197
13450
|
return "added";
|
|
13198
13451
|
}
|
|
13199
|
-
|
|
13200
|
-
//#region src/presence-spawn.ts
|
|
13452
|
+
// src/presence-spawn.ts
|
|
13201
13453
|
/** The brief a presence spawned into a standing owner's home starts from: whom it stands in for, and
|
|
13202
13454
|
* where that owner's mail is. What to *do* with the mail is the home's agent definition's business. */
|
|
13203
13455
|
function presenceBrief(handle) {
|
|
@@ -13292,8 +13544,7 @@ async function spawnPresence(ctx, owner, options = {}) {
|
|
|
13292
13544
|
...turn
|
|
13293
13545
|
};
|
|
13294
13546
|
}
|
|
13295
|
-
|
|
13296
|
-
//#region src/project.ts
|
|
13547
|
+
// src/project.ts
|
|
13297
13548
|
/**
|
|
13298
13549
|
* The canonical git common dir for `dir`, or undefined outside a repository. The common dir is the
|
|
13299
13550
|
* one path every checkout of a repository shares — the default checkout and each linked worktree
|
|
@@ -13412,8 +13663,7 @@ function byPath(ctx, dir) {
|
|
|
13412
13663
|
if (!commonDir) throw new Error(`"${dir}" is not inside a git repository`);
|
|
13413
13664
|
return ctx.store.getProject(projectIdOf(commonDir)) ?? registerProject(ctx, { dir });
|
|
13414
13665
|
}
|
|
13415
|
-
|
|
13416
|
-
//#region src/runtime/inject-inbox.ts
|
|
13666
|
+
// src/runtime/inject-inbox.ts
|
|
13417
13667
|
const EVENTS = ["SessionStart"];
|
|
13418
13668
|
/**
|
|
13419
13669
|
* Resolve the calling agent, gather its unread mail (and a standing owner's, when this session is
|
|
@@ -13468,8 +13718,7 @@ function injectInbox(ctx, event) {
|
|
|
13468
13718
|
additionalContext: parts.join("\n\n")
|
|
13469
13719
|
} };
|
|
13470
13720
|
}
|
|
13471
|
-
|
|
13472
|
-
//#region src/service.ts
|
|
13721
|
+
// src/service.ts
|
|
13473
13722
|
/** A refused ownership transition. `stale` means the caller's generation, token, or holder claim is
|
|
13474
13723
|
* not the current one — the caller is not (or no longer) the authority it claims to be. */
|
|
13475
13724
|
var ServiceOwnershipError = class extends Error {
|
|
@@ -13774,8 +14023,7 @@ async function startService(ctx, projectRef, name, input) {
|
|
|
13774
14023
|
throw err;
|
|
13775
14024
|
}
|
|
13776
14025
|
}
|
|
13777
|
-
|
|
13778
|
-
//#region src/standing-home.ts
|
|
14026
|
+
// src/standing-home.ts
|
|
13779
14027
|
/**
|
|
13780
14028
|
* Decide what a registration does to a standing owner's home: `undefined` keeps whatever the record
|
|
13781
14029
|
* already has, `null` drops it, and a `StandingHome` replaces it. Every way a home can be wrong is
|
|
@@ -13822,8 +14070,7 @@ function resolveHomeFlags(exec, flags) {
|
|
|
13822
14070
|
...launch
|
|
13823
14071
|
};
|
|
13824
14072
|
}
|
|
13825
|
-
|
|
13826
|
-
//#region src/store/errors.ts
|
|
14073
|
+
// src/store/errors.ts
|
|
13827
14074
|
/** A record file exists but its content didn't parse as JSON — a torn write (crash mid-`writeFileSync`,
|
|
13828
14075
|
* pre-atomic-write code path) or on-disk tampering. Carries the file path and the original parse
|
|
13829
14076
|
* error so a caller can report exactly what's broken and where, rather than "Unexpected token" with
|
|
@@ -13836,8 +14083,7 @@ var CorruptRecordError = class extends Error {
|
|
|
13836
14083
|
this.name = "CorruptRecordError";
|
|
13837
14084
|
}
|
|
13838
14085
|
};
|
|
13839
|
-
|
|
13840
|
-
//#region src/store/process-liveness.ts
|
|
14086
|
+
// src/store/process-liveness.ts
|
|
13841
14087
|
/**
|
|
13842
14088
|
* Probe whether `pid` is alive using `process.kill(pid, 0)` — the POSIX "does this process exist"
|
|
13843
14089
|
* idiom; it sends no signal, it only tests deliverability. `ESRCH` (no such process) is the only
|
|
@@ -13857,8 +14103,7 @@ function probeProcess(pid) {
|
|
|
13857
14103
|
return err.code === "ESRCH" ? "dead" : "unknown";
|
|
13858
14104
|
}
|
|
13859
14105
|
}
|
|
13860
|
-
|
|
13861
|
-
//#region src/store/lock.ts
|
|
14106
|
+
// src/store/lock.ts
|
|
13862
14107
|
var LockTimeoutError = class extends Error {
|
|
13863
14108
|
name;
|
|
13864
14109
|
constructor(name) {
|
|
@@ -13995,8 +14240,7 @@ async function withLockAsync(root, name, fn, opts) {
|
|
|
13995
14240
|
handle.release();
|
|
13996
14241
|
}
|
|
13997
14242
|
}
|
|
13998
|
-
|
|
13999
|
-
//#region src/store/file-store.ts
|
|
14243
|
+
// src/store/file-store.ts
|
|
14000
14244
|
/** Parse a record file's content, wrapping a `JSON.parse` failure in a typed, file-named
|
|
14001
14245
|
* `CorruptRecordError` instead of letting a bare `SyntaxError` bubble up from deep inside
|
|
14002
14246
|
* `listInbox`/`listAgents`/`getAgent` with no indication of WHICH file broke. A file that doesn't
|
|
@@ -14206,8 +14450,7 @@ var FileStore = class {
|
|
|
14206
14450
|
return withLockAsync(this.root, name, fn);
|
|
14207
14451
|
}
|
|
14208
14452
|
};
|
|
14209
|
-
|
|
14210
|
-
//#region src/unit-runtime.ts
|
|
14453
|
+
// src/unit-runtime.ts
|
|
14211
14454
|
/** A standing or service record is an address with no runtime of its own — nothing to stop or start. */
|
|
14212
14455
|
function assertHasRuntime(rec, ref) {
|
|
14213
14456
|
if (rec.kind === "standing" || rec.kind === "service") throw new Error(`"${ref}" is a ${rec.kind} record — it has no runtime to stop, restart, or rebind`);
|
|
@@ -14295,8 +14538,9 @@ function stopUnit(ctx, ref) {
|
|
|
14295
14538
|
* record to the new pane, and ring it. When the record carries the harness's conversation id and the
|
|
14296
14539
|
* harness can resume by id, the session resumes that conversation and is rung to continue, with the
|
|
14297
14540
|
* plain launch as a shell fallback should the harness reject the id; otherwise (or with `fresh`) it
|
|
14298
|
-
* starts empty and is rung to read its brief — a rebrief.
|
|
14299
|
-
*
|
|
14541
|
+
* starts empty and is rung to read its brief — a rebrief. An exited unit is refused: its session
|
|
14542
|
+
* ended without a `unit stop`, and the multiplexer that resumes agents has already brought back any
|
|
14543
|
+
* that were coming back. Every refusal runs before anything is torn down.
|
|
14300
14544
|
*
|
|
14301
14545
|
* The unit is recorded `stopped` before the open, so a restart interrupted between the two leaves an
|
|
14302
14546
|
* ordinary stopped unit and a rerun recovers it; nothing needs a separate repair path.
|
|
@@ -14305,6 +14549,7 @@ async function restartUnit(ctx, ref, options = {}) {
|
|
|
14305
14549
|
const rec = resolveAgent(ctx.store, ref);
|
|
14306
14550
|
assertHasRuntime(rec, ref);
|
|
14307
14551
|
assertNotSelf(ctx, rec, "restart");
|
|
14552
|
+
if (rec.status === "exited") throw new Error(`unit "${rec.handle}" has exited — restart takes only a running or stopped unit; spawn a new unit instead, or run unit rebind ${rec.handle} from a session resumed by hand`);
|
|
14308
14553
|
const harness = rec.harness;
|
|
14309
14554
|
if (!harness || !(harness in LAUNCH_MAP)) throw new Error(`unit "${rec.handle}" has harness "${rec.harness ?? ""}", not in the launch map (${Object.keys(LAUNCH_MAP).join(" | ")}) — cannot restart it`);
|
|
14310
14555
|
if (!existsSync(rec.cwd)) throw new Error(`unit "${rec.handle}" cannot restart — its cwd ${rec.cwd} is gone; spawn a new unit instead`);
|
|
@@ -14346,10 +14591,7 @@ async function restartUnit(ctx, ref, options = {}) {
|
|
|
14346
14591
|
const bound = {
|
|
14347
14592
|
...stopped,
|
|
14348
14593
|
status: "active",
|
|
14349
|
-
pane:
|
|
14350
|
-
mux,
|
|
14351
|
-
id: target.id
|
|
14352
|
-
} : null,
|
|
14594
|
+
pane: openedPaneLocator(exec, mux, target.id),
|
|
14353
14595
|
lastSeen: new Date(ctx.now?.() ?? Date.now()).toISOString()
|
|
14354
14596
|
};
|
|
14355
14597
|
saveAgent(ctx.store, bound);
|
|
@@ -14375,6 +14617,10 @@ function probeLiveness(ctx, rec) {
|
|
|
14375
14617
|
if (rec.kind === "standing" || rec.kind === "service") return "none";
|
|
14376
14618
|
if (rec.status === "stopped") return "stopped";
|
|
14377
14619
|
if (rec.status === "exited") return "exited";
|
|
14620
|
+
if (rec.pane?.mux === "herdr" && rec.pane.terminal) {
|
|
14621
|
+
const at = locateTerminal(ctx.exec ?? realExec, rec.pane.terminal);
|
|
14622
|
+
return at === void 0 ? "unknown" : at === "gone" ? "gone" : "live";
|
|
14623
|
+
}
|
|
14378
14624
|
const pane = runtimePane(ctx, rec);
|
|
14379
14625
|
const adapter = pane ? paneAdapter(ctx, rec) : void 0;
|
|
14380
14626
|
if (!pane || !adapter) return "unknown";
|
|
@@ -14391,7 +14637,7 @@ function probeLiveness(ctx, rec) {
|
|
|
14391
14637
|
function rebindUnit(ctx, ref) {
|
|
14392
14638
|
const rec = resolveAgent(ctx.store, ref);
|
|
14393
14639
|
assertHasRuntime(rec, ref);
|
|
14394
|
-
const cur =
|
|
14640
|
+
const cur = callingStorablePane(ctx);
|
|
14395
14641
|
if (!cur) throw new Error("unit rebind needs to run inside a tmux or herdr pane — there is no pane to bind");
|
|
14396
14642
|
const oldPane = runtimePane(ctx, rec);
|
|
14397
14643
|
if (rec.pane?.mux === cur.mux && rec.pane.id === cur.pane && ctx.store.resolvePaneId(cur.pane) === rec.id) return rec;
|
|
@@ -14399,13 +14645,15 @@ function rebindUnit(ctx, ref) {
|
|
|
14399
14645
|
const holder = holderId && holderId !== rec.id ? loadAgent(ctx.store, holderId) : void 0;
|
|
14400
14646
|
if (holder && holder.status !== "stopped" && holder.status !== "exited") throw new Error(`this pane belongs to unit "${holder.handle}" (${holder.id}) — rebind never takes another unit's pane`);
|
|
14401
14647
|
const liveness = probeLiveness(ctx, rec);
|
|
14402
|
-
|
|
14648
|
+
const ownTerminal = !!cur.terminal && rec.pane?.mux === "herdr" && rec.pane.terminal === cur.terminal;
|
|
14649
|
+
if (oldPane && oldPane !== cur.pane && !ownTerminal && (liveness === "live" || liveness === "unknown")) throw new Error(`unit "${rec.handle}" may still be running in pane ${oldPane} — stop it first (unit stop ${rec.handle})`);
|
|
14403
14650
|
const bound = {
|
|
14404
14651
|
...rec,
|
|
14405
14652
|
status: "active",
|
|
14406
14653
|
pane: {
|
|
14407
14654
|
mux: cur.mux,
|
|
14408
|
-
id: cur.pane
|
|
14655
|
+
id: cur.pane,
|
|
14656
|
+
...cur.terminal ? { terminal: cur.terminal } : {}
|
|
14409
14657
|
},
|
|
14410
14658
|
lastSeen: new Date(ctx.now?.() ?? Date.now()).toISOString()
|
|
14411
14659
|
};
|
|
@@ -14433,7 +14681,7 @@ function controlsFor(ctx, rec, liveness) {
|
|
|
14433
14681
|
if (hasHonestReset(rec.harness)) controls.push("clear");
|
|
14434
14682
|
}
|
|
14435
14683
|
if (runtimePane(ctx, rec) !== void 0 && (liveness === "live" || liveness === "gone" || liveness === "unknown")) controls.push("stop");
|
|
14436
|
-
if (rec.harness && rec.harness in LAUNCH_MAP && existsSync(rec.cwd)) controls.push("restart");
|
|
14684
|
+
if (liveness !== "exited" && rec.harness && rec.harness in LAUNCH_MAP && existsSync(rec.cwd)) controls.push("restart");
|
|
14437
14685
|
if (liveness === "stopped" || liveness === "exited" || liveness === "gone") controls.push("rebind");
|
|
14438
14686
|
controls.push("close");
|
|
14439
14687
|
return controls;
|
|
@@ -14460,6 +14708,7 @@ function showUnit(ctx, ref) {
|
|
|
14460
14708
|
} : null,
|
|
14461
14709
|
cwd: rec.cwd,
|
|
14462
14710
|
worktree: rec.worktree ?? null,
|
|
14711
|
+
sharedWorktree: rec.sharedWorktree ?? null,
|
|
14463
14712
|
lastSeen: rec.lastSeen,
|
|
14464
14713
|
controls: controlsFor(ctx, rec, liveness)
|
|
14465
14714
|
};
|
|
@@ -14487,8 +14736,7 @@ function recordConversation(ctx, hookInput) {
|
|
|
14487
14736
|
conversation: id
|
|
14488
14737
|
});
|
|
14489
14738
|
}
|
|
14490
|
-
|
|
14491
|
-
//#region src/wake/await.ts
|
|
14739
|
+
// src/wake/await.ts
|
|
14492
14740
|
/** Internal poll interval — not user-facing (the round-trip cost of one `mail await` cycle). */
|
|
14493
14741
|
const POLL_MS$1 = 1e3;
|
|
14494
14742
|
const DEFAULT_TIMEOUT_MS = 6e5;
|
|
@@ -14533,8 +14781,7 @@ async function awaitReply(ctx, input) {
|
|
|
14533
14781
|
await sleep(POLL_MS$1);
|
|
14534
14782
|
}
|
|
14535
14783
|
}
|
|
14536
|
-
|
|
14537
|
-
//#region src/wake/watch.ts
|
|
14784
|
+
// src/wake/watch.ts
|
|
14538
14785
|
const POLL_MS = 1e3;
|
|
14539
14786
|
/**
|
|
14540
14787
|
* A continuous foreground observer: prints each NEW matching message as it arrives, polling on a
|
|
@@ -14566,8 +14813,7 @@ async function watchMail(ctx, input, onMessage, opts = {}) {
|
|
|
14566
14813
|
await sleep(POLL_MS);
|
|
14567
14814
|
}
|
|
14568
14815
|
}
|
|
14569
|
-
|
|
14570
|
-
//#region src/cli.ts
|
|
14816
|
+
// src/cli.ts
|
|
14571
14817
|
const VERSION = globalThis.__SKILL_SCRIPT__?.version ?? JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
14572
14818
|
function ctxOf(opts) {
|
|
14573
14819
|
return {
|
|
@@ -14759,6 +15005,10 @@ function runWho(opts) {
|
|
|
14759
15005
|
{
|
|
14760
15006
|
key: "pane",
|
|
14761
15007
|
get: (a) => a.pane?.id ?? "-"
|
|
15008
|
+
},
|
|
15009
|
+
{
|
|
15010
|
+
key: "shared",
|
|
15011
|
+
get: (a) => a.sharedWorktree?.root ?? "-"
|
|
14762
15012
|
}
|
|
14763
15013
|
], `${agents.length} units`),
|
|
14764
15014
|
json: agents
|
|
@@ -14766,18 +15016,25 @@ function runWho(opts) {
|
|
|
14766
15016
|
if (agents.length === 0) nextStep("cyberlegion unit register to join");
|
|
14767
15017
|
}
|
|
14768
15018
|
syncsUnits(withGlobals(unit.command("who"))).description("list the addressable units").option("--all", "include exited units").option("--reconcile", "live-probe the current mux: cull dead-pane records and adopt unbound harness-bearing panes before listing").action(runWho);
|
|
14769
|
-
syncsUnits(withGlobals(unit.command("prune"))).description("mark dead units exited and
|
|
15019
|
+
syncsUnits(withGlobals(unit.command("prune"))).description("mark dead units exited and delete exited units with no worktree on disk").action((opts) => {
|
|
14770
15020
|
const ctx = ctxOf(opts);
|
|
14771
15021
|
touch(ctx);
|
|
14772
15022
|
const changed = prune(ctx);
|
|
14773
15023
|
emit(formatOf(opts), {
|
|
14774
|
-
toon: toonList("pruned", changed, [
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
15024
|
+
toon: toonList("pruned", changed, [
|
|
15025
|
+
{
|
|
15026
|
+
key: "id",
|
|
15027
|
+
get: (a) => a.id
|
|
15028
|
+
},
|
|
15029
|
+
{
|
|
15030
|
+
key: "handle",
|
|
15031
|
+
get: (a) => a.handle
|
|
15032
|
+
},
|
|
15033
|
+
{
|
|
15034
|
+
key: "pruned",
|
|
15035
|
+
get: (a) => a.pruned
|
|
15036
|
+
}
|
|
15037
|
+
], `${changed.length} pruned (${changed.filter((a) => a.pruned === "deleted").length} deleted)`),
|
|
14781
15038
|
json: changed
|
|
14782
15039
|
});
|
|
14783
15040
|
});
|
|
@@ -14958,6 +15215,7 @@ withGlobals(unit.command("show")).description("show a unit's runtime: where it i
|
|
|
14958
15215
|
pane: view.pane ? `${view.pane.mux}:${view.pane.id}` : "-",
|
|
14959
15216
|
cwd: view.cwd,
|
|
14960
15217
|
worktree: view.worktree?.root ?? "-",
|
|
15218
|
+
shared: view.sharedWorktree?.root ?? "-",
|
|
14961
15219
|
lastSeen: view.lastSeen,
|
|
14962
15220
|
controls: view.controls.join(",") || "-"
|
|
14963
15221
|
}),
|
|
@@ -15763,5 +16021,4 @@ async function runCli(argv = process.argv) {
|
|
|
15763
16021
|
process.exit(1);
|
|
15764
16022
|
}
|
|
15765
16023
|
}
|
|
15766
|
-
//#endregion
|
|
15767
16024
|
export { runCli };
|