cyberlegion 1.10.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 +22 -0
- package/dist/cli.mjs +344 -143
- package/dist/index.d.mts +28 -48
- package/dist/index.mjs +278 -74
- 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 +50 -50
- package/skills/init-cyberlegion/scripts/cyberlegion.mjs +50 -50
- package/skills/legate/SKILL.md +1 -1
- package/skills/legate/scripts/cyberlegion.mjs +50 -50
- package/skills/manage-inbox/SKILL.md +1 -1
- package/skills/manage-inbox/scripts/cyberlegion.mjs +50 -50
- package/skills/relay-governance/SKILL.md +1 -1
- package/skills/relay-governance/scripts/cyberlegion.mjs +50 -50
- package/skills/subagent-backend-governance/SKILL.md +1 -1
- package/skills/subagent-backend-governance/scripts/cyberlegion.mjs +50 -50
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,8 +10647,8 @@ 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),
|
|
@@ -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
|
|
@@ -10855,10 +10993,26 @@ function bumpLastSeen(ctx, id) {
|
|
|
10855
10993
|
rec.lastSeen = nowIso(ctx);
|
|
10856
10994
|
saveAgent(ctx.store, rec);
|
|
10857
10995
|
}
|
|
10858
|
-
/**
|
|
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
|
+
*/
|
|
10859
11001
|
function touch(ctx) {
|
|
10860
|
-
const id =
|
|
10861
|
-
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);
|
|
10862
11016
|
}
|
|
10863
11017
|
const STALE_MS = 9e5;
|
|
10864
11018
|
/** Standing and service records have no session of their own, so nothing about a pane or a timer
|
|
@@ -10885,7 +11039,9 @@ function isPrunable(rec) {
|
|
|
10885
11039
|
function sessionLive(ctx, rec) {
|
|
10886
11040
|
if (rec.status === "exited" || rec.status === "stopped") return false;
|
|
10887
11041
|
if (!rec.pane) return true;
|
|
10888
|
-
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);
|
|
10889
11045
|
if (panes.length === 0) return true;
|
|
10890
11046
|
const id = rec.pane.id;
|
|
10891
11047
|
return panes.some((p) => p.id === id);
|
|
@@ -10960,11 +11116,12 @@ function reconcile(ctx, opts) {
|
|
|
10960
11116
|
if (panes.length === 0) return [];
|
|
10961
11117
|
const live = new Set(panes.map((p) => p.id));
|
|
10962
11118
|
const changed = [];
|
|
11119
|
+
const cache = {};
|
|
10963
11120
|
for (const rec of listAgents(ctx.store)) {
|
|
10964
11121
|
if (!isPrunable(rec)) continue;
|
|
10965
11122
|
if (!rec.pane) continue;
|
|
10966
11123
|
if (rec.pane.mux !== cur.mux) continue;
|
|
10967
|
-
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)) {
|
|
10968
11125
|
rec.status = "exited";
|
|
10969
11126
|
saveAgent(ctx.store, rec);
|
|
10970
11127
|
changed.push(rec);
|
|
@@ -10989,27 +11146,93 @@ function paneGone(exec, pane, lists) {
|
|
|
10989
11146
|
}
|
|
10990
11147
|
return panes.length > 0 && !panes.some((p) => p.id === pane.id);
|
|
10991
11148
|
}
|
|
10992
|
-
/**
|
|
10993
|
-
*
|
|
10994
|
-
*
|
|
10995
|
-
*
|
|
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
|
+
*/
|
|
10996
11169
|
function prune(ctx) {
|
|
10997
11170
|
const exec = ctx.exec ?? realExec;
|
|
10998
11171
|
const now = ctx.now?.() ?? Date.now();
|
|
10999
|
-
const
|
|
11172
|
+
const marked = reconcile(ctx);
|
|
11000
11173
|
const lists = /* @__PURE__ */ new Map();
|
|
11174
|
+
const cache = {};
|
|
11001
11175
|
for (const rec of listAgents(ctx.store)) {
|
|
11002
11176
|
if (!isPrunable(rec)) continue;
|
|
11003
|
-
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)) {
|
|
11004
11178
|
rec.status = "exited";
|
|
11005
11179
|
saveAgent(ctx.store, rec);
|
|
11006
|
-
|
|
11007
|
-
}
|
|
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);
|
|
11008
11232
|
}
|
|
11009
|
-
return
|
|
11233
|
+
return deleted;
|
|
11010
11234
|
}
|
|
11011
|
-
|
|
11012
|
-
//#region src/console/prompt-guard.ts
|
|
11235
|
+
// src/console/prompt-guard.ts
|
|
11013
11236
|
const CODEX_PLACEHOLDERS = /* @__PURE__ */ new Set([
|
|
11014
11237
|
"Ask Codex to do anything",
|
|
11015
11238
|
"Explain this codebase",
|
|
@@ -11167,8 +11390,7 @@ async function clearLine(adapter, exec, target, sleep) {
|
|
|
11167
11390
|
adapter.sendText(exec, target, KILL_LINE);
|
|
11168
11391
|
await sleep(KEY_SETTLE_MS);
|
|
11169
11392
|
}
|
|
11170
|
-
|
|
11171
|
-
//#region src/console/ring.ts
|
|
11393
|
+
// src/console/ring.ts
|
|
11172
11394
|
const DEFAULT_ATTEMPTS = 10;
|
|
11173
11395
|
const DEFAULT_SETTLE_MS = 400;
|
|
11174
11396
|
/** cyber-mux's `isStaged` matches on the same prefix length. */
|
|
@@ -11273,8 +11495,7 @@ async function ringTurn(adapter, exec, target, message, opts = {}) {
|
|
|
11273
11495
|
const why = state === "lost" ? "the text vanished without the harness posting it" : `input still staged after ${attempts} re-submit attempts`;
|
|
11274
11496
|
throw new Error(`nudge failed: peer at pane ${target.id} never took the turn — ${why}`);
|
|
11275
11497
|
}
|
|
11276
|
-
|
|
11277
|
-
//#region src/console/doorbell.ts
|
|
11498
|
+
// src/console/doorbell.ts
|
|
11278
11499
|
/** The doorbell text delivered to a woken recipient; also the standalone `unit nudge` default. */
|
|
11279
11500
|
const DELIVERY_DOORBELL = "You have unread mail — check your inbox.";
|
|
11280
11501
|
/**
|
|
@@ -11473,8 +11694,7 @@ async function wakeSpawn(getAdapter, exec, input, nudgeOpts = SPAWN_NUDGE_OPTS,
|
|
|
11473
11694
|
};
|
|
11474
11695
|
}
|
|
11475
11696
|
}
|
|
11476
|
-
|
|
11477
|
-
//#region src/console/trust.ts
|
|
11697
|
+
// src/console/trust.ts
|
|
11478
11698
|
/**
|
|
11479
11699
|
* Each harness's folder-trust prompt, as it draws it, and the keys that accept it. Read from Claude
|
|
11480
11700
|
* Code 2.1.286, Codex 0.153.4 (and its newer "Trust this folder?" wording), and cursor-agent
|
|
@@ -11584,8 +11804,7 @@ function stuck(where) {
|
|
|
11584
11804
|
message: `${where}, and the prompt is still showing after spawn sent the keys that accept it. A person must answer it.`
|
|
11585
11805
|
};
|
|
11586
11806
|
}
|
|
11587
|
-
|
|
11588
|
-
//#region src/mux-select.ts
|
|
11807
|
+
// src/mux-select.ts
|
|
11589
11808
|
/**
|
|
11590
11809
|
* Backend selection via cyber-mux's two-mode mux probe, normalized through the transitional
|
|
11591
11810
|
* `$CYBERLEGION_MUX*` → `$CYBER_MUX*` env seam (`mux-env.ts`) — tmux/herdr map to their existing
|
|
@@ -11605,8 +11824,7 @@ function selectSessionAdapter(env, exec = realExec) {
|
|
|
11605
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`);
|
|
11606
11825
|
throw new Error("spawn requires a session backend — run inside tmux ($TMUX) or herdr ($HERDR_ENV=1)");
|
|
11607
11826
|
}
|
|
11608
|
-
|
|
11609
|
-
//#region src/workspace-label.ts
|
|
11827
|
+
// src/workspace-label.ts
|
|
11610
11828
|
/** Leading actions that tear down or revert — the `A2-` class, checked first. */
|
|
11611
11829
|
const TEARDOWN_ACTIONS = /* @__PURE__ */ new Set([
|
|
11612
11830
|
"clean",
|
|
@@ -11749,8 +11967,7 @@ function fitWords(words, budget) {
|
|
|
11749
11967
|
}
|
|
11750
11968
|
return out;
|
|
11751
11969
|
}
|
|
11752
|
-
|
|
11753
|
-
//#region src/worktree-lease.ts
|
|
11970
|
+
// src/worktree-lease.ts
|
|
11754
11971
|
/**
|
|
11755
11972
|
* The two worktree-library calls a unit's lifetime makes: `unit spawn` acquires a worktree, `unit
|
|
11756
11973
|
* close` releases it. Gathered here so a test can stand in for them and drive spawn's routing without
|
|
@@ -11774,8 +11991,7 @@ function leaseHolder(id) {
|
|
|
11774
11991
|
* cyberlegion's own file, not the unit's work, and counting it would keep every slot from reuse in a
|
|
11775
11992
|
* project that does not track it. */
|
|
11776
11993
|
const LEASE_IGNORE = [WORKTREE_MARKER];
|
|
11777
|
-
|
|
11778
|
-
//#region src/session.ts
|
|
11994
|
+
// src/session.ts
|
|
11779
11995
|
/** How each harness's own CLI is launched in the new pane. */
|
|
11780
11996
|
const LAUNCH_MAP = {
|
|
11781
11997
|
claude: "claude",
|
|
@@ -12000,10 +12216,7 @@ async function spawn(ctx, input) {
|
|
|
12000
12216
|
cwd,
|
|
12001
12217
|
worktree,
|
|
12002
12218
|
...sharedWorktree ? { sharedWorktree } : {},
|
|
12003
|
-
pane:
|
|
12004
|
-
mux: muxName,
|
|
12005
|
-
id: target.id
|
|
12006
|
-
} : null,
|
|
12219
|
+
pane: openedPaneLocator(exec, muxName, target.id),
|
|
12007
12220
|
status: "active",
|
|
12008
12221
|
createdAt: ts,
|
|
12009
12222
|
lastSeen: ts,
|
|
@@ -12325,8 +12538,7 @@ function resolveBrief(input, readStdin = () => readFileSync(0, "utf8")) {
|
|
|
12325
12538
|
if (input.task != null && input.task !== "") return input.task;
|
|
12326
12539
|
return null;
|
|
12327
12540
|
}
|
|
12328
|
-
|
|
12329
|
-
//#region src/agentdef/resolve.ts
|
|
12541
|
+
// src/agentdef/resolve.ts
|
|
12330
12542
|
function parseAgentDefFile(text) {
|
|
12331
12543
|
const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n)?([\s\S]*)$/.exec(text);
|
|
12332
12544
|
if (!m) return {
|
|
@@ -12446,8 +12658,7 @@ function listAgentDefs(input = {}) {
|
|
|
12446
12658
|
}
|
|
12447
12659
|
return defs;
|
|
12448
12660
|
}
|
|
12449
|
-
|
|
12450
|
-
//#region src/agentdef/realize.ts
|
|
12661
|
+
// src/agentdef/realize.ts
|
|
12451
12662
|
const DEFAULT_HARNESS = "claude";
|
|
12452
12663
|
/** POSIX single-quote a value for safe inclusion in a shell command line. */
|
|
12453
12664
|
function shellQuote(value) {
|
|
@@ -12565,8 +12776,7 @@ function resolveSpawnLaunch(input) {
|
|
|
12565
12776
|
...input.searchRoots ? { searchRoots: input.searchRoots } : {}
|
|
12566
12777
|
}), overrides);
|
|
12567
12778
|
}
|
|
12568
|
-
|
|
12569
|
-
//#region src/cli-input.ts
|
|
12779
|
+
// src/cli-input.ts
|
|
12570
12780
|
/**
|
|
12571
12781
|
* Translate `unit spawn`'s options into the spawn input and its wake decision — resolving an
|
|
12572
12782
|
* `--agent`/`--agent-file` def into the harness and composed launch command, with an explicit
|
|
@@ -12614,8 +12824,7 @@ function spawnCommandInput(opts, listCursorModels) {
|
|
|
12614
12824
|
function readCommandOutput(format, result) {
|
|
12615
12825
|
return format === "json" ? JSON.stringify(result, null, 2) : result.output;
|
|
12616
12826
|
}
|
|
12617
|
-
|
|
12618
|
-
//#region src/cursor-models.ts
|
|
12827
|
+
// src/cursor-models.ts
|
|
12619
12828
|
/** The model ids in `cursor-agent models` output: the first token of each line, ANSI colour and list
|
|
12620
12829
|
* bullets stripped. Headings and tips come along too; callers only test a specific id for membership. */
|
|
12621
12830
|
function parseCursorModels(output) {
|
|
@@ -12638,8 +12847,7 @@ function listCursorModels() {
|
|
|
12638
12847
|
return [];
|
|
12639
12848
|
}
|
|
12640
12849
|
}
|
|
12641
|
-
|
|
12642
|
-
//#region src/message.ts
|
|
12850
|
+
// src/message.ts
|
|
12643
12851
|
/** Write one message into the recipient's inbox. Collision-free by <epochMs>-<hex>. */
|
|
12644
12852
|
function send(ctx, input) {
|
|
12645
12853
|
const toId = resolveRecipient(ctx.store, input.to);
|
|
@@ -12736,8 +12944,7 @@ function deleteMessage(ctx, meId, msgId) {
|
|
|
12736
12944
|
function deleteMailbox(store, id) {
|
|
12737
12945
|
store.removeMailbox(id);
|
|
12738
12946
|
}
|
|
12739
|
-
|
|
12740
|
-
//#region src/decommission.ts
|
|
12947
|
+
// src/decommission.ts
|
|
12741
12948
|
/**
|
|
12742
12949
|
* Tear a unit down and reap its registry record — the deterministic inverse of `spawn`. Refuses a
|
|
12743
12950
|
* dirty worktree unless `--force`. Teardown always precedes reap: an already-gone worktree or pane
|
|
@@ -12773,7 +12980,7 @@ async function decommission(ctx, input) {
|
|
|
12773
12980
|
const worktreeExists = worktreeRoot != null && existsSync(worktreeRoot);
|
|
12774
12981
|
const primaryRoot = worktreeExists ? await primaryRootOf(exec, worktreeRoot) : rec.worktree?.primaryRoot;
|
|
12775
12982
|
const held = rec.worktree?.lease;
|
|
12776
|
-
const onPrimary =
|
|
12983
|
+
const onPrimary = isPrimaryCheckout(worktreeRoot, primaryRoot);
|
|
12777
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`);
|
|
12778
12985
|
const removesWorktree = worktreeExists && !input.keepWorktree && !held && !onPrimary;
|
|
12779
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`);
|
|
@@ -12839,10 +13046,9 @@ function isDirty(exec, worktreeRoot) {
|
|
|
12839
13046
|
"--porcelain",
|
|
12840
13047
|
"--untracked-files=all"
|
|
12841
13048
|
]);
|
|
12842
|
-
return !!status && status.split("\n").some((line) => line && line !== `??
|
|
13049
|
+
return !!status && status.split("\n").some((line) => line && line !== `?? .agents/cyberlegion/config.json`);
|
|
12843
13050
|
}
|
|
12844
|
-
|
|
12845
|
-
//#region src/install.ts
|
|
13051
|
+
// src/install.ts
|
|
12846
13052
|
const PIN_TOKEN = /^[0-9A-Za-z][0-9A-Za-z._+-]*$/;
|
|
12847
13053
|
function validatePin(pin) {
|
|
12848
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)`);
|
|
@@ -12995,8 +13201,7 @@ function removeCursor(settings, event, canonical) {
|
|
|
12995
13201
|
else delete hooks[event];
|
|
12996
13202
|
return true;
|
|
12997
13203
|
}
|
|
12998
|
-
|
|
12999
|
-
//#region src/output.ts
|
|
13204
|
+
// src/output.ts
|
|
13000
13205
|
function stringifyCell(v) {
|
|
13001
13206
|
if (v == null) return "";
|
|
13002
13207
|
const s = String(v);
|
|
@@ -13029,8 +13234,7 @@ function fail(msg) {
|
|
|
13029
13234
|
console.error(JSON.stringify({ error: msg }));
|
|
13030
13235
|
process.exit(1);
|
|
13031
13236
|
}
|
|
13032
|
-
|
|
13033
|
-
//#region src/participants.ts
|
|
13237
|
+
// src/participants.ts
|
|
13034
13238
|
const UNIT_PREFIX = "cyberlegion:unit/";
|
|
13035
13239
|
/** This hub's own participant key. One per hub root, so reconciling one hub (a `--space`, say)
|
|
13036
13240
|
* never retires the units another hub registered in the same cynapse store. */
|
|
@@ -13194,8 +13398,7 @@ async function syncCynapse(hub, env = process.env, load = importCynapse) {
|
|
|
13194
13398
|
cyn?.close();
|
|
13195
13399
|
}
|
|
13196
13400
|
}
|
|
13197
|
-
|
|
13198
|
-
//#region src/permission.ts
|
|
13401
|
+
// src/permission.ts
|
|
13199
13402
|
const CLI_RULE = "Bash(cyberlegion *)";
|
|
13200
13403
|
const COVERING = /^Bash(?:\((?:\*|cyberlegion(?: \*|:\*|\*))\))?$/;
|
|
13201
13404
|
/** Claude Code's user settings file: `$CLAUDE_CONFIG_DIR/settings.json`, else `~/.claude/settings.json`. */
|
|
@@ -13246,8 +13449,7 @@ function addPermissionRule(file) {
|
|
|
13246
13449
|
writeFileSync(file, `${JSON.stringify(settings, null, 2)}\n`);
|
|
13247
13450
|
return "added";
|
|
13248
13451
|
}
|
|
13249
|
-
|
|
13250
|
-
//#region src/presence-spawn.ts
|
|
13452
|
+
// src/presence-spawn.ts
|
|
13251
13453
|
/** The brief a presence spawned into a standing owner's home starts from: whom it stands in for, and
|
|
13252
13454
|
* where that owner's mail is. What to *do* with the mail is the home's agent definition's business. */
|
|
13253
13455
|
function presenceBrief(handle) {
|
|
@@ -13342,8 +13544,7 @@ async function spawnPresence(ctx, owner, options = {}) {
|
|
|
13342
13544
|
...turn
|
|
13343
13545
|
};
|
|
13344
13546
|
}
|
|
13345
|
-
|
|
13346
|
-
//#region src/project.ts
|
|
13547
|
+
// src/project.ts
|
|
13347
13548
|
/**
|
|
13348
13549
|
* The canonical git common dir for `dir`, or undefined outside a repository. The common dir is the
|
|
13349
13550
|
* one path every checkout of a repository shares — the default checkout and each linked worktree
|
|
@@ -13462,8 +13663,7 @@ function byPath(ctx, dir) {
|
|
|
13462
13663
|
if (!commonDir) throw new Error(`"${dir}" is not inside a git repository`);
|
|
13463
13664
|
return ctx.store.getProject(projectIdOf(commonDir)) ?? registerProject(ctx, { dir });
|
|
13464
13665
|
}
|
|
13465
|
-
|
|
13466
|
-
//#region src/runtime/inject-inbox.ts
|
|
13666
|
+
// src/runtime/inject-inbox.ts
|
|
13467
13667
|
const EVENTS = ["SessionStart"];
|
|
13468
13668
|
/**
|
|
13469
13669
|
* Resolve the calling agent, gather its unread mail (and a standing owner's, when this session is
|
|
@@ -13518,8 +13718,7 @@ function injectInbox(ctx, event) {
|
|
|
13518
13718
|
additionalContext: parts.join("\n\n")
|
|
13519
13719
|
} };
|
|
13520
13720
|
}
|
|
13521
|
-
|
|
13522
|
-
//#region src/service.ts
|
|
13721
|
+
// src/service.ts
|
|
13523
13722
|
/** A refused ownership transition. `stale` means the caller's generation, token, or holder claim is
|
|
13524
13723
|
* not the current one — the caller is not (or no longer) the authority it claims to be. */
|
|
13525
13724
|
var ServiceOwnershipError = class extends Error {
|
|
@@ -13824,8 +14023,7 @@ async function startService(ctx, projectRef, name, input) {
|
|
|
13824
14023
|
throw err;
|
|
13825
14024
|
}
|
|
13826
14025
|
}
|
|
13827
|
-
|
|
13828
|
-
//#region src/standing-home.ts
|
|
14026
|
+
// src/standing-home.ts
|
|
13829
14027
|
/**
|
|
13830
14028
|
* Decide what a registration does to a standing owner's home: `undefined` keeps whatever the record
|
|
13831
14029
|
* already has, `null` drops it, and a `StandingHome` replaces it. Every way a home can be wrong is
|
|
@@ -13872,8 +14070,7 @@ function resolveHomeFlags(exec, flags) {
|
|
|
13872
14070
|
...launch
|
|
13873
14071
|
};
|
|
13874
14072
|
}
|
|
13875
|
-
|
|
13876
|
-
//#region src/store/errors.ts
|
|
14073
|
+
// src/store/errors.ts
|
|
13877
14074
|
/** A record file exists but its content didn't parse as JSON — a torn write (crash mid-`writeFileSync`,
|
|
13878
14075
|
* pre-atomic-write code path) or on-disk tampering. Carries the file path and the original parse
|
|
13879
14076
|
* error so a caller can report exactly what's broken and where, rather than "Unexpected token" with
|
|
@@ -13886,8 +14083,7 @@ var CorruptRecordError = class extends Error {
|
|
|
13886
14083
|
this.name = "CorruptRecordError";
|
|
13887
14084
|
}
|
|
13888
14085
|
};
|
|
13889
|
-
|
|
13890
|
-
//#region src/store/process-liveness.ts
|
|
14086
|
+
// src/store/process-liveness.ts
|
|
13891
14087
|
/**
|
|
13892
14088
|
* Probe whether `pid` is alive using `process.kill(pid, 0)` — the POSIX "does this process exist"
|
|
13893
14089
|
* idiom; it sends no signal, it only tests deliverability. `ESRCH` (no such process) is the only
|
|
@@ -13907,8 +14103,7 @@ function probeProcess(pid) {
|
|
|
13907
14103
|
return err.code === "ESRCH" ? "dead" : "unknown";
|
|
13908
14104
|
}
|
|
13909
14105
|
}
|
|
13910
|
-
|
|
13911
|
-
//#region src/store/lock.ts
|
|
14106
|
+
// src/store/lock.ts
|
|
13912
14107
|
var LockTimeoutError = class extends Error {
|
|
13913
14108
|
name;
|
|
13914
14109
|
constructor(name) {
|
|
@@ -14045,8 +14240,7 @@ async function withLockAsync(root, name, fn, opts) {
|
|
|
14045
14240
|
handle.release();
|
|
14046
14241
|
}
|
|
14047
14242
|
}
|
|
14048
|
-
|
|
14049
|
-
//#region src/store/file-store.ts
|
|
14243
|
+
// src/store/file-store.ts
|
|
14050
14244
|
/** Parse a record file's content, wrapping a `JSON.parse` failure in a typed, file-named
|
|
14051
14245
|
* `CorruptRecordError` instead of letting a bare `SyntaxError` bubble up from deep inside
|
|
14052
14246
|
* `listInbox`/`listAgents`/`getAgent` with no indication of WHICH file broke. A file that doesn't
|
|
@@ -14256,8 +14450,7 @@ var FileStore = class {
|
|
|
14256
14450
|
return withLockAsync(this.root, name, fn);
|
|
14257
14451
|
}
|
|
14258
14452
|
};
|
|
14259
|
-
|
|
14260
|
-
//#region src/unit-runtime.ts
|
|
14453
|
+
// src/unit-runtime.ts
|
|
14261
14454
|
/** A standing or service record is an address with no runtime of its own — nothing to stop or start. */
|
|
14262
14455
|
function assertHasRuntime(rec, ref) {
|
|
14263
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`);
|
|
@@ -14345,8 +14538,9 @@ function stopUnit(ctx, ref) {
|
|
|
14345
14538
|
* record to the new pane, and ring it. When the record carries the harness's conversation id and the
|
|
14346
14539
|
* harness can resume by id, the session resumes that conversation and is rung to continue, with the
|
|
14347
14540
|
* plain launch as a shell fallback should the harness reject the id; otherwise (or with `fresh`) it
|
|
14348
|
-
* starts empty and is rung to read its brief — a rebrief.
|
|
14349
|
-
*
|
|
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.
|
|
14350
14544
|
*
|
|
14351
14545
|
* The unit is recorded `stopped` before the open, so a restart interrupted between the two leaves an
|
|
14352
14546
|
* ordinary stopped unit and a rerun recovers it; nothing needs a separate repair path.
|
|
@@ -14355,6 +14549,7 @@ async function restartUnit(ctx, ref, options = {}) {
|
|
|
14355
14549
|
const rec = resolveAgent(ctx.store, ref);
|
|
14356
14550
|
assertHasRuntime(rec, ref);
|
|
14357
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`);
|
|
14358
14553
|
const harness = rec.harness;
|
|
14359
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`);
|
|
14360
14555
|
if (!existsSync(rec.cwd)) throw new Error(`unit "${rec.handle}" cannot restart — its cwd ${rec.cwd} is gone; spawn a new unit instead`);
|
|
@@ -14396,10 +14591,7 @@ async function restartUnit(ctx, ref, options = {}) {
|
|
|
14396
14591
|
const bound = {
|
|
14397
14592
|
...stopped,
|
|
14398
14593
|
status: "active",
|
|
14399
|
-
pane:
|
|
14400
|
-
mux,
|
|
14401
|
-
id: target.id
|
|
14402
|
-
} : null,
|
|
14594
|
+
pane: openedPaneLocator(exec, mux, target.id),
|
|
14403
14595
|
lastSeen: new Date(ctx.now?.() ?? Date.now()).toISOString()
|
|
14404
14596
|
};
|
|
14405
14597
|
saveAgent(ctx.store, bound);
|
|
@@ -14425,6 +14617,10 @@ function probeLiveness(ctx, rec) {
|
|
|
14425
14617
|
if (rec.kind === "standing" || rec.kind === "service") return "none";
|
|
14426
14618
|
if (rec.status === "stopped") return "stopped";
|
|
14427
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
|
+
}
|
|
14428
14624
|
const pane = runtimePane(ctx, rec);
|
|
14429
14625
|
const adapter = pane ? paneAdapter(ctx, rec) : void 0;
|
|
14430
14626
|
if (!pane || !adapter) return "unknown";
|
|
@@ -14441,7 +14637,7 @@ function probeLiveness(ctx, rec) {
|
|
|
14441
14637
|
function rebindUnit(ctx, ref) {
|
|
14442
14638
|
const rec = resolveAgent(ctx.store, ref);
|
|
14443
14639
|
assertHasRuntime(rec, ref);
|
|
14444
|
-
const cur =
|
|
14640
|
+
const cur = callingStorablePane(ctx);
|
|
14445
14641
|
if (!cur) throw new Error("unit rebind needs to run inside a tmux or herdr pane — there is no pane to bind");
|
|
14446
14642
|
const oldPane = runtimePane(ctx, rec);
|
|
14447
14643
|
if (rec.pane?.mux === cur.mux && rec.pane.id === cur.pane && ctx.store.resolvePaneId(cur.pane) === rec.id) return rec;
|
|
@@ -14449,13 +14645,15 @@ function rebindUnit(ctx, ref) {
|
|
|
14449
14645
|
const holder = holderId && holderId !== rec.id ? loadAgent(ctx.store, holderId) : void 0;
|
|
14450
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`);
|
|
14451
14647
|
const liveness = probeLiveness(ctx, rec);
|
|
14452
|
-
|
|
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})`);
|
|
14453
14650
|
const bound = {
|
|
14454
14651
|
...rec,
|
|
14455
14652
|
status: "active",
|
|
14456
14653
|
pane: {
|
|
14457
14654
|
mux: cur.mux,
|
|
14458
|
-
id: cur.pane
|
|
14655
|
+
id: cur.pane,
|
|
14656
|
+
...cur.terminal ? { terminal: cur.terminal } : {}
|
|
14459
14657
|
},
|
|
14460
14658
|
lastSeen: new Date(ctx.now?.() ?? Date.now()).toISOString()
|
|
14461
14659
|
};
|
|
@@ -14483,7 +14681,7 @@ function controlsFor(ctx, rec, liveness) {
|
|
|
14483
14681
|
if (hasHonestReset(rec.harness)) controls.push("clear");
|
|
14484
14682
|
}
|
|
14485
14683
|
if (runtimePane(ctx, rec) !== void 0 && (liveness === "live" || liveness === "gone" || liveness === "unknown")) controls.push("stop");
|
|
14486
|
-
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");
|
|
14487
14685
|
if (liveness === "stopped" || liveness === "exited" || liveness === "gone") controls.push("rebind");
|
|
14488
14686
|
controls.push("close");
|
|
14489
14687
|
return controls;
|
|
@@ -14538,8 +14736,7 @@ function recordConversation(ctx, hookInput) {
|
|
|
14538
14736
|
conversation: id
|
|
14539
14737
|
});
|
|
14540
14738
|
}
|
|
14541
|
-
|
|
14542
|
-
//#region src/wake/await.ts
|
|
14739
|
+
// src/wake/await.ts
|
|
14543
14740
|
/** Internal poll interval — not user-facing (the round-trip cost of one `mail await` cycle). */
|
|
14544
14741
|
const POLL_MS$1 = 1e3;
|
|
14545
14742
|
const DEFAULT_TIMEOUT_MS = 6e5;
|
|
@@ -14584,8 +14781,7 @@ async function awaitReply(ctx, input) {
|
|
|
14584
14781
|
await sleep(POLL_MS$1);
|
|
14585
14782
|
}
|
|
14586
14783
|
}
|
|
14587
|
-
|
|
14588
|
-
//#region src/wake/watch.ts
|
|
14784
|
+
// src/wake/watch.ts
|
|
14589
14785
|
const POLL_MS = 1e3;
|
|
14590
14786
|
/**
|
|
14591
14787
|
* A continuous foreground observer: prints each NEW matching message as it arrives, polling on a
|
|
@@ -14617,8 +14813,7 @@ async function watchMail(ctx, input, onMessage, opts = {}) {
|
|
|
14617
14813
|
await sleep(POLL_MS);
|
|
14618
14814
|
}
|
|
14619
14815
|
}
|
|
14620
|
-
|
|
14621
|
-
//#region src/cli.ts
|
|
14816
|
+
// src/cli.ts
|
|
14622
14817
|
const VERSION = globalThis.__SKILL_SCRIPT__?.version ?? JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
14623
14818
|
function ctxOf(opts) {
|
|
14624
14819
|
return {
|
|
@@ -14821,18 +15016,25 @@ function runWho(opts) {
|
|
|
14821
15016
|
if (agents.length === 0) nextStep("cyberlegion unit register to join");
|
|
14822
15017
|
}
|
|
14823
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);
|
|
14824
|
-
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) => {
|
|
14825
15020
|
const ctx = ctxOf(opts);
|
|
14826
15021
|
touch(ctx);
|
|
14827
15022
|
const changed = prune(ctx);
|
|
14828
15023
|
emit(formatOf(opts), {
|
|
14829
|
-
toon: toonList("pruned", changed, [
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
|
|
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)`),
|
|
14836
15038
|
json: changed
|
|
14837
15039
|
});
|
|
14838
15040
|
});
|
|
@@ -15819,5 +16021,4 @@ async function runCli(argv = process.argv) {
|
|
|
15819
16021
|
process.exit(1);
|
|
15820
16022
|
}
|
|
15821
16023
|
}
|
|
15822
|
-
//#endregion
|
|
15823
16024
|
export { runCli };
|