@sjawhar/pi-legion-envoy 1.0.0 → 1.1.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/README.md +3 -1
- package/dist/envoy.js +33 -4
- package/dist/legion.js +47 -8
- package/dist/skills/legion-controller/SKILL.md +3 -2
- package/dist/skills/legion-worker/SKILL.md +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,7 +104,9 @@ Configure the shared `envoy.json` with:
|
|
|
104
104
|
|
|
105
105
|
The user file is `~/.config/opencode/envoy.json`; a
|
|
106
106
|
`<cwd>/.opencode/envoy.json` file shallow-merges over it. `DISPATCH_URL` and
|
|
107
|
-
`DISPATCH_TOKEN` override the file values for one process
|
|
107
|
+
`DISPATCH_TOKEN` override the file values for one process; `DISPATCH_TOKEN_FILE`
|
|
108
|
+
(a path whose trimmed contents are the token — how the Legion daemon delivers it
|
|
109
|
+
to a pane) wins over both and never falls back when unreadable. Omitting
|
|
108
110
|
`dispatch.serverUrl` while `dispatch.enabled` is true targets
|
|
109
111
|
`http://localhost:8766`, the Go server's listen address. Invalid configuration,
|
|
110
112
|
an invalid URL, or an empty token leaves the twelve tools unavailable and reports
|
package/dist/envoy.js
CHANGED
|
@@ -31189,7 +31189,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31189
31189
|
}
|
|
31190
31190
|
|
|
31191
31191
|
// ../envoy-client/src/dispatch-config.ts
|
|
31192
|
-
import { readFileSync } from "fs";
|
|
31192
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
31193
31193
|
import { homedir } from "os";
|
|
31194
31194
|
import * as path from "path";
|
|
31195
31195
|
|
|
@@ -31198,6 +31198,21 @@ function messageFor(error) {
|
|
|
31198
31198
|
return error instanceof Error ? error.message : String(error);
|
|
31199
31199
|
}
|
|
31200
31200
|
|
|
31201
|
+
// ../envoy-client/src/secret-file.ts
|
|
31202
|
+
import { readFileSync } from "fs";
|
|
31203
|
+
function readSecretFile(variable, filePath) {
|
|
31204
|
+
let contents;
|
|
31205
|
+
try {
|
|
31206
|
+
contents = readFileSync(filePath, "utf8");
|
|
31207
|
+
} catch (error) {
|
|
31208
|
+
throw new Error(`${variable} names ${filePath}, which could not be read: ${messageFor(error)}`);
|
|
31209
|
+
}
|
|
31210
|
+
const value = contents.trim();
|
|
31211
|
+
if (value.length === 0)
|
|
31212
|
+
throw new Error(`${variable} names ${filePath}, which is empty`);
|
|
31213
|
+
return value;
|
|
31214
|
+
}
|
|
31215
|
+
|
|
31201
31216
|
// ../envoy-client/src/dispatch-config.ts
|
|
31202
31217
|
var DEFAULT_SERVER_URL = "http://localhost:8766";
|
|
31203
31218
|
function normalizeDispatchUrl(url2) {
|
|
@@ -31234,7 +31249,7 @@ function describeSchemaIssue(filePath, error) {
|
|
|
31234
31249
|
function readEnvoyFile(filePath) {
|
|
31235
31250
|
let raw;
|
|
31236
31251
|
try {
|
|
31237
|
-
raw =
|
|
31252
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
31238
31253
|
} catch {
|
|
31239
31254
|
return { kind: "absent" };
|
|
31240
31255
|
}
|
|
@@ -31267,8 +31282,22 @@ function resolveDispatchConfig(env, options = {}) {
|
|
|
31267
31282
|
};
|
|
31268
31283
|
const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
|
|
31269
31284
|
const url2 = rawUrl ? parsedDispatchUrl(rawUrl.value, rawUrl.source) : { url: null, error: null };
|
|
31270
|
-
|
|
31271
|
-
|
|
31285
|
+
let token;
|
|
31286
|
+
let tokenSource;
|
|
31287
|
+
if (env.DISPATCH_TOKEN_FILE !== undefined) {
|
|
31288
|
+
tokenSource = "DISPATCH_TOKEN_FILE";
|
|
31289
|
+
try {
|
|
31290
|
+
token = readSecretFile(tokenSource, env.DISPATCH_TOKEN_FILE);
|
|
31291
|
+
} catch (error) {
|
|
31292
|
+
return { enabled: false, url: url2.url, token: null, error: messageFor(error) };
|
|
31293
|
+
}
|
|
31294
|
+
} else if (env.DISPATCH_TOKEN !== undefined) {
|
|
31295
|
+
tokenSource = "DISPATCH_TOKEN";
|
|
31296
|
+
token = env.DISPATCH_TOKEN;
|
|
31297
|
+
} else {
|
|
31298
|
+
tokenSource = "dispatch.token";
|
|
31299
|
+
token = merged.token ?? null;
|
|
31300
|
+
}
|
|
31272
31301
|
if (url2.error !== null)
|
|
31273
31302
|
return { enabled: false, url: null, token, error: url2.error };
|
|
31274
31303
|
if (url2.url === null)
|
package/dist/legion.js
CHANGED
|
@@ -30432,6 +30432,21 @@ function messageFor(error) {
|
|
|
30432
30432
|
var import_nats2 = __toESM(require_mod4(), 1);
|
|
30433
30433
|
import { logger as logger2 } from "@oh-my-pi/pi-utils";
|
|
30434
30434
|
|
|
30435
|
+
// ../envoy-client/src/secret-file.ts
|
|
30436
|
+
import { readFileSync } from "fs";
|
|
30437
|
+
function readSecretFile(variable, filePath) {
|
|
30438
|
+
let contents;
|
|
30439
|
+
try {
|
|
30440
|
+
contents = readFileSync(filePath, "utf8");
|
|
30441
|
+
} catch (error) {
|
|
30442
|
+
throw new Error(`${variable} names ${filePath}, which could not be read: ${messageFor(error)}`);
|
|
30443
|
+
}
|
|
30444
|
+
const value = contents.trim();
|
|
30445
|
+
if (value.length === 0)
|
|
30446
|
+
throw new Error(`${variable} names ${filePath}, which is empty`);
|
|
30447
|
+
return value;
|
|
30448
|
+
}
|
|
30449
|
+
|
|
30435
30450
|
// src/legion/classify.ts
|
|
30436
30451
|
function classifySession(env) {
|
|
30437
30452
|
if (env.LEGION_CONTROLLER !== undefined && env.LEGION_TREE !== undefined) {
|
|
@@ -30457,10 +30472,20 @@ function requiredEnvironment(env, key) {
|
|
|
30457
30472
|
throw new Error(`${key} is required for Legion`);
|
|
30458
30473
|
return value;
|
|
30459
30474
|
}
|
|
30475
|
+
function requiredSecret(env, key) {
|
|
30476
|
+
const fileKey = `${key}_FILE`;
|
|
30477
|
+
const file = env[fileKey];
|
|
30478
|
+
if (file !== undefined)
|
|
30479
|
+
return readSecretFile(fileKey, file);
|
|
30480
|
+
return requiredEnvironment(env, key);
|
|
30481
|
+
}
|
|
30460
30482
|
function requiredControllerCapability(env) {
|
|
30483
|
+
if (env.LEGION_CONTROLLER_SECRET_FILE !== undefined) {
|
|
30484
|
+
return readSecretFile("LEGION_CONTROLLER_SECRET_FILE", env.LEGION_CONTROLLER_SECRET_FILE);
|
|
30485
|
+
}
|
|
30461
30486
|
const secret = env.LEGION_CONTROLLER_SECRET;
|
|
30462
30487
|
if (!secret) {
|
|
30463
|
-
throw new Error("LEGION_CONTROLLER_SECRET is required to claim the controller. " + "Launch OMP with
|
|
30488
|
+
throw new Error("LEGION_CONTROLLER_SECRET or LEGION_CONTROLLER_SECRET_FILE is required to claim the controller. " + "Launch OMP with one of them in its environment before running /legion-claim-controller.");
|
|
30464
30489
|
}
|
|
30465
30490
|
return secret;
|
|
30466
30491
|
}
|
|
@@ -31612,7 +31637,7 @@ ${envelope.payload ?? ""}`;
|
|
|
31612
31637
|
}
|
|
31613
31638
|
|
|
31614
31639
|
// ../envoy-client/src/dispatch-config.ts
|
|
31615
|
-
import { readFileSync } from "fs";
|
|
31640
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
31616
31641
|
import { homedir } from "os";
|
|
31617
31642
|
import * as path3 from "path";
|
|
31618
31643
|
var DEFAULT_SERVER_URL = "http://localhost:8766";
|
|
@@ -31650,7 +31675,7 @@ function describeSchemaIssue(filePath, error) {
|
|
|
31650
31675
|
function readEnvoyFile(filePath) {
|
|
31651
31676
|
let raw;
|
|
31652
31677
|
try {
|
|
31653
|
-
raw =
|
|
31678
|
+
raw = readFileSync2(filePath, "utf-8");
|
|
31654
31679
|
} catch {
|
|
31655
31680
|
return { kind: "absent" };
|
|
31656
31681
|
}
|
|
@@ -31683,8 +31708,22 @@ function resolveDispatchConfig(env, options = {}) {
|
|
|
31683
31708
|
};
|
|
31684
31709
|
const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
|
|
31685
31710
|
const url2 = rawUrl ? parsedDispatchUrl(rawUrl.value, rawUrl.source) : { url: null, error: null };
|
|
31686
|
-
|
|
31687
|
-
|
|
31711
|
+
let token;
|
|
31712
|
+
let tokenSource;
|
|
31713
|
+
if (env.DISPATCH_TOKEN_FILE !== undefined) {
|
|
31714
|
+
tokenSource = "DISPATCH_TOKEN_FILE";
|
|
31715
|
+
try {
|
|
31716
|
+
token = readSecretFile(tokenSource, env.DISPATCH_TOKEN_FILE);
|
|
31717
|
+
} catch (error) {
|
|
31718
|
+
return { enabled: false, url: url2.url, token: null, error: messageFor(error) };
|
|
31719
|
+
}
|
|
31720
|
+
} else if (env.DISPATCH_TOKEN !== undefined) {
|
|
31721
|
+
tokenSource = "DISPATCH_TOKEN";
|
|
31722
|
+
token = env.DISPATCH_TOKEN;
|
|
31723
|
+
} else {
|
|
31724
|
+
tokenSource = "dispatch.token";
|
|
31725
|
+
token = merged.token ?? null;
|
|
31726
|
+
}
|
|
31688
31727
|
if (url2.error !== null)
|
|
31689
31728
|
return { enabled: false, url: null, token, error: url2.error };
|
|
31690
31729
|
if (url2.url === null)
|
|
@@ -33985,7 +34024,7 @@ function legionExtension(pi) {
|
|
|
33985
34024
|
return createLegionDaemonClient(requiredEnvironment(process.env, "LEGION_DAEMON_URL"), fetch, {
|
|
33986
34025
|
recoveryToken: (sessionId) => {
|
|
33987
34026
|
if (capability !== undefined && sessionId === capability.sessionID) {
|
|
33988
|
-
return
|
|
34027
|
+
return requiredSecret(process.env, "LEGION_BOOT_TOKEN");
|
|
33989
34028
|
}
|
|
33990
34029
|
throw new Error(`Legion session ${sessionId} has no persisted recovery token`);
|
|
33991
34030
|
},
|
|
@@ -34068,7 +34107,7 @@ function legionExtension(pi) {
|
|
|
34068
34107
|
return;
|
|
34069
34108
|
if (bootstrap)
|
|
34070
34109
|
return bootstrap;
|
|
34071
|
-
const bootToken =
|
|
34110
|
+
const bootToken = requiredSecret(process.env, "LEGION_BOOT_TOKEN");
|
|
34072
34111
|
bootstrap = (async () => {
|
|
34073
34112
|
const { sessionFile, agentId } = await persistedTranscript(context);
|
|
34074
34113
|
const started = await (async () => {
|
|
@@ -34135,7 +34174,7 @@ function legionExtension(pi) {
|
|
|
34135
34174
|
return;
|
|
34136
34175
|
if (bootstrap)
|
|
34137
34176
|
return bootstrap;
|
|
34138
|
-
const bootToken =
|
|
34177
|
+
const bootToken = requiredSecret(process.env, "LEGION_BOOT_TOKEN");
|
|
34139
34178
|
const workspace = requiredEnvironment(process.env, "LEGION_WORKSPACE");
|
|
34140
34179
|
bootstrap = (async () => {
|
|
34141
34180
|
const { sessionFile, agentId } = await persistedTranscript(context);
|
|
@@ -18,8 +18,9 @@ until that PR merges, this skill's contract is not yet runnable on `main`.
|
|
|
18
18
|
The Legion extension claims `legion-<project>-controller` and registers controller readiness
|
|
19
19
|
with the daemon during session startup. Do not handle a wake unless that startup succeeded.
|
|
20
20
|
|
|
21
|
-
For an interactive takeover, start OMP with `LEGION_CONTROLLER_SECRET`
|
|
22
|
-
`LEGION_DAEMON_URL` in its
|
|
21
|
+
For an interactive takeover, start OMP with `LEGION_CONTROLLER_SECRET` (or
|
|
22
|
+
`LEGION_CONTROLLER_SECRET_FILE`, a path to a file holding it) and `LEGION_DAEMON_URL` in its
|
|
23
|
+
environment, then run:
|
|
23
24
|
|
|
24
25
|
```text
|
|
25
26
|
/legion-claim-controller
|
|
@@ -19,8 +19,9 @@ until that PR merges, this skill's contract is not yet runnable on `main`.
|
|
|
19
19
|
|
|
20
20
|
The daemon spawns you as a separate `omp --mode rpc` process (behind `legion worker-shim`,
|
|
21
21
|
in a tmux pane) with `LEGION_TREE`, `LEGION_ISSUE`, `LEGION_ROLE`, `LEGION_GENERATION`,
|
|
22
|
-
`
|
|
23
|
-
|
|
22
|
+
`LEGION_BOOT_TOKEN_FILE` (a 0600 file under `$LEGION_STATE_DIR/secrets` holding your boot token;
|
|
23
|
+
the extension reads it for you), `LEGION_DAEMON_URL`, `LEGION_STATE_DIR`, and `LEGION_WORKSPACE`
|
|
24
|
+
in your environment (`LEGION_PROJECT` is also supplied, but nothing reads it). The extension
|
|
24
25
|
completes the boot handshake for you at session start — it registers with the daemon, claims
|
|
25
26
|
your role, and signals readiness. You never call `envoy_role_set` yourself.
|
|
26
27
|
|