cyberlegion 1.8.0 → 1.9.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 +10 -0
- package/dist/cli.mjs +144 -8
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +50 -6
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/dispatch-governance/SKILL.md +1 -1
- package/skills/dispatch-governance/scripts/cyberlegion.mjs +46 -45
- package/skills/init-cyberlegion/scripts/cyberlegion.mjs +46 -45
- package/skills/legate/SKILL.md +1 -1
- package/skills/legate/scripts/cyberlegion.mjs +46 -45
- package/skills/manage-inbox/SKILL.md +1 -1
- package/skills/manage-inbox/scripts/cyberlegion.mjs +46 -45
- package/skills/relay-governance/SKILL.md +1 -1
- package/skills/relay-governance/scripts/cyberlegion.mjs +46 -45
- package/skills/subagent-backend-governance/SKILL.md +1 -1
- package/skills/subagent-backend-governance/scripts/cyberlegion.mjs +46 -45
package/.plugin/pins.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# cyberlegion
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- deed34d: `mail inbox --unit <handle|id>` lists another unit's mail without acking it and without the caller taking that unit's identity. Exited units resolve too, so a cleanup sweep can check whether a dead unit left unread mail without reading the hub's files itself. The output carries a `mailbox` state of `none` (no mail was ever delivered), `present`, or `unreadable`. An unreadable mailbox exits 1, so a caller never reads it as empty.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- a7a2d57: `unit spawn` no longer leaves several unsent "Read your brief" lines in a new session's input box. The first-turn ring now waits for the harness to draw its input box before typing; keys typed while a harness boots are taken as one paste, so each Enter became a newline and re-typed copies piled up. Copies of the doorbell sitting in the input box are also no longer counted as a posted turn, so a staged doorbell is flushed with Enter instead of reported rung.
|
|
12
|
+
|
|
3
13
|
## 1.8.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -10671,6 +10671,20 @@ function resolveOwnerMailbox(store, ref) {
|
|
|
10671
10671
|
if (services.length > 1) throw new Error(`"${ref}" names ${services.length} service endpoints — pass an endpoint id (${services.map((a) => a.id).join(", ")})`);
|
|
10672
10672
|
return resolveStandingOwner(store, ref);
|
|
10673
10673
|
}
|
|
10674
|
+
/** Resolve the unit whose mailbox `mail inbox --unit` reads, by id or handle. Unlike a recipient, an
|
|
10675
|
+
* exited unit resolves: the caller is deciding about that unit (a sweep asking whether a dead unit left
|
|
10676
|
+
* unread mail), not writing to it. A live holder of the handle wins; several exited holders and no live
|
|
10677
|
+
* one fail rather than picking one. */
|
|
10678
|
+
function resolveUnitMailbox(store, ref) {
|
|
10679
|
+
const byId = loadAgent(store, ref);
|
|
10680
|
+
if (byId) return byId;
|
|
10681
|
+
const { live, exited } = matchHandle(listAgents(store), ref);
|
|
10682
|
+
const byHandle = pickLive(ref, live);
|
|
10683
|
+
if (byHandle) return byHandle;
|
|
10684
|
+
if (exited.length > 1) throw new Error(`"${ref}" names ${exited.length} exited units — pass an id (${exited.map((a) => a.id).join(", ")})`);
|
|
10685
|
+
if (exited[0]) return exited[0];
|
|
10686
|
+
throw new Error(`no unit addressable as "${ref}" (tried id and handle)`);
|
|
10687
|
+
}
|
|
10674
10688
|
/**
|
|
10675
10689
|
* Bind the caller's own unit as a standing owner's presence — the live unit standing in for a
|
|
10676
10690
|
* durable record that has no session of its own. Order matters: resolve the standing record first
|
|
@@ -11016,6 +11030,10 @@ function readShape(rows, shape) {
|
|
|
11016
11030
|
}
|
|
11017
11031
|
return { kind: "unknown" };
|
|
11018
11032
|
}
|
|
11033
|
+
/** Whether this reader knows `harness`'s input box, so a missing box on screen means it is not drawn yet. */
|
|
11034
|
+
function knowsPromptOf(harness) {
|
|
11035
|
+
return harness !== void 0 && SHAPES[harness] !== void 0;
|
|
11036
|
+
}
|
|
11019
11037
|
/**
|
|
11020
11038
|
* Read the input box off `screen` — a plain-text scrape of the pane — with the shape of `harness`,
|
|
11021
11039
|
* or, when the harness is not known, with each known shape in turn until one recognizes the box.
|
|
@@ -11116,6 +11134,27 @@ function copiesOn(screen, message) {
|
|
|
11116
11134
|
return screen.split("\n").filter((row) => row.replace(/\s+/g, " ").includes(needle)).length;
|
|
11117
11135
|
}
|
|
11118
11136
|
/**
|
|
11137
|
+
* Read `screen` for the ring: whether `message` sits in the input box, and how many copies of it
|
|
11138
|
+
* are on screen outside the box. With the box read by its harness's shape, copies inside the box are
|
|
11139
|
+
* left out of the count: a booting harness can take several typed copies as one paste, and those
|
|
11140
|
+
* copies are staged text, not posted turns. A screen with no input box the reader knows falls back to
|
|
11141
|
+
* cyber-mux's `isStaged`, and the box's copy is counted with the rest.
|
|
11142
|
+
*/
|
|
11143
|
+
function readRing(screen, message, harness) {
|
|
11144
|
+
const box = screen ? readPrompt(screen, harness) : { kind: "unknown" };
|
|
11145
|
+
if (box.kind === "unknown") return {
|
|
11146
|
+
boxKnown: false,
|
|
11147
|
+
staged: isStaged(screen, message),
|
|
11148
|
+
outside: copiesOn(screen, message)
|
|
11149
|
+
};
|
|
11150
|
+
const inBox = box.kind === "draft" ? copiesOn(box.text, message) : 0;
|
|
11151
|
+
return {
|
|
11152
|
+
boxKnown: true,
|
|
11153
|
+
staged: inBox > 0,
|
|
11154
|
+
outside: copiesOn(screen, message) - inBox
|
|
11155
|
+
};
|
|
11156
|
+
}
|
|
11157
|
+
/**
|
|
11119
11158
|
* Submit `message` to `target` as a taken turn: cyber-mux's `nudge`, with one more way to see that the
|
|
11120
11159
|
* turn was taken.
|
|
11121
11160
|
*
|
|
@@ -11135,18 +11174,32 @@ function copiesOn(screen, message) {
|
|
|
11135
11174
|
* queue. Text that is neither staged nor posted is lost: after one more settle for a slow redraw, it
|
|
11136
11175
|
* is typed again. It is opt-in because it needs the posted copy to stay on screen: a busy peer's
|
|
11137
11176
|
* output can scroll earlier copies away, so a ring to it could read as lost and be typed twice.
|
|
11177
|
+
*
|
|
11178
|
+
* With `requirePosted` and a harness whose input box this reader knows, nothing is typed until the
|
|
11179
|
+
* harness has drawn that box, for up to the
|
|
11180
|
+
* whole retry budget. Keys typed while a harness boots are buffered, and the harness takes them as one
|
|
11181
|
+
* paste once it draws the box: each Enter becomes a newline, so re-typed copies pile up in the box
|
|
11182
|
+
* unsent. A box that never shows within the budget is typed into anyway, as before.
|
|
11183
|
+
*
|
|
11184
|
+
* Where the input box can be read, copies of the text inside it are not counted as posted: only a
|
|
11185
|
+
* copy outside the box means the harness took the turn.
|
|
11138
11186
|
*/
|
|
11139
11187
|
async function ringTurn(adapter, exec, target, message, opts = {}) {
|
|
11140
11188
|
const attempts = opts.attempts ?? DEFAULT_ATTEMPTS;
|
|
11141
11189
|
const settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS;
|
|
11142
11190
|
const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
11143
11191
|
if (!adapter.paneExists(exec, target)) throw new Error(`nudge failed: pane ${target.id} no longer exists — the peer's session is gone, not busy.`);
|
|
11144
|
-
|
|
11192
|
+
let first = adapter.read(exec, target).text;
|
|
11193
|
+
if (opts.requirePosted && knowsPromptOf(opts.harness)) for (let wait = 0; wait < attempts && !readRing(first, message, opts.harness).boxKnown; wait++) {
|
|
11194
|
+
await sleep(settleMs);
|
|
11195
|
+
first = adapter.read(exec, target).text;
|
|
11196
|
+
}
|
|
11197
|
+
const before = readRing(first, message, opts.harness).outside;
|
|
11145
11198
|
const check = () => {
|
|
11146
|
-
const
|
|
11147
|
-
const
|
|
11148
|
-
if (
|
|
11149
|
-
return
|
|
11199
|
+
const seen = readRing(adapter.read(exec, target).text, message, opts.harness);
|
|
11200
|
+
const posted = seen.outside >= before + (seen.boxKnown || !seen.staged ? 1 : 2);
|
|
11201
|
+
if (seen.staged) return posted ? "taken" : "staged";
|
|
11202
|
+
return posted || !opts.requirePosted ? "taken" : "lost";
|
|
11150
11203
|
};
|
|
11151
11204
|
adapter.submit(exec, target, message);
|
|
11152
11205
|
await sleep(settleMs);
|
|
@@ -11306,7 +11359,10 @@ async function wakeRecipient(store, getAdapter, exec, input, nudgeOpts, guardOpt
|
|
|
11306
11359
|
try {
|
|
11307
11360
|
const adapter = getAdapter();
|
|
11308
11361
|
const target = { id: pane };
|
|
11309
|
-
await withDraftGuard(adapter, exec, target, () => ringTurn(adapter, exec, target, DELIVERY_DOORBELL,
|
|
11362
|
+
await withDraftGuard(adapter, exec, target, () => ringTurn(adapter, exec, target, DELIVERY_DOORBELL, {
|
|
11363
|
+
...nudgeOpts,
|
|
11364
|
+
harness
|
|
11365
|
+
}), {
|
|
11310
11366
|
...guardOpts,
|
|
11311
11367
|
harness,
|
|
11312
11368
|
ownText: (text) => isRingText(text)
|
|
@@ -11351,7 +11407,8 @@ async function wakeSpawn(getAdapter, exec, input, nudgeOpts = SPAWN_NUDGE_OPTS,
|
|
|
11351
11407
|
const doorbell = (input.resumed ? resumeDoorbell : spawnDoorbell)(input.briefPath);
|
|
11352
11408
|
await withDraftGuard(adapter, exec, input.target, () => ringTurn(adapter, exec, input.target, doorbell, {
|
|
11353
11409
|
...nudgeOpts,
|
|
11354
|
-
requirePosted: true
|
|
11410
|
+
requirePosted: true,
|
|
11411
|
+
harness: input.harness
|
|
11355
11412
|
}), {
|
|
11356
11413
|
...guardOpts,
|
|
11357
11414
|
harness: input.harness,
|
|
@@ -12575,6 +12632,26 @@ function inbox(ctx, q) {
|
|
|
12575
12632
|
if (q.thread) items = items.filter((m) => m.thread === q.thread);
|
|
12576
12633
|
return items.sort((a, b) => a.id.localeCompare(b.id));
|
|
12577
12634
|
}
|
|
12635
|
+
/** List another unit's mail read-only, for a caller deciding about that unit (a cleanup sweep): never
|
|
12636
|
+
* acks, and reports a missing or unreadable mailbox as its own state rather than as an empty one. */
|
|
12637
|
+
function unitInbox(ctx, q) {
|
|
12638
|
+
if (!ctx.store.hasMailbox(q.meId)) return {
|
|
12639
|
+
mailbox: "none",
|
|
12640
|
+
messages: []
|
|
12641
|
+
};
|
|
12642
|
+
try {
|
|
12643
|
+
return {
|
|
12644
|
+
mailbox: "present",
|
|
12645
|
+
messages: inbox(ctx, q)
|
|
12646
|
+
};
|
|
12647
|
+
} catch (err) {
|
|
12648
|
+
return {
|
|
12649
|
+
mailbox: "unreadable",
|
|
12650
|
+
messages: [],
|
|
12651
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12652
|
+
};
|
|
12653
|
+
}
|
|
12654
|
+
}
|
|
12578
12655
|
/** Peek at a message (unread or already-acked) without changing its state. */
|
|
12579
12656
|
function peek(ctx, meId, msgId) {
|
|
12580
12657
|
const snap = ctx.store.listInbox(meId);
|
|
@@ -14008,6 +14085,10 @@ var FileStore = class {
|
|
|
14008
14085
|
read: read ? readMessages(read) : []
|
|
14009
14086
|
};
|
|
14010
14087
|
}
|
|
14088
|
+
hasMailbox(id) {
|
|
14089
|
+
const dir = readPathOrUndefined(() => paths.inboxDir(this.root, id));
|
|
14090
|
+
return dir !== void 0 && existsSync(dir);
|
|
14091
|
+
}
|
|
14011
14092
|
ackMessage(id, msgId) {
|
|
14012
14093
|
const src = readPathOrUndefined(() => paths.messageFile(this.root, id, msgId));
|
|
14013
14094
|
if (!src || !existsSync(src)) throw new Error(`"${msgId}" is not an unread message in this inbox`);
|
|
@@ -15238,12 +15319,67 @@ function emitInbox(ctx, opts, meId, readCmd) {
|
|
|
15238
15319
|
const firstUnread = items.find((m) => !m.read);
|
|
15239
15320
|
if (firstUnread) nextStep(`${readCmd} ${firstUnread.id}`);
|
|
15240
15321
|
}
|
|
15322
|
+
/** List another unit's mailbox read-only (`--unit`). Its `mailbox` state tells a missing mailbox
|
|
15323
|
+
* from an empty one; an unreadable one is reported on stdout and exits 1, never read as empty. */
|
|
15324
|
+
function emitUnitInbox(ctx, opts, ref) {
|
|
15325
|
+
let unit;
|
|
15326
|
+
try {
|
|
15327
|
+
unit = resolveUnitMailbox(ctx.store, ref);
|
|
15328
|
+
} catch (err) {
|
|
15329
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
15330
|
+
}
|
|
15331
|
+
const result = unitInbox({ store: ctx.store }, {
|
|
15332
|
+
meId: unit.id,
|
|
15333
|
+
unread: opts.unread,
|
|
15334
|
+
from: opts.from,
|
|
15335
|
+
thread: opts.thread
|
|
15336
|
+
});
|
|
15337
|
+
const unreadCount = result.messages.filter((m) => !m.read).length;
|
|
15338
|
+
const summary = result.mailbox === "none" ? "no mailbox" : result.mailbox === "unreadable" ? `mailbox unreadable: ${result.error}` : `${result.messages.length} messages (${unreadCount} unread)`;
|
|
15339
|
+
emit(formatOf(opts), {
|
|
15340
|
+
toon: [toonObject({
|
|
15341
|
+
unit: unit.id,
|
|
15342
|
+
handle: unit.handle,
|
|
15343
|
+
mailbox: result.mailbox
|
|
15344
|
+
}), toonList("messages", result.messages, [
|
|
15345
|
+
{
|
|
15346
|
+
key: "id",
|
|
15347
|
+
get: (m) => m.id
|
|
15348
|
+
},
|
|
15349
|
+
{
|
|
15350
|
+
key: "from",
|
|
15351
|
+
get: (m) => m.fromHandle
|
|
15352
|
+
},
|
|
15353
|
+
{
|
|
15354
|
+
key: "subject",
|
|
15355
|
+
get: (m) => m.subject ?? ""
|
|
15356
|
+
},
|
|
15357
|
+
{
|
|
15358
|
+
key: "read",
|
|
15359
|
+
get: (m) => m.read
|
|
15360
|
+
}
|
|
15361
|
+
], summary)].join("\n"),
|
|
15362
|
+
json: {
|
|
15363
|
+
unit: unit.id,
|
|
15364
|
+
handle: unit.handle,
|
|
15365
|
+
...result
|
|
15366
|
+
}
|
|
15367
|
+
});
|
|
15368
|
+
if (result.mailbox === "unreadable") {
|
|
15369
|
+
console.error(JSON.stringify({ error: `mailbox of ${unit.id} is unreadable: ${result.error}` }));
|
|
15370
|
+
process.exitCode = 1;
|
|
15371
|
+
}
|
|
15372
|
+
}
|
|
15241
15373
|
function runInbox(opts) {
|
|
15242
15374
|
const ctx = ctxOf(opts);
|
|
15243
15375
|
touch(ctx);
|
|
15376
|
+
if (opts.unit) {
|
|
15377
|
+
emitUnitInbox(ctx, opts, opts.unit);
|
|
15378
|
+
return;
|
|
15379
|
+
}
|
|
15244
15380
|
emitInbox(ctx, opts, opts.owner ? resolveOwnerMailbox(ctx.store, opts.owner) : requireSelf(ctx), "cyberlegion mail read");
|
|
15245
15381
|
}
|
|
15246
|
-
withGlobals(mail.command("inbox")).description("list your mail").option("--unread", "only un-acked mail").option("--from <id>", "filter by sender").option("--thread <id>", "filter to messages carrying this thread id").option("--owner <handle>", "target a standing owner's mailbox instead of this session's own").action(runInbox);
|
|
15382
|
+
withGlobals(mail.command("inbox")).description("list your mail").option("--unread", "only un-acked mail").option("--from <id>", "filter by sender").option("--thread <id>", "filter to messages carrying this thread id").option("--owner <handle>", "target a standing owner's mailbox instead of this session's own").addOption(new Option("--unit <handle|id>", "list another unit's mailbox read-only (exited units too); never acks, reports mailbox: none|present|unreadable").conflicts("owner")).action(runInbox);
|
|
15247
15383
|
withGlobals(mail.command("read")).description("read a message; --ack consumes it in the same step, otherwise it only peeks").argument("<msg-id>", "message id").option("--ack", "acknowledge the message in the same step (idempotent — no error if already acked)").option("--owner <handle>", "read a standing owner's mailbox instead of this session's own").action((msgId, opts) => {
|
|
15248
15384
|
const ctx = ctxOf(opts);
|
|
15249
15385
|
const meId = opts.owner ? resolveOwnerMailbox(ctx.store, opts.owner) : requireSelf(ctx);
|
package/dist/index.d.mts
CHANGED
|
@@ -162,6 +162,8 @@ interface MailboxStore {
|
|
|
162
162
|
putMessage(toId: string, msg: Message): void;
|
|
163
163
|
/** The full unread/read split for one agent's inbox. */
|
|
164
164
|
listInbox(id: string): InboxSnapshot;
|
|
165
|
+
/** Whether `id` has a mailbox at all — one exists once mail was first delivered to it. */
|
|
166
|
+
hasMailbox(id: string): boolean;
|
|
165
167
|
/** Ack a message by moving it out of the unread set; throws if it is not currently unread. */
|
|
166
168
|
ackMessage(id: string, msgId: string): Message;
|
|
167
169
|
/** Permanently remove a message (unread or already-acked) from `id`'s inbox; throws if absent. */
|
|
@@ -864,6 +866,7 @@ export declare class FileStore implements Store {
|
|
|
864
866
|
ensureMarker(): void;
|
|
865
867
|
putMessage(toId: string, msg: Message): void;
|
|
866
868
|
listInbox(id: string): InboxSnapshot;
|
|
869
|
+
hasMailbox(id: string): boolean;
|
|
867
870
|
ackMessage(id: string, msgId: string): Message;
|
|
868
871
|
removeMessage(id: string, msgId: string): void;
|
|
869
872
|
removeMailbox(id: string): void;
|
package/dist/index.mjs
CHANGED
|
@@ -599,6 +599,10 @@ function readShape(rows, shape) {
|
|
|
599
599
|
}
|
|
600
600
|
return { kind: "unknown" };
|
|
601
601
|
}
|
|
602
|
+
/** Whether this reader knows `harness`'s input box, so a missing box on screen means it is not drawn yet. */
|
|
603
|
+
function knowsPromptOf(harness) {
|
|
604
|
+
return harness !== void 0 && SHAPES[harness] !== void 0;
|
|
605
|
+
}
|
|
602
606
|
/**
|
|
603
607
|
* Read the input box off `screen` — a plain-text scrape of the pane — with the shape of `harness`,
|
|
604
608
|
* or, when the harness is not known, with each known shape in turn until one recognizes the box.
|
|
@@ -699,6 +703,27 @@ function copiesOn(screen, message) {
|
|
|
699
703
|
return screen.split("\n").filter((row) => row.replace(/\s+/g, " ").includes(needle)).length;
|
|
700
704
|
}
|
|
701
705
|
/**
|
|
706
|
+
* Read `screen` for the ring: whether `message` sits in the input box, and how many copies of it
|
|
707
|
+
* are on screen outside the box. With the box read by its harness's shape, copies inside the box are
|
|
708
|
+
* left out of the count: a booting harness can take several typed copies as one paste, and those
|
|
709
|
+
* copies are staged text, not posted turns. A screen with no input box the reader knows falls back to
|
|
710
|
+
* cyber-mux's `isStaged`, and the box's copy is counted with the rest.
|
|
711
|
+
*/
|
|
712
|
+
function readRing(screen, message, harness) {
|
|
713
|
+
const box = screen ? readPrompt(screen, harness) : { kind: "unknown" };
|
|
714
|
+
if (box.kind === "unknown") return {
|
|
715
|
+
boxKnown: false,
|
|
716
|
+
staged: isStaged(screen, message),
|
|
717
|
+
outside: copiesOn(screen, message)
|
|
718
|
+
};
|
|
719
|
+
const inBox = box.kind === "draft" ? copiesOn(box.text, message) : 0;
|
|
720
|
+
return {
|
|
721
|
+
boxKnown: true,
|
|
722
|
+
staged: inBox > 0,
|
|
723
|
+
outside: copiesOn(screen, message) - inBox
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
702
727
|
* Submit `message` to `target` as a taken turn: cyber-mux's `nudge`, with one more way to see that the
|
|
703
728
|
* turn was taken.
|
|
704
729
|
*
|
|
@@ -718,18 +743,32 @@ function copiesOn(screen, message) {
|
|
|
718
743
|
* queue. Text that is neither staged nor posted is lost: after one more settle for a slow redraw, it
|
|
719
744
|
* is typed again. It is opt-in because it needs the posted copy to stay on screen: a busy peer's
|
|
720
745
|
* output can scroll earlier copies away, so a ring to it could read as lost and be typed twice.
|
|
746
|
+
*
|
|
747
|
+
* With `requirePosted` and a harness whose input box this reader knows, nothing is typed until the
|
|
748
|
+
* harness has drawn that box, for up to the
|
|
749
|
+
* whole retry budget. Keys typed while a harness boots are buffered, and the harness takes them as one
|
|
750
|
+
* paste once it draws the box: each Enter becomes a newline, so re-typed copies pile up in the box
|
|
751
|
+
* unsent. A box that never shows within the budget is typed into anyway, as before.
|
|
752
|
+
*
|
|
753
|
+
* Where the input box can be read, copies of the text inside it are not counted as posted: only a
|
|
754
|
+
* copy outside the box means the harness took the turn.
|
|
721
755
|
*/
|
|
722
756
|
async function ringTurn(adapter, exec, target, message, opts = {}) {
|
|
723
757
|
const attempts = opts.attempts ?? DEFAULT_ATTEMPTS;
|
|
724
758
|
const settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS;
|
|
725
759
|
const sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
726
760
|
if (!adapter.paneExists(exec, target)) throw new Error(`nudge failed: pane ${target.id} no longer exists — the peer's session is gone, not busy.`);
|
|
727
|
-
|
|
761
|
+
let first = adapter.read(exec, target).text;
|
|
762
|
+
if (opts.requirePosted && knowsPromptOf(opts.harness)) for (let wait = 0; wait < attempts && !readRing(first, message, opts.harness).boxKnown; wait++) {
|
|
763
|
+
await sleep(settleMs);
|
|
764
|
+
first = adapter.read(exec, target).text;
|
|
765
|
+
}
|
|
766
|
+
const before = readRing(first, message, opts.harness).outside;
|
|
728
767
|
const check = () => {
|
|
729
|
-
const
|
|
730
|
-
const
|
|
731
|
-
if (
|
|
732
|
-
return
|
|
768
|
+
const seen = readRing(adapter.read(exec, target).text, message, opts.harness);
|
|
769
|
+
const posted = seen.outside >= before + (seen.boxKnown || !seen.staged ? 1 : 2);
|
|
770
|
+
if (seen.staged) return posted ? "taken" : "staged";
|
|
771
|
+
return posted || !opts.requirePosted ? "taken" : "lost";
|
|
733
772
|
};
|
|
734
773
|
adapter.submit(exec, target, message);
|
|
735
774
|
await sleep(settleMs);
|
|
@@ -829,7 +868,8 @@ async function wakeSpawn(getAdapter, exec, input, nudgeOpts = SPAWN_NUDGE_OPTS,
|
|
|
829
868
|
const doorbell = (input.resumed ? resumeDoorbell : spawnDoorbell)(input.briefPath);
|
|
830
869
|
await withDraftGuard(adapter, exec, input.target, () => ringTurn(adapter, exec, input.target, doorbell, {
|
|
831
870
|
...nudgeOpts,
|
|
832
|
-
requirePosted: true
|
|
871
|
+
requirePosted: true,
|
|
872
|
+
harness: input.harness
|
|
833
873
|
}), {
|
|
834
874
|
...guardOpts,
|
|
835
875
|
harness: input.harness,
|
|
@@ -2857,6 +2897,10 @@ var FileStore = class {
|
|
|
2857
2897
|
read: read ? readMessages(read) : []
|
|
2858
2898
|
};
|
|
2859
2899
|
}
|
|
2900
|
+
hasMailbox(id) {
|
|
2901
|
+
const dir = readPathOrUndefined(() => paths.inboxDir(this.root, id));
|
|
2902
|
+
return dir !== void 0 && existsSync(dir);
|
|
2903
|
+
}
|
|
2860
2904
|
ackMessage(id, msgId) {
|
|
2861
2905
|
const src = readPathOrUndefined(() => paths.messageFile(this.root, id, msgId));
|
|
2862
2906
|
if (!src || !existsSync(src)) throw new Error(`"${msgId}" is not an unread message in this inbox`);
|
package/package.json
CHANGED
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.
|
|
4
|
+
"version": "1.9.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.
|
|
20
|
+
> with the same arguments: `npx -y cyberlegion@1.9.0`.
|
|
21
21
|
|
|
22
22
|
## 1. Resolve the agent definition
|
|
23
23
|
|