@karlorz/codexhost-cli-win32-arm64 0.13.3-5 → 0.13.3-6

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 CHANGED
@@ -7,7 +7,7 @@ Run Pi and Claude Code as first-class external harnesses inside Codex Desktop.
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install -g @karlorz/codexhost-cli@0.13.3-5
10
+ npm install -g @karlorz/codexhost-cli@0.13.3-6
11
11
  ```
12
12
 
13
13
  Do not install this package directly. npm selects it through the optional dependencies of `@karlorz/codexhost-cli`.
@@ -1 +1 @@
1
- {"schemaVersion":1,"version":"0.13.3-5","distribution":"npm","target":"windows-arm64"}
1
+ {"schemaVersion":1,"version":"0.13.3-6","distribution":"npm","target":"windows-arm64"}
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // packages/console-server/src/main.ts
8
- import path17 from "node:path";
8
+ import path19 from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
 
11
11
  // packages/update-manager/dist/runtime-metadata.js
@@ -1663,10 +1663,10 @@ function mergeDefs(...defs) {
1663
1663
  function cloneDef(schema) {
1664
1664
  return mergeDefs(schema._zod.def);
1665
1665
  }
1666
- function getElementAtPath(obj, path18) {
1667
- if (!path18)
1666
+ function getElementAtPath(obj, path20) {
1667
+ if (!path20)
1668
1668
  return obj;
1669
- return path18.reduce((acc, key) => acc?.[key], obj);
1669
+ return path20.reduce((acc, key) => acc?.[key], obj);
1670
1670
  }
1671
1671
  function promiseAllObject(promisesObj) {
1672
1672
  const keys = Object.keys(promisesObj);
@@ -2075,11 +2075,11 @@ function explicitlyAborted(x, startIndex = 0) {
2075
2075
  }
2076
2076
  return false;
2077
2077
  }
2078
- function prefixIssues(path18, issues) {
2078
+ function prefixIssues(path20, issues) {
2079
2079
  return issues.map((iss) => {
2080
2080
  var _a3;
2081
2081
  (_a3 = iss).path ?? (_a3.path = []);
2082
- iss.path.unshift(path18);
2082
+ iss.path.unshift(path20);
2083
2083
  return iss;
2084
2084
  });
2085
2085
  }
@@ -2226,16 +2226,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2226
2226
  }
2227
2227
  function formatError(error51, mapper = (issue2) => issue2.message) {
2228
2228
  const fieldErrors = { _errors: [] };
2229
- const processError = (error52, path18 = []) => {
2229
+ const processError = (error52, path20 = []) => {
2230
2230
  for (const issue2 of error52.issues) {
2231
2231
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2232
- issue2.errors.map((issues) => processError({ issues }, [...path18, ...issue2.path]));
2232
+ issue2.errors.map((issues) => processError({ issues }, [...path20, ...issue2.path]));
2233
2233
  } else if (issue2.code === "invalid_key") {
2234
- processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
2234
+ processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
2235
2235
  } else if (issue2.code === "invalid_element") {
2236
- processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
2236
+ processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
2237
2237
  } else {
2238
- const fullpath = [...path18, ...issue2.path];
2238
+ const fullpath = [...path20, ...issue2.path];
2239
2239
  if (fullpath.length === 0) {
2240
2240
  fieldErrors._errors.push(mapper(issue2));
2241
2241
  } else {
@@ -2262,17 +2262,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2262
2262
  }
2263
2263
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2264
2264
  const result = { errors: [] };
2265
- const processError = (error52, path18 = []) => {
2265
+ const processError = (error52, path20 = []) => {
2266
2266
  var _a3, _b;
2267
2267
  for (const issue2 of error52.issues) {
2268
2268
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2269
- issue2.errors.map((issues) => processError({ issues }, [...path18, ...issue2.path]));
2269
+ issue2.errors.map((issues) => processError({ issues }, [...path20, ...issue2.path]));
2270
2270
  } else if (issue2.code === "invalid_key") {
2271
- processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
2271
+ processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
2272
2272
  } else if (issue2.code === "invalid_element") {
2273
- processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
2273
+ processError({ issues: issue2.issues }, [...path20, ...issue2.path]);
2274
2274
  } else {
2275
- const fullpath = [...path18, ...issue2.path];
2275
+ const fullpath = [...path20, ...issue2.path];
2276
2276
  if (fullpath.length === 0) {
2277
2277
  result.errors.push(mapper(issue2));
2278
2278
  continue;
@@ -2304,8 +2304,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2304
2304
  }
2305
2305
  function toDotPath(_path) {
2306
2306
  const segs = [];
2307
- const path18 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2308
- for (const seg of path18) {
2307
+ const path20 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2308
+ for (const seg of path20) {
2309
2309
  if (typeof seg === "number")
2310
2310
  segs.push(`[${seg}]`);
2311
2311
  else if (typeof seg === "symbol")
@@ -14997,13 +14997,13 @@ function resolveRef(ref, ctx) {
14997
14997
  if (!ref.startsWith("#")) {
14998
14998
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14999
14999
  }
15000
- const path18 = ref.slice(1).split("/").filter(Boolean);
15001
- if (path18.length === 0) {
15000
+ const path20 = ref.slice(1).split("/").filter(Boolean);
15001
+ if (path20.length === 0) {
15002
15002
  return ctx.rootSchema;
15003
15003
  }
15004
15004
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15005
- if (path18[0] === defsKey) {
15006
- const key = path18[1];
15005
+ if (path20[0] === defsKey) {
15006
+ const key = path20[1];
15007
15007
  if (!key || !ctx.defs[key]) {
15008
15008
  throw new Error(`Reference not found: ${ref}`);
15009
15009
  }
@@ -15709,6 +15709,7 @@ var consoleOpenResultSchema = external_exports.strictObject({
15709
15709
  url: external_exports.string().max(2048).regex(/^http:\/\/(?:127\.0\.0\.1|localhost):\d{1,5}\/(?:\?.*)?$/u)
15710
15710
  });
15711
15711
  var CONSOLE_HOST_METHODS = Object.freeze([
15712
+ "codexhost/voice/gateway-status",
15712
15713
  "codexhost/console/remote-connections",
15713
15714
  "codexhost/runtime/status",
15714
15715
  "codexhost/remote/ssh-setup",
@@ -16816,6 +16817,53 @@ var remoteConnectionsReplySchema = external_exports.union([
16816
16817
  external_exports.strictObject({ error: external_exports.strictObject({ code: external_exports.number().int(), message: external_exports.string() }) })
16817
16818
  ]);
16818
16819
 
16820
+ // packages/shared-contracts/dist/voice-settings.js
16821
+ var VOICE_GATEWAY_STATUS_METHOD = "codexhost/voice/gateway-status";
16822
+ var voiceRoutesSchema = external_exports.strictObject({
16823
+ chatExperience: external_exports.enum(["chatgpt", "codex"]),
16824
+ chatConnection: external_exports.enum(["desktop", "gateway"]),
16825
+ workConnection: external_exports.enum(["desktop", "gateway"])
16826
+ });
16827
+ var DEFAULT_VOICE_ROUTES = Object.freeze({
16828
+ chatExperience: "chatgpt",
16829
+ chatConnection: "desktop",
16830
+ workConnection: "gateway"
16831
+ });
16832
+ var voiceSettingsSchema = voiceRoutesSchema.extend({ inject: external_exports.boolean() });
16833
+ var voiceSettingsRequestSchema = external_exports.discriminatedUnion("operation", [
16834
+ external_exports.strictObject({
16835
+ operation: external_exports.literal("get"),
16836
+ runtime: external_exports.strictObject({
16837
+ id: external_exports.string().uuid(),
16838
+ revision: external_exports.string().max(64),
16839
+ activeCall: external_exports.boolean()
16840
+ }).optional()
16841
+ }),
16842
+ external_exports.strictObject({ operation: external_exports.literal("migrate"), routes: voiceRoutesSchema }),
16843
+ external_exports.strictObject({
16844
+ operation: external_exports.literal("set"),
16845
+ settings: voiceSettingsSchema,
16846
+ revision: external_exports.string().max(64)
16847
+ }),
16848
+ external_exports.strictObject({ operation: external_exports.literal("restart"), confirm: external_exports.literal(true) })
16849
+ ]);
16850
+ var voiceSettingsStateSchema = external_exports.strictObject({
16851
+ settings: voiceSettingsSchema,
16852
+ initialized: external_exports.boolean(),
16853
+ revision: external_exports.string(),
16854
+ activeInject: external_exports.boolean().nullable(),
16855
+ envOverride: external_exports.enum(["0", "1"]).nullable(),
16856
+ activeEnvOverride: external_exports.enum(["0", "1"]).nullable(),
16857
+ routesActive: external_exports.boolean().nullable(),
16858
+ activeCall: external_exports.boolean().nullable(),
16859
+ gatewayReady: external_exports.boolean().nullable(),
16860
+ restartAvailable: external_exports.boolean(),
16861
+ restart: external_exports.strictObject({
16862
+ phase: external_exports.enum(["idle", "restarting", "succeeded", "failed"]),
16863
+ error: external_exports.string().nullable()
16864
+ })
16865
+ });
16866
+
16819
16867
  // packages/shared-contracts/dist/index.js
16820
16868
  var workspaceContractVersionSchema = external_exports.literal(WORKSPACE_CONTRACT_VERSION);
16821
16869
 
@@ -17164,6 +17212,8 @@ async function readControllerStatus(filePath) {
17164
17212
  return {
17165
17213
  pid: document.pid,
17166
17214
  startedAt: document.startedAt,
17215
+ ...typeof document.voiceInjection === "boolean" ? { voiceInjection: document.voiceInjection } : {},
17216
+ ...document.voiceEnvOverride === "0" || document.voiceEnvOverride === "1" || document.voiceEnvOverride === null ? { voiceEnvOverride: document.voiceEnvOverride } : {},
17167
17217
  renderer: {
17168
17218
  state: renderer.state,
17169
17219
  error: typeof renderer.error === "string" ? renderer.error : null,
@@ -17311,7 +17361,7 @@ function createConsoleHostClient(descriptorDirectory, options = {}) {
17311
17361
  headers: { authorization: `Bearer ${host.token}`, "content-type": "application/json" },
17312
17362
  body: JSON.stringify({ method, params: params ?? {} }),
17313
17363
  signal: AbortSignal.timeout(
17314
- method === "codexhost/remote/ssh-setup" ? Math.max(timeoutMs, 33e4) : timeoutMs
17364
+ method === VOICE_GATEWAY_STATUS_METHOD ? Math.min(timeoutMs, 2e3) : method === "codexhost/remote/ssh-setup" ? Math.max(timeoutMs, 33e4) : timeoutMs
17315
17365
  )
17316
17366
  });
17317
17367
  const body = await response.json().catch(() => null);
@@ -17389,22 +17439,22 @@ function inspectInstallation(launcherExecutable) {
17389
17439
  );
17390
17440
  });
17391
17441
  }
17392
- function launchCommand(installation, environment = process.env) {
17442
+ function launchCommand(installation, environment = process.env, action = "launch") {
17393
17443
  if (installation.distribution?.distribution === "npm") {
17394
17444
  const node = environment.CODEXHOST_NPM_NODE_PATH;
17395
17445
  const wrapper = environment.CODEXHOST_NPM_LAUNCHER_PATH;
17396
17446
  if (node && wrapper && path13.isAbsolute(node) && path13.isAbsolute(wrapper)) {
17397
- return { command: node, args: [wrapper] };
17447
+ return { command: node, args: [wrapper, ...action === "restart" ? [action] : []] };
17398
17448
  }
17399
17449
  return null;
17400
17450
  }
17401
- return installation.launcherExecutable ? { command: installation.launcherExecutable, args: ["launch"] } : null;
17451
+ return installation.launcherExecutable ? { command: installation.launcherExecutable, args: [action] } : null;
17402
17452
  }
17403
17453
 
17404
17454
  // packages/console-server/src/open.ts
17405
17455
  import { spawn as spawn3 } from "node:child_process";
17406
17456
  import { stat as stat4 } from "node:fs/promises";
17407
- import path16 from "node:path";
17457
+ import path18 from "node:path";
17408
17458
 
17409
17459
  // packages/console-server/src/page.ts
17410
17460
  import { readFile as readFile8 } from "node:fs/promises";
@@ -17454,6 +17504,49 @@ function allowedChange(origin, consoleHeader, port) {
17454
17504
  return origin === void 0 || origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;
17455
17505
  }
17456
17506
 
17507
+ // packages/console-server/src/restart.ts
17508
+ var ConsoleRestart = class {
17509
+ constructor(run2, observe, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), now = Date.now, timeoutMs = 12e4) {
17510
+ this.run = run2;
17511
+ this.observe = observe;
17512
+ this.sleep = sleep;
17513
+ this.now = now;
17514
+ this.timeoutMs = timeoutMs;
17515
+ }
17516
+ run;
17517
+ observe;
17518
+ sleep;
17519
+ now;
17520
+ timeoutMs;
17521
+ status = { phase: "idle", error: null };
17522
+ start(previous) {
17523
+ if (this.status.phase === "restarting") throw new Error("Restart is already in progress");
17524
+ this.status = { phase: "restarting", error: null };
17525
+ void this.restart(previous);
17526
+ }
17527
+ async restart(previous) {
17528
+ try {
17529
+ await this.run();
17530
+ const deadline = this.now() + this.timeoutMs;
17531
+ while (this.now() < deadline) {
17532
+ const current = await this.observe();
17533
+ if (current.failure) throw new Error(current.failure);
17534
+ if (current.launcherPid !== null && current.launcherPid !== previous.launcherPid && current.controllerPid !== null && current.controllerPid !== previous.controllerPid && current.installed) {
17535
+ this.status = { phase: "succeeded", error: null };
17536
+ return;
17537
+ }
17538
+ await this.sleep(250);
17539
+ }
17540
+ throw new Error("Restart timed out. Check startup diagnostics before retrying.");
17541
+ } catch (error51) {
17542
+ this.status = {
17543
+ phase: "failed",
17544
+ error: error51 instanceof Error ? error51.message : String(error51)
17545
+ };
17546
+ }
17547
+ }
17548
+ };
17549
+
17457
17550
  // packages/console-server/src/server.ts
17458
17551
  import { spawn as spawn2 } from "node:child_process";
17459
17552
  import { createServer } from "node:http";
@@ -17862,6 +17955,135 @@ function createConsoleUpdates(options) {
17862
17955
  });
17863
17956
  }
17864
17957
 
17958
+ // packages/console-server/src/live-voice-inject.ts
17959
+ import { randomUUID as randomUUID3 } from "node:crypto";
17960
+ import { mkdir as mkdir4, readFile as readFile9, rename as rename3, rm as rm5, writeFile as writeFile4 } from "node:fs/promises";
17961
+ import path16 from "node:path";
17962
+ var CPA_LIVE_VOICE_ENV = "CODEXHOST_CPA_LIVE_VOICE";
17963
+ var LIVE_VOICE_INJECT_FILE = "live-voice-inject-v1.json";
17964
+ function injectPath(dataDirectory2) {
17965
+ return path16.join(dataDirectory2, LIVE_VOICE_INJECT_FILE);
17966
+ }
17967
+ async function readLiveVoiceInjectState(dataDirectory2, environment = process.env) {
17968
+ const env = environment[CPA_LIVE_VOICE_ENV];
17969
+ const envOverride = env === "0" || env === "1" ? env : null;
17970
+ let inject = true;
17971
+ try {
17972
+ const parsed = JSON.parse(await readFile9(injectPath(dataDirectory2), "utf8"));
17973
+ inject = parsed.version !== 1 || parsed.inject !== false;
17974
+ } catch {
17975
+ inject = true;
17976
+ }
17977
+ const savedInject = inject;
17978
+ if (envOverride === "0") inject = false;
17979
+ if (envOverride === "1") inject = true;
17980
+ return { inject, savedInject, envOverride };
17981
+ }
17982
+ async function writeLiveVoiceInjectFile(dataDirectory2, inject, routes) {
17983
+ if (!routes) {
17984
+ try {
17985
+ const saved = JSON.parse(await readFile9(injectPath(dataDirectory2), "utf8"));
17986
+ if (saved?.version === 1) routes = voiceRoutesSchema.safeParse(saved.routes).data;
17987
+ } catch {
17988
+ }
17989
+ }
17990
+ await mkdir4(dataDirectory2, { recursive: true, mode: 448 });
17991
+ const temporaryPath = path16.join(dataDirectory2, `.${LIVE_VOICE_INJECT_FILE}.${randomUUID3()}.tmp`);
17992
+ try {
17993
+ await writeFile4(
17994
+ temporaryPath,
17995
+ `${JSON.stringify({ version: 1, inject, ...routes ? { routes } : {} }, null, 2)}
17996
+ `,
17997
+ {
17998
+ mode: 384,
17999
+ flag: "wx"
18000
+ }
18001
+ );
18002
+ await rename3(temporaryPath, injectPath(dataDirectory2));
18003
+ } finally {
18004
+ await rm5(temporaryPath, { force: true });
18005
+ }
18006
+ }
18007
+
18008
+ // packages/console-server/src/voice-settings.ts
18009
+ import { createHash as createHash2 } from "node:crypto";
18010
+ import { readFile as readFile10 } from "node:fs/promises";
18011
+ import path17 from "node:path";
18012
+ var VoiceSettingsConflict = class extends Error {
18013
+ };
18014
+ var VoiceSettingsStore = class {
18015
+ constructor(directory) {
18016
+ this.directory = directory;
18017
+ }
18018
+ directory;
18019
+ #writes = Promise.resolve();
18020
+ #runtime = /* @__PURE__ */ new Map();
18021
+ async read() {
18022
+ let document = null;
18023
+ try {
18024
+ document = JSON.parse(
18025
+ await readFile10(path17.join(this.directory, LIVE_VOICE_INJECT_FILE), "utf8")
18026
+ );
18027
+ } catch (error51) {
18028
+ if (!(error51 instanceof SyntaxError) && error51.code !== "ENOENT")
18029
+ throw new Error("Could not read voice settings");
18030
+ }
18031
+ const routes = document?.version === 1 ? voiceRoutesSchema.safeParse(document.routes).data : void 0;
18032
+ const settings = {
18033
+ ...routes ?? DEFAULT_VOICE_ROUTES,
18034
+ inject: document?.version !== 1 || document.inject !== false
18035
+ };
18036
+ const revision = createHash2("sha256").update(JSON.stringify({ settings, initialized: !!routes })).digest("hex");
18037
+ const runtime = [...this.#runtime.values()].filter((entry) => Date.now() - entry.at < 12e3);
18038
+ return {
18039
+ settings,
18040
+ initialized: !!routes,
18041
+ revision,
18042
+ activeCall: runtime.length ? runtime.some((entry) => entry.activeCall) : null,
18043
+ routesActive: runtime.length ? runtime.every((entry) => entry.revision === revision) : null
18044
+ };
18045
+ }
18046
+ report(runtime) {
18047
+ for (const [id, entry] of this.#runtime)
18048
+ if (Date.now() - entry.at >= 12e3) this.#runtime.delete(id);
18049
+ if (this.#runtime.size < 32 || this.#runtime.has(runtime.id))
18050
+ this.#runtime.set(runtime.id, { ...runtime, at: Date.now() });
18051
+ }
18052
+ async setInjection(inject) {
18053
+ const operation = this.#writes.then(async () => {
18054
+ const current = await this.read();
18055
+ if (current.activeCall)
18056
+ throw new VoiceSettingsConflict("End your voice call before applying changes.");
18057
+ await writeLiveVoiceInjectFile(this.directory, inject);
18058
+ });
18059
+ this.#writes = operation.catch(() => void 0);
18060
+ await operation;
18061
+ }
18062
+ async write(input) {
18063
+ const operation = this.#writes.then(async () => {
18064
+ const current = await this.read();
18065
+ if (input.operation === "migrate" && current.initialized) return current;
18066
+ if (input.operation === "set") {
18067
+ if (current.activeCall)
18068
+ throw new VoiceSettingsConflict("End your voice call before applying changes.");
18069
+ if (input.revision !== current.revision)
18070
+ throw new VoiceSettingsConflict(
18071
+ "Voice settings changed in another window. Reload and try again."
18072
+ );
18073
+ }
18074
+ const settings = input.operation === "set" ? input.settings : { ...current.settings, ...input.routes };
18075
+ await writeLiveVoiceInjectFile(this.directory, settings.inject, {
18076
+ chatExperience: settings.chatExperience,
18077
+ chatConnection: settings.chatConnection,
18078
+ workConnection: settings.workConnection
18079
+ });
18080
+ return this.read();
18081
+ });
18082
+ this.#writes = operation.catch(() => void 0);
18083
+ return operation;
18084
+ }
18085
+ };
18086
+
17865
18087
  // packages/console-server/src/server.ts
17866
18088
  var CONSOLE_SERVICE = "codexhost-console";
17867
18089
  var MAX_BODY_BYTES = 16 * 1024;
@@ -17917,11 +18139,42 @@ function defaultLaunch(command2, args) {
17917
18139
  });
17918
18140
  child.unref();
17919
18141
  }
18142
+ function defaultRestart(command2, args) {
18143
+ return new Promise((resolve, reject) => {
18144
+ const child = spawn2(command2, args, { detached: true, stdio: "ignore", windowsHide: true });
18145
+ child.once("error", () => reject(new Error("Could not start the installed restart command")));
18146
+ child.once("spawn", () => {
18147
+ child.unref();
18148
+ resolve();
18149
+ });
18150
+ });
18151
+ }
17920
18152
  function startConsoleServer(options) {
17921
18153
  const environment = options.environment ?? process.env;
17922
18154
  const inspect = options.inspect ?? inspectInstallation;
17923
18155
  const launch = options.launch ?? defaultLaunch;
17924
18156
  const loadAnnouncement = options.announcement ?? readAnnouncement;
18157
+ const voiceSettings = new VoiceSettingsStore(options.paths.dataDirectory);
18158
+ let restartRequestedAt = 0;
18159
+ const restart = new ConsoleRestart(
18160
+ async () => {
18161
+ restartRequestedAt = Date.now();
18162
+ const command2 = launchCommand(options.installation, environment, "restart");
18163
+ if (!command2) throw new Error("This installation cannot be restarted from the console");
18164
+ await (options.restart ?? defaultRestart)(command2.command, command2.args);
18165
+ },
18166
+ async () => {
18167
+ inspectCache = null;
18168
+ const collected = await collect();
18169
+ const latest = collected.startup[0];
18170
+ return {
18171
+ launcherPid: collected.inspectDocument?.runtime.launcherPid ?? null,
18172
+ controllerPid: collected.controllerAlive ? collected.controller?.pid ?? null : null,
18173
+ installed: collected.controllerAlive && collected.controller?.renderer.state === "installed",
18174
+ failure: latest?.outcome === "failed" && latest.startedAtMs >= restartRequestedAt ? latest.error ?? "Desktop startup failed" : null
18175
+ };
18176
+ }
18177
+ );
17925
18178
  let port = options.port;
17926
18179
  let inspectCache = null;
17927
18180
  let idleTimer;
@@ -18003,7 +18256,13 @@ function startConsoleServer(options) {
18003
18256
  launchAvailable: launchCommand(options.installation, environment) !== null,
18004
18257
  summary,
18005
18258
  issueUrl: reportIssueUrl,
18006
- hostAvailable: await options.host.available()
18259
+ hostAvailable: await options.host.available(),
18260
+ liveVoice: {
18261
+ ...await readLiveVoiceInjectState(options.paths.dataDirectory, environment),
18262
+ activeInject: collected.controllerAlive && typeof controller?.voiceInjection === "boolean" ? controller.voiceInjection : null,
18263
+ activeEnvOverride: collected.controllerAlive ? controller?.voiceEnvOverride ?? null : null
18264
+ },
18265
+ restart: restart.status
18007
18266
  };
18008
18267
  }
18009
18268
  async function diagnosticReport(collected, withLogs = true) {
@@ -18036,7 +18295,7 @@ function startConsoleServer(options) {
18036
18295
  scheduleIdleExit();
18037
18296
  const url2 = new URL(request.url ?? "/", `http://127.0.0.1:${port}`);
