cyberlegion 0.1.0 → 0.3.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/bin/cyberlegion.mjs +2 -1
- package/dist/cli.d.mts +12 -1
- package/dist/cli.mjs +328 -265
- package/dist/index.d.mts +105 -286
- package/dist/index.mjs +4 -28
- package/dist/watch-BZmEAqUo.mjs +1791 -0
- package/package.json +7 -5
- package/readme.md +35 -30
- package/dist/watch-BuYsdwhx.mjs +0 -1609
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Exec, Exec as Exec$1, MuxAdapter, MuxPlacement, MuxProbe, NudgeOptions, probeMultiplexer } from "cyber-mux";
|
|
2
|
+
import { assertDistinctFromPrimary, gitWorktreeAdapter, resolvePrimaryRoot } from "cyber-mux/worktree";
|
|
3
|
+
|
|
1
4
|
//#region src/store/store.d.ts
|
|
2
5
|
interface Message {
|
|
3
6
|
id: string;
|
|
@@ -12,7 +15,10 @@ interface Message {
|
|
|
12
15
|
sentAt: string;
|
|
13
16
|
}
|
|
14
17
|
type Harness = 'claude' | 'cursor' | 'codex';
|
|
15
|
-
|
|
18
|
+
/** The statuses this version writes. `spawning` is retired: spawn registers a peer `active`
|
|
19
|
+
* outright, since nothing flips it any more (the SessionStart hook injects no brief and mutates no
|
|
20
|
+
* status — see `mail/surface`). */
|
|
21
|
+
type AgentStatus = 'active' | 'idle' | 'stale' | 'exited' | 'paused';
|
|
16
22
|
interface AgentRecord {
|
|
17
23
|
id: string;
|
|
18
24
|
handle: string;
|
|
@@ -23,26 +29,47 @@ interface AgentRecord {
|
|
|
23
29
|
root: string;
|
|
24
30
|
branch?: string;
|
|
25
31
|
} | null;
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
/** Where this session lives, tagged with its multiplexer so `prune` runs the right liveness check.
|
|
33
|
+
* `null` for a session in no pane and for a standing record. `window`/`session` are tmux-only. */
|
|
34
|
+
pane?: {
|
|
35
|
+
mux: 'tmux' | 'herdr';
|
|
36
|
+
id: string;
|
|
28
37
|
window?: string;
|
|
29
38
|
session?: string;
|
|
30
39
|
} | null;
|
|
31
40
|
pid?: number;
|
|
32
|
-
status
|
|
41
|
+
/** A record migrated from an older hub (`admin migrate`) may carry a status this version no
|
|
42
|
+
* longer writes — the retired `spawning` is the known case. **Reads preserve it verbatim**: no
|
|
43
|
+
* read path validates, coerces, or normalizes a status, and none may start doing so
|
|
44
|
+
* (`mail/surface` freezes that a legacy `spawning` record keeps the status it was migrated with).
|
|
45
|
+
* A write that merely round-trips a record preserves it too.
|
|
46
|
+
*
|
|
47
|
+
* The exceptions are deliberate lifecycle writes, not normalizations: `register` asserts a session
|
|
48
|
+
* is live *now* and so writes `active`, while `prune`/`reconcile` write `exited` over whatever a
|
|
49
|
+
* record carried (`identity.ts`). A legacy record therefore does move off `spawning` — by an
|
|
50
|
+
* explicit lifecycle act, never by being read.
|
|
51
|
+
*
|
|
52
|
+
* The open member keeps the guarantee typed rather than accidental. */
|
|
53
|
+
status: AgentStatus | (string & {});
|
|
33
54
|
createdAt: string;
|
|
34
55
|
lastSeen: string;
|
|
35
56
|
brief?: string;
|
|
36
57
|
spawnedBy?: string;
|
|
37
58
|
/** Absent ⇒ session (backward compat, no migration). 'standing' = a session-independent,
|
|
38
|
-
* prune-exempt owner inbox minted by `
|
|
59
|
+
* prune-exempt owner inbox minted by `unit register --standing`. */
|
|
39
60
|
kind?: 'session' | 'standing';
|
|
61
|
+
/** Only meaningful on a `kind: 'standing'` record — the id of the unit currently standing in for
|
|
62
|
+
* it (`unit claim`), a per-record singleton pointer that moves as the principal moves between
|
|
63
|
+
* units (last claim wins). Resolved live, never trusted: a presence unit that has exited reads as
|
|
64
|
+
* no presence bound, same as if none were ever claimed — nothing self-heals a stale pointer, it
|
|
65
|
+
* stays inert until re-claimed. Independent of the hub's `mainPane` (the human's read-pane). */
|
|
66
|
+
presence?: string;
|
|
40
67
|
}
|
|
41
68
|
interface InboxSnapshot {
|
|
42
69
|
unread: Message[];
|
|
43
70
|
read: Message[];
|
|
44
71
|
}
|
|
45
|
-
/** The seam all mailbox + registry + brief
|
|
72
|
+
/** The seam all mailbox + registry + brief access goes through. */
|
|
46
73
|
interface Store {
|
|
47
74
|
/** This store's root (for path-relative concerns the Store itself doesn't cover, e.g. spawning
|
|
48
75
|
* a worktree under a project-local root distinct from this hub). */
|
|
@@ -71,22 +98,19 @@ interface Store {
|
|
|
71
98
|
removePaneIndex(pane: string): void;
|
|
72
99
|
writeBrief(agentId: string, text: string): void;
|
|
73
100
|
readBrief(agentId: string): string | undefined;
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
readResult(id: string): string | undefined;
|
|
101
|
+
/** Set (or move) the hub's single main pane — the standing owner's live presence — or clear it
|
|
102
|
+
* (`null`, a no-op when already unbound). A hub-level singleton, independent of any agent record. */
|
|
103
|
+
setMainPane(pane: string | null): void;
|
|
104
|
+
/** The hub's currently bound main pane, or undefined when none is bound. */
|
|
105
|
+
getMainPane(): string | undefined;
|
|
80
106
|
}
|
|
81
107
|
//#endregion
|
|
82
108
|
//#region src/identity.d.ts
|
|
83
|
-
|
|
84
|
-
type Exec = (cmd: string, args: string[]) => string | null;
|
|
85
|
-
declare const realExec: Exec;
|
|
109
|
+
declare const realExec: Exec$1;
|
|
86
110
|
interface IdContext {
|
|
87
111
|
store: Store;
|
|
88
112
|
env?: NodeJS.ProcessEnv;
|
|
89
|
-
exec?: Exec;
|
|
113
|
+
exec?: Exec$1;
|
|
90
114
|
now?: () => number;
|
|
91
115
|
}
|
|
92
116
|
declare function loadAgent(store: Store, id: string): AgentRecord | undefined;
|
|
@@ -94,10 +118,11 @@ declare function listAgents(store: Store): AgentRecord[];
|
|
|
94
118
|
/** Detect the harness: explicit wins, then env probes, then the tmux pane command. */
|
|
95
119
|
declare function detectHarness(explicit: string | undefined, ctx: IdContext): Harness | undefined;
|
|
96
120
|
/**
|
|
97
|
-
* Recover the calling agent's own id.
|
|
98
|
-
* pane entry is simply unregistered and must NOT
|
|
99
|
-
*
|
|
100
|
-
*
|
|
121
|
+
* Recover the calling agent's own id, mux-agnostically. Inside any multiplexer pane (tmux or herdr)
|
|
122
|
+
* the pane index is authoritative — a pane with no pane entry is simply unregistered and must NOT
|
|
123
|
+
* adopt `$CYBERLEGION_AGENT_ID` (that env fallback applies ONLY when the session is in no
|
|
124
|
+
* multiplexer pane at all). There is no shared bare "self" file — self-id is always pane-keyed or
|
|
125
|
+
* explicit via the env var.
|
|
101
126
|
*/
|
|
102
127
|
declare function resolveSelfId(ctx: IdContext): string | undefined;
|
|
103
128
|
interface RegisterInput {
|
|
@@ -106,7 +131,8 @@ interface RegisterInput {
|
|
|
106
131
|
}
|
|
107
132
|
/** Register (or idempotently refresh) this session's identity. */
|
|
108
133
|
declare function register(ctx: IdContext, input: RegisterInput): AgentRecord;
|
|
109
|
-
/** Resolve a recipient argument (id or handle) to an agent id.
|
|
134
|
+
/** Resolve a recipient argument (id or handle) to an agent id. A handle resolves to live units
|
|
135
|
+
* only — mail addressed to an exited unit lands in an inbox nobody reads. */
|
|
110
136
|
declare function resolveRecipient(store: Store, to: string): string;
|
|
111
137
|
/**
|
|
112
138
|
* Resolve a unit reference by id, handle, or its worktree branch (the unit↔CR join key: an
|
|
@@ -117,7 +143,9 @@ declare function resolveAgent(store: Store, ref: string): AgentRecord;
|
|
|
117
143
|
declare function bumpLastSeen(ctx: IdContext, id: string): void;
|
|
118
144
|
/** Refresh this session's own last-seen if it is registered — best-effort, never throws. */
|
|
119
145
|
declare function touch(ctx: IdContext): void;
|
|
120
|
-
/** Mark agents whose pane is gone or whose last-seen is stale as exited.
|
|
146
|
+
/** Mark agents whose pane is gone or whose last-seen is stale as exited. Reconcile-culls against the
|
|
147
|
+
* current mux's live set first, then falls through to the per-record paneExists + staleness check
|
|
148
|
+
* (covers the other mux and sessions outside any multiplexer pane). */
|
|
121
149
|
declare function prune(ctx: IdContext): AgentRecord[];
|
|
122
150
|
//#endregion
|
|
123
151
|
//#region src/agentdef/resolve.d.ts
|
|
@@ -168,143 +196,6 @@ interface RealizedLaunch {
|
|
|
168
196
|
* `--model` when known, `--append-system-prompt` carrying the def's instructions body) since the
|
|
169
197
|
* exact per-harness flag surface is realized later by the launching caller, not by cyberlegion. */
|
|
170
198
|
declare function realizeLaunch(def: AgentDef, opts?: RealizeLaunchOptions): RealizedLaunch;
|
|
171
|
-
interface RealizeSubagentOptions {
|
|
172
|
-
/** File the spawned subagent reads its brief from. */
|
|
173
|
-
briefFile: string;
|
|
174
|
-
/** File the spawned subagent writes its result JSON to. */
|
|
175
|
-
resultFile: string;
|
|
176
|
-
}
|
|
177
|
-
/** Build the instruction string a parent model uses to spawn its OWN harness Task subagent for
|
|
178
|
-
* `def` — cyberlegion never invokes the Task tool itself. Names `subagent_type: <def.name>` for a
|
|
179
|
-
* harness that recognizes it, but always inlines the model + full instructions too so the same
|
|
180
|
-
* instruction string is correct even when the harness has no such named subagent type. */
|
|
181
|
-
declare function realizeSubagentInstruction(def: AgentDef, opts: RealizeSubagentOptions): string;
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region src/console/worktree.d.ts
|
|
184
|
-
/** Generic multiplexer/worktree seam — no legion-specific concepts. */
|
|
185
|
-
interface WorktreeAddOptions {
|
|
186
|
-
/** The primary checkout's root — the repo `git worktree add` runs against. */
|
|
187
|
-
primaryRoot: string;
|
|
188
|
-
/** Where the new worktree should be checked out. */
|
|
189
|
-
path: string;
|
|
190
|
-
/** Branch to create the worktree on. */
|
|
191
|
-
branch: string;
|
|
192
|
-
}
|
|
193
|
-
interface Worktree {
|
|
194
|
-
root: string;
|
|
195
|
-
branch: string;
|
|
196
|
-
}
|
|
197
|
-
interface WorktreeRemoveOptions {
|
|
198
|
-
primaryRoot: string;
|
|
199
|
-
}
|
|
200
|
-
interface WorktreeAdapter {
|
|
201
|
-
add(exec: Exec, opts: WorktreeAddOptions): Worktree;
|
|
202
|
-
remove(exec: Exec, path: string, opts: WorktreeRemoveOptions): void;
|
|
203
|
-
}
|
|
204
|
-
/** The only worktree backend at MVP — plain `git worktree`. */
|
|
205
|
-
declare const gitWorktreeAdapter: WorktreeAdapter;
|
|
206
|
-
/**
|
|
207
|
-
* Resolve the primary checkout's root regardless of whether the caller's cwd is the primary
|
|
208
|
-
* checkout or a linked worktree — `--git-common-dir` always points at the main repo's `.git`.
|
|
209
|
-
*/
|
|
210
|
-
declare function resolvePrimaryRoot(exec: Exec): string;
|
|
211
|
-
/**
|
|
212
|
-
* Refuse the primary checkout: a spawned unit's resolved worktree root must never be the primary
|
|
213
|
-
* checkout itself. A unit is never run in the primary checkout.
|
|
214
|
-
*/
|
|
215
|
-
declare function assertDistinctFromPrimary(worktreeRoot: string, primaryRoot: string): void;
|
|
216
|
-
//#endregion
|
|
217
|
-
//#region src/console/session.d.ts
|
|
218
|
-
/** Generic multiplexer seam — no legion-specific concepts, so this composes with any host. */
|
|
219
|
-
/** Where a new pane/window/session should be placed relative to the caller's current one.
|
|
220
|
-
* `'workspace'` opens a genuinely separate workspace/session (herdr: `workspace create`; tmux: a
|
|
221
|
-
* new detached session) — the caller's current workspace/session is left untouched, unlike every
|
|
222
|
-
* other placement, which adds a pane/window inside it. */
|
|
223
|
-
type SessionPlacement = 'pane:right' | 'pane:down' | 'tab' | 'window' | 'workspace';
|
|
224
|
-
interface SessionOpenOptions {
|
|
225
|
-
/** Working directory the new pane/window/session should start in. */
|
|
226
|
-
cwd: string;
|
|
227
|
-
/** Command line to launch inside the new pane once it is open. */
|
|
228
|
-
launch: string;
|
|
229
|
-
/** Placement relative to the caller; defaults to 'pane:right'. */
|
|
230
|
-
at?: SessionPlacement;
|
|
231
|
-
}
|
|
232
|
-
/** Opaque handle to an open pane/window/session; backend-specific id lives in `id`. */
|
|
233
|
-
interface SessionTarget {
|
|
234
|
-
id: string;
|
|
235
|
-
}
|
|
236
|
-
interface SessionReadOptions {
|
|
237
|
-
/** How many trailing lines of output to capture; omit for the backend's default. */
|
|
238
|
-
lines?: number;
|
|
239
|
-
}
|
|
240
|
-
interface OpenInNewWorktreeOptions {
|
|
241
|
-
/** The primary checkout's root — the repo the new worktree branches from. */
|
|
242
|
-
primaryRoot: string;
|
|
243
|
-
/** Branch to create the worktree on. */
|
|
244
|
-
branch: string;
|
|
245
|
-
/** Where the new worktree should be checked out. */
|
|
246
|
-
path: string;
|
|
247
|
-
/** Command line to launch inside the new session once it is open. */
|
|
248
|
-
launch: string;
|
|
249
|
-
}
|
|
250
|
-
interface SessionAdapter {
|
|
251
|
-
/** Backend name, e.g. "tmux" or "herdr". */
|
|
252
|
-
readonly name: string;
|
|
253
|
-
/** Create a new pane/window running `opts.launch` in `opts.cwd`; returns its target handle. */
|
|
254
|
-
open(exec: Exec, opts: SessionOpenOptions): SessionTarget;
|
|
255
|
-
/**
|
|
256
|
-
* Atomically create a new worktree AND open it in a genuinely new workspace/session, when the
|
|
257
|
-
* backend has a native primitive for that combination (herdr's `worktree create` both creates
|
|
258
|
-
* the worktree and nests it under its source workspace in one call — a real organizational
|
|
259
|
-
* improvement over a separate worktree-add followed by a plain `open()`). Backends without such
|
|
260
|
-
* a primitive (tmux has none) omit this; callers fall back to creating the worktree themselves
|
|
261
|
-
* and then calling `open()` with `at: 'workspace'`.
|
|
262
|
-
*/
|
|
263
|
-
openInNewWorktree?(exec: Exec, opts: OpenInNewWorktreeOptions): {
|
|
264
|
-
target: SessionTarget;
|
|
265
|
-
worktree: Worktree;
|
|
266
|
-
};
|
|
267
|
-
/** Type text into the target session (submitted, not queued). */
|
|
268
|
-
send(exec: Exec, target: SessionTarget, text: string): void;
|
|
269
|
-
/** Capture the target session's current output. */
|
|
270
|
-
read(exec: Exec, target: SessionTarget, opts?: SessionReadOptions): string;
|
|
271
|
-
/** Move input focus to the target session; best-effort (may no-op if the backend can't). */
|
|
272
|
-
focus(exec: Exec, target: SessionTarget): void;
|
|
273
|
-
/** Close the target session. */
|
|
274
|
-
teardown(exec: Exec, target: SessionTarget): void;
|
|
275
|
-
}
|
|
276
|
-
//#endregion
|
|
277
|
-
//#region src/console/index.d.ts
|
|
278
|
-
/**
|
|
279
|
-
* Backend selection via the two-mode mux probe (`$CYBERLEGION_MUX` fast-path/override, else
|
|
280
|
-
* ancestry discovery from `$$` falling back to the `$TMUX`/`$HERDR_ENV` hint when the walk is
|
|
281
|
-
* inconclusive) — tmux/herdr map to their existing adapters; anything else is a clear error.
|
|
282
|
-
*/
|
|
283
|
-
declare function selectSessionAdapter(env: NodeJS.ProcessEnv, exec?: Exec): SessionAdapter;
|
|
284
|
-
//#endregion
|
|
285
|
-
//#region src/console/mux-probe.d.ts
|
|
286
|
-
type Mux = 'tmux' | 'herdr' | 'screen' | 'none';
|
|
287
|
-
interface MuxProbe {
|
|
288
|
-
mux: Mux;
|
|
289
|
-
pane?: string;
|
|
290
|
-
/** 'env' — trusted the $CYBERLEGION_MUX fast-path/override. 'ancestry' — walked the process tree. */
|
|
291
|
-
via: 'env' | 'ancestry';
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Two-mode multiplexer detection.
|
|
295
|
-
*
|
|
296
|
-
* Fast-path: `$CYBERLEGION_MUX` (tmux | herdr | screen | none) is trusted outright — this also
|
|
297
|
-
* serves as an OVERRIDE (`=none` forces no-mux even inside a real multiplexer). `$CYBERLEGION_MUX_PANE`
|
|
298
|
-
* carries the pane id alongside it.
|
|
299
|
-
*
|
|
300
|
-
* Discovery (else): walk the process ancestry from `$$` via `ps -o ppid=,comm= -p <pid>`, since the
|
|
301
|
-
* tool's own shell may not be the human's pane. `$TMUX`/`$HERDR_ENV` are NOT trusted alone — they
|
|
302
|
-
* are used only as a fast-positive hint the ancestry walk falls back to when the walk itself is
|
|
303
|
-
* inconclusive (e.g. `ps` unavailable), never as a substitute for it.
|
|
304
|
-
*/
|
|
305
|
-
declare function probeMultiplexer(exec: Exec, env: NodeJS.ProcessEnv, opts?: {
|
|
306
|
-
discover?: boolean;
|
|
307
|
-
}): MuxProbe;
|
|
308
199
|
//#endregion
|
|
309
200
|
//#region src/decommission.d.ts
|
|
310
201
|
interface DecommissionInput {
|
|
@@ -326,78 +217,6 @@ interface DecommissionResult {
|
|
|
326
217
|
*/
|
|
327
218
|
declare function decommission(ctx: IdContext, input: DecommissionInput): DecommissionResult;
|
|
328
219
|
//#endregion
|
|
329
|
-
//#region src/dispatch/prep.d.ts
|
|
330
|
-
interface DispatchContext {
|
|
331
|
-
store: Store;
|
|
332
|
-
}
|
|
333
|
-
interface PrepInput {
|
|
334
|
-
/** Resolve an agent def (`.agents/agents/<name>.md`) to build the instruction from. */
|
|
335
|
-
agent?: string;
|
|
336
|
-
/** Read an exact agent def file instead of resolving by name (plugin-scoped escape hatch). */
|
|
337
|
-
agentFile?: string;
|
|
338
|
-
/** Role label folded into the generic instruction when no agent def is given. */
|
|
339
|
-
role?: string;
|
|
340
|
-
/** Brief body text. */
|
|
341
|
-
briefText?: string;
|
|
342
|
-
/** Read the brief from a file, or `-` for stdin. */
|
|
343
|
-
briefFile?: string;
|
|
344
|
-
/** JSON schema file the eventual result must satisfy — carried through by callers that validate. */
|
|
345
|
-
verdictSchema?: string;
|
|
346
|
-
/** Thread id correlating a channel-path reply; defaults to the minted dispatch id. */
|
|
347
|
-
thread?: string;
|
|
348
|
-
}
|
|
349
|
-
interface DispatchEnvelope {
|
|
350
|
-
id: string;
|
|
351
|
-
thread: string;
|
|
352
|
-
briefFile: string;
|
|
353
|
-
resultFile: string;
|
|
354
|
-
instruction: string;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Allocate a dispatch: mint an id (thread defaults to it), write the brief into the Store, compute
|
|
358
|
-
* the result-slot path, and build the instruction from a resolved agent def (or a generic one).
|
|
359
|
-
* Spawns nothing — the envelope is handed to whatever spawns next (a Task tool, `channel()`, a
|
|
360
|
-
* human).
|
|
361
|
-
*/
|
|
362
|
-
declare function prep(ctx: DispatchContext, input: PrepInput, readStdin?: () => string): DispatchEnvelope;
|
|
363
|
-
//#endregion
|
|
364
|
-
//#region src/dispatch/channel.d.ts
|
|
365
|
-
interface ChannelInput extends PrepInput {
|
|
366
|
-
/** Block for the mail-thread reply and return a validated `DispatchResult`; without it, the peer
|
|
367
|
-
* is spawned and the envelope is returned so the caller can `mail await` later. */
|
|
368
|
-
wait?: boolean;
|
|
369
|
-
/** Placement relative to the caller; defaults to 'pane:right'. */
|
|
370
|
-
at?: SessionPlacement;
|
|
371
|
-
timeoutMs?: number;
|
|
372
|
-
maxWaitS?: number;
|
|
373
|
-
}
|
|
374
|
-
interface DispatchResult {
|
|
375
|
-
id: string;
|
|
376
|
-
verdict?: unknown;
|
|
377
|
-
body: string;
|
|
378
|
-
ts: number;
|
|
379
|
-
}
|
|
380
|
-
/** Thrown by a `--wait` channel call when its internal poll-cycle cap is hit with no reply yet — a
|
|
381
|
-
* clean, re-armable sentinel (mirrors `mail await`'s "waiting" outcome), never a failure. */
|
|
382
|
-
declare class DispatchWaitingError extends Error {
|
|
383
|
-
readonly thread: string;
|
|
384
|
-
constructor(thread: string);
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* `prep()` + spawn a peer session realized from the same agent def + (optionally) `awaitReply` on
|
|
388
|
-
* the thread. Requires `--agent`/`--agent-file` (a channel peer needs a real launch to realize).
|
|
389
|
-
*/
|
|
390
|
-
declare function channel(ctx: IdContext, input: ChannelInput, readStdin?: () => string): Promise<DispatchResult | DispatchEnvelope>;
|
|
391
|
-
//#endregion
|
|
392
|
-
//#region src/dispatch/collect.d.ts
|
|
393
|
-
interface CollectContext {
|
|
394
|
-
store: Store;
|
|
395
|
-
now?: () => number;
|
|
396
|
-
}
|
|
397
|
-
/** Read + validate a dispatch's result file. Throws (fail-loud) when the file is not yet written,
|
|
398
|
-
* or when it fails the optional verdict schema. */
|
|
399
|
-
declare function collect(ctx: CollectContext, id: string, verdictSchema?: string): DispatchResult;
|
|
400
|
-
//#endregion
|
|
401
220
|
//#region src/runtime/inject-inbox.d.ts
|
|
402
221
|
type HookEvent = 'SessionStart' | 'PostToolUse';
|
|
403
222
|
interface InjectPayload {
|
|
@@ -407,9 +226,13 @@ interface InjectPayload {
|
|
|
407
226
|
};
|
|
408
227
|
}
|
|
409
228
|
/**
|
|
410
|
-
* Resolve the calling agent, gather its
|
|
411
|
-
* SessionStart-style injection payload — or null when there is
|
|
412
|
-
* unregistered caller or an empty inbox never fails the harness hook).
|
|
229
|
+
* Resolve the calling agent, gather its unread mail (and a standing owner's, when this session is
|
|
230
|
+
* the hub's main pane), and return the SessionStart-style injection payload — or null when there is
|
|
231
|
+
* nothing to inject (an unregistered caller or an empty inbox never fails the harness hook).
|
|
232
|
+
*
|
|
233
|
+
* This never injects a brief. Brief delivery is the spawn wake's job: the first-turn doorbell
|
|
234
|
+
* carries the instruction and names the brief's file path, so pickup does not depend on this hook
|
|
235
|
+
* firing in the child (`unit/lifecycle`, superseding ADR-0027).
|
|
413
236
|
*/
|
|
414
237
|
declare function injectInbox(ctx: IdContext, event: string): InjectPayload | null;
|
|
415
238
|
//#endregion
|
|
@@ -423,7 +246,7 @@ interface InstallResult {
|
|
|
423
246
|
status: InstallStatus;
|
|
424
247
|
}
|
|
425
248
|
/** Register the surfacing hook into one harness's config, idempotently. */
|
|
426
|
-
declare function install(harness: Harness, projectDir?: string): InstallResult[];
|
|
249
|
+
declare function install(harness: Harness, projectDir?: string, pin?: string): InstallResult[];
|
|
427
250
|
//#endregion
|
|
428
251
|
//#region src/message.d.ts
|
|
429
252
|
interface MsgContext {
|
|
@@ -461,6 +284,21 @@ declare function ack(ctx: MsgContext, meId: string, msgId: string): Message;
|
|
|
461
284
|
/** Permanently remove a message (unread or already-acked) from the caller's inbox. */
|
|
462
285
|
declare function deleteMessage(ctx: MsgContext, meId: string, msgId: string): void;
|
|
463
286
|
//#endregion
|
|
287
|
+
//#region src/mux-select.d.ts
|
|
288
|
+
/**
|
|
289
|
+
* Backend selection via cyber-mux's two-mode mux probe, normalized through the transitional
|
|
290
|
+
* `$CYBERLEGION_MUX*` → `$CYBER_MUX*` env seam (`mux-env.ts`) — tmux/herdr map to their existing
|
|
291
|
+
* cyber-mux adapters.
|
|
292
|
+
*
|
|
293
|
+
* cyber-mux detects MORE backends than `unit/registry`'s `AgentRecord.pane` can carry a locator
|
|
294
|
+
* under (`'tmux' | 'herdr'` only). A DETECTED wezterm/zellij is refused HERE, before anything opens,
|
|
295
|
+
* naming the backend it found (mux.feature: "a detected backend a unit record cannot carry is
|
|
296
|
+
* refused before opening anything") — driving it would open a real pane no record could name,
|
|
297
|
+
* stranding a live session `prune` can never reap and no caller can nudge. Anything else (`none`,
|
|
298
|
+
* `screen`) falls through to the plain "no backend" refusal, unchanged from before the migration.
|
|
299
|
+
*/
|
|
300
|
+
declare function selectSessionAdapter(env: NodeJS.ProcessEnv, exec?: Exec): MuxAdapter;
|
|
301
|
+
//#endregion
|
|
464
302
|
//#region src/output.d.ts
|
|
465
303
|
type Format = 'toon' | 'json';
|
|
466
304
|
/** One TOON-encoded object: `key: value` lines, blank/undefined fields dropped. */
|
|
@@ -488,13 +326,13 @@ declare function fail(msg: string): never;
|
|
|
488
326
|
/** Walk up from `cwd` to the nearest git repo root; fall back to `cwd`. */
|
|
489
327
|
declare function projectRoot(cwd?: string): string;
|
|
490
328
|
/** Runs a command synchronously in `cwd`; returns trimmed stdout, or null on any failure. */
|
|
491
|
-
type Exec$
|
|
329
|
+
type Exec$2 = (cmd: string, args: string[]) => string | null;
|
|
492
330
|
interface RootOptions {
|
|
493
331
|
root?: string;
|
|
494
332
|
space?: string;
|
|
495
333
|
cwd?: string;
|
|
496
334
|
env?: NodeJS.ProcessEnv;
|
|
497
|
-
exec?: Exec$
|
|
335
|
+
exec?: Exec$2;
|
|
498
336
|
}
|
|
499
337
|
/**
|
|
500
338
|
* Resolve the cyberlegion hub root. Precedence: explicit --root/--space, then `$CYBERLEGION_ROOT`,
|
|
@@ -525,7 +363,7 @@ declare const paths: {
|
|
|
525
363
|
inboxReadDir: (root: string, id: string) => string;
|
|
526
364
|
dataDir: (root: string, id: string) => string;
|
|
527
365
|
briefFile: (root: string, id: string) => string;
|
|
528
|
-
|
|
366
|
+
mainPaneFile: (root: string) => string;
|
|
529
367
|
};
|
|
530
368
|
//#endregion
|
|
531
369
|
//#region src/session.d.ts
|
|
@@ -548,8 +386,8 @@ interface SpawnInput {
|
|
|
548
386
|
/** Spawn into this existing directory instead — creates no worktree. Mutually exclusive with
|
|
549
387
|
* `branch`/`worktreePath` (those create a worktree; this reuses one). */
|
|
550
388
|
cwd?: string;
|
|
551
|
-
/** Placement relative to the caller; defaults to '
|
|
552
|
-
at?:
|
|
389
|
+
/** Placement relative to the caller; defaults to 'tab'. */
|
|
390
|
+
at?: MuxPlacement;
|
|
553
391
|
}
|
|
554
392
|
interface SpawnResult {
|
|
555
393
|
agent: AgentRecord;
|
|
@@ -559,8 +397,10 @@ interface SpawnResult {
|
|
|
559
397
|
/**
|
|
560
398
|
* Launch a new peer session as a genuine sibling unit: create a real git worktree distinct from
|
|
561
399
|
* the primary checkout (refuse the primary checkout), open a session backend (tmux or herdr) with
|
|
562
|
-
* its cwd set to that worktree, pre-register the peer, and drop its brief as a file
|
|
563
|
-
*
|
|
400
|
+
* its cwd set to that worktree, pre-register the peer, and drop its brief as a file — never typed
|
|
401
|
+
* into its prompt. Nothing loads that file for the peer: it is the spawn wake that tells the peer to
|
|
402
|
+
* go read it, naming its path (`spawnAndWake` below, and ADR-0032). `spawn` alone therefore leaves a
|
|
403
|
+
* peer sitting idle with its brief unread, which is why callers want `spawnAndWake`.
|
|
564
404
|
*
|
|
565
405
|
* Spawned unit worktrees live sibling to the primary checkout (`<parent>/<repo>.worktrees/legion-<id6>`),
|
|
566
406
|
* never nested inside it, even though the registry/mailbox itself lives in the global hub — the hub
|
|
@@ -568,6 +408,26 @@ interface SpawnResult {
|
|
|
568
408
|
* scoped to this one project's git remote.
|
|
569
409
|
*/
|
|
570
410
|
declare function spawn(ctx: IdContext, input: SpawnInput): SpawnResult;
|
|
411
|
+
/**
|
|
412
|
+
* `unit spawn` end to end: open the peer, then deliver its first turn.
|
|
413
|
+
*
|
|
414
|
+
* The two acts live together here rather than at the CLI call site so the **brief path the doorbell
|
|
415
|
+
* names is derived from the record `spawn` just wrote**, not handed in by a caller. That is the
|
|
416
|
+
* point of the seam: a caller cannot ring with the wrong path because it supplies no path — an
|
|
417
|
+
* empty string, a stale path, or the brief's whole body are all unrepresentable here, rather than
|
|
418
|
+
* merely untested.
|
|
419
|
+
*
|
|
420
|
+
* The ring is best-effort on top of the guaranteed spawn effect (worktree + session + registry
|
|
421
|
+
* record): `--no-wake` rings nothing, and a ring that never completes is reported as a warning on
|
|
422
|
+
* the result rather than thrown, so it can never fail a spawn that already landed.
|
|
423
|
+
*/
|
|
424
|
+
declare function spawnAndWake(ctx: IdContext, input: SpawnInput, options?: {
|
|
425
|
+
noWake?: boolean;
|
|
426
|
+
nudgeOpts?: NudgeOptions;
|
|
427
|
+
}): Promise<SpawnResult & {
|
|
428
|
+
rung: boolean;
|
|
429
|
+
warning?: string;
|
|
430
|
+
}>;
|
|
571
431
|
//#endregion
|
|
572
432
|
//#region src/store/file-store.d.ts
|
|
573
433
|
/** The on-disk `Store` implementation — current per-writer sharded `.json` layout (ADR-0020):
|
|
@@ -591,9 +451,8 @@ declare class FileStore implements Store {
|
|
|
591
451
|
removePaneIndex(pane: string): void;
|
|
592
452
|
writeBrief(agentId: string, text: string): void;
|
|
593
453
|
readBrief(agentId: string): string | undefined;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
readResult(id: string): string | undefined;
|
|
454
|
+
setMainPane(pane: string | null): void;
|
|
455
|
+
getMainPane(): string | undefined;
|
|
597
456
|
}
|
|
598
457
|
//#endregion
|
|
599
458
|
//#region src/wake/await.d.ts
|
|
@@ -630,46 +489,6 @@ type AwaitOutcome = {
|
|
|
630
489
|
*/
|
|
631
490
|
declare function awaitReply(ctx: MsgContext, input: AwaitInput): Promise<AwaitOutcome>;
|
|
632
491
|
//#endregion
|
|
633
|
-
//#region src/wake/wake-path.d.ts
|
|
634
|
-
/**
|
|
635
|
-
* The wake-matrix path a gateway would drive a turn through. Pure decision helper — no I/O, no
|
|
636
|
-
* polling, no session backend calls; the gateway (`legion-gateway-legate`, CR-5) composes this with
|
|
637
|
-
* the actual `mail await` / `session nudge` primitives.
|
|
638
|
-
*
|
|
639
|
-
* - **A** — a single unbounded `mail await` (not returned here; superseded by A-loop everywhere the
|
|
640
|
-
* gateway drives this decision, since a raw A risks a harness tool-timeout SIGKILL).
|
|
641
|
-
* - **A-loop** — the portable default: a bounded `mail await` cycle, re-armed by the caller.
|
|
642
|
-
* - **A-prime** — Claude Code with an observable background task: run `await` via
|
|
643
|
-
* `run_in_background` and poll its result instead of blocking the turn.
|
|
644
|
-
* - **B** — a live foreign session behind a verified multiplexer: ring the doorbell
|
|
645
|
-
* (`session nudge`) instead of polling. Never selected without a mux.
|
|
646
|
-
* - **C** — own-the-PTY (dropped from the shipped matrix; kept as a type for completeness).
|
|
647
|
-
* - **D** — `/loop`-style external re-invocation (dropped from the shipped matrix).
|
|
648
|
-
* - **E** — hook surfacing at a turn boundary (selected by the caller directly, not by this
|
|
649
|
-
* function — it has no wait/poll shape to decide between).
|
|
650
|
-
*/
|
|
651
|
-
type WakePath = 'A' | 'A-loop' | 'A-prime' | 'B' | 'C' | 'D' | 'E';
|
|
652
|
-
interface WakePathInput {
|
|
653
|
-
/** The calling harness, e.g. 'claude' | 'cursor' | 'codex'. */
|
|
654
|
-
harness: string;
|
|
655
|
-
mux: MuxProbe;
|
|
656
|
-
/** The harness can observe a backgrounded task's result without blocking the turn. */
|
|
657
|
-
observable?: boolean;
|
|
658
|
-
/** The target is a live, foreign interactive session that can be rung rather than polled. */
|
|
659
|
-
dedicatedListener?: boolean;
|
|
660
|
-
}
|
|
661
|
-
interface WakePathResult {
|
|
662
|
-
path: WakePath;
|
|
663
|
-
why: string;
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Select a wake path from the shipped matrix. Rule order: a `mux.mux === 'none'` caller can never
|
|
667
|
-
* get **B** — it always falls through to the bounded poll. Claude Code + an observable background
|
|
668
|
-
* task prefers **A-prime**. A live foreign session behind a verified mux prefers **B**. Otherwise
|
|
669
|
-
* the portable default is **A-loop**.
|
|
670
|
-
*/
|
|
671
|
-
declare function selectWakePath(input: WakePathInput): WakePathResult;
|
|
672
|
-
//#endregion
|
|
673
492
|
//#region src/wake/watch.d.ts
|
|
674
493
|
interface WatchInput {
|
|
675
494
|
meId: string;
|
|
@@ -688,4 +507,4 @@ interface WatchOptions {
|
|
|
688
507
|
*/
|
|
689
508
|
declare function watchMail(ctx: MsgContext, input: WatchInput, onMessage: (msg: InboxItem) => void, opts?: WatchOptions): Promise<void>;
|
|
690
509
|
//#endregion
|
|
691
|
-
export { type AgentDef, type AgentRecord, type AwaitInput, type AwaitOutcome, type
|
|
510
|
+
export { type AgentDef, type AgentRecord, type AwaitInput, type AwaitOutcome, type DecommissionInput, type DecommissionResult, type Exec, FileStore, type Format, type Harness, type HookEvent, type IdContext, type InboxItem, type InboxQuery, type InboxSnapshot, type Message, type MuxProbe, type RegisterInput, type RootOptions, type SendInput, type SpawnInput, type SpawnResult, type Store, ack, assertDistinctFromPrimary, awaitReply, bumpLastSeen, decommission, deleteMessage, detectHarness, emit, ensureMarker, fail, gitWorktreeAdapter, inbox, injectInbox, install, listAgentDefs, listAgents, loadAgent, nextStep, paths, peek, probeMultiplexer, projectRoot, prune, realExec, realizeLaunch, register, resolveAgent, resolveAgentDef, resolveBody, resolvePrimaryRoot, resolveProjectLocalRoot, resolveRecipient, resolveRoot, resolveSelfId, selectSessionAdapter, send, spawn, spawnAndWake, toonList, toonObject, touch, watchMail };
|
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* get **B** — it always falls through to the bounded poll. Claude Code + an observable background
|
|
6
|
-
* task prefers **A-prime**. A live foreign session behind a verified mux prefers **B**. Otherwise
|
|
7
|
-
* the portable default is **A-loop**.
|
|
8
|
-
*/
|
|
9
|
-
function selectWakePath(input) {
|
|
10
|
-
if (input.mux.mux === "none") return {
|
|
11
|
-
path: "A-loop",
|
|
12
|
-
why: "no multiplexer detected — bounded poll only, B is never available"
|
|
13
|
-
};
|
|
14
|
-
if (input.harness === "claude" && input.observable) return {
|
|
15
|
-
path: "A-prime",
|
|
16
|
-
why: "Claude Code + an observable background task — await via run_in_background"
|
|
17
|
-
};
|
|
18
|
-
if (input.dedicatedListener) return {
|
|
19
|
-
path: "B",
|
|
20
|
-
why: `a live foreign session behind a verified ${input.mux.mux} mux — ring the doorbell`
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
23
|
-
path: "A-loop",
|
|
24
|
-
why: "portable default — bounded mail await cycle"
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
|
-
export { DispatchWaitingError, FileStore, ack, assertDistinctFromPrimary, awaitReply, bumpLastSeen, channel, collect, decommission, deleteMessage, detectHarness, emit, ensureMarker, fail, gitWorktreeAdapter, inbox, injectInbox, install, listAgentDefs, listAgents, loadAgent, nextStep, paths, peek, prep, probeMultiplexer, projectRoot, prune, realExec, realizeLaunch, realizeSubagentInstruction, register, resolveAgent, resolveAgentDef, resolveBody, resolvePrimaryRoot, resolveProjectLocalRoot, resolveRecipient, resolveRoot, resolveSelfId, selectSessionAdapter, selectWakePath, send, spawn, toonList, toonObject, touch, watchMail };
|
|
1
|
+
import { $ as resolveProjectLocalRoot, A as bumpLastSeen, D as selectSessionAdapter, E as spawnAndWake, F as loadAgent, I as prune, J as listAgentDefs, K as touch, L as realExec, N as detectHarness, P as listAgents, Q as projectRoot, T as spawn, U as resolveRecipient, V as resolveAgent, W as resolveSelfId, X as ensureMarker, Y as resolveAgentDef, Z as paths, _ as install, a as emit, c as toonList, d as deleteMessage, et as resolveRoot, f as inbox, g as send, h as resolveBody, i as injectInbox, l as toonObject, n as awaitReply, o as fail, p as peek, r as FileStore, s as nextStep, t as watchMail, u as ack, v as decommission, y as realizeLaunch, z as register } from "./watch-BZmEAqUo.mjs";
|
|
2
|
+
import { probeMultiplexer } from "cyber-mux";
|
|
3
|
+
import { assertDistinctFromPrimary, gitWorktreeAdapter, resolvePrimaryRoot } from "cyber-mux/worktree";
|
|
4
|
+
export { FileStore, ack, assertDistinctFromPrimary, awaitReply, bumpLastSeen, decommission, deleteMessage, detectHarness, emit, ensureMarker, fail, gitWorktreeAdapter, inbox, injectInbox, install, listAgentDefs, listAgents, loadAgent, nextStep, paths, peek, probeMultiplexer, projectRoot, prune, realExec, realizeLaunch, register, resolveAgent, resolveAgentDef, resolveBody, resolvePrimaryRoot, resolveProjectLocalRoot, resolveRecipient, resolveRoot, resolveSelfId, selectSessionAdapter, send, spawn, spawnAndWake, toonList, toonObject, touch, watchMail };
|