cyberlegion 1.9.0 → 1.10.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberlegion",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Agent session spawning, messaging, and dispatch — the Legion and its Legate.",
5
5
  "author": {
6
6
  "name": "unional"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberlegion",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Agent session spawning, messaging, and dispatch — the Legion and its Legate.",
5
5
  "author": {
6
6
  "name": "unional"
package/.plugin/pins.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "cyberlegion": "1.9.0"
2
+ "cyberlegion": "1.10.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # cyberlegion
2
2
 
3
+ ## 1.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7a1c849: A unit now records a worktree it works in but does not own as `sharedWorktree` (`root`, `branch`, `primaryRoot`, and `owner`, the id of the unit that owns it). `unit spawn --cwd` records the checkout its directory sits in, and `unit register` records a worktree another unit's record owns this way instead of taking it as its own `worktree`. `unit close` never removes, prunes, or releases a shared worktree, so closing such a unit can no longer remove another unit's worktree or release its lease. `unit who` gains a `shared` column and `unit show` a `shared` field (`sharedWorktree` in JSON), so a sweep can judge the unit by that checkout. Records without the field read as before.
8
+
3
9
  ## 1.9.0
4
10
 
5
11
  ### Minor Changes
package/dist/cli.mjs CHANGED
@@ -10517,7 +10517,7 @@ function register(ctx, input) {
10517
10517
  handle: input.handle ?? existing?.handle ?? id.slice(0, 6),
10518
10518
  harness,
10519
10519
  cwd: process.cwd(),
10520
- worktree: existing?.worktree ? keptWorktree(existing.worktree, exec) : gitWorktree(exec),
10520
+ ...checkoutFor(ctx.store, exec, id, existing),
10521
10521
  pane: cur ? paneLocator(cur, exec, env) : null,
10522
10522
  status: "active",
10523
10523
  createdAt: existing?.createdAt ?? ts,
@@ -10584,24 +10584,71 @@ function writeStanding(ctx, id, input) {
10584
10584
  saveAgent(ctx.store, rec);
10585
10585
  return rec;
10586
10586
  }
10587
- /** The checkout the cwd sits in: its root, its branch, and the repository's primary checkout — the
10588
- * same `primaryRoot` spawn records, so a lookup by repository finds a unit in a linked worktree by
10589
- * the repository's name rather than the worktree folder's. */
10590
- function gitWorktree(exec) {
10591
- const root = exec("git", ["rev-parse", "--show-toplevel"]);
10587
+ /** The checkout `dir` (else the cwd) sits in: its root, its branch, and the repository's primary
10588
+ * checkout — the same `primaryRoot` spawn records, so a lookup by repository finds a unit in a linked
10589
+ * worktree by the repository's name rather than the worktree folder's. */
10590
+ function gitWorktree(exec, dir) {
10591
+ const git = dir === void 0 ? exec : (cmd, args) => exec(cmd, cmd === "git" ? [
10592
+ "-C",
10593
+ dir,
10594
+ ...args
10595
+ ] : args);
10596
+ const root = git("git", ["rev-parse", "--show-toplevel"]);
10592
10597
  if (!root) return null;
10593
- const branch = exec("git", [
10598
+ const branch = git("git", [
10594
10599
  "rev-parse",
10595
10600
  "--abbrev-ref",
10596
10601
  "HEAD"
10597
10602
  ]) ?? void 0;
10598
- const primaryRoot = primaryRootHere(exec);
10603
+ const primaryRoot = primaryRootHere(git);
10599
10604
  return {
10600
10605
  root,
10601
10606
  ...branch !== void 0 ? { branch } : {},
10602
10607
  ...primaryRoot !== void 0 ? { primaryRoot } : {}
10603
10608
  };
10604
10609
  }
10610
+ /** The id of the unit, other than `self`, whose record owns the worktree at `root`. */
10611
+ function ownerOf(store, root, self) {
10612
+ const at = resolve(root);
10613
+ return listAgents(store).find((a) => a.id !== self && a.worktree?.root && resolve(a.worktree.root) === at)?.id;
10614
+ }
10615
+ /** `checkout` as a shared reference, naming the unit that owns it when one does. */
10616
+ function asShared(store, checkout, self) {
10617
+ const owner = ownerOf(store, checkout.root, self);
10618
+ return {
10619
+ ...checkout,
10620
+ ...owner ? { owner } : {}
10621
+ };
10622
+ }
10623
+ /** The checkout a `--cwd` spawn opens into, as a shared reference — spawn created none, so the unit
10624
+ * owns none, whoever does. `undefined` when `dir` is in no git checkout. */
10625
+ function sharedCheckoutAt(store, exec, dir) {
10626
+ const checkout = gitWorktree(exec, dir);
10627
+ return checkout ? asShared(store, checkout) : void 0;
10628
+ }
10629
+ /**
10630
+ * Which checkout a registering session owns and which it only shares. An existing owned worktree is
10631
+ * kept. Otherwise the cwd's checkout is the session's own — unless another unit's record owns it, or
10632
+ * this unit already records it as shared (a `--cwd` spawn): then it is a shared reference, and the
10633
+ * session owns none, so its close can never remove another unit's worktree or release its lease.
10634
+ */
10635
+ function checkoutFor(store, exec, id, existing) {
10636
+ const kept = existing?.sharedWorktree ? { sharedWorktree: existing.sharedWorktree } : {};
10637
+ if (existing?.worktree) return {
10638
+ worktree: keptWorktree(existing.worktree, exec),
10639
+ ...kept
10640
+ };
10641
+ const here = gitWorktree(exec);
10642
+ if (!here) return {
10643
+ worktree: null,
10644
+ ...kept
10645
+ };
10646
+ if (existing?.sharedWorktree || ownerOf(store, here.root, id)) return {
10647
+ worktree: null,
10648
+ sharedWorktree: asShared(store, here, id)
10649
+ };
10650
+ return { worktree: here };
10651
+ }
10605
10652
  function primaryRootHere(exec) {
10606
10653
  try {
10607
10654
  return resolvePrimaryRoot(exec);
@@ -11852,6 +11899,7 @@ async function spawn(ctx, input) {
11852
11899
  const from = callerPane(sessionAdapter, normalizedEnv);
11853
11900
  let cwd;
11854
11901
  let worktree;
11902
+ let sharedWorktree;
11855
11903
  let base;
11856
11904
  let reused;
11857
11905
  let target;
@@ -11860,6 +11908,7 @@ async function spawn(ctx, input) {
11860
11908
  cwd = resolve(input.cwd);
11861
11909
  if (primaryRoot$1 !== void 0) assertDistinctFromPrimary(cwd, primaryRoot$1);
11862
11910
  worktree = null;
11911
+ sharedWorktree = sharedCheckoutAt(ctx.store, exec, cwd);
11863
11912
  const at = input.at ?? "tab";
11864
11913
  target = sessionAdapter.open(exec, {
11865
11914
  cwd,
@@ -11950,6 +11999,7 @@ async function spawn(ctx, input) {
11950
11999
  harness,
11951
12000
  cwd,
11952
12001
  worktree,
12002
+ ...sharedWorktree ? { sharedWorktree } : {},
11953
12003
  pane: muxName === "tmux" || muxName === "herdr" ? {
11954
12004
  mux: muxName,
11955
12005
  id: target.id
@@ -14460,6 +14510,7 @@ function showUnit(ctx, ref) {
14460
14510
  } : null,
14461
14511
  cwd: rec.cwd,
14462
14512
  worktree: rec.worktree ?? null,
14513
+ sharedWorktree: rec.sharedWorktree ?? null,
14463
14514
  lastSeen: rec.lastSeen,
14464
14515
  controls: controlsFor(ctx, rec, liveness)
14465
14516
  };
@@ -14759,6 +14810,10 @@ function runWho(opts) {
14759
14810
  {
14760
14811
  key: "pane",
14761
14812
  get: (a) => a.pane?.id ?? "-"
14813
+ },
14814
+ {
14815
+ key: "shared",
14816
+ get: (a) => a.sharedWorktree?.root ?? "-"
14762
14817
  }
14763
14818
  ], `${agents.length} units`),
14764
14819
  json: agents
@@ -14958,6 +15013,7 @@ withGlobals(unit.command("show")).description("show a unit's runtime: where it i
14958
15013
  pane: view.pane ? `${view.pane.mux}:${view.pane.id}` : "-",
14959
15014
  cwd: view.cwd,
14960
15015
  worktree: view.worktree?.root ?? "-",
15016
+ shared: view.sharedWorktree?.root ?? "-",
14961
15017
  lastSeen: view.lastSeen,
14962
15018
  controls: view.controls.join(",") || "-"
14963
15019
  }),
package/dist/index.d.mts CHANGED
@@ -40,6 +40,16 @@ interface AgentRecord {
40
40
  holder: string;
41
41
  };
42
42
  } | null;
43
+ /** A checkout the unit works in but does not own: a `--cwd` spawn into an existing checkout, or a
44
+ * session registered in a worktree another unit's record owns. `owner` is that unit's id, when one
45
+ * owned the root at the time of recording. Close never removes, prunes, or releases it — it is read
46
+ * only, so a sweep can judge the unit by the work there. Absent on records from before it existed. */
47
+ sharedWorktree?: {
48
+ root: string;
49
+ branch?: string;
50
+ primaryRoot?: string;
51
+ owner?: string;
52
+ };
43
53
  /** Where this session lives, tagged with its multiplexer so `prune` runs the right liveness check.
44
54
  * `null` for a session in no pane and for a standing record. `window`/`session` are tmux-only. */
45
55
  pane?: {
package/dist/index.mjs CHANGED
@@ -247,7 +247,7 @@ function register(ctx, input) {
247
247
  handle: input.handle ?? existing?.handle ?? id.slice(0, 6),
248
248
  harness,
249
249
  cwd: process.cwd(),
250
- worktree: existing?.worktree ? keptWorktree(existing.worktree, exec) : gitWorktree(exec),
250
+ ...checkoutFor(ctx.store, exec, id, existing),
251
251
  pane: cur ? paneLocator(cur, exec, env) : null,
252
252
  status: "active",
253
253
  createdAt: existing?.createdAt ?? ts,
@@ -279,24 +279,71 @@ function paneLocator(cur, exec, env) {
279
279
  id: cur.pane
280
280
  };
281
281
  }
282
- /** The checkout the cwd sits in: its root, its branch, and the repository's primary checkout — the
283
- * same `primaryRoot` spawn records, so a lookup by repository finds a unit in a linked worktree by
284
- * the repository's name rather than the worktree folder's. */
285
- function gitWorktree(exec) {
286
- const root = exec("git", ["rev-parse", "--show-toplevel"]);
282
+ /** The checkout `dir` (else the cwd) sits in: its root, its branch, and the repository's primary
283
+ * checkout — the same `primaryRoot` spawn records, so a lookup by repository finds a unit in a linked
284
+ * worktree by the repository's name rather than the worktree folder's. */
285
+ function gitWorktree(exec, dir) {
286
+ const git = dir === void 0 ? exec : (cmd, args) => exec(cmd, cmd === "git" ? [
287
+ "-C",
288
+ dir,
289
+ ...args
290
+ ] : args);
291
+ const root = git("git", ["rev-parse", "--show-toplevel"]);
287
292
  if (!root) return null;
288
- const branch = exec("git", [
293
+ const branch = git("git", [
289
294
  "rev-parse",
290
295
  "--abbrev-ref",
291
296
  "HEAD"
292
297
  ]) ?? void 0;
293
- const primaryRoot = primaryRootHere(exec);
298
+ const primaryRoot = primaryRootHere(git);
294
299
  return {
295
300
  root,
296
301
  ...branch !== void 0 ? { branch } : {},
297
302
  ...primaryRoot !== void 0 ? { primaryRoot } : {}
298
303
  };
299
304
  }
305
+ /** The id of the unit, other than `self`, whose record owns the worktree at `root`. */
306
+ function ownerOf(store, root, self) {
307
+ const at = resolve(root);
308
+ return listAgents(store).find((a) => a.id !== self && a.worktree?.root && resolve(a.worktree.root) === at)?.id;
309
+ }
310
+ /** `checkout` as a shared reference, naming the unit that owns it when one does. */
311
+ function asShared(store, checkout, self) {
312
+ const owner = ownerOf(store, checkout.root, self);
313
+ return {
314
+ ...checkout,
315
+ ...owner ? { owner } : {}
316
+ };
317
+ }
318
+ /** The checkout a `--cwd` spawn opens into, as a shared reference — spawn created none, so the unit
319
+ * owns none, whoever does. `undefined` when `dir` is in no git checkout. */
320
+ function sharedCheckoutAt(store, exec, dir) {
321
+ const checkout = gitWorktree(exec, dir);
322
+ return checkout ? asShared(store, checkout) : void 0;
323
+ }
324
+ /**
325
+ * Which checkout a registering session owns and which it only shares. An existing owned worktree is
326
+ * kept. Otherwise the cwd's checkout is the session's own — unless another unit's record owns it, or
327
+ * this unit already records it as shared (a `--cwd` spawn): then it is a shared reference, and the
328
+ * session owns none, so its close can never remove another unit's worktree or release its lease.
329
+ */
330
+ function checkoutFor(store, exec, id, existing) {
331
+ const kept = existing?.sharedWorktree ? { sharedWorktree: existing.sharedWorktree } : {};
332
+ if (existing?.worktree) return {
333
+ worktree: keptWorktree(existing.worktree, exec),
334
+ ...kept
335
+ };
336
+ const here = gitWorktree(exec);
337
+ if (!here) return {
338
+ worktree: null,
339
+ ...kept
340
+ };
341
+ if (existing?.sharedWorktree || ownerOf(store, here.root, id)) return {
342
+ worktree: null,
343
+ sharedWorktree: asShared(store, here, id)
344
+ };
345
+ return { worktree: here };
346
+ }
300
347
  function primaryRootHere(exec) {
301
348
  try {
302
349
  return resolvePrimaryRoot$1(exec);
@@ -1282,6 +1329,7 @@ async function spawn(ctx, input) {
1282
1329
  const from = callerPane(sessionAdapter, normalizedEnv);
1283
1330
  let cwd;
1284
1331
  let worktree;
1332
+ let sharedWorktree;
1285
1333
  let base;
1286
1334
  let reused;
1287
1335
  let target;
@@ -1290,6 +1338,7 @@ async function spawn(ctx, input) {
1290
1338
  cwd = resolve(input.cwd);
1291
1339
  if (primaryRoot$1 !== void 0) assertDistinctFromPrimary$1(cwd, primaryRoot$1);
1292
1340
  worktree = null;
1341
+ sharedWorktree = sharedCheckoutAt(ctx.store, exec, cwd);
1293
1342
  const at = input.at ?? "tab";
1294
1343
  target = sessionAdapter.open(exec, {
1295
1344
  cwd,
@@ -1380,6 +1429,7 @@ async function spawn(ctx, input) {
1380
1429
  harness,
1381
1430
  cwd,
1382
1431
  worktree,
1432
+ ...sharedWorktree ? { sharedWorktree } : {},
1383
1433
  pane: muxName === "tmux" || muxName === "herdr" ? {
1384
1434
  mux: muxName,
1385
1435
  id: target.id
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberlegion",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Harness-agnostic agent session spawning and messaging over the filesystem",
5
5
  "keywords": [
6
6
  "agent",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "cyberlegion",
4
- "version": "1.9.0",
4
+ "version": "1.10.0",
5
5
  "description": "Agent session spawning, messaging, and dispatch — the Legion and its Legate.",
6
6
  "author": {
7
7
  "name": "unional"
@@ -17,7 +17,7 @@ satisfies verdict schema `V`.
17
17
  > **Running the CLI.** Every `node scripts/cyberlegion.mjs …` command below runs the `cyberlegion` CLI
18
18
  > this plugin ships. The path is relative to this skill's own directory, not the working directory.
19
19
  > If you cannot resolve it, run the published CLI of the version that shipped this skill instead,
20
- > with the same arguments: `npx -y cyberlegion@1.9.0`.
20
+ > with the same arguments: `npx -y cyberlegion@1.10.0`.
21
21
 
22
22
  ## 1. Resolve the agent definition
23
23