@sjawhar/opencode-legion-envoy 7.8.0 → 8.0.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 +35 -9
- package/dist/dispatch.js +643 -225
- package/dist/src/server.js +836 -284
- package/package.json +1 -1
- package/skills/dispatch/references/api.md +16 -3
- package/skills/dispatch/references/asks.md +3 -3
- package/skills/dispatch/references/issues.md +5 -1
- package/skills/legion-architect/SKILL.md +8 -4
- package/skills/legion-retro/SKILL.md +1 -1
- package/skills/legion-worker/references/merge-gate.md +9 -5
- package/src/server.ts +226 -190
package/dist/src/server.js
CHANGED
|
@@ -15,7 +15,7 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/server.ts
|
|
18
|
-
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
18
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
19
19
|
import path3 from "path";
|
|
20
20
|
import { fileURLToPath } from "url";
|
|
21
21
|
|
|
@@ -14372,36 +14372,135 @@ function zodSchemaApi(zod) {
|
|
|
14372
14372
|
}
|
|
14373
14373
|
};
|
|
14374
14374
|
}
|
|
14375
|
-
// ../envoy-client/src/
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
function
|
|
14380
|
-
|
|
14381
|
-
const heartbeatMs = Number.isFinite(rawHeartbeatMs) && rawHeartbeatMs > 0 ? Math.max(rawHeartbeatMs, MIN_HEARTBEAT_MS) : DEFAULT_HEARTBEAT_MS;
|
|
14382
|
-
const natsUrls = environment["ENVOY_NATS_URL"]?.split(",").map((url2) => url2.trim()).filter((url2) => url2.length > 0) ?? [];
|
|
14383
|
-
return {
|
|
14384
|
-
envoyUrl: normalizeEnvoyUrl(environment["ENVOY_URL"] ?? DEFAULT_ENVOY_URL),
|
|
14385
|
-
natsUrls,
|
|
14386
|
-
heartbeatMs
|
|
14387
|
-
};
|
|
14375
|
+
// ../envoy-client/src/dispatch-first.ts
|
|
14376
|
+
import { join } from "path";
|
|
14377
|
+
|
|
14378
|
+
// ../envoy-client/src/errors.ts
|
|
14379
|
+
function messageFor(error48) {
|
|
14380
|
+
return error48 instanceof Error ? error48.message : String(error48);
|
|
14388
14381
|
}
|
|
14389
|
-
function
|
|
14390
|
-
return
|
|
14382
|
+
function hasErrnoCode(error48, code) {
|
|
14383
|
+
return error48 instanceof Error && "code" in error48 && error48.code === code;
|
|
14391
14384
|
}
|
|
14392
14385
|
|
|
14393
|
-
// ../envoy-client/src/dispatch-
|
|
14394
|
-
|
|
14395
|
-
|
|
14386
|
+
// ../envoy-client/src/dispatch-first.ts
|
|
14387
|
+
function dispatchFirstSkillFile(skillsDirectory) {
|
|
14388
|
+
return join(skillsDirectory, "dispatch-first", "SKILL.md");
|
|
14389
|
+
}
|
|
14390
|
+
|
|
14391
|
+
// ../envoy-client/src/machine.ts
|
|
14392
|
+
import { hostname as hostname3 } from "os";
|
|
14393
|
+
function machineID() {
|
|
14394
|
+
return process.env["ENVOY_MACHINE_ID"] || hostname3();
|
|
14395
|
+
}
|
|
14396
|
+
|
|
14397
|
+
// ../envoy-client/src/envoy-config.ts
|
|
14398
|
+
import {
|
|
14399
|
+
chmodSync,
|
|
14400
|
+
closeSync,
|
|
14401
|
+
existsSync,
|
|
14402
|
+
lstatSync,
|
|
14403
|
+
mkdtempSync,
|
|
14404
|
+
openSync,
|
|
14405
|
+
readFileSync as readFileSync2,
|
|
14406
|
+
readlinkSync,
|
|
14407
|
+
rmSync,
|
|
14408
|
+
statSync
|
|
14409
|
+
} from "fs";
|
|
14410
|
+
import { homedir, tmpdir } from "os";
|
|
14396
14411
|
import * as path from "path";
|
|
14397
14412
|
|
|
14398
|
-
// ../envoy-client/src/
|
|
14399
|
-
function
|
|
14400
|
-
|
|
14413
|
+
// ../envoy-client/src/json-position.ts
|
|
14414
|
+
function jsonErrorPosition(text) {
|
|
14415
|
+
let at = 0;
|
|
14416
|
+
const fail = () => {
|
|
14417
|
+
throw new RangeError("not JSON");
|
|
14418
|
+
};
|
|
14419
|
+
const space = () => {
|
|
14420
|
+
while (at < text.length && `
|
|
14421
|
+
\r`.includes(text.charAt(at)))
|
|
14422
|
+
at += 1;
|
|
14423
|
+
};
|
|
14424
|
+
const expect = (char) => {
|
|
14425
|
+
if (text.charAt(at) !== char)
|
|
14426
|
+
fail();
|
|
14427
|
+
at += 1;
|
|
14428
|
+
};
|
|
14429
|
+
const string4 = () => {
|
|
14430
|
+
expect('"');
|
|
14431
|
+
while (at < text.length) {
|
|
14432
|
+
const char = text.charAt(at);
|
|
14433
|
+
if (char === '"') {
|
|
14434
|
+
at += 1;
|
|
14435
|
+
return;
|
|
14436
|
+
}
|
|
14437
|
+
if (char < " ")
|
|
14438
|
+
fail();
|
|
14439
|
+
at += char === "\\" ? 2 : 1;
|
|
14440
|
+
}
|
|
14441
|
+
fail();
|
|
14442
|
+
};
|
|
14443
|
+
const value = () => {
|
|
14444
|
+
space();
|
|
14445
|
+
const char = text.charAt(at);
|
|
14446
|
+
if (char === "{" || char === "[") {
|
|
14447
|
+
const close = char === "{" ? "}" : "]";
|
|
14448
|
+
at += 1;
|
|
14449
|
+
space();
|
|
14450
|
+
if (text.charAt(at) === close) {
|
|
14451
|
+
at += 1;
|
|
14452
|
+
return;
|
|
14453
|
+
}
|
|
14454
|
+
for (;; ) {
|
|
14455
|
+
if (close === "}") {
|
|
14456
|
+
space();
|
|
14457
|
+
string4();
|
|
14458
|
+
space();
|
|
14459
|
+
expect(":");
|
|
14460
|
+
}
|
|
14461
|
+
value();
|
|
14462
|
+
space();
|
|
14463
|
+
if (text.charAt(at) === ",") {
|
|
14464
|
+
at += 1;
|
|
14465
|
+
continue;
|
|
14466
|
+
}
|
|
14467
|
+
expect(close);
|
|
14468
|
+
return;
|
|
14469
|
+
}
|
|
14470
|
+
}
|
|
14471
|
+
if (char === '"') {
|
|
14472
|
+
string4();
|
|
14473
|
+
return;
|
|
14474
|
+
}
|
|
14475
|
+
for (const word of ["true", "false", "null"]) {
|
|
14476
|
+
if (text.startsWith(word, at)) {
|
|
14477
|
+
at += word.length;
|
|
14478
|
+
return;
|
|
14479
|
+
}
|
|
14480
|
+
}
|
|
14481
|
+
const number4 = /^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/.exec(text.slice(at));
|
|
14482
|
+
if (number4 === null)
|
|
14483
|
+
fail();
|
|
14484
|
+
at += number4?.[0].length ?? 0;
|
|
14485
|
+
};
|
|
14486
|
+
try {
|
|
14487
|
+
value();
|
|
14488
|
+
space();
|
|
14489
|
+
if (at < text.length)
|
|
14490
|
+
fail();
|
|
14491
|
+
return;
|
|
14492
|
+
} catch {
|
|
14493
|
+
const before = text.slice(0, at).split(`
|
|
14494
|
+
`);
|
|
14495
|
+
return `line ${before.length}, column ${(before.at(-1)?.length ?? 0) + 1}`;
|
|
14496
|
+
}
|
|
14401
14497
|
}
|
|
14402
14498
|
|
|
14403
14499
|
// ../envoy-client/src/secret-file.ts
|
|
14404
14500
|
import { readFileSync } from "fs";
|
|
14501
|
+
function sendable(value) {
|
|
14502
|
+
return !/[\s\p{Cc}]/u.test(value);
|
|
14503
|
+
}
|
|
14405
14504
|
function readSecretFile(variable, filePath) {
|
|
14406
14505
|
let contents;
|
|
14407
14506
|
try {
|
|
@@ -14412,134 +14511,578 @@ function readSecretFile(variable, filePath) {
|
|
|
14412
14511
|
const value = contents.trim();
|
|
14413
14512
|
if (value.length === 0)
|
|
14414
14513
|
throw new Error(`${variable} names ${filePath}, which is empty`);
|
|
14514
|
+
if (!sendable(value)) {
|
|
14515
|
+
throw new Error(`${variable} names ${filePath}, which holds whitespace or a control character`);
|
|
14516
|
+
}
|
|
14415
14517
|
return value;
|
|
14416
14518
|
}
|
|
14417
14519
|
|
|
14418
|
-
// ../envoy-client/src/
|
|
14419
|
-
var
|
|
14420
|
-
function
|
|
14421
|
-
return
|
|
14520
|
+
// ../envoy-client/src/envoy-config.ts
|
|
14521
|
+
var COMMAND_TIMEOUT_MS = 1e4;
|
|
14522
|
+
function nonEmpty(value) {
|
|
14523
|
+
return value === undefined || value === "" ? undefined : value;
|
|
14422
14524
|
}
|
|
14423
|
-
function
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
new URL(url2);
|
|
14427
|
-
return { url: url2, error: null };
|
|
14428
|
-
} catch {
|
|
14429
|
-
return { url: null, error: `${source} must be a valid URL` };
|
|
14525
|
+
function absolute(name, value) {
|
|
14526
|
+
if (value !== undefined && !path.isAbsolute(value)) {
|
|
14527
|
+
throw new Error(`${name} is ${value}, which is not an absolute path`);
|
|
14430
14528
|
}
|
|
14529
|
+
return value;
|
|
14431
14530
|
}
|
|
14531
|
+
function neutralConfig(env) {
|
|
14532
|
+
const xdgConfigHome = nonEmpty(env.XDG_CONFIG_HOME);
|
|
14533
|
+
if (xdgConfigHome !== undefined && !path.isAbsolute(xdgConfigHome)) {
|
|
14534
|
+
return { error: `XDG_CONFIG_HOME is ${xdgConfigHome}, which is not an absolute path` };
|
|
14535
|
+
}
|
|
14536
|
+
const configHome = xdgConfigHome ?? path.join(nonEmpty(env.HOME) ?? homedir(), ".config");
|
|
14537
|
+
return { path: path.resolve(configHome, "legion", "envoy.json") };
|
|
14538
|
+
}
|
|
14539
|
+
var HARNESS_DIRECTORY_VARIABLE = {
|
|
14540
|
+
omp: "PI_CODING_AGENT_DIR",
|
|
14541
|
+
claude: "CLAUDE_CONFIG_DIR",
|
|
14542
|
+
opencode: "OPENCODE_CONFIG_DIR"
|
|
14543
|
+
};
|
|
14544
|
+
var PLUGIN_HARNESSES = Object.keys(HARNESS_DIRECTORY_VARIABLE);
|
|
14545
|
+
function namedDirectory(harness, env) {
|
|
14546
|
+
if (harness === "omp") {
|
|
14547
|
+
const profile = nonEmpty(env.OMP_PROFILE);
|
|
14548
|
+
if (profile === undefined || profile === "default")
|
|
14549
|
+
return;
|
|
14550
|
+
}
|
|
14551
|
+
return nonEmpty(env[HARNESS_DIRECTORY_VARIABLE[harness]]);
|
|
14552
|
+
}
|
|
14553
|
+
function harnessDirectory(harness, env) {
|
|
14554
|
+
for (const plugin of harness === "command" ? PLUGIN_HARNESSES : [harness]) {
|
|
14555
|
+
if (harness === "command" && plugin === "claude" && env.CLAUDE_CODE_CHILD_SESSION !== "1") {
|
|
14556
|
+
continue;
|
|
14557
|
+
}
|
|
14558
|
+
const directory = namedDirectory(plugin, env);
|
|
14559
|
+
if (directory !== undefined)
|
|
14560
|
+
return absolute(HARNESS_DIRECTORY_VARIABLE[plugin], directory);
|
|
14561
|
+
}
|
|
14562
|
+
return;
|
|
14563
|
+
}
|
|
14564
|
+
function handedConfigPath(env) {
|
|
14565
|
+
const handed = nonEmpty(env.ENVOY_CONFIG_FILE);
|
|
14566
|
+
return handed === undefined ? undefined : path.resolve(absolute("ENVOY_CONFIG_FILE", handed));
|
|
14567
|
+
}
|
|
14568
|
+
function envoyConfigPath(harness, env) {
|
|
14569
|
+
const handedFirst = harness === "command" ? handedConfigPath(env) : undefined;
|
|
14570
|
+
if (handedFirst !== undefined)
|
|
14571
|
+
return handedFirst;
|
|
14572
|
+
const directory = harnessDirectory(harness, env);
|
|
14573
|
+
if (directory !== undefined)
|
|
14574
|
+
return path.resolve(directory, "envoy.json");
|
|
14575
|
+
const handed = handedConfigPath(env);
|
|
14576
|
+
if (handed !== undefined)
|
|
14577
|
+
return handed;
|
|
14578
|
+
const neutral = neutralConfig(env);
|
|
14579
|
+
if ("error" in neutral)
|
|
14580
|
+
throw new Error(neutral.error);
|
|
14581
|
+
return neutral.path;
|
|
14582
|
+
}
|
|
14583
|
+
var httpUrlSchema = exports_external.url({ protocol: /^https?$/, error: "must be an http or https URL" });
|
|
14584
|
+
var DispatchSectionSchema = exports_external.strictObject({
|
|
14585
|
+
enabled: exports_external.boolean().optional(),
|
|
14586
|
+
serverUrl: httpUrlSchema.optional(),
|
|
14587
|
+
token: exports_external.string().optional()
|
|
14588
|
+
});
|
|
14589
|
+
var EnvoySectionSchema = exports_external.strictObject({
|
|
14590
|
+
enabled: exports_external.boolean().optional(),
|
|
14591
|
+
url: httpUrlSchema.optional(),
|
|
14592
|
+
natsUrls: exports_external.array(exports_external.string().trim().min(1, "must not be empty")).optional(),
|
|
14593
|
+
token: exports_external.string().optional(),
|
|
14594
|
+
natsSeed: exports_external.string().optional()
|
|
14595
|
+
});
|
|
14432
14596
|
var EnvoyFileSchema = exports_external.looseObject({
|
|
14433
14597
|
$schema: exports_external.string().optional(),
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14598
|
+
dispatch: DispatchSectionSchema.optional(),
|
|
14599
|
+
envoy: EnvoySectionSchema.optional()
|
|
14600
|
+
});
|
|
14601
|
+
var CheckoutFileSchema = exports_external.looseObject({
|
|
14602
|
+
$schema: exports_external.string().optional(),
|
|
14603
|
+
dispatch: DispatchSectionSchema.optional()
|
|
14440
14604
|
});
|
|
14605
|
+
function offWithNotice(notice) {
|
|
14606
|
+
return { enabled: false, notice, error: null };
|
|
14607
|
+
}
|
|
14608
|
+
function offWithError(error48) {
|
|
14609
|
+
return { enabled: false, notice: null, error: error48 };
|
|
14610
|
+
}
|
|
14611
|
+
function httpUrl2(value) {
|
|
14612
|
+
let url2;
|
|
14613
|
+
try {
|
|
14614
|
+
url2 = new URL(value);
|
|
14615
|
+
} catch {
|
|
14616
|
+
return;
|
|
14617
|
+
}
|
|
14618
|
+
return url2.protocol === "http:" || url2.protocol === "https:" ? url2 : undefined;
|
|
14619
|
+
}
|
|
14441
14620
|
function describeSchemaIssue(filePath, error48) {
|
|
14442
14621
|
const issue2 = error48.issues[0];
|
|
14443
14622
|
if (!issue2)
|
|
14444
|
-
return `${filePath}: invalid
|
|
14623
|
+
return `${filePath}: invalid envoy.json`;
|
|
14445
14624
|
if (issue2.code === "unrecognized_keys") {
|
|
14446
|
-
const
|
|
14447
|
-
|
|
14625
|
+
const section = issue2.path.join(".");
|
|
14626
|
+
const keys = issue2.keys.map((key) => `${section}.${key}`).join(", ");
|
|
14627
|
+
return `${filePath}: unrecognized ${section} key(s): ${keys}`;
|
|
14448
14628
|
}
|
|
14449
14629
|
return `${filePath}: ${issue2.path.join(".")}: ${issue2.message}`;
|
|
14450
14630
|
}
|
|
14451
|
-
function
|
|
14631
|
+
function danglingLink(filePath) {
|
|
14632
|
+
try {
|
|
14633
|
+
return lstatSync(filePath).isSymbolicLink() ? readlinkSync(filePath) : null;
|
|
14634
|
+
} catch {
|
|
14635
|
+
return null;
|
|
14636
|
+
}
|
|
14637
|
+
}
|
|
14638
|
+
function parseFile(filePath, schema, checkout) {
|
|
14452
14639
|
let raw;
|
|
14453
14640
|
try {
|
|
14454
14641
|
raw = readFileSync2(filePath, "utf-8");
|
|
14455
|
-
} catch {
|
|
14456
|
-
|
|
14642
|
+
} catch (error48) {
|
|
14643
|
+
if (hasErrnoCode(error48, "ENOENT") || hasErrnoCode(error48, "ENOTDIR")) {
|
|
14644
|
+
return { kind: "absent", link: danglingLink(filePath) };
|
|
14645
|
+
}
|
|
14646
|
+
return { kind: "invalid", reason: `${filePath}: could not be read (${messageFor(error48)})` };
|
|
14457
14647
|
}
|
|
14458
14648
|
let parsedJson;
|
|
14459
14649
|
try {
|
|
14460
14650
|
parsedJson = JSON.parse(raw);
|
|
14461
|
-
} catch
|
|
14462
|
-
|
|
14651
|
+
} catch {
|
|
14652
|
+
const position = jsonErrorPosition(raw);
|
|
14653
|
+
return {
|
|
14654
|
+
kind: "invalid",
|
|
14655
|
+
reason: `${filePath}: invalid JSON${position === undefined ? "" : ` at ${position}`}`
|
|
14656
|
+
};
|
|
14463
14657
|
}
|
|
14464
|
-
|
|
14658
|
+
if (typeof parsedJson === "object" && parsedJson !== null && "natsUrls" in parsedJson) {
|
|
14659
|
+
return {
|
|
14660
|
+
kind: "invalid",
|
|
14661
|
+
reason: checkout ? `${filePath}: the top-level natsUrls is not read, and a checkout's envoy.json holds only a dispatch section` : `${filePath}: the top-level natsUrls is not read; move it to envoy.natsUrls`
|
|
14662
|
+
};
|
|
14663
|
+
}
|
|
14664
|
+
const parsed = schema.safeParse(parsedJson);
|
|
14465
14665
|
if (!parsed.success) {
|
|
14466
14666
|
return { kind: "invalid", reason: describeSchemaIssue(filePath, parsed.error) };
|
|
14467
14667
|
}
|
|
14468
|
-
return { kind: "valid", settings: parsed.data
|
|
14469
|
-
}
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
}
|
|
14481
|
-
const
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14485
|
-
|
|
14486
|
-
|
|
14487
|
-
|
|
14668
|
+
return { kind: "valid", settings: parsed.data };
|
|
14669
|
+
}
|
|
14670
|
+
var FAILURE_KEPT_MS = 60000;
|
|
14671
|
+
var COMMAND_OUTCOMES = Symbol.for("legion.envoy-config.commands.v1");
|
|
14672
|
+
var processGlobal = globalThis;
|
|
14673
|
+
var commandOutcomes = processGlobal[COMMAND_OUTCOMES] ?? new Map;
|
|
14674
|
+
processGlobal[COMMAND_OUTCOMES] = commandOutcomes;
|
|
14675
|
+
function runCommand(command, env, cwd) {
|
|
14676
|
+
const childEnv = {};
|
|
14677
|
+
for (const [name, variable] of Object.entries(env)) {
|
|
14678
|
+
if (variable !== undefined)
|
|
14679
|
+
childEnv[name] = variable;
|
|
14680
|
+
}
|
|
14681
|
+
const directory = mkdtempSync(path.join(tmpdir(), "envoy-command-"));
|
|
14682
|
+
try {
|
|
14683
|
+
chmodSync(directory, 448);
|
|
14684
|
+
const output = path.join(directory, "stdout");
|
|
14685
|
+
const fd = openSync(output, "wx", 384);
|
|
14686
|
+
let result;
|
|
14687
|
+
try {
|
|
14688
|
+
result = Bun.spawnSync(["/bin/sh", "-c", command], {
|
|
14689
|
+
cwd,
|
|
14690
|
+
env: childEnv,
|
|
14691
|
+
stdin: "ignore",
|
|
14692
|
+
stdout: fd,
|
|
14693
|
+
stderr: "ignore",
|
|
14694
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
14695
|
+
detached: true
|
|
14696
|
+
});
|
|
14697
|
+
} catch (error48) {
|
|
14698
|
+
return { failure: `could not start: ${messageFor(error48)}`, timedOut: false };
|
|
14699
|
+
} finally {
|
|
14700
|
+
closeSync(fd);
|
|
14701
|
+
}
|
|
14702
|
+
try {
|
|
14703
|
+
process.kill(-result.pid, "SIGKILL");
|
|
14704
|
+
} catch {}
|
|
14705
|
+
if (result.exitedDueToTimeout) {
|
|
14706
|
+
return {
|
|
14707
|
+
failure: `did not finish within ${COMMAND_TIMEOUT_MS / 1000} s, and does not run again in this process`,
|
|
14708
|
+
timedOut: true
|
|
14709
|
+
};
|
|
14710
|
+
}
|
|
14711
|
+
if (!result.success) {
|
|
14488
14712
|
return {
|
|
14489
|
-
|
|
14490
|
-
|
|
14491
|
-
token: null,
|
|
14492
|
-
error: "repository dispatch.serverUrl requires dispatch.token from the same .opencode/envoy.json or DISPATCH_URL with DISPATCH_TOKEN"
|
|
14713
|
+
failure: `exited with ${result.signalCode ?? `status ${result.exitCode}`}`,
|
|
14714
|
+
timedOut: false
|
|
14493
14715
|
};
|
|
14494
14716
|
}
|
|
14495
|
-
const
|
|
14496
|
-
if (
|
|
14497
|
-
return {
|
|
14498
|
-
|
|
14717
|
+
const value = readFileSync2(output, "utf-8").trim();
|
|
14718
|
+
if (value.length === 0)
|
|
14719
|
+
return { failure: "printed nothing", timedOut: false };
|
|
14720
|
+
if (!sendable(value)) {
|
|
14721
|
+
return {
|
|
14722
|
+
failure: "printed more than one line, or whitespace inside its value",
|
|
14723
|
+
timedOut: false
|
|
14724
|
+
};
|
|
14725
|
+
}
|
|
14726
|
+
return { value };
|
|
14727
|
+
} finally {
|
|
14728
|
+
rmSync(directory, { recursive: true, force: true });
|
|
14729
|
+
}
|
|
14730
|
+
}
|
|
14731
|
+
function fileValue(value, field, filePath, env) {
|
|
14732
|
+
if (!value.startsWith("!")) {
|
|
14733
|
+
if (!sendable(value)) {
|
|
14734
|
+
throw new Error(`${filePath}: ${field} holds whitespace or a control character`);
|
|
14735
|
+
}
|
|
14736
|
+
return value;
|
|
14737
|
+
}
|
|
14738
|
+
const command = value.slice(1);
|
|
14739
|
+
const cwd = path.dirname(path.resolve(filePath));
|
|
14740
|
+
const key = JSON.stringify([command, filePath, cwd]);
|
|
14741
|
+
let outcome = commandOutcomes.get(key);
|
|
14742
|
+
if (outcome === undefined || "failure" in outcome && !outcome.timedOut && Date.now() - outcome.at >= FAILURE_KEPT_MS) {
|
|
14743
|
+
outcome = { ...runCommand(command, env, cwd), at: Date.now() };
|
|
14744
|
+
commandOutcomes.set(key, outcome);
|
|
14745
|
+
}
|
|
14746
|
+
if ("failure" in outcome)
|
|
14747
|
+
throw new Error(`${filePath}: ${field}'s command ${outcome.failure}`);
|
|
14748
|
+
return outcome.value;
|
|
14749
|
+
}
|
|
14750
|
+
function statDevice(directory) {
|
|
14751
|
+
try {
|
|
14752
|
+
return statSync(directory).dev;
|
|
14753
|
+
} catch {
|
|
14754
|
+
return;
|
|
14755
|
+
}
|
|
14756
|
+
}
|
|
14757
|
+
function moveNotice(oldFile, newFile, move, deviceOf) {
|
|
14758
|
+
const oldDevice = deviceOf(path.dirname(oldFile));
|
|
14759
|
+
let ancestor = path.dirname(newFile);
|
|
14760
|
+
let newDevice = deviceOf(ancestor);
|
|
14761
|
+
while (newDevice === undefined && path.dirname(ancestor) !== ancestor) {
|
|
14762
|
+
ancestor = path.dirname(ancestor);
|
|
14763
|
+
newDevice = deviceOf(ancestor);
|
|
14764
|
+
}
|
|
14765
|
+
if (oldDevice !== undefined && newDevice !== undefined && oldDevice !== newDevice) {
|
|
14766
|
+
return `${oldFile} is not read, and is on another filesystem than ${newFile} (a mount): create ${newFile}, and move ${oldFile} on the machine that owns it`;
|
|
14767
|
+
}
|
|
14768
|
+
return `${oldFile} is not read: ${move}`;
|
|
14769
|
+
}
|
|
14770
|
+
function oldUserFileNotice(filePath, env, deviceOf) {
|
|
14771
|
+
const home = nonEmpty(env.HOME) ?? homedir();
|
|
14772
|
+
const old = path.join(home, ".config", "opencode", "envoy.json");
|
|
14773
|
+
if (!existsSync(old))
|
|
14774
|
+
return;
|
|
14775
|
+
const selected = path.resolve(filePath);
|
|
14776
|
+
const userFile = path.resolve(home, ".config", "legion", "envoy.json");
|
|
14777
|
+
if (selected === userFile) {
|
|
14778
|
+
return moveNotice(old, userFile, "mkdir -p ~/.config/legion && mv ~/.config/opencode/envoy.json ~/.config/legion/", deviceOf);
|
|
14779
|
+
}
|
|
14780
|
+
const profileFile = PLUGIN_HARNESSES.map((harness) => namedDirectory(harness, env)).some((directory) => directory !== undefined && path.isAbsolute(directory) && selected === path.resolve(directory, "envoy.json"));
|
|
14781
|
+
if (profileFile) {
|
|
14782
|
+
return `${old} is not read for this profile; copy or link it to ${filePath}, or write that file`;
|
|
14783
|
+
}
|
|
14784
|
+
const configHome = nonEmpty(env.XDG_CONFIG_HOME);
|
|
14785
|
+
const neutral = neutralConfig(env);
|
|
14786
|
+
if (configHome !== undefined && path.isAbsolute(configHome) && "path" in neutral && selected === neutral.path) {
|
|
14787
|
+
return `${old} is not read under XDG_CONFIG_HOME=${configHome}`;
|
|
14788
|
+
}
|
|
14789
|
+
return;
|
|
14790
|
+
}
|
|
14791
|
+
function missingConfigNotice(filePath, missing, off) {
|
|
14792
|
+
const link = missing.link === null ? "" : ` (a link to ${missing.link}, which does not exist)`;
|
|
14793
|
+
const old = missing.oldUserFileNotice === undefined ? "" : `; ${missing.oldUserFileNotice}`;
|
|
14794
|
+
return `no envoy.json at ${filePath}${link}: ${off}${old}`;
|
|
14795
|
+
}
|
|
14796
|
+
function readEnvoyConfig(filePath, env, options) {
|
|
14797
|
+
const deviceOf = options.deviceOf ?? statDevice;
|
|
14798
|
+
const chosen = parseFile(filePath, EnvoyFileSchema, false);
|
|
14799
|
+
if (chosen.kind === "invalid")
|
|
14800
|
+
return { kind: "failed", path: filePath, error: chosen.reason };
|
|
14801
|
+
const resolvedChosen = path.resolve(filePath);
|
|
14802
|
+
const checkoutPath = path.resolve(options.cwd, ".envoy", "envoy.json");
|
|
14803
|
+
const checkoutFile = checkoutPath === resolvedChosen ? { kind: "absent", link: null } : parseFile(checkoutPath, CheckoutFileSchema, true);
|
|
14804
|
+
const own = chosen.kind === "valid" ? chosen.settings : {};
|
|
14805
|
+
let checkout = null;
|
|
14806
|
+
if (checkoutFile.kind === "invalid") {
|
|
14807
|
+
checkout = { kind: "invalid", path: checkoutPath, error: checkoutFile.reason };
|
|
14808
|
+
} else if (checkoutFile.kind === "valid") {
|
|
14809
|
+
checkout = {
|
|
14810
|
+
kind: "valid",
|
|
14811
|
+
path: checkoutPath,
|
|
14812
|
+
dispatch: checkoutFile.settings.dispatch ?? {},
|
|
14813
|
+
envoyError: Object.hasOwn(checkoutFile.settings, "envoy") ? `${checkoutPath}: envoy is not read from a checkout's envoy.json; put it in ${filePath}` : null
|
|
14814
|
+
};
|
|
14815
|
+
}
|
|
14816
|
+
const oldCheckout = path.resolve(options.cwd, ".opencode", "envoy.json");
|
|
14817
|
+
const warnings = oldCheckout !== resolvedChosen && !existsSync(checkoutPath) && existsSync(oldCheckout) ? [
|
|
14818
|
+
moveNotice(oldCheckout, checkoutPath, `mkdir -p ${path.dirname(checkoutPath)} && mv ${oldCheckout} ${checkoutPath}`, deviceOf)
|
|
14819
|
+
] : [];
|
|
14820
|
+
const unreadOldUserFile = chosen.kind === "absent" ? oldUserFileNotice(filePath, env, deviceOf) : undefined;
|
|
14821
|
+
return {
|
|
14822
|
+
kind: "read",
|
|
14823
|
+
path: filePath,
|
|
14824
|
+
dispatch: own.dispatch ?? {},
|
|
14825
|
+
envoy: own.envoy ?? {},
|
|
14826
|
+
checkout,
|
|
14827
|
+
missing: chosen.kind === "absent" ? {
|
|
14828
|
+
link: chosen.link,
|
|
14829
|
+
...unreadOldUserFile === undefined ? {} : { oldUserFileNotice: unreadOldUserFile }
|
|
14830
|
+
} : null,
|
|
14831
|
+
warnings
|
|
14832
|
+
};
|
|
14833
|
+
}
|
|
14834
|
+
|
|
14835
|
+
// ../envoy-client/src/bearer.ts
|
|
14836
|
+
function bearerSource(env, section, written) {
|
|
14837
|
+
const pointer = nonEmpty(env[`${section}_TOKEN_FILE`]);
|
|
14838
|
+
if (pointer !== undefined) {
|
|
14839
|
+
return { kind: "variable", token: readSecretFile(`${section}_TOKEN_FILE`, pointer) };
|
|
14840
|
+
}
|
|
14841
|
+
const plain = nonEmpty(env[`${section}_TOKEN`]);
|
|
14842
|
+
if (plain !== undefined) {
|
|
14843
|
+
if (!sendable(plain)) {
|
|
14844
|
+
throw new Error(`${section}_TOKEN holds whitespace or a control character`);
|
|
14845
|
+
}
|
|
14846
|
+
return { kind: "variable", token: plain };
|
|
14847
|
+
}
|
|
14848
|
+
const bearer = written();
|
|
14849
|
+
return bearer === undefined ? { kind: "none" } : { kind: "file", ...bearer };
|
|
14850
|
+
}
|
|
14851
|
+
function fileBearer(bearer, url2, section, env) {
|
|
14852
|
+
const named = httpUrl2(url2)?.origin;
|
|
14853
|
+
const paired = httpUrl2(bearer.server)?.origin;
|
|
14854
|
+
if (named !== paired) {
|
|
14855
|
+
throw new Error(`${section}_URL names ${named}, but ${bearer.path}'s ${bearer.field} is for ${paired}; set ${section}_TOKEN or ${section}_TOKEN_FILE for it`);
|
|
14856
|
+
}
|
|
14857
|
+
if (bearer.checkout && bearer.token.startsWith("!")) {
|
|
14858
|
+
throw new Error(`${bearer.path}: ${bearer.field} is a command, which a checkout's envoy.json cannot run`);
|
|
14859
|
+
}
|
|
14860
|
+
if (bearer.token === "") {
|
|
14861
|
+
throw new Error(`${bearer.field} in ${bearer.path} must be a non-empty bearer token`);
|
|
14862
|
+
}
|
|
14863
|
+
return fileValue(bearer.token, bearer.field, bearer.path, env);
|
|
14864
|
+
}
|
|
14865
|
+
|
|
14866
|
+
// ../envoy-client/src/dispatch-config.ts
|
|
14867
|
+
var DEFAULT_SERVER_URL = "http://localhost:8766";
|
|
14868
|
+
function checkoutPair(checkout, serverUrl) {
|
|
14869
|
+
const { token } = checkout.dispatch;
|
|
14870
|
+
if (token === undefined) {
|
|
14871
|
+
throw new Error(`${checkout.path}: dispatch.serverUrl requires dispatch.token in the same file`);
|
|
14872
|
+
}
|
|
14873
|
+
return {
|
|
14874
|
+
kind: "file",
|
|
14875
|
+
token,
|
|
14876
|
+
field: "dispatch.token",
|
|
14877
|
+
path: checkout.path,
|
|
14878
|
+
server: serverUrl,
|
|
14879
|
+
checkout: true
|
|
14880
|
+
};
|
|
14881
|
+
}
|
|
14882
|
+
function writtenToken(config2, checkout) {
|
|
14883
|
+
if (checkout?.dispatch.token !== undefined) {
|
|
14884
|
+
throw new Error(`${checkout.path}: dispatch.token requires dispatch.serverUrl in the same file`);
|
|
14885
|
+
}
|
|
14886
|
+
const own = config2.dispatch;
|
|
14887
|
+
if (own.token === undefined)
|
|
14888
|
+
return;
|
|
14889
|
+
const server = own.serverUrl ?? DEFAULT_SERVER_URL;
|
|
14890
|
+
return {
|
|
14891
|
+
token: own.token,
|
|
14892
|
+
field: "dispatch.token",
|
|
14893
|
+
path: config2.path,
|
|
14894
|
+
server,
|
|
14895
|
+
checkout: false
|
|
14896
|
+
};
|
|
14897
|
+
}
|
|
14898
|
+
function dispatchSettings(env, config2) {
|
|
14899
|
+
if (config2.kind === "failed")
|
|
14900
|
+
return offWithError(config2.error);
|
|
14901
|
+
const explicitUrl = nonEmpty(env.DISPATCH_URL);
|
|
14902
|
+
const checkoutFile = explicitUrl === undefined ? config2.checkout : null;
|
|
14903
|
+
if (checkoutFile?.kind === "invalid")
|
|
14904
|
+
return offWithError(checkoutFile.error);
|
|
14905
|
+
const checkout = checkoutFile?.kind === "valid" ? checkoutFile : null;
|
|
14906
|
+
const own = config2.dispatch;
|
|
14907
|
+
if (own.enabled === false) {
|
|
14908
|
+
return offWithNotice(`${config2.path} sets dispatch.enabled to false: Dispatch is off`);
|
|
14909
|
+
}
|
|
14910
|
+
if (checkout?.dispatch.enabled === false) {
|
|
14911
|
+
return offWithNotice(`${checkout.path} sets dispatch.enabled to false: Dispatch is off`);
|
|
14912
|
+
}
|
|
14913
|
+
const checkoutServer = checkout?.dispatch.serverUrl;
|
|
14914
|
+
const addressed = explicitUrl !== undefined || checkoutServer !== undefined || own.serverUrl !== undefined || own.enabled === true;
|
|
14915
|
+
if (!addressed) {
|
|
14916
|
+
return offWithNotice(config2.missing === null ? `${config2.path} names no Dispatch server, and DISPATCH_URL is not set: Dispatch is off` : missingConfigNotice(config2.path, config2.missing, "Dispatch is off"));
|
|
14917
|
+
}
|
|
14918
|
+
if (explicitUrl !== undefined && httpUrl2(explicitUrl) === undefined) {
|
|
14919
|
+
return offWithError("DISPATCH_URL must be an http or https URL");
|
|
14920
|
+
}
|
|
14921
|
+
try {
|
|
14922
|
+
const source = checkout !== null && checkoutServer !== undefined ? checkoutPair(checkout, checkoutServer) : bearerSource(env, "DISPATCH", () => writtenToken(config2, checkout));
|
|
14923
|
+
if (source.kind === "none") {
|
|
14924
|
+
return offWithError(explicitUrl === undefined ? `${config2.path}: dispatch is enabled and no token is: set dispatch.token, or DISPATCH_TOKEN or DISPATCH_TOKEN_FILE` : `DISPATCH_URL is set and no token is: set DISPATCH_TOKEN or DISPATCH_TOKEN_FILE, or dispatch.token in ${config2.path}`);
|
|
14925
|
+
}
|
|
14926
|
+
const url2 = (explicitUrl ?? (source.kind === "file" ? source.server : own.serverUrl ?? DEFAULT_SERVER_URL)).replace(/\/+$/, "");
|
|
14927
|
+
const token = source.kind === "variable" ? source.token : fileBearer(source, url2, "DISPATCH", env);
|
|
14928
|
+
return { enabled: true, url: url2, token };
|
|
14929
|
+
} catch (error48) {
|
|
14930
|
+
return offWithError(messageFor(error48));
|
|
14931
|
+
}
|
|
14932
|
+
}
|
|
14933
|
+
|
|
14934
|
+
// ../envoy-client/src/envoy-settings.ts
|
|
14935
|
+
var DEFAULT_ENVOY_URL = "http://127.0.0.1:9020";
|
|
14936
|
+
var DEFAULT_HEARTBEAT_MS = 120000;
|
|
14937
|
+
var MIN_HEARTBEAT_MS = 25;
|
|
14938
|
+
function natsSeedFromEnvironment(env) {
|
|
14939
|
+
const { NATS_NKEY_SEED_FILE: file2, NATS_NKEY_SEED: plain } = env;
|
|
14940
|
+
if (file2 !== undefined) {
|
|
14941
|
+
if (file2 === "")
|
|
14942
|
+
throw new Error("NATS_NKEY_SEED_FILE is set but empty");
|
|
14943
|
+
return {
|
|
14944
|
+
value: readSecretFile("NATS_NKEY_SEED_FILE", file2),
|
|
14945
|
+
source: `NATS_NKEY_SEED_FILE (${file2})`
|
|
14946
|
+
};
|
|
14947
|
+
}
|
|
14948
|
+
if (plain === undefined)
|
|
14949
|
+
return;
|
|
14950
|
+
const value = plain.trim();
|
|
14951
|
+
if (value.length === 0)
|
|
14952
|
+
throw new Error("NATS_NKEY_SEED is set but empty");
|
|
14953
|
+
return { value, source: "NATS_NKEY_SEED" };
|
|
14954
|
+
}
|
|
14955
|
+
var NO_NATS_SERVER = "Envoy has no NATS server (envoy.natsUrls, ENVOY_NATS_URL)";
|
|
14956
|
+
var NO_NATS_CONNECTION = "this session opens no NATS connection";
|
|
14957
|
+
function natsServerKey(url2) {
|
|
14958
|
+
const match = /^([a-z][a-z0-9+.-]*):\/\/([^@/]*@)?(\[[^\]]*\]|[^:/]*)(?::(\d+))?(\/.*)?$/i.exec(url2);
|
|
14959
|
+
if (match === null)
|
|
14960
|
+
return url2;
|
|
14961
|
+
const [, scheme = "", userinfo = "", host = "", port = "4222", rest = ""] = match;
|
|
14962
|
+
return `${scheme.toLowerCase()}://${userinfo}${host.toLowerCase()}:${port}${rest.replace(/\/+$/, "")}`;
|
|
14963
|
+
}
|
|
14964
|
+
function natsCredential(env, section, namedNatsUrls, filePath) {
|
|
14965
|
+
const fromEnvironment = natsSeedFromEnvironment(env);
|
|
14966
|
+
if (fromEnvironment !== undefined || section.natsSeed === undefined)
|
|
14967
|
+
return fromEnvironment;
|
|
14968
|
+
if (namedNatsUrls.length > 0) {
|
|
14969
|
+
const servers = new Set((section.natsUrls ?? []).map(natsServerKey));
|
|
14970
|
+
const named = new Set(namedNatsUrls.map(natsServerKey));
|
|
14971
|
+
if (named.size !== servers.size || ![...named].every((server) => servers.has(server))) {
|
|
14972
|
+
throw new Error(`ENVOY_NATS_URL names other servers than ${filePath}'s envoy.natsUrls; set NATS_NKEY_SEED_FILE for them`);
|
|
14973
|
+
}
|
|
14974
|
+
}
|
|
14975
|
+
return {
|
|
14976
|
+
value: fileValue(section.natsSeed, "envoy.natsSeed", filePath, env),
|
|
14977
|
+
source: `envoy.natsSeed in ${filePath}`
|
|
14978
|
+
};
|
|
14979
|
+
}
|
|
14980
|
+
function envoySettings(env, config2, options = {}) {
|
|
14981
|
+
if (config2.kind === "failed")
|
|
14982
|
+
return offWithError(config2.error);
|
|
14983
|
+
const section = config2.envoy;
|
|
14984
|
+
if (section.enabled === false) {
|
|
14985
|
+
return offWithNotice(`${config2.path} sets envoy.enabled to false: Envoy is off`);
|
|
14499
14986
|
}
|
|
14500
|
-
const
|
|
14501
|
-
const
|
|
14987
|
+
const namedUrl = nonEmpty(env.ENVOY_URL);
|
|
14988
|
+
const namedNatsUrls = (env.ENVOY_NATS_URL ?? "").split(",").map((url3) => url3.trim()).filter((url3) => url3.length > 0);
|
|
14989
|
+
const addressed = namedUrl !== undefined || namedNatsUrls.length > 0 || section.url !== undefined || (section.natsUrls ?? []).length > 0 || section.enabled === true;
|
|
14990
|
+
if (!addressed) {
|
|
14991
|
+
return offWithNotice(config2.missing === null ? `${config2.path} names no Envoy listener or NATS server, and neither ENVOY_URL nor ENVOY_NATS_URL is set: Envoy is off` : missingConfigNotice(config2.path, config2.missing, "Envoy is off"));
|
|
14992
|
+
}
|
|
14993
|
+
if (namedUrl !== undefined && httpUrl2(namedUrl) === undefined) {
|
|
14994
|
+
return offWithError("ENVOY_URL must be an http or https URL");
|
|
14995
|
+
}
|
|
14996
|
+
const listener = section.url ?? DEFAULT_ENVOY_URL;
|
|
14997
|
+
const url2 = namedUrl ?? listener;
|
|
14502
14998
|
let token;
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14999
|
+
try {
|
|
15000
|
+
const source = bearerSource(env, "ENVOY", () => section.token === undefined ? undefined : {
|
|
15001
|
+
token: section.token,
|
|
15002
|
+
field: "envoy.token",
|
|
15003
|
+
path: config2.path,
|
|
15004
|
+
server: listener,
|
|
15005
|
+
checkout: false
|
|
15006
|
+
});
|
|
15007
|
+
token = source.kind === "none" ? undefined : source.kind === "variable" ? source.token : fileBearer(source, url2, "ENVOY", env);
|
|
15008
|
+
} catch (error48) {
|
|
15009
|
+
return offWithError(messageFor(error48));
|
|
15010
|
+
}
|
|
15011
|
+
const natsUrls = namedNatsUrls.length > 0 ? namedNatsUrls : section.natsUrls ?? [];
|
|
15012
|
+
let inbound;
|
|
15013
|
+
if (options.nats === false)
|
|
15014
|
+
inbound = { off: NO_NATS_CONNECTION };
|
|
15015
|
+
else if (natsUrls.length === 0)
|
|
15016
|
+
inbound = { off: NO_NATS_SERVER };
|
|
15017
|
+
else {
|
|
14506
15018
|
try {
|
|
14507
|
-
|
|
15019
|
+
inbound = { natsUrls, natsSeed: natsCredential(env, section, namedNatsUrls, config2.path) };
|
|
14508
15020
|
} catch (error48) {
|
|
14509
|
-
|
|
15021
|
+
inbound = { off: messageFor(error48) };
|
|
14510
15022
|
}
|
|
14511
|
-
} else if (env.DISPATCH_TOKEN !== undefined) {
|
|
14512
|
-
tokenSource = "DISPATCH_TOKEN";
|
|
14513
|
-
token = env.DISPATCH_TOKEN;
|
|
14514
|
-
} else {
|
|
14515
|
-
tokenSource = "dispatch.token";
|
|
14516
|
-
token = merged.token ?? null;
|
|
14517
|
-
}
|
|
14518
|
-
if (url2.error !== null)
|
|
14519
|
-
return { enabled: false, url: null, token, error: url2.error };
|
|
14520
|
-
if (url2.url === null)
|
|
14521
|
-
return { enabled: false, url: null, token, error: null };
|
|
14522
|
-
if (!token) {
|
|
14523
|
-
return {
|
|
14524
|
-
enabled: false,
|
|
14525
|
-
url: url2.url,
|
|
14526
|
-
token,
|
|
14527
|
-
error: `${tokenSource} must be a non-empty bearer token`
|
|
14528
|
-
};
|
|
14529
15023
|
}
|
|
14530
|
-
|
|
15024
|
+
const rawHeartbeatMs = Number(env.ENVOY_HEARTBEAT_MS);
|
|
15025
|
+
return {
|
|
15026
|
+
enabled: true,
|
|
15027
|
+
url: normalizeEnvoyUrl(url2),
|
|
15028
|
+
token,
|
|
15029
|
+
inbound,
|
|
15030
|
+
heartbeatMs: Number.isFinite(rawHeartbeatMs) && rawHeartbeatMs > 0 ? Math.max(rawHeartbeatMs, MIN_HEARTBEAT_MS) : DEFAULT_HEARTBEAT_MS
|
|
15031
|
+
};
|
|
14531
15032
|
}
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
import { join as join2 } from "path";
|
|
14535
|
-
function dispatchFirstSkillFile(skillsDirectory) {
|
|
14536
|
-
return join2(skillsDirectory, "dispatch-first", "SKILL.md");
|
|
15033
|
+
function normalizeEnvoyUrl(value) {
|
|
15034
|
+
return value.replace(/\/+$/, "");
|
|
14537
15035
|
}
|
|
14538
15036
|
|
|
14539
|
-
// ../envoy-client/src/
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
15037
|
+
// ../envoy-client/src/session-settings.ts
|
|
15038
|
+
function sessionConfigFile(harness, env) {
|
|
15039
|
+
try {
|
|
15040
|
+
return { path: envoyConfigPath(harness, env), error: null };
|
|
15041
|
+
} catch (error48) {
|
|
15042
|
+
return { path: null, error: messageFor(error48) };
|
|
15043
|
+
}
|
|
15044
|
+
}
|
|
15045
|
+
function startNotice(config2, dispatch, envoy, nats) {
|
|
15046
|
+
if (config2.kind === "failed")
|
|
15047
|
+
return `${config2.error}: Dispatch and Envoy are off`;
|
|
15048
|
+
const { missing, checkout } = config2;
|
|
15049
|
+
const dispatchQuiet = !dispatch.enabled && dispatch.error === null;
|
|
15050
|
+
const envoyQuiet = !envoy.enabled && envoy.error === null;
|
|
15051
|
+
const parts = [];
|
|
15052
|
+
if (missing !== null && dispatchQuiet && envoyQuiet) {
|
|
15053
|
+
parts.push(missingConfigNotice(config2.path, missing, "Dispatch and Envoy are off"));
|
|
15054
|
+
} else {
|
|
15055
|
+
if (!dispatch.enabled && dispatch.error !== null)
|
|
15056
|
+
parts.push(`Dispatch is off: ${dispatch.error}`);
|
|
15057
|
+
else if (dispatchQuiet && missing !== null) {
|
|
15058
|
+
parts.push(missingConfigNotice(config2.path, missing, "Dispatch is off"));
|
|
15059
|
+
}
|
|
15060
|
+
if (!envoy.enabled && envoy.error !== null)
|
|
15061
|
+
parts.push(`Envoy is off: ${envoy.error}`);
|
|
15062
|
+
else if (envoyQuiet && missing !== null) {
|
|
15063
|
+
parts.push(missingConfigNotice(config2.path, missing, "Envoy is off"));
|
|
15064
|
+
}
|
|
15065
|
+
}
|
|
15066
|
+
if (checkout?.kind === "valid" && checkout.envoyError !== null)
|
|
15067
|
+
parts.push(checkout.envoyError);
|
|
15068
|
+
if (checkout?.kind === "invalid" && (dispatch.enabled || dispatch.error !== checkout.error)) {
|
|
15069
|
+
parts.push(`${checkout.error}; not read while DISPATCH_URL is set`);
|
|
15070
|
+
}
|
|
15071
|
+
parts.push(...config2.warnings);
|
|
15072
|
+
if (envoy.enabled && nats && "off" in envoy.inbound) {
|
|
15073
|
+
parts.push(`Envoy's inbound messages are off: ${envoy.inbound.off}; the Envoy tools still work`);
|
|
15074
|
+
}
|
|
15075
|
+
return parts.length === 0 ? null : parts.join("; ");
|
|
15076
|
+
}
|
|
15077
|
+
function sessionSettings(file2, env, options) {
|
|
15078
|
+
if (file2.path === null) {
|
|
15079
|
+
const off = offWithError(file2.error);
|
|
15080
|
+
return { dispatch: off, envoy: off, notice: `${file2.error}: Dispatch and Envoy are off` };
|
|
15081
|
+
}
|
|
15082
|
+
const config2 = readEnvoyConfig(file2.path, env, options);
|
|
15083
|
+
const dispatch = dispatchSettings(env, config2);
|
|
15084
|
+
const envoy = envoySettings(env, config2, { nats: options.nats });
|
|
15085
|
+
return { dispatch, envoy, notice: startNotice(config2, dispatch, envoy, options.nats !== false) };
|
|
14543
15086
|
}
|
|
14544
15087
|
|
|
14545
15088
|
// ../envoy-client/src/dispatch-command.ts
|
|
@@ -14736,16 +15279,10 @@ class EnvoyApiError extends Error {
|
|
|
14736
15279
|
this.expected = parsed?.expected;
|
|
14737
15280
|
}
|
|
14738
15281
|
}
|
|
14739
|
-
function resolveEnvoyToken(env) {
|
|
14740
|
-
const { ENVOY_TOKEN_FILE: file2, ENVOY_TOKEN: plain } = env;
|
|
14741
|
-
if (file2 !== undefined && file2 !== "")
|
|
14742
|
-
return readSecretFile("ENVOY_TOKEN_FILE", file2);
|
|
14743
|
-
return plain || undefined;
|
|
14744
|
-
}
|
|
14745
15282
|
function createEnvoyClient(config2) {
|
|
14746
15283
|
const baseUrl = normalizeEnvoyUrl(config2.baseUrl);
|
|
14747
15284
|
const timeoutMs = config2.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
14748
|
-
const apiToken =
|
|
15285
|
+
const apiToken = config2.token;
|
|
14749
15286
|
const request = async (path2, init) => {
|
|
14750
15287
|
const url2 = `${baseUrl}${path2}`;
|
|
14751
15288
|
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
@@ -14876,8 +15413,8 @@ function createEnvoyClient(config2) {
|
|
|
14876
15413
|
};
|
|
14877
15414
|
}
|
|
14878
15415
|
function waitForRetry() {
|
|
14879
|
-
const { promise: promise2, resolve } = Promise.withResolvers();
|
|
14880
|
-
setTimeout(
|
|
15416
|
+
const { promise: promise2, resolve: resolve2 } = Promise.withResolvers();
|
|
15417
|
+
setTimeout(resolve2, RETRY_DELAY_MS);
|
|
14881
15418
|
return promise2;
|
|
14882
15419
|
}
|
|
14883
15420
|
function messageMetadata(input) {
|
|
@@ -14957,12 +15494,12 @@ function portFromSsOutput(output, pid) {
|
|
|
14957
15494
|
}
|
|
14958
15495
|
|
|
14959
15496
|
// src/port.ts
|
|
14960
|
-
var defaultExec = (command, args, options) => new Promise((
|
|
15497
|
+
var defaultExec = (command, args, options) => new Promise((resolve2, reject) => {
|
|
14961
15498
|
execFile(command, args, { encoding: options.encoding }, (error48, stdout) => {
|
|
14962
15499
|
if (error48)
|
|
14963
15500
|
reject(error48);
|
|
14964
15501
|
else
|
|
14965
|
-
|
|
15502
|
+
resolve2(stdout);
|
|
14966
15503
|
});
|
|
14967
15504
|
});
|
|
14968
15505
|
async function resolvePort(serverUrl, exec = defaultExec) {
|
|
@@ -14983,7 +15520,7 @@ var moduleDirectory = path3.dirname(fileURLToPath(import.meta.url));
|
|
|
14983
15520
|
var skillsDirectory = [
|
|
14984
15521
|
path3.resolve(moduleDirectory, "../../skills"),
|
|
14985
15522
|
path3.resolve(moduleDirectory, "../../../skills")
|
|
14986
|
-
].find((dir) =>
|
|
15523
|
+
].find((dir) => existsSync2(dir));
|
|
14987
15524
|
var PACKAGE_NAME = "@sjawhar/opencode-legion-envoy";
|
|
14988
15525
|
function packageRoot(from) {
|
|
14989
15526
|
for (let directory = from;; directory = path3.dirname(directory)) {
|
|
@@ -15017,24 +15554,138 @@ var publishSpec = toolSpec("envoy_publish");
|
|
|
15017
15554
|
var roleSetSpec = toolSpec("envoy_role_set");
|
|
15018
15555
|
var whoamiSpec = toolSpec("envoy_whoami");
|
|
15019
15556
|
var sessionsSpec = toolSpec("envoy_sessions");
|
|
15557
|
+
function envoyTools(client, port, title) {
|
|
15558
|
+
return {
|
|
15559
|
+
envoy_subscribe: tool({
|
|
15560
|
+
description: subscribeSpec.description,
|
|
15561
|
+
args: subscribeSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15562
|
+
async execute(args, ctx) {
|
|
15563
|
+
ctx.metadata({ title: "Envoy subscribe" });
|
|
15564
|
+
return JSON.stringify(await client.subscribe({
|
|
15565
|
+
sessionID: ctx.sessionID,
|
|
15566
|
+
directory: ctx.directory,
|
|
15567
|
+
topics: args.topics,
|
|
15568
|
+
port: port() ?? 0,
|
|
15569
|
+
title: title() ?? "",
|
|
15570
|
+
driving: true
|
|
15571
|
+
}));
|
|
15572
|
+
}
|
|
15573
|
+
}),
|
|
15574
|
+
envoy_unsubscribe: tool({
|
|
15575
|
+
description: unsubscribeSpec.description,
|
|
15576
|
+
args: unsubscribeSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15577
|
+
async execute(args, ctx) {
|
|
15578
|
+
ctx.metadata({ title: "Envoy unsubscribe" });
|
|
15579
|
+
await client.unsubscribe({
|
|
15580
|
+
sessionID: ctx.sessionID,
|
|
15581
|
+
topics: args.topics ?? []
|
|
15582
|
+
});
|
|
15583
|
+
return "ok";
|
|
15584
|
+
}
|
|
15585
|
+
}),
|
|
15586
|
+
envoy_list: tool({
|
|
15587
|
+
description: listSpec.description,
|
|
15588
|
+
args: listSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15589
|
+
async execute(_args, ctx) {
|
|
15590
|
+
ctx.metadata({ title: "Envoy list" });
|
|
15591
|
+
return JSON.stringify(await client.getInterest(ctx.sessionID));
|
|
15592
|
+
}
|
|
15593
|
+
}),
|
|
15594
|
+
envoy_send: tool({
|
|
15595
|
+
description: sendSpec.description,
|
|
15596
|
+
args: sendSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15597
|
+
async execute(args, ctx) {
|
|
15598
|
+
ctx.metadata({ title: "Envoy send" });
|
|
15599
|
+
return JSON.stringify(await client.send({
|
|
15600
|
+
sourceSessionID: ctx.sessionID,
|
|
15601
|
+
targetSessionID: args.session_id,
|
|
15602
|
+
message: args.message,
|
|
15603
|
+
...toMessageMetadata(args)
|
|
15604
|
+
}));
|
|
15605
|
+
}
|
|
15606
|
+
}),
|
|
15607
|
+
envoy_publish: tool({
|
|
15608
|
+
description: publishSpec.description,
|
|
15609
|
+
args: publishSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15610
|
+
async execute(args, ctx) {
|
|
15611
|
+
ctx.metadata({ title: "Envoy publish" });
|
|
15612
|
+
return JSON.stringify(await client.publish({
|
|
15613
|
+
sourceSessionID: ctx.sessionID,
|
|
15614
|
+
topic: args.topic,
|
|
15615
|
+
message: args.message,
|
|
15616
|
+
...toMessageMetadata(args)
|
|
15617
|
+
}));
|
|
15618
|
+
}
|
|
15619
|
+
}),
|
|
15620
|
+
envoy_role_set: tool({
|
|
15621
|
+
description: roleSetSpec.description,
|
|
15622
|
+
args: roleSetSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15623
|
+
async execute(args, ctx) {
|
|
15624
|
+
ctx.metadata({ title: "Set Envoy role" });
|
|
15625
|
+
const result = await client.setRole({
|
|
15626
|
+
sessionID: ctx.sessionID,
|
|
15627
|
+
role: args.role
|
|
15628
|
+
});
|
|
15629
|
+
if (!result.claimed)
|
|
15630
|
+
throw new Error(`role ${args.role} is held by ${result.holder}`);
|
|
15631
|
+
return JSON.stringify(result.interest);
|
|
15632
|
+
}
|
|
15633
|
+
}),
|
|
15634
|
+
envoy_whoami: tool({
|
|
15635
|
+
description: whoamiSpec.description,
|
|
15636
|
+
args: whoamiSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15637
|
+
async execute(_args, ctx) {
|
|
15638
|
+
ctx.metadata({ title: "Envoy whoami" });
|
|
15639
|
+
return JSON.stringify({
|
|
15640
|
+
session_id: ctx.sessionID,
|
|
15641
|
+
machine_id: machineID(),
|
|
15642
|
+
port: port(),
|
|
15643
|
+
dir: ctx.directory
|
|
15644
|
+
}, null, 2);
|
|
15645
|
+
}
|
|
15646
|
+
}),
|
|
15647
|
+
envoy_sessions: tool({
|
|
15648
|
+
description: sessionsSpec.description,
|
|
15649
|
+
args: sessionsSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15650
|
+
async execute(args, ctx) {
|
|
15651
|
+
ctx.metadata({ title: "Envoy sessions" });
|
|
15652
|
+
const sessions = await client.listSessions({
|
|
15653
|
+
...args.dir === undefined ? {} : { directory: args.dir },
|
|
15654
|
+
...args.title === undefined ? {} : { title: args.title }
|
|
15655
|
+
});
|
|
15656
|
+
const machine = args.machine;
|
|
15657
|
+
return JSON.stringify(machine ? sessions.filter((session) => session.machine_id === machine) : sessions, null, 2);
|
|
15658
|
+
}
|
|
15659
|
+
})
|
|
15660
|
+
};
|
|
15661
|
+
}
|
|
15020
15662
|
var server_default = async (input) => {
|
|
15021
15663
|
const cwd = process.cwd();
|
|
15022
|
-
const
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15664
|
+
const configFile = sessionConfigFile("opencode", process.env);
|
|
15665
|
+
const { dispatch, envoy, notice } = sessionSettings(configFile, process.env, {
|
|
15666
|
+
cwd,
|
|
15667
|
+
nats: false
|
|
15668
|
+
});
|
|
15669
|
+
if (notice !== null)
|
|
15670
|
+
logger.warn(`envoy: ${notice}`);
|
|
15026
15671
|
let dispatchFirst;
|
|
15027
|
-
if (
|
|
15672
|
+
if (dispatch.enabled) {
|
|
15028
15673
|
if (skillsDirectory === undefined) {
|
|
15029
15674
|
throw new Error(`envoy: no skills directory beside ${moduleDirectory}`);
|
|
15030
15675
|
}
|
|
15031
15676
|
dispatchFirst = dispatchFirstSkillFile(skillsDirectory);
|
|
15032
|
-
if (!
|
|
15677
|
+
if (!existsSync2(dispatchFirst)) {
|
|
15033
15678
|
throw new Error(`envoy: the dispatch-first skill ${dispatchFirst} is missing`);
|
|
15034
15679
|
}
|
|
15035
15680
|
}
|
|
15036
|
-
const
|
|
15037
|
-
|
|
15681
|
+
const envoyOn = envoy.enabled ? {
|
|
15682
|
+
heartbeatMs: envoy.heartbeatMs,
|
|
15683
|
+
client: createEnvoyClient({
|
|
15684
|
+
baseUrl: envoy.url,
|
|
15685
|
+
fetch: globalThis.fetch,
|
|
15686
|
+
token: envoy.token
|
|
15687
|
+
})
|
|
15688
|
+
} : undefined;
|
|
15038
15689
|
let activeSessionID = null;
|
|
15039
15690
|
let activeSessionTitle = null;
|
|
15040
15691
|
const trackedSessions = new Map;
|
|
@@ -15073,15 +15724,7 @@ var server_default = async (input) => {
|
|
|
15073
15724
|
return null;
|
|
15074
15725
|
}
|
|
15075
15726
|
};
|
|
15076
|
-
const
|
|
15077
|
-
syncPort().then((resolved) => {
|
|
15078
|
-
if (resolved)
|
|
15079
|
-
clearInterval(timer);
|
|
15080
|
-
});
|
|
15081
|
-
}, 1000);
|
|
15082
|
-
timer.unref();
|
|
15083
|
-
syncPort().catch(() => {});
|
|
15084
|
-
const subscribeSession = (sessionID, title, port, driving) => envoy.subscribe({
|
|
15727
|
+
const subscribeSession = (client, sessionID, title, port, driving) => client.subscribe({
|
|
15085
15728
|
sessionID,
|
|
15086
15729
|
directory: cwd,
|
|
15087
15730
|
topics: [agentSubject(sessionID)],
|
|
@@ -15089,20 +15732,31 @@ var server_default = async (input) => {
|
|
|
15089
15732
|
title: title ?? "",
|
|
15090
15733
|
driving
|
|
15091
15734
|
}).catch(() => {});
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
const
|
|
15095
|
-
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
|
|
15099
|
-
}
|
|
15100
|
-
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15735
|
+
let stopEnvoy = () => {};
|
|
15736
|
+
if (envoyOn !== undefined) {
|
|
15737
|
+
const portTimer = setInterval(() => {
|
|
15738
|
+
syncPort().then((resolved) => {
|
|
15739
|
+
if (resolved)
|
|
15740
|
+
clearInterval(portTimer);
|
|
15741
|
+
});
|
|
15742
|
+
}, 1000);
|
|
15743
|
+
portTimer.unref();
|
|
15744
|
+
syncPort().catch(() => {});
|
|
15745
|
+
const heartbeatTimer = setInterval(() => {
|
|
15746
|
+
const port = currentPort();
|
|
15747
|
+
if (!port)
|
|
15748
|
+
return;
|
|
15749
|
+
for (const [sessionID, info] of trackedSessions) {
|
|
15750
|
+
subscribeSession(envoyOn.client, sessionID, info.title, port, info.driving);
|
|
15751
|
+
}
|
|
15752
|
+
}, envoyOn.heartbeatMs);
|
|
15753
|
+
heartbeatTimer.unref?.();
|
|
15754
|
+
stopEnvoy = () => {
|
|
15755
|
+
clearInterval(portTimer);
|
|
15756
|
+
clearInterval(heartbeatTimer);
|
|
15757
|
+
};
|
|
15758
|
+
}
|
|
15759
|
+
process.on("exit", stopEnvoy);
|
|
15106
15760
|
return {
|
|
15107
15761
|
config: (cfg) => {
|
|
15108
15762
|
if (skillsDirectory) {
|
|
@@ -15121,6 +15775,8 @@ var server_default = async (input) => {
|
|
|
15121
15775
|
"shell.env": async (input2, output) => {
|
|
15122
15776
|
output.env.PATH = `${PLUGIN_BIN}${path3.delimiter}${output.env.PATH ?? process.env.PATH ?? ""}`;
|
|
15123
15777
|
output.env.DISPATCH_HOST = "opencode";
|
|
15778
|
+
if (configFile.path !== null)
|
|
15779
|
+
output.env.ENVOY_CONFIG_FILE = configFile.path;
|
|
15124
15780
|
output.env.DISPATCH_SESSION_ID = "";
|
|
15125
15781
|
output.env.DISPATCH_SESSION_TITLE = "";
|
|
15126
15782
|
if (input2.sessionID) {
|
|
@@ -15133,7 +15789,7 @@ var server_default = async (input) => {
|
|
|
15133
15789
|
event: async ({
|
|
15134
15790
|
event
|
|
15135
15791
|
}) => {
|
|
15136
|
-
if (activeSessionID)
|
|
15792
|
+
if (envoy.enabled && activeSessionID)
|
|
15137
15793
|
syncPort().catch(() => {});
|
|
15138
15794
|
if (event.type === "session.status" && event.properties?.status?.type === "busy") {
|
|
15139
15795
|
const sessionID = event.properties?.sessionID;
|
|
@@ -15143,26 +15799,29 @@ var server_default = async (input) => {
|
|
|
15143
15799
|
if (!trackedSessions.has(sessionID)) {
|
|
15144
15800
|
trackedSessions.set(sessionID, { title: null, driving: true });
|
|
15145
15801
|
}
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15802
|
+
const titlePromise = fetchTitle(sessionID).then((title) => {
|
|
15803
|
+
if (!title)
|
|
15804
|
+
return null;
|
|
15805
|
+
const tracked = trackedSessions.get(sessionID);
|
|
15806
|
+
if (tracked !== undefined) {
|
|
15807
|
+
trackedSessions.set(sessionID, { title, driving: tracked.driving });
|
|
15808
|
+
}
|
|
15809
|
+
if (activeSessionID === sessionID)
|
|
15810
|
+
activeSessionTitle = title;
|
|
15811
|
+
return title;
|
|
15152
15812
|
});
|
|
15153
|
-
if (
|
|
15154
|
-
await
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
const
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
|
|
15165
|
-
});
|
|
15813
|
+
if (envoyOn !== undefined) {
|
|
15814
|
+
await syncPort();
|
|
15815
|
+
const port = currentPort();
|
|
15816
|
+
if (port) {
|
|
15817
|
+
await subscribeSession(envoyOn.client, sessionID, activeSessionTitle, port, true);
|
|
15818
|
+
titlePromise.then((title) => {
|
|
15819
|
+
const tracked = trackedSessions.get(sessionID);
|
|
15820
|
+
if (title && tracked !== undefined) {
|
|
15821
|
+
subscribeSession(envoyOn.client, sessionID, title, currentPort() ?? 0, tracked.driving);
|
|
15822
|
+
}
|
|
15823
|
+
});
|
|
15824
|
+
}
|
|
15166
15825
|
}
|
|
15167
15826
|
}
|
|
15168
15827
|
}
|
|
@@ -15175,119 +15834,12 @@ var server_default = async (input) => {
|
|
|
15175
15834
|
activeSessionID = null;
|
|
15176
15835
|
activeSessionTitle = null;
|
|
15177
15836
|
}
|
|
15178
|
-
|
|
15837
|
+
envoyOn?.client.unsubscribe({ sessionID: deletedID, topics: [] }).catch(() => {});
|
|
15179
15838
|
}
|
|
15180
15839
|
}
|
|
15181
15840
|
},
|
|
15182
|
-
dispose:
|
|
15183
|
-
|
|
15184
|
-
clearInterval(heartbeatInterval);
|
|
15185
|
-
},
|
|
15186
|
-
tool: {
|
|
15187
|
-
envoy_subscribe: tool({
|
|
15188
|
-
description: subscribeSpec.description,
|
|
15189
|
-
args: subscribeSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15190
|
-
async execute(args, ctx) {
|
|
15191
|
-
ctx.metadata({ title: "Envoy subscribe" });
|
|
15192
|
-
return JSON.stringify(await envoy.subscribe({
|
|
15193
|
-
sessionID: ctx.sessionID,
|
|
15194
|
-
directory: ctx.directory,
|
|
15195
|
-
topics: args.topics,
|
|
15196
|
-
port: currentPort() ?? 0,
|
|
15197
|
-
title: activeSessionTitle ?? "",
|
|
15198
|
-
driving: true
|
|
15199
|
-
}));
|
|
15200
|
-
}
|
|
15201
|
-
}),
|
|
15202
|
-
envoy_unsubscribe: tool({
|
|
15203
|
-
description: unsubscribeSpec.description,
|
|
15204
|
-
args: unsubscribeSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15205
|
-
async execute(args, ctx) {
|
|
15206
|
-
ctx.metadata({ title: "Envoy unsubscribe" });
|
|
15207
|
-
await envoy.unsubscribe({
|
|
15208
|
-
sessionID: ctx.sessionID,
|
|
15209
|
-
topics: args.topics ?? []
|
|
15210
|
-
});
|
|
15211
|
-
return "ok";
|
|
15212
|
-
}
|
|
15213
|
-
}),
|
|
15214
|
-
envoy_list: tool({
|
|
15215
|
-
description: listSpec.description,
|
|
15216
|
-
args: listSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15217
|
-
async execute(_args, ctx) {
|
|
15218
|
-
ctx.metadata({ title: "Envoy list" });
|
|
15219
|
-
return JSON.stringify(await envoy.getInterest(ctx.sessionID));
|
|
15220
|
-
}
|
|
15221
|
-
}),
|
|
15222
|
-
envoy_send: tool({
|
|
15223
|
-
description: sendSpec.description,
|
|
15224
|
-
args: sendSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15225
|
-
async execute(args, ctx) {
|
|
15226
|
-
ctx.metadata({ title: "Envoy send" });
|
|
15227
|
-
return JSON.stringify(await envoy.send({
|
|
15228
|
-
sourceSessionID: ctx.sessionID,
|
|
15229
|
-
targetSessionID: args.session_id,
|
|
15230
|
-
message: args.message,
|
|
15231
|
-
...toMessageMetadata(args)
|
|
15232
|
-
}));
|
|
15233
|
-
}
|
|
15234
|
-
}),
|
|
15235
|
-
envoy_publish: tool({
|
|
15236
|
-
description: publishSpec.description,
|
|
15237
|
-
args: publishSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15238
|
-
async execute(args, ctx) {
|
|
15239
|
-
ctx.metadata({ title: "Envoy publish" });
|
|
15240
|
-
return JSON.stringify(await envoy.publish({
|
|
15241
|
-
sourceSessionID: ctx.sessionID,
|
|
15242
|
-
topic: args.topic,
|
|
15243
|
-
message: args.message,
|
|
15244
|
-
...toMessageMetadata(args)
|
|
15245
|
-
}));
|
|
15246
|
-
}
|
|
15247
|
-
}),
|
|
15248
|
-
envoy_role_set: tool({
|
|
15249
|
-
description: roleSetSpec.description,
|
|
15250
|
-
args: roleSetSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15251
|
-
async execute(args, ctx) {
|
|
15252
|
-
ctx.metadata({ title: "Set Envoy role" });
|
|
15253
|
-
const result = await envoy.setRole({
|
|
15254
|
-
sessionID: ctx.sessionID,
|
|
15255
|
-
role: args.role
|
|
15256
|
-
});
|
|
15257
|
-
if (!result.claimed)
|
|
15258
|
-
throw new Error(`role ${args.role} is held by ${result.holder}`);
|
|
15259
|
-
return JSON.stringify(result.interest);
|
|
15260
|
-
}
|
|
15261
|
-
}),
|
|
15262
|
-
envoy_whoami: tool({
|
|
15263
|
-
description: whoamiSpec.description,
|
|
15264
|
-
args: whoamiSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15265
|
-
async execute(_args, ctx) {
|
|
15266
|
-
ctx.metadata({ title: "Envoy whoami" });
|
|
15267
|
-
const sessionID = ctx.sessionID;
|
|
15268
|
-
const port = currentPort();
|
|
15269
|
-
return JSON.stringify({
|
|
15270
|
-
session_id: sessionID,
|
|
15271
|
-
machine_id: machineID(),
|
|
15272
|
-
port,
|
|
15273
|
-
dir: ctx.directory
|
|
15274
|
-
}, null, 2);
|
|
15275
|
-
}
|
|
15276
|
-
}),
|
|
15277
|
-
envoy_sessions: tool({
|
|
15278
|
-
description: sessionsSpec.description,
|
|
15279
|
-
args: sessionsSpec.arguments(zodSchemaApi(tool.schema)),
|
|
15280
|
-
async execute(args, ctx) {
|
|
15281
|
-
ctx.metadata({ title: "Envoy sessions" });
|
|
15282
|
-
const sessions = await envoy.listSessions({
|
|
15283
|
-
...args.dir === undefined ? {} : { directory: args.dir },
|
|
15284
|
-
...args.title === undefined ? {} : { title: args.title }
|
|
15285
|
-
});
|
|
15286
|
-
const machine = args.machine;
|
|
15287
|
-
return JSON.stringify(machine ? sessions.filter((session) => session.machine_id === machine) : sessions, null, 2);
|
|
15288
|
-
}
|
|
15289
|
-
})
|
|
15290
|
-
}
|
|
15841
|
+
dispose: stopEnvoy,
|
|
15842
|
+
tool: envoyOn === undefined ? {} : envoyTools(envoyOn.client, currentPort, () => activeSessionTitle)
|
|
15291
15843
|
};
|
|
15292
15844
|
};
|
|
15293
15845
|
export {
|