@sjawhar/pi-envoy 9.7.0 → 10.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/dist/dispatch.js CHANGED
@@ -14958,150 +14958,6 @@ function commandsHelp() {
14958
14958
  `);
14959
14959
  }
14960
14960
 
14961
- // ../envoy-client/src/dispatch-config.ts
14962
- import { readFileSync as readFileSync2 } from "fs";
14963
- import { homedir } from "os";
14964
- import * as path from "path";
14965
-
14966
- // ../envoy-client/src/secret-file.ts
14967
- import { readFileSync } from "fs";
14968
- function readSecretFile(variable, filePath) {
14969
- let contents;
14970
- try {
14971
- contents = readFileSync(filePath, "utf8");
14972
- } catch (error48) {
14973
- throw new Error(`${variable} names ${filePath}, which could not be read: ${messageFor(error48)}`);
14974
- }
14975
- const value = contents.trim();
14976
- if (value.length === 0)
14977
- throw new Error(`${variable} names ${filePath}, which is empty`);
14978
- return value;
14979
- }
14980
-
14981
- // ../envoy-client/src/dispatch-config.ts
14982
- var DEFAULT_SERVER_URL = "http://localhost:8766";
14983
- function normalizeDispatchUrl(url2) {
14984
- return url2.replace(/\/+$/, "");
14985
- }
14986
- function parsedDispatchUrl(value, source) {
14987
- const url2 = normalizeDispatchUrl(value);
14988
- try {
14989
- new URL(url2);
14990
- return { url: url2, error: null };
14991
- } catch {
14992
- return { url: null, error: `${source} must be a valid URL` };
14993
- }
14994
- }
14995
- var EnvoyFileSchema = exports_external.looseObject({
14996
- $schema: exports_external.string().optional(),
14997
- natsUrls: exports_external.array(exports_external.string()).optional(),
14998
- dispatch: exports_external.strictObject({
14999
- enabled: exports_external.boolean().optional(),
15000
- serverUrl: exports_external.url().optional(),
15001
- token: exports_external.string().optional()
15002
- }).optional()
15003
- });
15004
- function describeSchemaIssue(filePath, error48) {
15005
- const issue2 = error48.issues[0];
15006
- if (!issue2)
15007
- return `${filePath}: invalid dispatch config`;
15008
- if (issue2.code === "unrecognized_keys") {
15009
- const keys = issue2.keys.map((key) => `dispatch.${key}`).join(", ");
15010
- return `${filePath}: unrecognized dispatch key(s): ${keys}`;
15011
- }
15012
- return `${filePath}: ${issue2.path.join(".")}: ${issue2.message}`;
15013
- }
15014
- function readEnvoyFile(filePath) {
15015
- let raw;
15016
- try {
15017
- raw = readFileSync2(filePath, "utf-8");
15018
- } catch {
15019
- return { kind: "absent" };
15020
- }
15021
- let parsedJson;
15022
- try {
15023
- parsedJson = JSON.parse(raw);
15024
- } catch (err) {
15025
- return { kind: "invalid", reason: `${filePath}: invalid JSON (${messageFor(err)})` };
15026
- }
15027
- const parsed = EnvoyFileSchema.safeParse(parsedJson);
15028
- if (!parsed.success) {
15029
- return { kind: "invalid", reason: describeSchemaIssue(filePath, parsed.error) };
15030
- }
15031
- return { kind: "valid", settings: parsed.data.dispatch ?? null };
15032
- }
15033
- function resolveDispatchConfig(env, options = {}) {
15034
- const explicitUrl = env.DISPATCH_URL;
15035
- const home = options.home ?? env.HOME ?? homedir();
15036
- const cwd = options.cwd ?? process.cwd();
15037
- const userFile = readEnvoyFile(path.join(home, ".config", "opencode", "envoy.json"));
15038
- const repoFile = readEnvoyFile(path.join(cwd, ".opencode", "envoy.json"));
15039
- for (const file2 of [userFile, repoFile]) {
15040
- if (file2.kind === "invalid") {
15041
- return { enabled: false, url: null, token: null, error: file2.reason };
15042
- }
15043
- }
15044
- const userSettings = userFile.kind === "valid" ? userFile.settings : null;
15045
- const repoSettings = repoFile.kind === "valid" ? repoFile.settings : null;
15046
- const merged = { ...userSettings, ...repoSettings };
15047
- const repoURL = repoSettings?.serverUrl;
15048
- if (explicitUrl === undefined && merged.enabled === true && repoURL !== undefined) {
15049
- const token2 = repoSettings?.token ?? null;
15050
- if (!token2) {
15051
- return {
15052
- enabled: false,
15053
- url: null,
15054
- token: null,
15055
- error: "repository dispatch.serverUrl requires dispatch.token from the same .opencode/envoy.json or DISPATCH_URL with DISPATCH_TOKEN"
15056
- };
15057
- }
15058
- const url3 = parsedDispatchUrl(repoURL, "repository dispatch.serverUrl");
15059
- if (url3.error !== null)
15060
- return { enabled: false, url: null, token: null, error: url3.error };
15061
- return { enabled: true, url: url3.url, token: token2, error: null };
15062
- }
15063
- const rawUrl = explicitUrl !== undefined ? { value: explicitUrl, source: "DISPATCH_URL" } : merged.enabled === true ? { value: merged.serverUrl ?? DEFAULT_SERVER_URL, source: "dispatch.serverUrl" } : null;
15064
- const url2 = rawUrl ? parsedDispatchUrl(rawUrl.value, rawUrl.source) : { url: null, error: null };
15065
- let token;
15066
- let tokenSource;
15067
- if (env.DISPATCH_TOKEN_FILE !== undefined) {
15068
- tokenSource = "DISPATCH_TOKEN_FILE";
15069
- try {
15070
- token = readSecretFile(tokenSource, env.DISPATCH_TOKEN_FILE);
15071
- } catch (error48) {
15072
- return { enabled: false, url: url2.url, token: null, error: messageFor(error48) };
15073
- }
15074
- } else if (env.DISPATCH_TOKEN !== undefined) {
15075
- tokenSource = "DISPATCH_TOKEN";
15076
- token = env.DISPATCH_TOKEN;
15077
- } else {
15078
- tokenSource = "dispatch.token";
15079
- token = merged.token ?? null;
15080
- }
15081
- if (url2.error !== null)
15082
- return { enabled: false, url: null, token, error: url2.error };
15083
- if (url2.url === null)
15084
- return { enabled: false, url: null, token, error: null };
15085
- if (!token) {
15086
- return {
15087
- enabled: false,
15088
- url: url2.url,
15089
- token,
15090
- error: `${tokenSource} must be a non-empty bearer token`
15091
- };
15092
- }
15093
- return { enabled: true, url: url2.url, token, error: null };
15094
- }
15095
- function activeDispatchConfig(env, options = {}) {
15096
- const config2 = resolveDispatchConfig(env, options);
15097
- if (config2.error !== null)
15098
- throw new Error(`dispatch config: ${config2.error}`);
15099
- const { url: url2, token } = config2;
15100
- if (!config2.enabled || url2 === null || token === null)
15101
- return null;
15102
- return { ...config2, url: url2, token };
15103
- }
15104
-
15105
14961
  // ../envoy-client/src/dispatch-execute.ts
15106
14962
  import { resolve as resolvePath2 } from "path";
15107
14963
 
@@ -15337,13 +15193,13 @@ class DispatchClient {
15337
15193
  return this.#resolveIssue(issueReference);
15338
15194
  }
15339
15195
  async listIssuePage(options, page) {
15340
- const path2 = ["api", "v1", "issues"];
15196
+ const path = ["api", "v1", "issues"];
15341
15197
  const query = { ...options, limit: page.limit, offset: page.offset };
15342
- const answer = await this.#json("GET", path2, undefined, query);
15198
+ const answer = await this.#json("GET", path, undefined, query);
15343
15199
  const refusal = whyNotAPage(answer);
15344
15200
  if (refusal === undefined)
15345
15201
  return answer;
15346
- throw new Error(`GET ${this.#url(path2, query)} asked for a page ({issues, total, limit, offset}) and got ` + refusal);
15202
+ throw new Error(`GET ${this.#url(path, query)} asked for a page ({issues, total, limit, offset}) and got ` + refusal);
15347
15203
  }
15348
15204
  async listProjectArtifacts(project, unlinked = false) {
15349
15205
  return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
@@ -15584,17 +15440,17 @@ class DispatchClient {
15584
15440
  throw error48;
15585
15441
  }
15586
15442
  }
15587
- async#json(method, path2, body, query) {
15588
- return (await this.#jsonAnswer(method, path2, body, query)).payload;
15443
+ async#json(method, path, body, query) {
15444
+ return (await this.#jsonAnswer(method, path, body, query)).payload;
15589
15445
  }
15590
- async#jsonAnswer(method, path2, body, query) {
15446
+ async#jsonAnswer(method, path, body, query) {
15591
15447
  const headers = {
15592
15448
  Accept: "application/json",
15593
15449
  Authorization: `Bearer ${this.token}`
15594
15450
  };
15595
15451
  if (body !== undefined)
15596
15452
  headers["Content-Type"] = "application/json";
15597
- const url2 = this.#url(path2, query);
15453
+ const url2 = this.#url(path, query);
15598
15454
  const response = await this.fetchImpl(url2, {
15599
15455
  method,
15600
15456
  headers,
@@ -15603,9 +15459,9 @@ class DispatchClient {
15603
15459
  });
15604
15460
  return { status: response.status, payload: await this.#response(method, url2, response) };
15605
15461
  }
15606
- async#upload(path2, input) {
15462
+ async#upload(path, input) {
15607
15463
  if ("content" in input)
15608
- return this.#json("POST", path2, input);
15464
+ return this.#json("POST", path, input);
15609
15465
  const form = new FormData;
15610
15466
  form.set("name", input.name);
15611
15467
  if (input.summary !== undefined)
@@ -15613,10 +15469,10 @@ class DispatchClient {
15613
15469
  if (input.actor !== undefined)
15614
15470
  form.set("actor", JSON.stringify(input.actor));
15615
15471
  form.set("file", input.file, input.name);
15616
- return this.#form("POST", path2, form);
15472
+ return this.#form("POST", path, form);
15617
15473
  }
15618
- async#form(method, path2, body) {
15619
- const url2 = this.#url(path2);
15474
+ async#form(method, path, body) {
15475
+ const url2 = this.#url(path);
15620
15476
  const response = await this.fetchImpl(url2, {
15621
15477
  method,
15622
15478
  headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
@@ -15625,8 +15481,8 @@ class DispatchClient {
15625
15481
  });
15626
15482
  return this.#response(method, url2, response);
15627
15483
  }
15628
- #url(path2, query) {
15629
- const url2 = new URL(`${path2.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
15484
+ #url(path, query) {
15485
+ const url2 = new URL(`${path.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
15630
15486
  if (query) {
15631
15487
  for (const [name, value] of Object.entries(query)) {
15632
15488
  if (typeof value === "string" || typeof value === "number") {
@@ -15852,9 +15708,9 @@ function shapeOf(schema) {
15852
15708
  const def = unwrapped.def;
15853
15709
  return def.type === "object" && "shape" in def ? def.shape : undefined;
15854
15710
  }
15855
- function schemaAt(schema, path2) {
15711
+ function schemaAt(schema, path) {
15856
15712
  let current = schema;
15857
- for (const key of path2) {
15713
+ for (const key of path) {
15858
15714
  const unwrapped = unwrap(current);
15859
15715
  if (unwrapped === undefined)
15860
15716
  return;
@@ -15901,10 +15757,10 @@ function describeExpected(expected, schema) {
15901
15757
  return `a ${expected}`;
15902
15758
  }
15903
15759
  }
15904
- function pathText(path2, cliTool) {
15905
- const [head, ...rest] = path2.map(String);
15760
+ function pathText(path, cliTool) {
15761
+ const [head, ...rest] = path.map(String);
15906
15762
  if (cliTool === undefined || head === undefined)
15907
- return path2.map(String).join(".");
15763
+ return path.map(String).join(".");
15908
15764
  let flag = fieldFlag(cliTool, head);
15909
15765
  let index = 0;
15910
15766
  for (;index < rest.length && /^\d+$/.test(rest[index]); index++) {
@@ -15917,33 +15773,33 @@ function formatZodIssues(issues, schema, tool) {
15917
15773
  const cliTool = isDispatchTool(tool) ? tool : undefined;
15918
15774
  const allowed = Object.keys(shapeOf(schema) ?? {}).join(", ");
15919
15775
  return issues.flatMap((issue2) => {
15920
- const path2 = pathText(issue2.path, cliTool);
15776
+ const path = pathText(issue2.path, cliTool);
15921
15777
  switch (issue2.code) {
15922
15778
  case "invalid_type":
15923
- return issue2.input === undefined ? [`${path2} is required (${issue2.expected})`] : [
15924
- `${path2} must be ${describeExpected(issue2.expected, schemaAt(schema, issue2.path))}, not ${describeInput(issue2.input)}`
15779
+ return issue2.input === undefined ? [`${path} is required (${issue2.expected})`] : [
15780
+ `${path} must be ${describeExpected(issue2.expected, schemaAt(schema, issue2.path))}, not ${describeInput(issue2.input)}`
15925
15781
  ];
15926
15782
  case "unrecognized_keys": {
15927
15783
  if (cliTool !== undefined && issue2.path.length === 0) {
15928
15784
  return issue2.keys.map((key) => `unknown flag ${fieldFlag(cliTool, key)}`);
15929
15785
  }
15930
15786
  const here = Object.keys(shapeOf(schemaAt(schema, issue2.path)) ?? {}).join(", ") || allowed;
15931
- const where = path2 === "" ? "" : ` in ${path2}`;
15787
+ const where = path === "" ? "" : ` in ${path}`;
15932
15788
  return issue2.keys.map((key) => `unknown field "${key}"${where}; allowed: ${here}`);
15933
15789
  }
15934
15790
  case "invalid_value":
15935
15791
  return [
15936
- `${path2} must be one of ${issue2.values.map(String).join("|")}; got ${JSON.stringify(issue2.input)}`
15792
+ `${path} must be one of ${issue2.values.map(String).join("|")}; got ${JSON.stringify(issue2.input)}`
15937
15793
  ];
15938
15794
  case "too_big":
15939
15795
  if (issue2.origin === "array" && Array.isArray(issue2.input)) {
15940
- return [`${path2} has ${issue2.input.length} items; the limit is ${issue2.maximum}`];
15796
+ return [`${path} has ${issue2.input.length} items; the limit is ${issue2.maximum}`];
15941
15797
  }
15942
- return [`${path2} ${issue2.message}`];
15798
+ return [`${path} ${issue2.message}`];
15943
15799
  case "custom":
15944
15800
  return [issue2.message];
15945
15801
  default:
15946
- return [path2 === "" ? issue2.message : `${path2}: ${issue2.message}`];
15802
+ return [path === "" ? issue2.message : `${path}: ${issue2.message}`];
15947
15803
  }
15948
15804
  });
15949
15805
  }
@@ -15953,7 +15809,7 @@ var PICTURE_TYPE_NAMES = "a PNG, JPEG, GIF or WebP";
15953
15809
  async function localPictures(images, cwd, problems) {
15954
15810
  if (!Array.isArray(images))
15955
15811
  return [];
15956
- const checked = await Promise.all(images.map((path2) => localPicture(path2, cwd)));
15812
+ const checked = await Promise.all(images.map((path) => localPicture(path, cwd)));
15957
15813
  const pictures = [];
15958
15814
  for (const outcome of checked) {
15959
15815
  if (typeof outcome === "string")
@@ -15963,30 +15819,30 @@ async function localPictures(images, cwd, problems) {
15963
15819
  }
15964
15820
  return pictures;
15965
15821
  }
15966
- async function localPicture(path2, cwd) {
15967
- if (typeof path2 !== "string" || path2 === "")
15822
+ async function localPicture(path, cwd) {
15823
+ if (typeof path !== "string" || path === "")
15968
15824
  return;
15969
- const absolute = resolvePath(cwd, path2);
15825
+ const absolute = resolvePath(cwd, path);
15970
15826
  const file2 = Bun.file(absolute);
15971
15827
  let size;
15972
15828
  let head;
15973
15829
  try {
15974
15830
  const stats = await file2.stat();
15975
15831
  if (!stats.isFile())
15976
- return `images: ${path2} is not a file`;
15832
+ return `images: ${path} is not a file`;
15977
15833
  size = stats.size;
15978
15834
  head = new Uint8Array(await file2.slice(0, PICTURE_SNIFF_BYTES).arrayBuffer());
15979
15835
  } catch (error48) {
15980
- return `images: ${path2} cannot be read: ${messageFor(error48)}`;
15836
+ return `images: ${path} cannot be read: ${messageFor(error48)}`;
15981
15837
  }
15982
15838
  if (size > PICTURE_UPLOAD_MAX_BYTES) {
15983
- return `images: ${path2} is ${size.toLocaleString("en-US")} bytes, over the ${PICTURE_UPLOAD_MAX_BYTES / 1024 / 1024} MiB one Dispatch upload takes`;
15839
+ return `images: ${path} is ${size.toLocaleString("en-US")} bytes, over the ${PICTURE_UPLOAD_MAX_BYTES / 1024 / 1024} MiB one Dispatch upload takes`;
15984
15840
  }
15985
15841
  const type = sniffPictureType(head);
15986
15842
  if (type === undefined) {
15987
- return `images: ${path2} is not ${PICTURE_TYPE_NAMES} picture (judged by its bytes, not its name)`;
15843
+ return `images: ${path} is not ${PICTURE_TYPE_NAMES} picture (judged by its bytes, not its name)`;
15988
15844
  }
15989
- return { path: path2, name: basename(absolute), file: Bun.file(absolute, { type }) };
15845
+ return { path, name: basename(absolute), file: Bun.file(absolute, { type }) };
15990
15846
  }
15991
15847
  function uploadSlug(name) {
15992
15848
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
@@ -17226,11 +17082,11 @@ function anchorLines(record2) {
17226
17082
  ];
17227
17083
  }
17228
17084
  function positionText(block) {
17229
- const { table, path: path2 } = block;
17230
- const segments = path2.map((entry) => `${entry.type}[${entry.index}]`);
17085
+ const { table, path } = block;
17086
+ const segments = path.map((entry) => `${entry.type}[${entry.index}]`);
17231
17087
  if (table === undefined || table.row === null)
17232
17088
  return segments.join(" \u203A ");
17233
- const tableAt = path2.findIndex((entry) => entry.type === "table");
17089
+ const tableAt = path.findIndex((entry) => entry.type === "table");
17234
17090
  const cells = table.cells ?? [];
17235
17091
  const label = (table.column === null ? cells : cells.slice(0, table.column)).map((cell) => cell.trim()).filter((cell) => cell !== "" && !/^\d+$/.test(cell)).join(" \xB7 ");
17236
17092
  const row = label === "" ? `row ${table.row}` : `row ${table.row} (${label})`;
@@ -17471,11 +17327,7 @@ function withAccount(error48, account) {
17471
17327
  return new Error(told, { cause: error48 });
17472
17328
  }
17473
17329
  async function executeDispatchTool(input) {
17474
- const configUrl = input.config.url;
17475
- const configToken = input.config.token;
17476
- if (!input.config.enabled || !configUrl || !configToken) {
17477
- throw new Error("Dispatch is disabled; resolve both DISPATCH_URL and DISPATCH_TOKEN");
17478
- }
17330
+ const { url: configUrl, token: configToken } = input.config;
17479
17331
  const baseFetch = input.fetchImpl ?? fetch;
17480
17332
  const fetchImpl = Object.assign(async (...args2) => {
17481
17333
  try {
@@ -18428,15 +18280,15 @@ import {
18428
18280
  mkdirSync,
18429
18281
  openSync,
18430
18282
  readdirSync,
18431
- readFileSync as readFileSync3,
18283
+ readFileSync,
18432
18284
  readSync,
18433
18285
  renameSync,
18434
18286
  rmSync,
18435
18287
  statSync,
18436
18288
  writeFileSync
18437
18289
  } from "fs";
18438
- import { homedir as homedir2 } from "os";
18439
- import { join as join2 } from "path";
18290
+ import { homedir } from "os";
18291
+ import { join } from "path";
18440
18292
  var SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
18441
18293
  var PRIVATE_DIR = 448;
18442
18294
  var PRIVATE_FILE = 384;
@@ -18452,44 +18304,44 @@ var PICTURE_EXTENSIONS = {
18452
18304
  function sessionStateRoot(env) {
18453
18305
  if (env.DISPATCH_STATE_DIR)
18454
18306
  return env.DISPATCH_STATE_DIR;
18455
- return join2(env.XDG_STATE_HOME || join2(homedir2(), ".local", "state"), "dispatch");
18307
+ return join(env.XDG_STATE_HOME || join(homedir(), ".local", "state"), "dispatch");
18456
18308
  }
18457
18309
  function sessionDirectory(env, sessionId) {
18458
18310
  if (!SESSION_ID.test(sessionId) || sessionId === "." || sessionId === "..") {
18459
18311
  throw new Error(`dispatch: the session id ${JSON.stringify(sessionId)} is not 1-128 letters, digits, ".", "_" or "-"`);
18460
18312
  }
18461
- return join2(sessionStateRoot(env), "sessions", sessionId);
18313
+ return join(sessionStateRoot(env), "sessions", sessionId);
18462
18314
  }
18463
18315
  function ensureDirectory(dir) {
18464
18316
  mkdirSync(dir, { recursive: true, mode: PRIVATE_DIR });
18465
18317
  }
18466
- function readIfPresent(path2) {
18318
+ function readIfPresent(path) {
18467
18319
  try {
18468
- return readFileSync3(path2, "utf-8");
18320
+ return readFileSync(path, "utf-8");
18469
18321
  } catch (error48) {
18470
18322
  if (hasErrnoCode(error48, "ENOENT"))
18471
18323
  return;
18472
18324
  throw error48;
18473
18325
  }
18474
18326
  }
18475
- function writeAtomically(path2, text) {
18476
- const temporary = `${path2}.${process.pid}.${Date.now()}.tmp`;
18327
+ function writeAtomically(path, text) {
18328
+ const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
18477
18329
  writeFileSync(temporary, text, { mode: PRIVATE_FILE });
18478
- renameSync(temporary, path2);
18330
+ renameSync(temporary, path);
18479
18331
  }
18480
18332
  function stringList(value) {
18481
18333
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
18482
18334
  }
18483
18335
  function loadSessionMemory(dir, sessionId) {
18484
- const path2 = join2(dir, "state.json");
18485
- const text = readIfPresent(path2);
18336
+ const path = join(dir, "state.json");
18337
+ const text = readIfPresent(path);
18486
18338
  if (text === undefined)
18487
18339
  return new Set;
18488
18340
  let state;
18489
18341
  try {
18490
18342
  state = JSON.parse(text);
18491
18343
  } catch (error48) {
18492
- throw new Error(`the session state ${path2} is not JSON (${error48.message}); remove it to start this session's memory over`);
18344
+ throw new Error(`the session state ${path} is not JSON (${error48.message}); remove it to start this session's memory over`);
18493
18345
  }
18494
18346
  const advice = adviceMemory();
18495
18347
  for (const key of stringList(state.advice))
@@ -18505,12 +18357,12 @@ function writeMemory(dir, sessionId, follows) {
18505
18357
  pictures: [...shownPictures(sessionId)],
18506
18358
  follows: [...follows]
18507
18359
  };
18508
- writeAtomically(join2(dir, "state.json"), `${JSON.stringify(state)}
18360
+ writeAtomically(join(dir, "state.json"), `${JSON.stringify(state)}
18509
18361
  `);
18510
18362
  }
18511
18363
  function writeResult(dir, entry) {
18512
18364
  const line = { at: new Date().toISOString(), ...entry };
18513
- appendFileSync(join2(dir, "results.jsonl"), `${JSON.stringify(line)}
18365
+ appendFileSync(join(dir, "results.jsonl"), `${JSON.stringify(line)}
18514
18366
  `, { mode: PRIVATE_FILE });
18515
18367
  }
18516
18368
  function recordOutcome(dir, sessionId, follows, entry) {
@@ -18521,29 +18373,29 @@ function recordOutcome(dir, sessionId, follows, entry) {
18521
18373
  function writePicture(dir, image) {
18522
18374
  const bytes = Buffer.from(image.data, "base64");
18523
18375
  const name = createHash("sha256").update(bytes).digest("hex").slice(0, 16);
18524
- const pictures = join2(dir, "pictures");
18525
- const path2 = join2(pictures, `${name}.${PICTURE_EXTENSIONS[image.mimeType]}`);
18376
+ const pictures = join(dir, "pictures");
18377
+ const path = join(pictures, `${name}.${PICTURE_EXTENSIONS[image.mimeType]}`);
18526
18378
  ensureDirectory(pictures);
18527
18379
  try {
18528
- writeFileSync(path2, bytes, { mode: PRIVATE_FILE, flag: "wx" });
18380
+ writeFileSync(path, bytes, { mode: PRIVATE_FILE, flag: "wx" });
18529
18381
  } catch (error48) {
18530
18382
  if (!hasErrnoCode(error48, "EEXIST"))
18531
18383
  throw error48;
18532
18384
  }
18533
- return { path: path2, bytes: bytes.length };
18385
+ return { path, bytes: bytes.length };
18534
18386
  }
18535
18387
  function writeLongOutput(dir, text) {
18536
- const out = join2(dir, "out");
18388
+ const out = join(dir, "out");
18537
18389
  ensureDirectory(out);
18538
- const path2 = join2(out, `${new Date().toISOString().replaceAll(":", "-")}-${process.pid}.md`);
18539
- writeFileSync(path2, text, { mode: PRIVATE_FILE });
18540
- return path2;
18390
+ const path = join(out, `${new Date().toISOString().replaceAll(":", "-")}-${process.pid}.md`);
18391
+ writeFileSync(path, text, { mode: PRIVATE_FILE });
18392
+ return path;
18541
18393
  }
18542
18394
  function readSessionTitle(dir) {
18543
- return readIfPresent(join2(dir, "title")) || undefined;
18395
+ return readIfPresent(join(dir, "title")) || undefined;
18544
18396
  }
18545
18397
  function pruneSessions(root, now) {
18546
- const stamp = join2(root, PRUNED_STAMP);
18398
+ const stamp = join(root, PRUNED_STAMP);
18547
18399
  try {
18548
18400
  if (now - statSync(stamp).mtimeMs < PRUNE_INTERVAL_MS)
18549
18401
  return;
@@ -18551,7 +18403,7 @@ function pruneSessions(root, now) {
18551
18403
  if (!hasErrnoCode(error48, "ENOENT"))
18552
18404
  throw error48;
18553
18405
  }
18554
- const sessions = join2(root, "sessions");
18406
+ const sessions = join(root, "sessions");
18555
18407
  let entries = [];
18556
18408
  try {
18557
18409
  entries = readdirSync(sessions, { withFileTypes: true });
@@ -18562,7 +18414,7 @@ function pruneSessions(root, now) {
18562
18414
  for (const entry of entries) {
18563
18415
  if (!entry.isDirectory())
18564
18416
  continue;
18565
- const dir = join2(sessions, entry.name);
18417
+ const dir = join(sessions, entry.name);
18566
18418
  try {
18567
18419
  if (now - statSync(dir).mtimeMs > IDLE_SESSION_MS) {
18568
18420
  rmSync(dir, { recursive: true, force: true });
@@ -18603,11 +18455,575 @@ function dispatchFollowNotice(details) {
18603
18455
  };
18604
18456
  }
18605
18457
 
18458
+ // ../envoy-client/src/envoy-config.ts
18459
+ import {
18460
+ chmodSync,
18461
+ closeSync as closeSync2,
18462
+ existsSync,
18463
+ lstatSync,
18464
+ mkdtempSync,
18465
+ openSync as openSync2,
18466
+ readFileSync as readFileSync3,
18467
+ readlinkSync,
18468
+ rmSync as rmSync2,
18469
+ statSync as statSync2
18470
+ } from "fs";
18471
+ import { homedir as homedir2, tmpdir } from "os";
18472
+ import * as path from "path";
18473
+
18474
+ // ../envoy-client/src/json-position.ts
18475
+ function jsonErrorPosition(text) {
18476
+ let at = 0;
18477
+ const fail = () => {
18478
+ throw new RangeError("not JSON");
18479
+ };
18480
+ const space = () => {
18481
+ while (at < text.length && `
18482
+ \r`.includes(text.charAt(at)))
18483
+ at += 1;
18484
+ };
18485
+ const expect = (char) => {
18486
+ if (text.charAt(at) !== char)
18487
+ fail();
18488
+ at += 1;
18489
+ };
18490
+ const string4 = () => {
18491
+ expect('"');
18492
+ while (at < text.length) {
18493
+ const char = text.charAt(at);
18494
+ if (char === '"') {
18495
+ at += 1;
18496
+ return;
18497
+ }
18498
+ if (char < " ")
18499
+ fail();
18500
+ at += char === "\\" ? 2 : 1;
18501
+ }
18502
+ fail();
18503
+ };
18504
+ const value = () => {
18505
+ space();
18506
+ const char = text.charAt(at);
18507
+ if (char === "{" || char === "[") {
18508
+ const close = char === "{" ? "}" : "]";
18509
+ at += 1;
18510
+ space();
18511
+ if (text.charAt(at) === close) {
18512
+ at += 1;
18513
+ return;
18514
+ }
18515
+ for (;; ) {
18516
+ if (close === "}") {
18517
+ space();
18518
+ string4();
18519
+ space();
18520
+ expect(":");
18521
+ }
18522
+ value();
18523
+ space();
18524
+ if (text.charAt(at) === ",") {
18525
+ at += 1;
18526
+ continue;
18527
+ }
18528
+ expect(close);
18529
+ return;
18530
+ }
18531
+ }
18532
+ if (char === '"') {
18533
+ string4();
18534
+ return;
18535
+ }
18536
+ for (const word of ["true", "false", "null"]) {
18537
+ if (text.startsWith(word, at)) {
18538
+ at += word.length;
18539
+ return;
18540
+ }
18541
+ }
18542
+ const number4 = /^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/.exec(text.slice(at));
18543
+ if (number4 === null)
18544
+ fail();
18545
+ at += number4?.[0].length ?? 0;
18546
+ };
18547
+ try {
18548
+ value();
18549
+ space();
18550
+ if (at < text.length)
18551
+ fail();
18552
+ return;
18553
+ } catch {
18554
+ const before = text.slice(0, at).split(`
18555
+ `);
18556
+ return `line ${before.length}, column ${(before.at(-1)?.length ?? 0) + 1}`;
18557
+ }
18558
+ }
18559
+
18560
+ // ../envoy-client/src/secret-file.ts
18561
+ import { readFileSync as readFileSync2 } from "fs";
18562
+ function sendable(value) {
18563
+ return !/[\s\p{Cc}]/u.test(value);
18564
+ }
18565
+ function readSecretFile(variable, filePath) {
18566
+ let contents;
18567
+ try {
18568
+ contents = readFileSync2(filePath, "utf8");
18569
+ } catch (error48) {
18570
+ throw new Error(`${variable} names ${filePath}, which could not be read: ${messageFor(error48)}`);
18571
+ }
18572
+ const value = contents.trim();
18573
+ if (value.length === 0)
18574
+ throw new Error(`${variable} names ${filePath}, which is empty`);
18575
+ if (!sendable(value)) {
18576
+ throw new Error(`${variable} names ${filePath}, which holds whitespace or a control character`);
18577
+ }
18578
+ return value;
18579
+ }
18580
+
18581
+ // ../envoy-client/src/envoy-config.ts
18582
+ var COMMAND_TIMEOUT_MS = 1e4;
18583
+ function nonEmpty(value) {
18584
+ return value === undefined || value === "" ? undefined : value;
18585
+ }
18586
+ function absolute(name, value) {
18587
+ if (value !== undefined && !path.isAbsolute(value)) {
18588
+ throw new Error(`${name} is ${value}, which is not an absolute path`);
18589
+ }
18590
+ return value;
18591
+ }
18592
+ function neutralConfig(env) {
18593
+ const xdgConfigHome = nonEmpty(env.XDG_CONFIG_HOME);
18594
+ if (xdgConfigHome !== undefined && !path.isAbsolute(xdgConfigHome)) {
18595
+ return { error: `XDG_CONFIG_HOME is ${xdgConfigHome}, which is not an absolute path` };
18596
+ }
18597
+ const configHome = xdgConfigHome ?? path.join(nonEmpty(env.HOME) ?? homedir2(), ".config");
18598
+ return { path: path.resolve(configHome, "legion", "envoy.json") };
18599
+ }
18600
+ var HARNESS_DIRECTORY_VARIABLE = {
18601
+ omp: "PI_CODING_AGENT_DIR",
18602
+ claude: "CLAUDE_CONFIG_DIR",
18603
+ opencode: "OPENCODE_CONFIG_DIR"
18604
+ };
18605
+ var PLUGIN_HARNESSES = Object.keys(HARNESS_DIRECTORY_VARIABLE);
18606
+ function namedDirectory(harness, env) {
18607
+ if (harness === "omp") {
18608
+ const profile = nonEmpty(env.OMP_PROFILE);
18609
+ if (profile === undefined || profile === "default")
18610
+ return;
18611
+ }
18612
+ return nonEmpty(env[HARNESS_DIRECTORY_VARIABLE[harness]]);
18613
+ }
18614
+ function harnessDirectory(harness, env) {
18615
+ for (const plugin of harness === "command" ? PLUGIN_HARNESSES : [harness]) {
18616
+ if (harness === "command" && plugin === "claude" && env.CLAUDE_CODE_CHILD_SESSION !== "1") {
18617
+ continue;
18618
+ }
18619
+ const directory = namedDirectory(plugin, env);
18620
+ if (directory !== undefined)
18621
+ return absolute(HARNESS_DIRECTORY_VARIABLE[plugin], directory);
18622
+ }
18623
+ return;
18624
+ }
18625
+ function handedConfigPath(env) {
18626
+ const handed = nonEmpty(env.ENVOY_CONFIG_FILE);
18627
+ return handed === undefined ? undefined : path.resolve(absolute("ENVOY_CONFIG_FILE", handed));
18628
+ }
18629
+ function envoyConfigPath(harness, env) {
18630
+ const handedFirst = harness === "command" ? handedConfigPath(env) : undefined;
18631
+ if (handedFirst !== undefined)
18632
+ return handedFirst;
18633
+ const directory = harnessDirectory(harness, env);
18634
+ if (directory !== undefined)
18635
+ return path.resolve(directory, "envoy.json");
18636
+ const handed = handedConfigPath(env);
18637
+ if (handed !== undefined)
18638
+ return handed;
18639
+ const neutral = neutralConfig(env);
18640
+ if ("error" in neutral)
18641
+ throw new Error(neutral.error);
18642
+ return neutral.path;
18643
+ }
18644
+ var httpUrlSchema = exports_external.url({ protocol: /^https?$/, error: "must be an http or https URL" });
18645
+ var DispatchSectionSchema = exports_external.strictObject({
18646
+ enabled: exports_external.boolean().optional(),
18647
+ serverUrl: httpUrlSchema.optional(),
18648
+ token: exports_external.string().optional()
18649
+ });
18650
+ var EnvoySectionSchema = exports_external.strictObject({
18651
+ enabled: exports_external.boolean().optional(),
18652
+ url: httpUrlSchema.optional(),
18653
+ natsUrls: exports_external.array(exports_external.string().trim().min(1, "must not be empty")).optional(),
18654
+ token: exports_external.string().optional(),
18655
+ natsSeed: exports_external.string().optional()
18656
+ });
18657
+ var EnvoyFileSchema = exports_external.looseObject({
18658
+ $schema: exports_external.string().optional(),
18659
+ dispatch: DispatchSectionSchema.optional(),
18660
+ envoy: EnvoySectionSchema.optional()
18661
+ });
18662
+ var CheckoutFileSchema = exports_external.looseObject({
18663
+ $schema: exports_external.string().optional(),
18664
+ dispatch: DispatchSectionSchema.optional()
18665
+ });
18666
+ function offWithNotice(notice) {
18667
+ return { enabled: false, notice, error: null };
18668
+ }
18669
+ function offWithError(error48) {
18670
+ return { enabled: false, notice: null, error: error48 };
18671
+ }
18672
+ function httpUrl2(value) {
18673
+ let url2;
18674
+ try {
18675
+ url2 = new URL(value);
18676
+ } catch {
18677
+ return;
18678
+ }
18679
+ return url2.protocol === "http:" || url2.protocol === "https:" ? url2 : undefined;
18680
+ }
18681
+ function describeSchemaIssue(filePath, error48) {
18682
+ const issue2 = error48.issues[0];
18683
+ if (!issue2)
18684
+ return `${filePath}: invalid envoy.json`;
18685
+ if (issue2.code === "unrecognized_keys") {
18686
+ const section = issue2.path.join(".");
18687
+ const keys = issue2.keys.map((key) => `${section}.${key}`).join(", ");
18688
+ return `${filePath}: unrecognized ${section} key(s): ${keys}`;
18689
+ }
18690
+ return `${filePath}: ${issue2.path.join(".")}: ${issue2.message}`;
18691
+ }
18692
+ function danglingLink(filePath) {
18693
+ try {
18694
+ return lstatSync(filePath).isSymbolicLink() ? readlinkSync(filePath) : null;
18695
+ } catch {
18696
+ return null;
18697
+ }
18698
+ }
18699
+ function parseFile(filePath, schema, checkout) {
18700
+ let raw;
18701
+ try {
18702
+ raw = readFileSync3(filePath, "utf-8");
18703
+ } catch (error48) {
18704
+ if (hasErrnoCode(error48, "ENOENT") || hasErrnoCode(error48, "ENOTDIR")) {
18705
+ return { kind: "absent", link: danglingLink(filePath) };
18706
+ }
18707
+ return { kind: "invalid", reason: `${filePath}: could not be read (${messageFor(error48)})` };
18708
+ }
18709
+ let parsedJson;
18710
+ try {
18711
+ parsedJson = JSON.parse(raw);
18712
+ } catch {
18713
+ const position = jsonErrorPosition(raw);
18714
+ return {
18715
+ kind: "invalid",
18716
+ reason: `${filePath}: invalid JSON${position === undefined ? "" : ` at ${position}`}`
18717
+ };
18718
+ }
18719
+ if (typeof parsedJson === "object" && parsedJson !== null && "natsUrls" in parsedJson) {
18720
+ return {
18721
+ kind: "invalid",
18722
+ 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`
18723
+ };
18724
+ }
18725
+ const parsed = schema.safeParse(parsedJson);
18726
+ if (!parsed.success) {
18727
+ return { kind: "invalid", reason: describeSchemaIssue(filePath, parsed.error) };
18728
+ }
18729
+ return { kind: "valid", settings: parsed.data };
18730
+ }
18731
+ var FAILURE_KEPT_MS = 60000;
18732
+ var COMMAND_OUTCOMES = Symbol.for("legion.envoy-config.commands.v1");
18733
+ var processGlobal = globalThis;
18734
+ var commandOutcomes = processGlobal[COMMAND_OUTCOMES] ?? new Map;
18735
+ processGlobal[COMMAND_OUTCOMES] = commandOutcomes;
18736
+ function runCommand(command, env, cwd) {
18737
+ const childEnv = {};
18738
+ for (const [name, variable] of Object.entries(env)) {
18739
+ if (variable !== undefined)
18740
+ childEnv[name] = variable;
18741
+ }
18742
+ const directory = mkdtempSync(path.join(tmpdir(), "envoy-command-"));
18743
+ try {
18744
+ chmodSync(directory, 448);
18745
+ const output = path.join(directory, "stdout");
18746
+ const fd = openSync2(output, "wx", 384);
18747
+ let result;
18748
+ try {
18749
+ result = Bun.spawnSync(["/bin/sh", "-c", command], {
18750
+ cwd,
18751
+ env: childEnv,
18752
+ stdin: "ignore",
18753
+ stdout: fd,
18754
+ stderr: "ignore",
18755
+ timeout: COMMAND_TIMEOUT_MS,
18756
+ detached: true
18757
+ });
18758
+ } catch (error48) {
18759
+ return { failure: `could not start: ${messageFor(error48)}`, timedOut: false };
18760
+ } finally {
18761
+ closeSync2(fd);
18762
+ }
18763
+ try {
18764
+ process.kill(-result.pid, "SIGKILL");
18765
+ } catch {}
18766
+ if (result.exitedDueToTimeout) {
18767
+ return {
18768
+ failure: `did not finish within ${COMMAND_TIMEOUT_MS / 1000} s, and does not run again in this process`,
18769
+ timedOut: true
18770
+ };
18771
+ }
18772
+ if (!result.success) {
18773
+ return {
18774
+ failure: `exited with ${result.signalCode ?? `status ${result.exitCode}`}`,
18775
+ timedOut: false
18776
+ };
18777
+ }
18778
+ const value = readFileSync3(output, "utf-8").trim();
18779
+ if (value.length === 0)
18780
+ return { failure: "printed nothing", timedOut: false };
18781
+ if (!sendable(value)) {
18782
+ return {
18783
+ failure: "printed more than one line, or whitespace inside its value",
18784
+ timedOut: false
18785
+ };
18786
+ }
18787
+ return { value };
18788
+ } finally {
18789
+ rmSync2(directory, { recursive: true, force: true });
18790
+ }
18791
+ }
18792
+ function fileValue(value, field, filePath, env) {
18793
+ if (!value.startsWith("!")) {
18794
+ if (!sendable(value)) {
18795
+ throw new Error(`${filePath}: ${field} holds whitespace or a control character`);
18796
+ }
18797
+ return value;
18798
+ }
18799
+ const command = value.slice(1);
18800
+ const cwd = path.dirname(path.resolve(filePath));
18801
+ const key = JSON.stringify([command, filePath, cwd]);
18802
+ let outcome = commandOutcomes.get(key);
18803
+ if (outcome === undefined || "failure" in outcome && !outcome.timedOut && Date.now() - outcome.at >= FAILURE_KEPT_MS) {
18804
+ outcome = { ...runCommand(command, env, cwd), at: Date.now() };
18805
+ commandOutcomes.set(key, outcome);
18806
+ }
18807
+ if ("failure" in outcome)
18808
+ throw new Error(`${filePath}: ${field}'s command ${outcome.failure}`);
18809
+ return outcome.value;
18810
+ }
18811
+ function statDevice(directory) {
18812
+ try {
18813
+ return statSync2(directory).dev;
18814
+ } catch {
18815
+ return;
18816
+ }
18817
+ }
18818
+ function moveNotice(oldFile, newFile, move, deviceOf) {
18819
+ const oldDevice = deviceOf(path.dirname(oldFile));
18820
+ let ancestor = path.dirname(newFile);
18821
+ let newDevice = deviceOf(ancestor);
18822
+ while (newDevice === undefined && path.dirname(ancestor) !== ancestor) {
18823
+ ancestor = path.dirname(ancestor);
18824
+ newDevice = deviceOf(ancestor);
18825
+ }
18826
+ if (oldDevice !== undefined && newDevice !== undefined && oldDevice !== newDevice) {
18827
+ 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`;
18828
+ }
18829
+ return `${oldFile} is not read: ${move}`;
18830
+ }
18831
+ function oldUserFileNotice(filePath, env, deviceOf) {
18832
+ const home = nonEmpty(env.HOME) ?? homedir2();
18833
+ const old = path.join(home, ".config", "opencode", "envoy.json");
18834
+ if (!existsSync(old))
18835
+ return;
18836
+ const selected = path.resolve(filePath);
18837
+ const userFile = path.resolve(home, ".config", "legion", "envoy.json");
18838
+ if (selected === userFile) {
18839
+ return moveNotice(old, userFile, "mkdir -p ~/.config/legion && mv ~/.config/opencode/envoy.json ~/.config/legion/", deviceOf);
18840
+ }
18841
+ const profileFile = PLUGIN_HARNESSES.map((harness) => namedDirectory(harness, env)).some((directory) => directory !== undefined && path.isAbsolute(directory) && selected === path.resolve(directory, "envoy.json"));
18842
+ if (profileFile) {
18843
+ return `${old} is not read for this profile; copy or link it to ${filePath}, or write that file`;
18844
+ }
18845
+ const configHome = nonEmpty(env.XDG_CONFIG_HOME);
18846
+ const neutral = neutralConfig(env);
18847
+ if (configHome !== undefined && path.isAbsolute(configHome) && "path" in neutral && selected === neutral.path) {
18848
+ return `${old} is not read under XDG_CONFIG_HOME=${configHome}`;
18849
+ }
18850
+ return;
18851
+ }
18852
+ function missingConfigNotice(filePath, missing, off) {
18853
+ const link = missing.link === null ? "" : ` (a link to ${missing.link}, which does not exist)`;
18854
+ const old = missing.oldUserFileNotice === undefined ? "" : `; ${missing.oldUserFileNotice}`;
18855
+ return `no envoy.json at ${filePath}${link}: ${off}${old}`;
18856
+ }
18857
+ function readEnvoyConfig(filePath, env, options) {
18858
+ const deviceOf = options.deviceOf ?? statDevice;
18859
+ const chosen = parseFile(filePath, EnvoyFileSchema, false);
18860
+ if (chosen.kind === "invalid")
18861
+ return { kind: "failed", path: filePath, error: chosen.reason };
18862
+ const resolvedChosen = path.resolve(filePath);
18863
+ const checkoutPath = path.resolve(options.cwd, ".envoy", "envoy.json");
18864
+ const checkoutFile = checkoutPath === resolvedChosen ? { kind: "absent", link: null } : parseFile(checkoutPath, CheckoutFileSchema, true);
18865
+ const own = chosen.kind === "valid" ? chosen.settings : {};
18866
+ let checkout = null;
18867
+ if (checkoutFile.kind === "invalid") {
18868
+ checkout = { kind: "invalid", path: checkoutPath, error: checkoutFile.reason };
18869
+ } else if (checkoutFile.kind === "valid") {
18870
+ checkout = {
18871
+ kind: "valid",
18872
+ path: checkoutPath,
18873
+ dispatch: checkoutFile.settings.dispatch ?? {},
18874
+ envoyError: Object.hasOwn(checkoutFile.settings, "envoy") ? `${checkoutPath}: envoy is not read from a checkout's envoy.json; put it in ${filePath}` : null
18875
+ };
18876
+ }
18877
+ const oldCheckout = path.resolve(options.cwd, ".opencode", "envoy.json");
18878
+ const warnings = oldCheckout !== resolvedChosen && !existsSync(checkoutPath) && existsSync(oldCheckout) ? [
18879
+ moveNotice(oldCheckout, checkoutPath, `mkdir -p ${path.dirname(checkoutPath)} && mv ${oldCheckout} ${checkoutPath}`, deviceOf)
18880
+ ] : [];
18881
+ const unreadOldUserFile = chosen.kind === "absent" ? oldUserFileNotice(filePath, env, deviceOf) : undefined;
18882
+ return {
18883
+ kind: "read",
18884
+ path: filePath,
18885
+ dispatch: own.dispatch ?? {},
18886
+ envoy: own.envoy ?? {},
18887
+ checkout,
18888
+ missing: chosen.kind === "absent" ? {
18889
+ link: chosen.link,
18890
+ ...unreadOldUserFile === undefined ? {} : { oldUserFileNotice: unreadOldUserFile }
18891
+ } : null,
18892
+ warnings
18893
+ };
18894
+ }
18895
+
18896
+ // ../envoy-client/src/bearer.ts
18897
+ function bearerSource(env, section, written) {
18898
+ const pointer = nonEmpty(env[`${section}_TOKEN_FILE`]);
18899
+ if (pointer !== undefined) {
18900
+ return { kind: "variable", token: readSecretFile(`${section}_TOKEN_FILE`, pointer) };
18901
+ }
18902
+ const plain = nonEmpty(env[`${section}_TOKEN`]);
18903
+ if (plain !== undefined) {
18904
+ if (!sendable(plain)) {
18905
+ throw new Error(`${section}_TOKEN holds whitespace or a control character`);
18906
+ }
18907
+ return { kind: "variable", token: plain };
18908
+ }
18909
+ const bearer = written();
18910
+ return bearer === undefined ? { kind: "none" } : { kind: "file", ...bearer };
18911
+ }
18912
+ function fileBearer(bearer, url2, section, env) {
18913
+ const named = httpUrl2(url2)?.origin;
18914
+ const paired = httpUrl2(bearer.server)?.origin;
18915
+ if (named !== paired) {
18916
+ 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`);
18917
+ }
18918
+ if (bearer.checkout && bearer.token.startsWith("!")) {
18919
+ throw new Error(`${bearer.path}: ${bearer.field} is a command, which a checkout's envoy.json cannot run`);
18920
+ }
18921
+ if (bearer.token === "") {
18922
+ throw new Error(`${bearer.field} in ${bearer.path} must be a non-empty bearer token`);
18923
+ }
18924
+ return fileValue(bearer.token, bearer.field, bearer.path, env);
18925
+ }
18926
+
18927
+ // ../envoy-client/src/dispatch-config.ts
18928
+ var DEFAULT_SERVER_URL = "http://localhost:8766";
18929
+ function checkoutPair(checkout, serverUrl) {
18930
+ const { token } = checkout.dispatch;
18931
+ if (token === undefined) {
18932
+ throw new Error(`${checkout.path}: dispatch.serverUrl requires dispatch.token in the same file`);
18933
+ }
18934
+ return {
18935
+ kind: "file",
18936
+ token,
18937
+ field: "dispatch.token",
18938
+ path: checkout.path,
18939
+ server: serverUrl,
18940
+ checkout: true
18941
+ };
18942
+ }
18943
+ function writtenToken(config2, checkout) {
18944
+ if (checkout?.dispatch.token !== undefined) {
18945
+ throw new Error(`${checkout.path}: dispatch.token requires dispatch.serverUrl in the same file`);
18946
+ }
18947
+ const own = config2.dispatch;
18948
+ if (own.token === undefined)
18949
+ return;
18950
+ const server = own.serverUrl ?? DEFAULT_SERVER_URL;
18951
+ return {
18952
+ token: own.token,
18953
+ field: "dispatch.token",
18954
+ path: config2.path,
18955
+ server,
18956
+ checkout: false
18957
+ };
18958
+ }
18959
+ function dispatchSettings(env, config2) {
18960
+ if (config2.kind === "failed")
18961
+ return offWithError(config2.error);
18962
+ const explicitUrl = nonEmpty(env.DISPATCH_URL);
18963
+ const checkoutFile = explicitUrl === undefined ? config2.checkout : null;
18964
+ if (checkoutFile?.kind === "invalid")
18965
+ return offWithError(checkoutFile.error);
18966
+ const checkout = checkoutFile?.kind === "valid" ? checkoutFile : null;
18967
+ const own = config2.dispatch;
18968
+ if (own.enabled === false) {
18969
+ return offWithNotice(`${config2.path} sets dispatch.enabled to false: Dispatch is off`);
18970
+ }
18971
+ if (checkout?.dispatch.enabled === false) {
18972
+ return offWithNotice(`${checkout.path} sets dispatch.enabled to false: Dispatch is off`);
18973
+ }
18974
+ const checkoutServer = checkout?.dispatch.serverUrl;
18975
+ const addressed = explicitUrl !== undefined || checkoutServer !== undefined || own.serverUrl !== undefined || own.enabled === true;
18976
+ if (!addressed) {
18977
+ 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"));
18978
+ }
18979
+ if (explicitUrl !== undefined && httpUrl2(explicitUrl) === undefined) {
18980
+ return offWithError("DISPATCH_URL must be an http or https URL");
18981
+ }
18982
+ try {
18983
+ const source = checkout !== null && checkoutServer !== undefined ? checkoutPair(checkout, checkoutServer) : bearerSource(env, "DISPATCH", () => writtenToken(config2, checkout));
18984
+ if (source.kind === "none") {
18985
+ 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}`);
18986
+ }
18987
+ const url2 = (explicitUrl ?? (source.kind === "file" ? source.server : own.serverUrl ?? DEFAULT_SERVER_URL)).replace(/\/+$/, "");
18988
+ const token = source.kind === "variable" ? source.token : fileBearer(source, url2, "DISPATCH", env);
18989
+ return { enabled: true, url: url2, token };
18990
+ } catch (error48) {
18991
+ return offWithError(messageFor(error48));
18992
+ }
18993
+ }
18994
+
18995
+ // ../envoy-client/src/session-settings.ts
18996
+ function sessionConfigFile(harness, env) {
18997
+ try {
18998
+ return { path: envoyConfigPath(harness, env), error: null };
18999
+ } catch (error48) {
19000
+ return { path: null, error: messageFor(error48) };
19001
+ }
19002
+ }
19003
+ function handedConfigFile(env) {
19004
+ try {
19005
+ const handed = handedConfigPath(env);
19006
+ return handed === undefined ? null : { path: handed, error: null };
19007
+ } catch (error48) {
19008
+ return { path: null, error: messageFor(error48) };
19009
+ }
19010
+ }
19011
+ function sessionDispatchSettings(file2, env, options) {
19012
+ if (file2.path === null)
19013
+ return { dispatch: offWithError(file2.error), warnings: [], missing: null };
19014
+ const config2 = readEnvoyConfig(file2.path, env, options);
19015
+ return {
19016
+ dispatch: dispatchSettings(env, config2),
19017
+ warnings: config2.kind === "read" ? config2.warnings : [],
19018
+ missing: config2.kind === "read" ? config2.missing : null
19019
+ };
19020
+ }
19021
+
18606
19022
  // ../envoy-client/src/dispatch-cli.ts
18607
19023
  var OK = 0;
18608
19024
  var REFUSED = 1;
18609
19025
  var USAGE = 2;
18610
- var HOSTS = { omp: "omp", claude: "claude", opencode: "opencode" };
19026
+ var HOSTS = ["omp", "claude", "opencode"];
18611
19027
  var CLAUDE_OUTPUT_MAX = 25000;
18612
19028
  var STATE_WRITE_FAILED = "dispatch: Dispatch took the call, but this session's state";
18613
19029
  function stateWriteFailed(problem) {
@@ -18675,8 +19091,8 @@ async function runDispatchCli(argv, rawEnv, io) {
18675
19091
  print(new ToolInputError(parsed.tool, parsed.problems).message);
18676
19092
  return REFUSED;
18677
19093
  }
18678
- const hostName = env.DISPATCH_HOST;
18679
- const host = hostName === undefined ? undefined : HOSTS[hostName];
19094
+ const hostName = env.DISPATCH_HOST ?? (env.CLAUDE_CODE_CHILD_SESSION === "1" ? "claude" : undefined);
19095
+ const host = HOSTS.find((name) => name === hostName);
18680
19096
  if (host === undefined) {
18681
19097
  print(hostName === undefined ? "dispatch: DISPATCH_HOST is not set. The host plugin sets it in an agent's shell; to run dispatch by hand, set DISPATCH_HOST and DISPATCH_SESSION_ID yourself." : `dispatch: DISPATCH_HOST is ${JSON.stringify(hostName)}, not omp, claude or opencode.`);
18682
19098
  return USAGE;
@@ -18698,17 +19114,19 @@ async function runDispatchCli(argv, rawEnv, io) {
18698
19114
  print(JSON.stringify(parsed.args, null, 2));
18699
19115
  return OK;
18700
19116
  }
18701
- let config2;
18702
- try {
18703
- config2 = activeDispatchConfig(env, { cwd: io.cwd });
18704
- } catch (error48) {
18705
- print(`dispatch: ${messageFor(error48)}`);
19117
+ const file2 = (env.DISPATCH_HOST === undefined ? null : handedConfigFile(env)) ?? sessionConfigFile(host, env);
19118
+ if (file2.path === null) {
19119
+ print(`dispatch: ${file2.error}`);
18706
19120
  return USAGE;
18707
19121
  }
18708
- if (config2 === null) {
18709
- print("dispatch: Dispatch is not configured (DISPATCH_URL with DISPATCH_TOKEN or DISPATCH_TOKEN_FILE, or envoy.json)");
19122
+ const { dispatch: resolution, warnings } = sessionDispatchSettings(file2, env, { cwd: io.cwd });
19123
+ for (const warning of warnings)
19124
+ print(`dispatch: ${warning}`);
19125
+ if (!resolution.enabled) {
19126
+ print(resolution.error === null ? `dispatch: ${resolution.notice}` : `dispatch: dispatch config: ${resolution.error}`);
18710
19127
  return USAGE;
18711
19128
  }
19129
+ const config2 = resolution;
18712
19130
  let follows;
18713
19131
  try {
18714
19132
  pruneSessions(sessionStateRoot(env), Date.now());