cyberlegion 0.3.2 → 0.5.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 +20 -0
- package/.codex-plugin/plugin.json +20 -0
- package/.plugin/pins.json +3 -0
- package/agents/headless-legate.md +59 -0
- package/bin/cyberlegion.mjs +18 -1
- package/dist/cli.mjs +7823 -72
- package/dist/index.d.mts +262 -11
- package/dist/index.mjs +2298 -4
- package/package.json +10 -3
- package/plugin.json +24 -0
- package/readme.md +63 -0
- package/skills/dispatch-governance/README.md +29 -0
- package/skills/dispatch-governance/SKILL.md +143 -0
- package/skills/dispatch-governance/scripts/cyberlegion.mjs +31 -0
- package/skills/init-cyberlegion/README.md +14 -0
- package/skills/init-cyberlegion/SKILL.md +112 -0
- package/skills/init-cyberlegion/scripts/cyberlegion.mjs +31 -0
- package/skills/legate/README.md +22 -0
- package/skills/legate/SKILL.md +62 -0
- package/skills/legate/scripts/cyberlegion.mjs +31 -0
- package/skills/manage-inbox/README.md +12 -0
- package/skills/manage-inbox/SKILL.md +90 -0
- package/skills/manage-inbox/scripts/cyberlegion.mjs +31 -0
- package/skills/relay-governance/README.md +36 -0
- package/skills/relay-governance/SKILL.md +204 -0
- package/skills/relay-governance/scripts/cyberlegion.mjs +31 -0
- package/skills/session-adapter-governance/README.md +17 -0
- package/skills/session-adapter-governance/SKILL.md +74 -0
- package/skills/subagent-backend-governance/README.md +35 -0
- package/skills/subagent-backend-governance/SKILL.md +99 -0
- package/skills/subagent-backend-governance/scripts/cyberlegion.mjs +31 -0
- package/dist/cli.d.mts +0 -12
- package/dist/watch-D33fQcUI.mjs +0 -2044
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cyberlegion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Harness-agnostic agent session spawning and messaging over the filesystem",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -30,7 +30,13 @@
|
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"bin",
|
|
33
|
-
"dist"
|
|
33
|
+
"dist",
|
|
34
|
+
"plugin.json",
|
|
35
|
+
".claude-plugin",
|
|
36
|
+
".codex-plugin",
|
|
37
|
+
".plugin",
|
|
38
|
+
"skills",
|
|
39
|
+
"agents"
|
|
34
40
|
],
|
|
35
41
|
"dependencies": {
|
|
36
42
|
"commander": "^14.0.3",
|
|
@@ -38,6 +44,7 @@
|
|
|
38
44
|
},
|
|
39
45
|
"devDependencies": {
|
|
40
46
|
"@types/node": "^24.10.1",
|
|
47
|
+
"smol-toml": "^1.8.0",
|
|
41
48
|
"tsdown": "^0.22.0",
|
|
42
49
|
"tsx": "^4.22.3",
|
|
43
50
|
"typescript": "^7.0.2",
|
|
@@ -53,7 +60,7 @@
|
|
|
53
60
|
"scripts": {
|
|
54
61
|
"build": "tsdown",
|
|
55
62
|
"check:metaphor-free": "tsx src/check-metaphor-free.ts",
|
|
56
|
-
"dev": "tsx src/
|
|
63
|
+
"dev": "tsx src/dev.ts",
|
|
57
64
|
"test": "vitest run src",
|
|
58
65
|
"test:watch": "vitest",
|
|
59
66
|
"typecheck": "tsc --noEmit"
|
package/plugin.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "cyberlegion",
|
|
4
|
+
"version": "0.5.0",
|
|
5
|
+
"description": "Agent session spawning, messaging, and dispatch — the Legion and its Legate.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "unional"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/cyberuni/cyberlegion",
|
|
10
|
+
"repository": "https://github.com/cyberuni/cyberlegion",
|
|
11
|
+
"keywords": ["agent", "dispatch", "inter-agent", "multiplexer"],
|
|
12
|
+
"extensions": {
|
|
13
|
+
"org.cyberuni.universal-plugin": {
|
|
14
|
+
"vendors": ["claude-code", "codex"],
|
|
15
|
+
"packagePath": ".",
|
|
16
|
+
"skills": "./skills",
|
|
17
|
+
"agents": ["./agents/headless-legate.md"],
|
|
18
|
+
"harnesses": {
|
|
19
|
+
"claude-code": {},
|
|
20
|
+
"codex": {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/readme.md
CHANGED
|
@@ -98,6 +98,69 @@ Top-level shortcuts are provided for the common verbs: `spawn`, `send`, `inbox`,
|
|
|
98
98
|
| `--space <path>` | Isolate the hub root (overrides the global hub / `$CYBERLEGION_ROOT`) |
|
|
99
99
|
| `--format <format>` | Output format: `toon` (default) or `json` |
|
|
100
100
|
|
|
101
|
+
## Agent plugin
|
|
102
|
+
|
|
103
|
+
Agent session spawning, messaging, and dispatch — harness-agnostic, MCP-free. The foundation both
|
|
104
|
+
SDD and cyberfleet build on: a **Legion** of addressable agent units, mustered and reaped, commanded
|
|
105
|
+
and communicating over the filesystem.
|
|
106
|
+
|
|
107
|
+
### The console
|
|
108
|
+
|
|
109
|
+
The `cyberlegion` CLI is the cold, deterministic mechanism: identity (`unit register`, `unit claim`,
|
|
110
|
+
`unit who`), warm peer sessions in their own git worktrees (`unit spawn`, `unit close`,
|
|
111
|
+
`unit prune`), durable mail (`mail send`, `mail inbox`, `mail read`, `mail await`),
|
|
112
|
+
agent-definition resolution (`agent resolve`), hook registration (`init`), and
|
|
113
|
+
admin/diagnostics (`mux doctor`, `admin migrate`). It never decides *when* to spawn a peer
|
|
114
|
+
versus a subagent, and it carries no dispatch/result-slot primitives of its own — a cold subagent
|
|
115
|
+
returns via the caller's own Task-result, a warm peer via `mail await`.
|
|
116
|
+
|
|
117
|
+
### The Legate
|
|
118
|
+
|
|
119
|
+
The **Legate** is the routing brain on top of the console — the judgment the CLI deliberately does
|
|
120
|
+
not carry. In an attended session it runs in-session as `dispatch-governance`; with no user channel
|
|
121
|
+
it is realized headless as the `headless-legate` agent. Given an intent (fulfill a role with a brief, expect a
|
|
122
|
+
verdict), it reads the target agent-definition's `warm`/`interactive` tags and the environment's
|
|
123
|
+
multiplexer availability, then picks exactly one strategy:
|
|
124
|
+
|
|
125
|
+
- **channel** — a warm, interactive peer in its own pane (`unit spawn` + `mail await` on the thread)
|
|
126
|
+
- **run-inline** — no multiplexer to host a peer, so the caller does the work itself, in-session
|
|
127
|
+
- **subagent** — a cold, one-shot unit realized via the caller's own Task tool, taking its
|
|
128
|
+
Task-result (final returned message) as the verdict
|
|
129
|
+
|
|
130
|
+
### The gateway
|
|
131
|
+
|
|
132
|
+
`legate` (the skill) is the thin front door — classify the request (send mail, check inbox,
|
|
133
|
+
spawn/close a unit, wait for a reply, dispatch work) and either run the matching CLI call directly or
|
|
134
|
+
hand routing judgment to `dispatch-governance`. It loads no governance itself and writes no state.
|
|
135
|
+
|
|
136
|
+
### Onboarding and the owner mailbox
|
|
137
|
+
|
|
138
|
+
Two more user-facing skills sit beside the gateway:
|
|
139
|
+
|
|
140
|
+
- **`init-cyberlegion`** — the onboarding front door: probe the environment, register the
|
|
141
|
+
mail-surfacing hook so incoming mail reaches you mid-session, and (only in a root session, only on
|
|
142
|
+
an explicit yes) bind this pane as the durable `legate` owner inbox. Every step is a
|
|
143
|
+
`cyberlegion` CLI call; the skill holds the conversation and the judgment.
|
|
144
|
+
- **`manage-inbox`** — the human's surface for that **owner mailbox**: the hub-level,
|
|
145
|
+
session-independent inbox a standing identity holds, where frameless agents (cron-started, no
|
|
146
|
+
parent frame) push their reports. It's how you read and ack those reports from whichever session
|
|
147
|
+
you happen to be in.
|
|
148
|
+
|
|
149
|
+
### Installation
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx skills add cyberuni/cyberplace --plugin cyberlegion --global
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This package doubles as the plugin root: `plugin.json`, the generated `.claude-plugin/` and
|
|
156
|
+
`.codex-plugin/` manifests, `skills/`, and `agents/` all ship in the npm tarball. The bundle
|
|
157
|
+
Each skill that runs the CLI carries a launcher, `scripts/cyberlegion.mjs`, that runs the CLI
|
|
158
|
+
shipped in this same package (`bin/cyberlegion.mjs` over the committed `dist/cli.mjs`), so a skill
|
|
159
|
+
never fetches a copy through npx and always runs the version its own text describes. Where the
|
|
160
|
+
launcher cannot be resolved, a skill names one fallback pinned to the version that shipped it,
|
|
161
|
+
`npx -y cyberlegion@<version>`. `pnpm version` rewrites those pins and `.plugin/pins.json` at every
|
|
162
|
+
release, and the test suite fails on a stale one.
|
|
163
|
+
|
|
101
164
|
## License
|
|
102
165
|
|
|
103
166
|
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# dispatch-governance
|
|
2
|
+
|
|
3
|
+
Partial Skill: invoke by name only — the Legate's routing brain. Not user-invocable — loaded by the `legate` gateway
|
|
4
|
+
in-session on a dispatch intent, and by the `headless-legate` agent when there is no user channel.
|
|
5
|
+
|
|
6
|
+
## When it loads
|
|
7
|
+
|
|
8
|
+
- The `legate` gateway classified a request as "dispatch work to fulfill a role and expect a
|
|
9
|
+
verdict back."
|
|
10
|
+
- The `headless-legate` agent realizes the same flow headless.
|
|
11
|
+
|
|
12
|
+
## What it does
|
|
13
|
+
|
|
14
|
+
- Resolves the target agent definition's `warm`/`interactive` tags (`cyberlegion agent resolve`).
|
|
15
|
+
- Probes the environment for a multiplexer (`cyberlegion mux doctor`).
|
|
16
|
+
- Picks exactly one of three strategies — **channel** (warm peer: `unit spawn` + `mail await`),
|
|
17
|
+
**run-inline** (caller does the work itself), or **subagent** (cold, one-shot, via
|
|
18
|
+
`subagent-backend-governance`) — and executes it by composing the `cyberlegion` CLI primitives.
|
|
19
|
+
- Folds in the wake-matrix decision (bounded await vs doorbell vs run-inline, gated on a verified
|
|
20
|
+
multiplexer) that the CLI no longer encodes.
|
|
21
|
+
- Defines the `subagent | channel` seam a dependent (SDD, ADR-0023) references by intent, never by
|
|
22
|
+
a literal command name.
|
|
23
|
+
|
|
24
|
+
## What it does not do
|
|
25
|
+
|
|
26
|
+
- Never lets the CLI auto-route — there is no `--backend auto` flag, and no `dispatch` command
|
|
27
|
+
group at all; `unit spawn`/`mail await`/`unit nudge` are composed here deliberately.
|
|
28
|
+
- Never invokes a harness's Task tool directly (that happens inside `subagent-backend-governance`,
|
|
29
|
+
using the caller's own tool).
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dispatch-governance
|
|
3
|
+
description: "Partial Skill: invoke by name only — the Legate's routing brain — picks and executes a dispatch strategy for a role intent. Loaded in-session by the legate gateway, and by the headless-legate agent headless. Not triggered by users directly."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Dispatch Governance
|
|
8
|
+
|
|
9
|
+
The judgment the `cyberlegion` CLI deliberately does not carry. The CLI is dumb hands — it never
|
|
10
|
+
auto-routes, never picks a backend, never invokes a Task tool itself. This governance is the one
|
|
11
|
+
place that judgment lives, loaded in-session by the `legate` gateway on a dispatch intent, and
|
|
12
|
+
realized headless by the `headless-legate` agent. Both loads run the exact same procedure below.
|
|
13
|
+
|
|
14
|
+
**Input:** an intent to fulfill role `R` with brief `B`, optionally expecting a result that
|
|
15
|
+
satisfies verdict schema `V`.
|
|
16
|
+
|
|
17
|
+
> **Running the CLI.** Every `node scripts/cyberlegion.mjs …` command below runs the `cyberlegion` CLI
|
|
18
|
+
> this plugin ships. The path is relative to this skill's own directory, not the working directory.
|
|
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@0.5.0`.
|
|
21
|
+
|
|
22
|
+
## 1. Resolve the agent definition
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
node scripts/cyberlegion.mjs agent resolve <R> --format json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Read `model`, `effort`, `harness`, `warm`, `interactive` off the resolved def. `warm` marks a role
|
|
29
|
+
that should run as a live peer session (not a fire-and-forget unit); `interactive` marks a role that
|
|
30
|
+
may need to grill the caller or converse over multiple rounds, not just return one result.
|
|
31
|
+
|
|
32
|
+
## 2. Probe the environment
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node scripts/cyberlegion.mjs mux doctor --format json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Read whether a multiplexer was ancestry-discovered (tmux/herdr) — a **channel** strategy needs one
|
|
39
|
+
to open a pane in; its absence rules that strategy out regardless of what the agent def wants.
|
|
40
|
+
|
|
41
|
+
## 3. Pick exactly one strategy
|
|
42
|
+
|
|
43
|
+
| `warm` | `interactive` | mux present | Strategy |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| true | true | true | **channel** |
|
|
46
|
+
| true | true | false | **run-inline** |
|
|
47
|
+
| false or true | false | — | **subagent** |
|
|
48
|
+
| false | true | — | **error** — unroutable def |
|
|
49
|
+
|
|
50
|
+
- **channel** — warm, interactive, and a pane is available: spin up a live peer that can converse
|
|
51
|
+
and mail back over rounds. Compose the primitives directly — no single CLI command does this:
|
|
52
|
+
|
|
53
|
+
mint a thread id, weave it (and an instruction to reply on it) into brief `B`, then:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node scripts/cyberlegion.mjs unit spawn --agent <R> --brief-file <B> --at <placement>
|
|
57
|
+
node scripts/cyberlegion.mjs mail await --thread <the-minted-thread-id> [--timeout <ms>] [--max-wait <s>]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`unit spawn` launches the peer (its brief tells it what thread to reply on); `mail await` then
|
|
61
|
+
blocks on that reply thread. Use `--at` to place the pane, `--timeout`/`--max-wait` to bound the
|
|
62
|
+
wait; a `waiting` outcome at `--max-wait` means re-run `mail await` on the same thread to keep
|
|
63
|
+
polling, not a failure.
|
|
64
|
+
|
|
65
|
+
**The wake-matrix decision** (which sub-mode of channel to run) belongs here too, since the CLI
|
|
66
|
+
carries no `selectWakePath` helper of its own:
|
|
67
|
+
|
|
68
|
+
| Environment | Wake mode |
|
|
69
|
+
|---|---|
|
|
70
|
+
| Portable default (mux unknown, or nothing verified) | Bounded await — poll `mail await` with `--max-wait`, re-arming on `waiting` ("A-loop"). |
|
|
71
|
+
| Claude Code, with an observable background task | Bounded await plus a background-task check-in ("A-prime") — same polling, but the harness's own background-task surface can short-circuit a wait. |
|
|
72
|
+
| A live foreign session behind a **verified** multiplexer (`mux doctor` reports tmux/herdr, not `none`) | Doorbell — `unit nudge` the peer's pane, then `mail await` ("B"). |
|
|
73
|
+
| Multiplexer is `none` | **Never** pick doorbell (B) — there is no pane to ring. Fall back to bounded await (A). |
|
|
74
|
+
|
|
75
|
+
- **run-inline** — the role is warm and interactive (needs a live back-and-forth), but there is no
|
|
76
|
+
multiplexer to host a peer in. **Do not delegate.** Return a `run-inline` verdict and let the
|
|
77
|
+
caller (the session that loaded this governance — e.g. the SDD conductor) do the work itself,
|
|
78
|
+
in-session. A cold subagent is the wrong substitute here: it cold-reloads its whole context every
|
|
79
|
+
round and holds no user channel, so a role that needs to converse cannot be served that way.
|
|
80
|
+
|
|
81
|
+
- **subagent** — the role is cold and one-shot (no live conversation expected, one result back).
|
|
82
|
+
Realize it via `subagent-backend-governance`: resolve the agent def → the caller's **own** harness
|
|
83
|
+
Task/subagent tool → the subagent's Task-result (its own final returned message) is the verdict.
|
|
84
|
+
The CLI cannot invoke a Task tool itself — that is always the caller's own tool, never
|
|
85
|
+
`cyberlegion`'s — and it collects nothing on the caller's behalf; there is no result file.
|
|
86
|
+
|
|
87
|
+
- **error (unroutable def)** — `interactive` with `warm` unset (`false`) has no strategy. A role that
|
|
88
|
+
needs live back-and-forth (`interactive`) must be a live peer (`warm`); a cold one-shot must not be
|
|
89
|
+
`interactive` (`subagent-backend-governance` is one-shot by design). So this pair is a **malformed
|
|
90
|
+
def**, not a routing choice — **fail loud** here, naming the def and the contradictory tags, so the
|
|
91
|
+
author fixes it. It is **not** a `needsInput` (that is for an under-specified task, not a bad def),
|
|
92
|
+
and it is **never** silently swept into **subagent**. This is the Legate's judgment, not the CLI's:
|
|
93
|
+
`agent resolve` reports the raw tags; only this governance holds the table that knows the pair maps
|
|
94
|
+
to nothing.
|
|
95
|
+
|
|
96
|
+
The CLI never chooses between these on its own — `unit spawn` + `mail await` only run in this
|
|
97
|
+
sequence when this governance picked **channel**; the caller's Task tool only runs when it picked
|
|
98
|
+
**subagent**; picking **run-inline** means neither runs at all.
|
|
99
|
+
|
|
100
|
+
## The `subagent | channel` seam
|
|
101
|
+
|
|
102
|
+
This is the seam a dependent (e.g. SDD, ADR-0023) references **by intent** — "dispatch a role
|
|
103
|
+
fulfillment and expect a verdict" — never by pinning `subagent` or `channel` as a literal command
|
|
104
|
+
name. The dependent states its intent (role, brief, verdict schema) and this governance decides the
|
|
105
|
+
mechanism; a dependent that hardcodes "always subagent" or "always channel" has broken the seam and
|
|
106
|
+
coupled itself to a mechanism instead of an intent.
|
|
107
|
+
|
|
108
|
+
## Result shape — DispatchResult
|
|
109
|
+
|
|
110
|
+
Every strategy resolves to one shape the caller can handle uniformly:
|
|
111
|
+
|
|
112
|
+
```jsonc
|
|
113
|
+
{
|
|
114
|
+
"strategy": "subagent" | "channel" | "run-inline",
|
|
115
|
+
"id": "<unit id, when one was spawned>", // omitted for run-inline
|
|
116
|
+
"verdict": "matched" | "waiting" | "timed-out" | "run-inline",
|
|
117
|
+
"result": { /* the subagent's Task-result, or the mail-await reply body, as-is */ },
|
|
118
|
+
"needsInput": ["<question>", "..."] // present only when the callee could not complete
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`result` is carried through unvalidated today — structured verdict-schema checking against `V` is a
|
|
123
|
+
deferred capability (`mail --verdict-schema`), not present in this CLI.
|
|
124
|
+
|
|
125
|
+
`waiting` (channel path, `--max-wait` cap hit) and `timed-out` (channel path, `--timeout` elapsed)
|
|
126
|
+
are re-armable and terminal respectively — treat them the same way `mail await`'s own three
|
|
127
|
+
outcomes are treated; never silently retry past a `timed-out`. A `needsInput` result on the subagent
|
|
128
|
+
path means the unit hit its own leash and returned a needs-input result rather than guessing.
|
|
129
|
+
|
|
130
|
+
**How you relay that `needsInput` (or your own) is `relay-governance`, not this skill.** Report/ask
|
|
131
|
+
transport is keyed on the reporting agent's own lifecycle — a framed callee returns `needsInput` to
|
|
132
|
+
its caller; a bare cron session with no frame pushes mail to the standing owner and exits. Load
|
|
133
|
+
`relay-governance` for the fork; this skill owns **strategy** choice (channel / run-inline /
|
|
134
|
+
subagent), relay owns how the result or an unanswerable question gets home.
|
|
135
|
+
|
|
136
|
+
## Non-goals
|
|
137
|
+
|
|
138
|
+
- **No auto-routing in the CLI.** The CLI carries no `dispatch` command group at all — `unit spawn`,
|
|
139
|
+
`mail await`, `unit nudge`, and the caller's own Task tool are invoked deliberately by this
|
|
140
|
+
governance (or `subagent-backend-governance`), never by a `--backend auto` flag — no such flag
|
|
141
|
+
exists.
|
|
142
|
+
- **No mid-flight strategy switch.** Once a strategy is picked for one dispatch, it runs to its one
|
|
143
|
+
of the three outcomes above; a failed subagent is not silently retried as a channel.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs the cyberlegion CLI this plugin ships, so the skill beside it never fetches a copy through npx.
|
|
3
|
+
// The package root is three levels up from this file (skills/<skill>/scripts/), resolved from the
|
|
4
|
+
// file's own location: the working directory is the repository the agent is working on, not the plugin.
|
|
5
|
+
// Arguments, output, and the exit code pass straight through the package's bin.
|
|
6
|
+
//
|
|
7
|
+
// A skill folder installed on its own has no package around it. Then this runs the published CLI of
|
|
8
|
+
// the version that shipped the skill instead, so the skill still works, only slower. The version sync
|
|
9
|
+
// run at release keeps FALLBACK at the package version.
|
|
10
|
+
import { spawnSync } from 'node:child_process'
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
|
|
14
|
+
const FALLBACK = 'npx -y cyberlegion@0.5.0'
|
|
15
|
+
const bin = new URL('../../../bin/cyberlegion.mjs', import.meta.url)
|
|
16
|
+
|
|
17
|
+
if (existsSync(fileURLToPath(bin))) {
|
|
18
|
+
await import(bin.href)
|
|
19
|
+
} else {
|
|
20
|
+
process.stderr.write(`cyberlegion: no plugin package around this skill; running ${FALLBACK}\n`)
|
|
21
|
+
const [command, ...args] = FALLBACK.split(' ')
|
|
22
|
+
const res = spawnSync(command, [...args, ...process.argv.slice(2)], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
shell: process.platform === 'win32',
|
|
25
|
+
})
|
|
26
|
+
if (res.error) {
|
|
27
|
+
process.stderr.write(`cyberlegion: could not run ${FALLBACK}: ${res.error.message}\n`)
|
|
28
|
+
process.exit(1)
|
|
29
|
+
}
|
|
30
|
+
process.exit(res.status ?? 1)
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# init-cyberlegion
|
|
2
|
+
|
|
3
|
+
The onboarding front door to the Legion — walks a session through getting `cyberlegion` working in
|
|
4
|
+
this repo: probe the environment, register the surfacing hook, and (root session only, only on an
|
|
5
|
+
explicit yes) bind this pane as the durable `legate` owner inbox.
|
|
6
|
+
|
|
7
|
+
User-facing. Triggers on "set up cyberlegion", "onboard the legion", "register the cyberlegion
|
|
8
|
+
surfacing hook", "make this pane my main legion inbox", "get cyberlegion working in this repo". Wraps
|
|
9
|
+
`cyberlegion mux doctor` / `init` / `unit register --standing` / `attach` — every mechanic is a
|
|
10
|
+
CLI call, no filesystem or hub state touched directly.
|
|
11
|
+
|
|
12
|
+
Distinct from `legate` (spawn/mail/dispatch a peer) and `manage-inbox` (read/ack owner mail once
|
|
13
|
+
bound). See `SKILL.md` for the full flow (probe → register hook → detect root vs spawned →
|
|
14
|
+
consent-gated bind).
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: init-cyberlegion
|
|
3
|
+
description: "Use this skill to set up or onboard cyberlegion in this session or repo — probe the environment, register the mail-surfacing hook, and (in a root session, on your yes) bind this pane as the durable legate owner inbox. Triggers: 'set up cyberlegion', 'onboard the legion', 'register the cyberlegion surfacing hook', 'make this pane my main legion inbox', 'get cyberlegion working in this repo'. Not for spawning/messaging/dispatching a peer (that is legate), reading or acking owner mail (that is manage-inbox), or unrelated init like a git repo, npm package, or commit discipline."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# init-cyberlegion
|
|
7
|
+
|
|
8
|
+
The onboarding front door to the Legion — a thin, user-invocable wrapper that walks a session through
|
|
9
|
+
getting `cyberlegion` working in this repo: probe the environment, register the surfacing hook, and
|
|
10
|
+
(only in a root session, only on an explicit yes) bind this pane as the durable `legate` owner inbox.
|
|
11
|
+
It is a **thin wrapper**: every mechanic is a `cyberlegion` CLI call. The skill holds the *conversation
|
|
12
|
+
and the judgment* — is this a root session? should we ask to bind? what does the environment look
|
|
13
|
+
like? — the CLI holds all the *mechanism*.
|
|
14
|
+
|
|
15
|
+
> **Running the CLI.** Every `node scripts/cyberlegion.mjs …` command below runs the `cyberlegion` CLI
|
|
16
|
+
> this plugin ships. The path is relative to this skill's own directory, not the working directory.
|
|
17
|
+
>
|
|
18
|
+
> **Version pin.** Resolve the CLI version **once, before the flow**, by reading the plugin's bundled
|
|
19
|
+
> map at `${CLAUDE_PLUGIN_ROOT}/.plugin/pins.json` — a flat `{ "<package>": "<version>" }` map the
|
|
20
|
+
> release version flow keeps equal to the shipped package version. Look up the `cyberlegion` key:
|
|
21
|
+
>
|
|
22
|
+
> - **A version is found** → pass it to the hook registration in step 2 as `init --pin <version>`
|
|
23
|
+
> so the installed surfacing hook is pinned to the same shipped version. If you cannot resolve
|
|
24
|
+
> `scripts/cyberlegion.mjs`, run `npx -y cyberlegion@<version>` in its place, with the same arguments.
|
|
25
|
+
> - **No `pins.json`, no `cyberlegion` key, or a malformed map** (an unbundled workspace checkout) →
|
|
26
|
+
> pass **no** `--pin`; if you cannot resolve `scripts/cyberlegion.mjs`, fall back to the unpinned
|
|
27
|
+
> `npx -y cyberlegion` form. **Never invent a version number.**
|
|
28
|
+
>
|
|
29
|
+
> Do not scrape the version from prose.
|
|
30
|
+
|
|
31
|
+
## Flow
|
|
32
|
+
|
|
33
|
+
### 1. Probe the environment
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node scripts/cyberlegion.mjs mux doctor
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run this **before** touching the hook or any identity. It reports `harness`, `mux`, `pane`,
|
|
40
|
+
`hubRoot`, and `selfId` — read it to learn the environment (is there a multiplexer? a pane?) and to
|
|
41
|
+
detect root vs spawned (see step 3). Narrate a short, grounded summary of what it found — do not
|
|
42
|
+
invent facts the probe did not report.
|
|
43
|
+
|
|
44
|
+
### 2. Register the surfacing hook
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node scripts/cyberlegion.mjs init --pin <version>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Pass `--pin <version>` with the version resolved above so the installed hook is pinned to the shipped
|
|
51
|
+
version; **omit `--pin`** when the map yielded no version.
|
|
52
|
+
|
|
53
|
+
Auto-detect is the default — no `--agent` flag. Pass `--agent <name>` **only** when `mux doctor` could
|
|
54
|
+
not auto-detect the harness, or the user named one explicitly (it composes with `--pin`):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
node scripts/cyberlegion.mjs init --pin <version> --agent <name>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This step is **idempotent**: if the hook is already registered, `init` reports `already present` —
|
|
61
|
+
that is a clean no-op, never a duplicate registration and never an error.
|
|
62
|
+
|
|
63
|
+
### 3. Detect root vs spawned — derived, never asked
|
|
64
|
+
|
|
65
|
+
Read the probe's `selfId` from step 1. A **root** session has `spawnedBy` unset; a **spawned** unit
|
|
66
|
+
has it set. Derive this from the probe — never ask the user to declare it.
|
|
67
|
+
|
|
68
|
+
- **Spawned (non-root) unit** — stop here, right after the hook. Do not offer to bind; a spawned unit
|
|
69
|
+
is never the owner inbox.
|
|
70
|
+
- **A hook-only request in a root session** ("just register the surfacing hook") — also stop here.
|
|
71
|
+
Registering the hook satisfies the ask; do not proceed to the bind offer unasked.
|
|
72
|
+
- **Root session, broader onboarding intent, no `legate` owner bound yet** — continue to step 4.
|
|
73
|
+
- **Root session where a `legate` owner is already bound** — stop; do not re-ask, do not re-mint.
|
|
74
|
+
|
|
75
|
+
### 4. Ask before binding — never silent
|
|
76
|
+
|
|
77
|
+
Only a root session with no `legate` owner bound is offered the bind. Ask plainly, e.g.:
|
|
78
|
+
|
|
79
|
+
> "This looks like a root session with no legate owner bound yet. Bind this pane as the main legate
|
|
80
|
+
> owner inbox?"
|
|
81
|
+
|
|
82
|
+
- **User declines** — the registered hook stays in place; nothing else runs. Do not mint or bind.
|
|
83
|
+
- **User agrees explicitly** — proceed to step 5.
|
|
84
|
+
- **Already bound** — never reach this ask (see step 3).
|
|
85
|
+
|
|
86
|
+
### 5. On an explicit yes — mint and bind
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
node scripts/cyberlegion.mjs unit register --standing --handle legate
|
|
90
|
+
node scripts/cyberlegion.mjs attach
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Run these **in this order** and only after the explicit yes: mint the durable, session-independent
|
|
94
|
+
`legate` owner inbox first, then bind the current pane as that owner's live presence.
|
|
95
|
+
|
|
96
|
+
**Non-mux parity.** If the probe reported no multiplexer or pane, `attach` is a no-op —
|
|
97
|
+
that is expected, not a failure. Still run `unit register --standing --handle legate` on yes, and complete
|
|
98
|
+
**without erroring**. The root session surfaces owner mail via the `!spawnedBy` fallback instead of a
|
|
99
|
+
bound pane.
|
|
100
|
+
|
|
101
|
+
## Boundaries
|
|
102
|
+
|
|
103
|
+
- Every mechanic here is a `cyberlegion` CLI call — this skill writes no hub state and invents no
|
|
104
|
+
config format. Its only filesystem read is the plugin's own bundled `${CLAUDE_PLUGIN_ROOT}/.plugin/pins.json`
|
|
105
|
+
version map, to resolve the CLI pin.
|
|
106
|
+
- It never mints or binds an owner identity without an explicit user yes.
|
|
107
|
+
- It is distinct from `legate` — sending/spawning/dispatching to a peer is `legate`'s job, not this
|
|
108
|
+
skill's.
|
|
109
|
+
- It is distinct from `manage-inbox` — reading or acking owner mail once bound is `manage-inbox`'s
|
|
110
|
+
job, not this skill's.
|
|
111
|
+
- An unrelated `init` intent (a git repo, an npm package, commit discipline) is out of scope — defer
|
|
112
|
+
to the matching unrelated skill or decline.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs the cyberlegion CLI this plugin ships, so the skill beside it never fetches a copy through npx.
|
|
3
|
+
// The package root is three levels up from this file (skills/<skill>/scripts/), resolved from the
|
|
4
|
+
// file's own location: the working directory is the repository the agent is working on, not the plugin.
|
|
5
|
+
// Arguments, output, and the exit code pass straight through the package's bin.
|
|
6
|
+
//
|
|
7
|
+
// A skill folder installed on its own has no package around it. Then this runs the published CLI of
|
|
8
|
+
// the version that shipped the skill instead, so the skill still works, only slower. The version sync
|
|
9
|
+
// run at release keeps FALLBACK at the package version.
|
|
10
|
+
import { spawnSync } from 'node:child_process'
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
|
|
14
|
+
const FALLBACK = 'npx -y cyberlegion@0.5.0'
|
|
15
|
+
const bin = new URL('../../../bin/cyberlegion.mjs', import.meta.url)
|
|
16
|
+
|
|
17
|
+
if (existsSync(fileURLToPath(bin))) {
|
|
18
|
+
await import(bin.href)
|
|
19
|
+
} else {
|
|
20
|
+
process.stderr.write(`cyberlegion: no plugin package around this skill; running ${FALLBACK}\n`)
|
|
21
|
+
const [command, ...args] = FALLBACK.split(' ')
|
|
22
|
+
const res = spawnSync(command, [...args, ...process.argv.slice(2)], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
shell: process.platform === 'win32',
|
|
25
|
+
})
|
|
26
|
+
if (res.error) {
|
|
27
|
+
process.stderr.write(`cyberlegion: could not run ${FALLBACK}: ${res.error.message}\n`)
|
|
28
|
+
process.exit(1)
|
|
29
|
+
}
|
|
30
|
+
process.exit(res.status ?? 1)
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# legate (gateway)
|
|
2
|
+
|
|
3
|
+
The Legate's front door to the Legion — a thin classifier skill that routes an agent-to-agent
|
|
4
|
+
messaging or dispatch request to the matching `cyberlegion` CLI call, or to `dispatch-governance`
|
|
5
|
+
when the request needs routing judgment.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- You need to send or check mail with another agent session.
|
|
10
|
+
- You need to spawn, close, or nudge a peer session.
|
|
11
|
+
- You need to wait for a threaded reply.
|
|
12
|
+
- You need work done to fulfill a role and a verdict back — but do not yet know whether that should
|
|
13
|
+
be a warm peer, a cold subagent, or done inline.
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
- Classifies the request against a fixed intent → CLI-call table (mail, session, identity, admin).
|
|
18
|
+
- Hands any dispatch intent to `dispatch-governance` rather than guessing a strategy.
|
|
19
|
+
- Spawns the `headless-legate` agent when there is no user channel to relay through.
|
|
20
|
+
- Loads no other governance and writes no state — a front door only.
|
|
21
|
+
|
|
22
|
+
Every mechanic is a `cyberlegion` CLI call — harness-agnostic, MCP-free.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: legate
|
|
3
|
+
description: "Use this skill when you need to reach another agent session — send a message to a named peer or \"the pane on the right\", check your inbox, wait for a reply, spawn or close a peer session, or dispatch work to fulfill a role and get a verdict back."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# legate
|
|
7
|
+
|
|
8
|
+
The Legate — gateway skill for the Legion, the front door to agent session spawning, messaging, and dispatch.
|
|
9
|
+
It **classifies** the request and either runs the matching `cyberlegion` CLI call directly or hands
|
|
10
|
+
routing judgment to `dispatch-governance`. It is a **thin classifier**: it holds no production
|
|
11
|
+
logic, loads no other governance, and writes no state of its own.
|
|
12
|
+
|
|
13
|
+
> **Running the CLI.** Every `node scripts/cyberlegion.mjs …` command below runs the `cyberlegion` CLI
|
|
14
|
+
> this plugin ships. The path is relative to this skill's own directory, not the working directory.
|
|
15
|
+
> If you cannot resolve it, run the published CLI of the version that shipped this skill instead,
|
|
16
|
+
> with the same arguments: `npx -y cyberlegion@0.5.0`.
|
|
17
|
+
|
|
18
|
+
## Doorbell vs mailbox
|
|
19
|
+
|
|
20
|
+
Two different primitives, never confused: a **nudge** (`unit nudge <ref>`) is a dumb doorbell —
|
|
21
|
+
it rings a peer's pane and carries no content. The **payload always lives in the mailbox**
|
|
22
|
+
(`mail send`, or the brief file `unit spawn` drops for a new unit) — a peer that receives a nudge
|
|
23
|
+
reads its mail to learn *why* it was rung. Never encode meaning in a nudge itself; never skip the
|
|
24
|
+
mailbox because a nudge already fired.
|
|
25
|
+
|
|
26
|
+
## Classification map
|
|
27
|
+
|
|
28
|
+
| User intent | Handling |
|
|
29
|
+
|---|---|
|
|
30
|
+
| Send a message to a named peer, "the pane on the right", a claude/cursor peer | `node scripts/cyberlegion.mjs mail send --to <handle>` (or `unit nudge <ref>` first if the peer needs waking, then `mail send`) |
|
|
31
|
+
| Check inbox / read unread mail | `node scripts/cyberlegion.mjs mail inbox` — `mail read <msg-id>` to peek, `mail ack <msg-id>` once handled |
|
|
32
|
+
| Spawn a new peer session | `node scripts/cyberlegion.mjs unit spawn --agent <name> ...` — cyberlegion owns worktree creation; pass `--cwd <dir>` only when the caller already has a directory to spawn into (e.g. reusing an existing worktree) |
|
|
33
|
+
| Close / tear down a peer session | `node scripts/cyberlegion.mjs unit close <id>` |
|
|
34
|
+
| Wait for a threaded reply | `node scripts/cyberlegion.mjs mail await --thread <id>` |
|
|
35
|
+
| Watch mail as it streams in (observer, never acks) | `node scripts/cyberlegion.mjs mail watch` |
|
|
36
|
+
| List addressable peers | `node scripts/cyberlegion.mjs unit who` |
|
|
37
|
+
| Sweep dead peers | `node scripts/cyberlegion.mjs unit prune` |
|
|
38
|
+
| Onboard / set up cyberlegion (register the surfacing hook, bind the main owner pane, first-run setup) | **invoke the `init-cyberlegion` skill** — the interactive onboarding front door over `cyberlegion init` / `unit register --standing` / `attach` |
|
|
39
|
+
| Diagnose the environment (harness, multiplexer, pane, hub root) | `node scripts/cyberlegion.mjs mux doctor` |
|
|
40
|
+
| Register the surfacing hook by hand (low-level, explicit harness) | `node scripts/cyberlegion.mjs init --agent <harness>` — the `init-cyberlegion` skill wraps this; use directly only when scripting a known harness |
|
|
41
|
+
| Dispatch work to fulfill a role with a brief and expect a verdict back (routing judgment needed — which strategy, which agent def, warm vs cold) | **hand off to `dispatch-governance`** — do not compose `unit spawn`/`mail await`/the Task tool yourself |
|
|
42
|
+
| No user channel at all (unattended trigger, multi-unit fan-out) | **spawn the `headless-legate` agent** by name — it realizes this same gateway + `dispatch-governance` flow headless |
|
|
43
|
+
|
|
44
|
+
## Load the handling skill in-session
|
|
45
|
+
|
|
46
|
+
For an attended session (a user or peer session holds this gateway), classify and act directly —
|
|
47
|
+
spawn nothing. The one exception is a **dispatch** intent: routing which strategy to use (warm
|
|
48
|
+
channel, cold subagent, or run-inline) is judgment this gateway does not carry — load
|
|
49
|
+
`dispatch-governance` in-session and let it decide.
|
|
50
|
+
|
|
51
|
+
**Headless (no user channel): spawn `headless-legate`.** When this gateway is reached with no live
|
|
52
|
+
user or peer channel to relay through — an unattended scheduler, a multi-unit fan-out — spawn the
|
|
53
|
+
`headless-legate` agent by name. It is not a separate role: it is this same classify-then-route flow,
|
|
54
|
+
realized headless, batching `needs-input` instead of asking live.
|
|
55
|
+
|
|
56
|
+
## Boundaries
|
|
57
|
+
|
|
58
|
+
This skill never talks to the filesystem or another session directly — every mechanic is a
|
|
59
|
+
`cyberlegion` CLI call. It never chooses a dispatch strategy itself (that is
|
|
60
|
+
`dispatch-governance`'s one job) and never invokes a harness's Task/subagent tool (that is the
|
|
61
|
+
caller's own tool, used only inside `dispatch-governance`'s subagent path via
|
|
62
|
+
`subagent-backend-governance`).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs the cyberlegion CLI this plugin ships, so the skill beside it never fetches a copy through npx.
|
|
3
|
+
// The package root is three levels up from this file (skills/<skill>/scripts/), resolved from the
|
|
4
|
+
// file's own location: the working directory is the repository the agent is working on, not the plugin.
|
|
5
|
+
// Arguments, output, and the exit code pass straight through the package's bin.
|
|
6
|
+
//
|
|
7
|
+
// A skill folder installed on its own has no package around it. Then this runs the published CLI of
|
|
8
|
+
// the version that shipped the skill instead, so the skill still works, only slower. The version sync
|
|
9
|
+
// run at release keeps FALLBACK at the package version.
|
|
10
|
+
import { spawnSync } from 'node:child_process'
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
|
|
14
|
+
const FALLBACK = 'npx -y cyberlegion@0.5.0'
|
|
15
|
+
const bin = new URL('../../../bin/cyberlegion.mjs', import.meta.url)
|
|
16
|
+
|
|
17
|
+
if (existsSync(fileURLToPath(bin))) {
|
|
18
|
+
await import(bin.href)
|
|
19
|
+
} else {
|
|
20
|
+
process.stderr.write(`cyberlegion: no plugin package around this skill; running ${FALLBACK}\n`)
|
|
21
|
+
const [command, ...args] = FALLBACK.split(' ')
|
|
22
|
+
const res = spawnSync(command, [...args, ...process.argv.slice(2)], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
shell: process.platform === 'win32',
|
|
25
|
+
})
|
|
26
|
+
if (res.error) {
|
|
27
|
+
process.stderr.write(`cyberlegion: could not run ${FALLBACK}: ${res.error.message}\n`)
|
|
28
|
+
process.exit(1)
|
|
29
|
+
}
|
|
30
|
+
process.exit(res.status ?? 1)
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# manage-inbox
|
|
2
|
+
|
|
3
|
+
The human's surface for the **owner mailbox** — the durable, session-independent inbox where
|
|
4
|
+
frameless (cron-started, no-parent-frame) agents push their reports via `relay-governance`.
|
|
5
|
+
|
|
6
|
+
User-facing. Triggers on "check my inbox", "any reports for me", "read that report", "ack that",
|
|
7
|
+
"clear my owner inbox", or when a surfaced owner-mail doorbell needs acting on. Wraps the
|
|
8
|
+
`cyberlegion` CLI's owner-scoped mail commands (`mail inbox/read/ack --owner <handle>`) so a human
|
|
9
|
+
roaming across sessions manages the one owner mailbox from anywhere.
|
|
10
|
+
|
|
11
|
+
Read is a deliberate `mail ack --owner` — surfacing shows a report inline but is never a read
|
|
12
|
+
receipt. See `SKILL.md` for the full flow (resolve owner → list → read → ack → reply).
|