18038
18297
  const method = request.method ?? "GET";
18039
- const route = `${method} ${url2.pathname}`;
18298
+ let route = `${method} ${url2.pathname}`;
18040
18299
  if (route === "GET /api/health") {
18041
18300
  sendJson(response, 200, {
18042
18301
  service: CONSOLE_SERVICE,
@@ -18077,6 +18336,65 @@ function startConsoleServer(options) {
18077
18336
  return;
18078
18337
  }
18079
18338
  }
18339
+ if (route === "POST /api/voice-settings") {
18340
+ const parsed = voiceSettingsRequestSchema.safeParse(body);
18341
+ if (!parsed.success) {
18342
+ sendJson(response, 400, { error: "Invalid voice settings request" });
18343
+ return;
18344
+ }
18345
+ const input = parsed.data;
18346
+ if (input.operation === "restart") {
18347
+ if ((await voiceSettings.read()).activeCall) {
18348
+ sendJson(response, 409, { error: "End your voice call before restarting." });
18349
+ return;
18350
+ }
18351
+ route = "POST /api/restart";
18352
+ } else {
18353
+ try {
18354
+ if (input.operation === "get" && input.runtime) voiceSettings.report(input.runtime);
18355
+ if (input.operation === "set") {
18356
+ const current = await readLiveVoiceInjectState(
18357
+ options.paths.dataDirectory,
18358
+ environment
18359
+ );
18360
+ if (current.envOverride && input.settings.inject !== current.savedInject) {
18361
+ sendJson(response, 409, {
18362
+ error: "Voice integration is controlled by this launch environment."
18363
+ });
18364
+ return;
18365
+ }
18366
+ }
18367
+ const saved = input.operation === "get" ? await voiceSettings.read() : await voiceSettings.write(input);
18368
+ const collected = await collect();
18369
+ const controller = collected.controller;
18370
+ const injection = await readLiveVoiceInjectState(
18371
+ options.paths.dataDirectory,
18372
+ environment
18373
+ );
18374
+ let gatewayReady = null;
18375
+ try {
18376
+ const reply = input.operation === "get" && input.runtime ? null : await options.host.request(VOICE_GATEWAY_STATUS_METHOD, {});
18377
+ if (reply && "result" in reply && typeof reply.result === "boolean")
18378
+ gatewayReady = reply.result;
18379
+ } catch {
18380
+ }
18381
+ sendJson(response, 200, {
18382
+ ...saved,
18383
+ activeInject: collected.controllerAlive && typeof controller?.voiceInjection === "boolean" ? controller.voiceInjection : null,
18384
+ envOverride: injection.envOverride,
18385
+ activeEnvOverride: collected.controllerAlive ? controller?.voiceEnvOverride ?? null : null,
18386
+ gatewayReady,
18387
+ restartAvailable: !!collected.inspectDocument?.runtime.running && !!launchCommand(options.installation, environment, "restart"),
18388
+ restart: restart.status
18389
+ });
18390
+ } catch (error51) {
18391
+ sendJson(response, error51 instanceof VoiceSettingsConflict ? 409 : 500, {
18392
+ error: error51 instanceof VoiceSettingsConflict ? error51.message : "Could not save or read voice settings"
18393
+ });
18394
+ }
18395
+ return;
18396
+ }
18397
+ }
18080
18398
  if (route === "POST /api/shutdown") {
18081
18399
  sendJson(response, 200, { ok: true });
18082
18400
  setImmediate(exit);
@@ -18168,6 +18486,59 @@ function startConsoleServer(options) {
18168
18486
  }
18169
18487
  return;
18170
18488
  }
18489
+ if (route === "GET /api/live-voice-inject") {
18490
+ sendJson(
18491
+ response,
18492
+ 200,
18493
+ await readLiveVoiceInjectState(options.paths.dataDirectory, environment)
18494
+ );
18495
+ return;
18496
+ }
18497
+ if (route === "POST /api/live-voice-inject") {
18498
+ if (typeof body.inject !== "boolean") {
18499
+ sendJson(response, 400, { error: "Expected { inject: boolean }" });
18500
+ return;
18501
+ }
18502
+ try {
18503
+ await voiceSettings.setInjection(body.inject);
18504
+ } catch (error51) {
18505
+ sendJson(response, error51 instanceof VoiceSettingsConflict ? 409 : 500, {
18506
+ error: error51 instanceof VoiceSettingsConflict ? error51.message : "Could not save voice integration"
18507
+ });
18508
+ return;
18509
+ }
18510
+ sendJson(
18511
+ response,
18512
+ 200,
18513
+ await readLiveVoiceInjectState(options.paths.dataDirectory, environment)
18514
+ );
18515
+ return;
18516
+ }
18517
+ if (route === "POST /api/restart") {
18518
+ if (body.confirm !== true) {
18519
+ sendJson(response, 400, {
18520
+ error: "Confirm that restart may interrupt voice and running tasks"
18521
+ });
18522
+ return;
18523
+ }
18524
+ const current = await collect();
18525
+ if (!current.inspectDocument?.runtime.running || !launchCommand(options.installation, environment, "restart")) {
18526
+ sendJson(response, 409, { error: "No restartable managed Desktop is running" });
18527
+ return;
18528
+ }
18529
+ if (restart.status.phase === "restarting") {
18530
+ sendJson(response, 409, { error: "Restart is already in progress" });
18531
+ return;
18532
+ }
18533
+ restart.start({
18534
+ launcherPid: current.inspectDocument.runtime.launcherPid,
18535
+ controllerPid: current.controller?.pid ?? null,
18536
+ installed: current.controller?.renderer.state === "installed",
18537
+ failure: null
18538
+ });
18539
+ sendJson(response, 202, restart.status);
18540
+ return;
18541
+ }
18171
18542
  if (route === "POST /api/launch") {
18172
18543
  const command2 = launchCommand(options.installation, environment);
18173
18544
  if (!command2) {
@@ -18259,7 +18630,7 @@ async function requestShutdown(port) {
18259
18630
  if (!response.ok) throw new Error(`console shutdown failed: HTTP ${response.status}`);
18260
18631
  }
18261
18632
  async function consoleBuildId(entryPath2, environment = process.env) {
18262
- const files = [entryPath2, ...consoleBundleCandidates(path16.dirname(entryPath2))];
18633
+ const files = [entryPath2, ...consoleBundleCandidates(path18.dirname(entryPath2))];
18263
18634
  const parts = [];
18264
18635
  for (const file2 of files) {
18265
18636
  const metadata = await stat4(file2).catch(() => null);
@@ -18362,7 +18733,7 @@ async function updateFromCommand(appDirectory2, arguments_) {
18362
18733
  var IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
18363
18734
  var HANDOFF_EXIT_DELAY_MS = 500;
18364
18735
  var entryPath = fileURLToPath(import.meta.url);
18365
- var appDirectory = path17.dirname(entryPath);
18736
+ var appDirectory = path19.dirname(entryPath);
18366
18737
  function usage() {
18367
18738
  console.error(
18368
18739
  "usage: console-server open [--no-browser] | console-server ensure | console-server serve | console-server update"