grok-bot-cli 0.4.1 → 0.4.5

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.
@@ -13884,6 +13884,140 @@ __webpack_require__.d(__webpack_exports__, {
13884
13884
  });
13885
13885
 
13886
13886
 
13887
+ },
13888
+ "./src/cli/codex/desktop-shim.tsx"(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
13889
+ __webpack_require__.r(__webpack_exports__);
13890
+ /* import */ var react_jsx_runtime__rspack_import_0 = __webpack_require__("./node_modules/react/jsx-runtime.react-server.js");
13891
+ /* import */ var _agent_bundle_runtime__rspack_import_3 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/506.js");
13892
+ /* import */ var zod__rspack_import_2 = __webpack_require__("./node_modules/zod/v4/classic/schemas.js");
13893
+ /* import */ var _core_desktop_shim_js__rspack_import_1 = __webpack_require__("./src/core/desktop-shim.js");
13894
+ /* import */ var _core_format_js__rspack_import_4 = __webpack_require__("./src/core/format.js");
13895
+
13896
+
13897
+
13898
+
13899
+
13900
+ const config = {
13901
+ description: 'Install, remove, or check the ChatGPT Desktop shim: a CODEX_CLI_PATH wrapper that bridges Desktop stdio onto the managed Codex daemon (stock app-server proxy hangs). macOS persists via LaunchAgent; always fails open to real Codex.',
13902
+ exitCode: 'result',
13903
+ inputJsonSchema: {
13904
+ additionalProperties: false,
13905
+ properties: {
13906
+ action: {
13907
+ description: 'install | uninstall | status',
13908
+ enum: [
13909
+ 'install',
13910
+ 'uninstall',
13911
+ 'status'
13912
+ ],
13913
+ type: 'string'
13914
+ }
13915
+ },
13916
+ required: [
13917
+ 'action'
13918
+ ],
13919
+ type: 'object'
13920
+ },
13921
+ positionals: [
13922
+ 'action'
13923
+ ]
13924
+ };
13925
+ const inputSchema = zod__rspack_import_2/* .object */.Ikc({
13926
+ action: zod__rspack_import_2/* ["enum"] */.k5n([
13927
+ 'install',
13928
+ 'uninstall',
13929
+ 'status'
13930
+ ])
13931
+ }).strict();
13932
+ const resultSchema = zod__rspack_import_2/* .object */.Ikc({
13933
+ exitCode: zod__rspack_import_2/* .union */.KCZ([
13934
+ zod__rspack_import_2/* .literal */.euz(0),
13935
+ zod__rspack_import_2/* .literal */.euz(1)
13936
+ ])
13937
+ }).passthrough();
13938
+ const formatWarnings = (warnings)=>warnings.length === 0 ? '' : `\nwarnings:\n${warnings.map((w)=>` - ${String(w)}`).join('\n')}`;
13939
+ async function codexDesktopShim({ input }) {
13940
+ switch(input.action){
13941
+ case 'install':
13942
+ {
13943
+ let out;
13944
+ try {
13945
+ out = (0,_core_desktop_shim_js__rspack_import_1/* .installDesktopShim */.B0)();
13946
+ } catch (error) {
13947
+ const message = error instanceof Error ? error.message : String(error);
13948
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
13949
+ value: {
13950
+ action: 'install',
13951
+ error: message,
13952
+ exitCode: 1
13953
+ },
13954
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
13955
+ children: `Desktop shim install failed: ${message}`
13956
+ })
13957
+ });
13958
+ }
13959
+ const text = `Installed ChatGPT Desktop shim:\n` + ` wrapper: ${out.wrapperPath}\n` + ` bridge: ${out.bridgePath}\n` + ` login env: ${out.envScriptPath}` + (out.plistPath ? `\n LaunchAgent: ${out.plistPath}` : '\n LaunchAgent: n/a (macOS-only)') + `\nFully quit and relaunch ChatGPT.app so it inherits CODEX_CLI_PATH; ` + `Desktop falls back to stock Codex if the shim is ever removed.` + formatWarnings(out.warnings);
13960
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
13961
+ value: out,
13962
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
13963
+ children: text
13964
+ })
13965
+ });
13966
+ }
13967
+ case 'uninstall':
13968
+ {
13969
+ let out;
13970
+ try {
13971
+ out = (0,_core_desktop_shim_js__rspack_import_1/* .uninstallDesktopShim */.gM)();
13972
+ } catch (error) {
13973
+ const message = error instanceof Error ? error.message : String(error);
13974
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
13975
+ value: {
13976
+ action: 'uninstall',
13977
+ error: message,
13978
+ exitCode: 1
13979
+ },
13980
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
13981
+ children: `Desktop shim uninstall failed: ${message}`
13982
+ })
13983
+ });
13984
+ }
13985
+ const removed = out.removed.length === 0 ? '(nothing installed)' : out.removed.join(', ');
13986
+ const text = `Removed ChatGPT Desktop shim: ${removed}. ` + `Desktop and Codex fall back to stock behavior; relaunch ChatGPT.app to pick it up.` + formatWarnings(out.warnings);
13987
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
13988
+ value: out,
13989
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
13990
+ children: text
13991
+ })
13992
+ });
13993
+ }
13994
+ case 'status':
13995
+ {
13996
+ const out = (0,_core_desktop_shim_js__rspack_import_1/* .desktopShimStatus */.em)();
13997
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
13998
+ value: out,
13999
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
14000
+ children: (0,_core_format_js__rspack_import_4/* .formatDesktopShimStatus */.fX)(out)
14001
+ })
14002
+ });
14003
+ }
14004
+ default:
14005
+ {
14006
+ const _exhaustive = input.action;
14007
+ throw new Error(`unknown desktop-shim action: ${String(_exhaustive)}`);
14008
+ }
14009
+ }
14010
+ }
14011
+
14012
+ __webpack_require__.d(__webpack_exports__, {
14013
+ "default": () => (codexDesktopShim)
14014
+ }, {
14015
+ config: config,
14016
+ inputSchema: inputSchema,
14017
+ resultSchema: resultSchema
14018
+ });
14019
+
14020
+
13887
14021
  },
13888
14022
  "./src/cli/codex/list-threads.tsx"(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
13889
14023
  __webpack_require__.r(__webpack_exports__);
@@ -26276,6 +26410,8 @@ __webpack_require__.d(__webpack_exports__, {
26276
26410
  /* import */ var node_child_process__rspack_import_5 = __webpack_require__("node:child_process");
26277
26411
  /* import */ var node_module__rspack_import_6 = __webpack_require__("node:module");
26278
26412
  /* import */ var _codex_contract_js__rspack_import_7 = __webpack_require__("./src/core/codex/contract.js");
26413
+ /* import */ var _desktop_shim_js__rspack_import_8 = __webpack_require__("./src/core/desktop-shim.js");
26414
+
26279
26415
 
26280
26416
 
26281
26417
 
@@ -26303,6 +26439,9 @@ const textDecoder = new TextDecoder("utf-8", {
26303
26439
  });
26304
26440
  const pkg = (0,node_module__rspack_import_6.createRequire)(import.meta.url)("../../package.json");
26305
26441
  function codexSocketPath(env = process.env) {
26442
+ // Explicit socket wins so status/send/queue agree with a shim-installed or
26443
+ // operator-exported CODEX_APP_SERVER_SOCK; otherwise derive from CODEX_HOME.
26444
+ if (env.CODEX_APP_SERVER_SOCK) return env.CODEX_APP_SERVER_SOCK;
26306
26445
  const home = env.CODEX_HOME || (0,node_path__rspack_import_4.join)((0,node_os__rspack_import_3.homedir)(), ".codex");
26307
26446
  return (0,node_path__rspack_import_4.join)(home, "app-server-control", "app-server-control.sock");
26308
26447
  }
@@ -26319,7 +26458,15 @@ function codexSocketPath(env = process.env) {
26319
26458
  /** Single-line fields (ids, names, paths, cursors, header tokens): no line breaks or tabs survive. */ function singleLine(text) {
26320
26459
  return stripTerminalControls(text).replace(/[\t\n\r\u2028\u2029]+/g, " ");
26321
26460
  }
26322
- function unreachableMessage(path) {
26461
+ function unreachableMessage(path, desktopAttached = "unknown") {
26462
+ if (desktopAttached === "private-stdio") {
26463
+ return [
26464
+ "ChatGPT Desktop is running its private stdio app-server, which external clients cannot reach",
26465
+ "(" + UPSTREAM_DESKTOP_ISSUES.join(", ") + ").",
26466
+ "No Codex app-server control socket at " + path + ": start a managed standalone daemon with `codex app-server daemon start`.",
26467
+ "gbot codex targets daemon-managed threads only."
26468
+ ].join("\n");
26469
+ }
26323
26470
  return [
26324
26471
  "No Codex app-server control socket at " + path + ".",
26325
26472
  "Either no daemon is running (start one with `codex app-server daemon start`),",
@@ -26522,6 +26669,43 @@ function isObject(value) {
26522
26669
  const sendJson = (obj)=>write(0x1, Buffer.from(JSON.stringify(obj)));
26523
26670
  const client = {
26524
26671
  refused,
26672
+ // Server-initiated requests are only *answered* once our own turn/start
26673
+ // is in flight, and then only when they name our own thread/turn:
26674
+ // anything earlier — or naming another thread or Desktop turn — belongs
26675
+ // to another client and must never be rejected on their behalf. Early
26676
+ // and foreign requests are still recorded; the connection closes right
26677
+ // after, so a pending request simply dies with it instead of denying
26678
+ // someone's approval.
26679
+ answerServerRequests: false,
26680
+ expectedThreadId: null,
26681
+ expectedTurnId: null,
26682
+ // Approvals that name a turn while our own turn id is still unknown
26683
+ // (arriving before — or in the same socket batch as — the turn/start
26684
+ // acknowledgment) wait here instead of being classified foreign: once
26685
+ // the acknowledgment supplies the turn id, matching ones are answered
26686
+ // and the rest stay silent.
26687
+ deferred: [],
26688
+ // Adopt the acknowledged turn id, answering deferred requests that
26689
+ // prove to be ours. Runs inside the connection closure so it can send.
26690
+ _adoptTurn (turnId) {
26691
+ client.expectedTurnId = turnId;
26692
+ for (const entry of client.deferred.splice(0)){
26693
+ const params = entry.params && typeof entry.params === "object" && !Array.isArray(entry.params) ? entry.params : null;
26694
+ const threadId = params ? params.threadId ?? params.thread_id : null;
26695
+ const namedTurnId = params ? params.turnId ?? params.turn_id ?? (params.turn && params.turn.id) : null;
26696
+ if (threadId === client.expectedThreadId && namedTurnId === turnId) {
26697
+ entry.answered = true;
26698
+ sendJson({
26699
+ jsonrpc: "2.0",
26700
+ id: entry.id,
26701
+ error: {
26702
+ code: -32601,
26703
+ message: "gbot codex does not answer " + entry.method + "; configure approval_policy on the daemon"
26704
+ }
26705
+ });
26706
+ }
26707
+ }
26708
+ },
26525
26709
  request (method, params) {
26526
26710
  const id = nextId++;
26527
26711
  return new Promise((res, rej)=>{
@@ -26585,11 +26769,37 @@ function isObject(value) {
26585
26769
  };
26586
26770
  const onMessage = (msg)=>{
26587
26771
  if (msg.id != null && msg.method) {
26588
- refused.push({
26772
+ let answered = client.answerServerRequests;
26773
+ let defer = false;
26774
+ // Approval ownership: only answer requests for the turn gbot itself
26775
+ // started. A request naming another thread — or naming a turn that is
26776
+ // not ours — is recorded foreign-silent, never rejected. A request
26777
+ // naming a turn while our turn id is still unknown is deferred, not
26778
+ // classified: the turn/start acknowledgment decides it.
26779
+ if (answered) {
26780
+ const params = msg.params && typeof msg.params === "object" && !Array.isArray(msg.params) ? msg.params : null;
26781
+ if (params) {
26782
+ const threadId = params.threadId ?? params.thread_id;
26783
+ const turnId = params.turnId ?? params.turn_id ?? (params.turn && params.turn.id);
26784
+ if (threadId !== client.expectedThreadId || turnId == null) answered = false;
26785
+ else if (client.expectedTurnId == null) {
26786
+ answered = false;
26787
+ defer = true;
26788
+ } else if (turnId !== client.expectedTurnId) answered = false;
26789
+ } else answered = false;
26790
+ }
26791
+ const entry = {
26589
26792
  id: msg.id,
26590
26793
  method: msg.method,
26591
- params: msg.params
26592
- });
26794
+ params: msg.params,
26795
+ answered
26796
+ };
26797
+ refused.push(entry);
26798
+ if (defer) {
26799
+ client.deferred.push(entry);
26800
+ return;
26801
+ }
26802
+ if (!answered) return;
26593
26803
  sendJson({
26594
26804
  jsonrpc: "2.0",
26595
26805
  id: msg.id,
@@ -26793,32 +27003,98 @@ const CODEX_VERSION_PROBE_TIMEOUT_MS = 3000;
26793
27003
  probe: "error"
26794
27004
  };
26795
27005
  }
27006
+ /** Bounded `ps` snapshot for Desktop detection: process names only, never pipes or sockets. */ const DESKTOP_PROCESS_LIST_TIMEOUT_MS = 3000;
27007
+ function listDesktopProcesses(timeoutMs = DESKTOP_PROCESS_LIST_TIMEOUT_MS) {
27008
+ if (process.platform === "win32") return "";
27009
+ try {
27010
+ const out = (0,node_child_process__rspack_import_5.spawnSync)("ps", [
27011
+ "-eo",
27012
+ "args"
27013
+ ], {
27014
+ encoding: "utf8",
27015
+ timeout: timeoutMs
27016
+ });
27017
+ if (out.error || out.status !== 0 || typeof out.stdout !== "string") return "";
27018
+ return out.stdout;
27019
+ } catch {
27020
+ return "";
27021
+ }
27022
+ }
27023
+ /**
27024
+ * Pure Desktop private-stdio detector. Returns `"private-stdio"` when the process list
27025
+ * shows Desktop's bundled `.../ChatGPT.app/.../Resources/codex` running `app-server`
27026
+ * (the `codex_app` override corroborates, the Resources path alone suffices); `"unknown"`
27027
+ * otherwise. Never `"detached"`: absence of a match means "not observed", not "not running".
27028
+ * Windows stays `"unknown"` (unsupported route, unchanged).
27029
+ *
27030
+ * @param {{ platform?: string, listProcesses?: string | string[] | null }} [opts]
27031
+ */ function detectDesktopPrivateAppServer({ platform = process.platform, listProcesses = "" } = {}) {
27032
+ if (platform === "win32") return "unknown";
27033
+ const lines = Array.isArray(listProcesses) ? listProcesses : String(listProcesses ?? "").split(/\r?\n/);
27034
+ for (const line of lines){
27035
+ if (!/app-server/.test(line)) continue;
27036
+ // Desktop's bundled binary, Mac or Windows-style separators with optional .exe.
27037
+ if (/ChatGPT\.app[\\/].*Resources[\\/]codex(\.exe)?(["'\s]|$)/.test(line)) return "private-stdio";
27038
+ // Same app-server line owned by ChatGPT.app carrying the app-tools override.
27039
+ if (/ChatGPT\.app/.test(line) && /(codex_app|mcp_servers\.codex_app|codex-app-tools)/.test(line)) return "private-stdio";
27040
+ }
27041
+ return "unknown";
27042
+ }
26796
27043
  /**
26797
27044
  * Status contract: `reachable` is endpoint reachability only. `schema.compatibility` is
26798
27045
  * `exact` when the daemon reports the pinned version, otherwise `unverified` (methods
26799
- * usually survive upgrades) or `unknown`. Whether ChatGPT Desktop owns any thread is not
26800
- * observable from the socket, so `desktopAttached` is always `"unknown"`.
26801
- */ async function codexStatus(env = process.env) {
27046
+ * usually survive upgrades) or `unknown`. `desktopShimConfigured` records an installed
27047
+ * wrapper selected by Desktop-facing CODEX_CLI_PATH (the GUI domain on Darwin).
27048
+ * Configuration does not prove a running Desktop inherited it or connected.
27049
+ * `desktopAttached` is `"private-stdio"` when a Desktop-bundled app-server process
27050
+ * is visible, otherwise `"unknown"`. Managed attachment is not observable here.
27051
+ *
27052
+ * @param {NodeJS.ProcessEnv} [env]
27053
+ * @param {{ listProcesses?: string | string[], shim?: { installed?: boolean, wrapperPointsAtShim?: boolean } | null }} [opts]
27054
+ * injected process list and shim state for tests; defaults to a live `ps` snapshot and
27055
+ * `desktopShimStatus`. Never reads pipes or connects to Desktop.
27056
+ */ async function codexStatus(env = process.env, { listProcesses, shim } = {}) {
26802
27057
  const path = codexSocketPath(env);
26803
27058
  const cli = probeLocalCodexVersion();
27059
+ let desktopShimConfigured = Boolean(shim && shim.installed && shim.wrapperPointsAtShim);
27060
+ if (shim === undefined) {
27061
+ try {
27062
+ const live = (0,_desktop_shim_js__rspack_import_8/* .desktopShimStatus */.em)({
27063
+ env
27064
+ });
27065
+ desktopShimConfigured = Boolean(live.installed && live.wrapperPointsAtShim);
27066
+ } catch {
27067
+ desktopShimConfigured = false;
27068
+ }
27069
+ }
27070
+ const desktopAttached = detectDesktopPrivateAppServer({
27071
+ platform: process.platform,
27072
+ listProcesses: listProcesses ?? listDesktopProcesses()
27073
+ });
26804
27074
  const base = {
26805
27075
  socketPath: path,
26806
27076
  socketState: socketState(path),
26807
27077
  pinnedVersion: PINNED_CODEX_VERSION,
26808
27078
  cliVersion: cli.version,
26809
27079
  cliVersionProbe: cli.probe,
26810
- desktopAttached: "unknown"
27080
+ desktopAttached,
27081
+ desktopShimConfigured
26811
27082
  };
26812
27083
  let session;
26813
27084
  try {
26814
27085
  session = await openSession(env);
26815
27086
  } catch (err) {
26816
- if (err instanceof CodexRouteError) return (0,_codex_contract_js__rspack_import_7/* .withStatusExitCode */.AQ)({
26817
- ...base,
26818
- reachable: false,
26819
- mode: err.mode,
26820
- message: err.message
26821
- });
27087
+ if (err instanceof CodexRouteError) {
27088
+ // Name the private-stdio case explicitly: Desktop is up but unreachable, so the
27089
+ // operator needs a managed standalone daemon, not a Desktop reconnect.
27090
+ const message = err.mode === "socket-absent" ? unreachableMessage(path, desktopAttached) : err.message;
27091
+ return (0,_codex_contract_js__rspack_import_7/* .withStatusExitCode */.AQ)({
27092
+ ...base,
27093
+ reachable: false,
27094
+ mode: err.mode,
27095
+ message
27096
+ });
27097
+ }
26822
27098
  // The endpoint answered; what it said does not match the pinned schema.
26823
27099
  if (err instanceof CodexProtocolError) return (0,_codex_contract_js__rspack_import_7/* .withStatusExitCode */.AQ)({
26824
27100
  ...base,
@@ -27204,6 +27480,16 @@ async function sendToCodexThreadInner(threadId, text, { env, envelope, whenBusy
27204
27480
  // the daemon exposes no compare-and-start request. Upgrade path: thread/queue/add + thread/queue/start once stable.
27205
27481
  // Scope refusals to this turn: server requests from earlier calls belong to another context.
27206
27482
  const seenRefused = client.refused.length;
27483
+ // Arm refusal replies only now that our own turn/start is in flight, scoped
27484
+ // to our own thread/turn: anything recorded earlier (resume, busy reject,
27485
+ // queue) — or naming another thread or Desktop turn — stays unanswered so
27486
+ // gbot never rejects another client's approval. Requests naming a turn
27487
+ // that arrives before the acknowledgment supplies our turn id wait in
27488
+ // client.deferred and are adopted only on a match. Requests missing
27489
+ // thread or turn IDs remain unanswered because ownership is unknown.
27490
+ client.expectedThreadId = threadId;
27491
+ client.expectedTurnId = null;
27492
+ client.answerServerRequests = true;
27207
27493
  let turn;
27208
27494
  try {
27209
27495
  turn = await client.request("turn/start", {
@@ -27238,7 +27524,17 @@ async function sendToCodexThreadInner(threadId, text, { env, envelope, whenBusy
27238
27524
  envelope
27239
27525
  });
27240
27526
  }
27241
- const freshRefused = client.refused.slice(seenRefused).filter((r)=>!r.params || r.params.threadId == null || r.params.threadId === threadId);
27527
+ client._adoptTurn(turnId);
27528
+ const freshRefused = client.refused.slice(seenRefused).filter((r)=>{
27529
+ if (!r.answered) return false;
27530
+ const params = r.params && typeof r.params === "object" && !Array.isArray(r.params) ? r.params : null;
27531
+ if (!params) return true;
27532
+ const refusedThreadId = params.threadId ?? params.thread_id;
27533
+ const refusedTurnId = params.turnId ?? params.turn_id ?? (params.turn && params.turn.id);
27534
+ if (refusedThreadId != null && refusedThreadId !== threadId) return false;
27535
+ if (refusedTurnId != null && refusedTurnId !== turnId) return false;
27536
+ return true;
27537
+ });
27242
27538
  if (freshRefused.length) {
27243
27539
  const methods = freshRefused.map((r)=>r.method).join(", ");
27244
27540
  throw new CodexSendError("Turn " + turnId + " started on thread " + threadId + " but Codex asked for " + methods + ", which gbot refused. " + "Answer it in a Codex client, or set `approval_policy = \"never\"` in the daemon's config.toml for unattended sends.", {
@@ -27474,6 +27770,548 @@ __webpack_require__.d(__webpack_exports__, {
27474
27770
  });
27475
27771
 
27476
27772
 
27773
+ },
27774
+ "./src/core/desktop-shim-bridge.js"(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
27775
+ // Stdio JSONL (Desktop) <-> WebSocket-over-unix bridge onto the managed
27776
+ // Codex app-server control socket. Vendored from uploads/codex-stdio-to-daemon-ws.py
27777
+ // (ChatGPT Desktop -> managed daemon shim) with fail-open deltas, marked below:
27778
+ // (1) ws_connect enforces one absolute monotonic deadline for connect + HTTP
27779
+ // upgrade (CODEX_BRIDGE_CONNECT_TIMEOUT, default 10s), requires HTTP/1.1 101
27780
+ // plus a matching Sec-WebSocket-Accept (a 200 fails even with a valid hash),
27781
+ // and retains bytes coalesced after the upgrade headers instead of discarding
27782
+ // the first data frame.
27783
+ // (2) read_message assembles continuation frames; control frames answer inline;
27784
+ // every socket send/lock shares one monotonic budget (remaining first-RPC
27785
+ // budget, else the mid-session write budget) so a non-reading peer cannot
27786
+ // wedge pong writes or timeout cleanup; timeouts close and exit. Reads wait
27787
+ // with no timeout after the handshake so healthy idle sessions survive.
27788
+ // The wrapper never starts the daemon on the spawn path, so no daemon lock can
27789
+ // block pre-stdin Desktop.
27790
+ // (3) A first-response deadline (CODEX_BRIDGE_FIRST_MESSAGE_TIMEOUT, default
27791
+ // 30s) bounds connect-to-matching-daemon-response covering the first RPC;
27792
+ // unrelated notifications and foreign responses never satisfy it, only the
27793
+ // matching response/error (or timeout) clears it. The preflight (3s) +
27794
+ // upgrade (10s) + first RPC (30s) budget stays far below a daemon-lock hang.
27795
+ // Exits are 0 served, 1 pre-stdio (wrapper falls back to real Codex on pristine
27796
+ // stdio+stdout), 2 mid-session (wrapper exits so Desktop reconnects).
27797
+ // (4) Byte-precise commit on input OR output, committed BEFORE the stdout
27798
+ // write: os.read into a userspace line buffer counts every stdin byte (even
27799
+ // an unforwarded blank line), and any daemon payload marks stdout used before
27800
+ // it is written via a bounded stdout write, so the fallback only ever runs
27801
+ // on truly pristine stdio+stdout and never spawns a second Codex after used
27802
+ // stdout. Buffered EOF-tail data flushes before the WS Close, which drains
27803
+ // via shutdown+close with a bounded join. The reader signals a done event so
27804
+ // WS close wakes the select loop; no path leaves a thread hung.
27805
+ // Desktop stdio hangs with stock `codex app-server proxy`, so this custom
27806
+ // bridge is required. Install writes it to the CODEX_HOME bin dir. Never reads
27807
+ // Desktop private pipes (CODEX_APP_TOOLS_PIPE_PATH, /tmp/codex-browser-use/).
27808
+ const BRIDGE_SOURCE = "#!/usr/bin/env python3\n\"\"\"Stdio JSONL (Desktop) <-> WebSocket-over-unix (managed app-server daemon).\nReversible: remove CODEX_CLI_PATH wrapper. Does not read Desktop private pipes.\n\"\"\"\nfrom __future__ import annotations\nimport base64, hashlib, json, os, select, socket, struct, sys, threading, time\n\nSOCK = os.environ.get(\n \"CODEX_APP_SERVER_SOCK\",\n os.path.expanduser(\"~/.codex/app-server-control/app-server-control.sock\"),\n)\nLOG = os.environ.get(\"CODEX_STDIO_BRIDGE_LOG\", os.path.expanduser(\"~/.codex/bin/codex-stdio-to-daemon-ws.log\"))\nWS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n\n# Wrapper contract: 0 served the session, 1 failed while stdio was still\n# pristine -- no stdin byte consumed and no stdout frame committed (wrapper\n# falls back to real Codex), 2 failed mid-session (wrapper exits promptly so\n# Desktop reconnects; the fallback must never run on half-consumed stdin or\n# dirty stdout).\nEXIT_SERVED = 0\nEXIT_PRE_STDIO = 1\nEXIT_MID_SESSION = 2\n\nCONNECT_TIMEOUT_S = float(os.environ.get(\"CODEX_BRIDGE_CONNECT_TIMEOUT\", \"10\"))\n# Bound from connect to the matching first daemon response: a daemon that\n# completes the upgrade but never answers the first RPC must not hold Desktop\n# past this. Only the matching initialize response/error (or the deadline\n# itself) clears it; notifications never do.\nFIRST_MESSAGE_TIMEOUT_S = float(os.environ.get(\"CODEX_BRIDGE_FIRST_MESSAGE_TIMEOUT\", \"30\"))\n# Bound on every mid-session socket send and stdout write: a wedged or\n# non-reading peer must not hang Desktop past this. Reads after a successful\n# init block with no timeout so healthy idle sessions survive silence.\nIO_TIMEOUT_S = float(os.environ.get(\"CODEX_BRIDGE_IO_TIMEOUT\", \"30\"))\n\ndef log(msg: str) -> None:\n try:\n with open(LOG, \"a\") as f:\n f.write(time.strftime(\"%Y-%m-%dT%H:%M:%SZ\", time.gmtime()) + \" \" + msg + \"\\n\")\n except OSError:\n pass\n\ndef ws_connect(path: str, timeout: float = CONNECT_TIMEOUT_S):\n \"\"\"Connect + HTTP upgrade under one absolute monotonic deadline.\n\n Returns (socket, trailing) where trailing holds bytes coalesced after the\n upgrade headers (TCP may deliver the 101 and the first WS frame together;\n discarding them would lose the first message). Requires HTTP/1.1 101 plus\n a matching Sec-WebSocket-Accept so a 200 (or any non-101) fails fast even\n when the accept hash happens to be valid.\n \"\"\"\n deadline = time.monotonic() + timeout\n s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n s.settimeout(timeout)\n try:\n s.connect(path)\n key = base64.b64encode(os.urandom(16)).decode()\n expected = base64.b64encode(hashlib.sha1((key + WS_GUID).encode()).digest()).decode()\n req = (\n f\"GET /rpc HTTP/1.1\\r\\nHost: localhost\\r\\nUpgrade: websocket\\r\\n\"\n f\"Connection: Upgrade\\r\\nSec-WebSocket-Key: {key}\\r\\nSec-WebSocket-Version: 13\\r\\n\\r\\n\"\n ).encode()\n s.settimeout(max(0.0, deadline - time.monotonic()))\n s.sendall(req)\n buf = b\"\"\n while True:\n if b\"\\r\\n\\r\\n\" in buf:\n break\n remaining = deadline - time.monotonic()\n if remaining <= 0:\n raise TimeoutError(\"WebSocket upgrade timed out\")\n if len(buf) > 65536:\n raise ConnectionError(\"WS upgrade headers too large\")\n s.settimeout(remaining)\n chunk = s.recv(4096)\n if not chunk:\n raise ConnectionError(\"daemon closed during WS upgrade\")\n buf += chunk\n head, _, trailing = buf.partition(b\"\\r\\n\\r\\n\")\n lines = head.split(b\"\\r\\n\")\n if not lines or lines[0].split(b\" \", 2)[:2] != [b\"HTTP/1.1\", b\"101\"]:\n raise ConnectionError(f\"WS upgrade failed: {head[:200]!r}\")\n accept = None\n for line in lines[1:]:\n name, sep, value = line.partition(b\":\")\n if sep and name.strip().lower() == b\"sec-websocket-accept\":\n accept = value.strip().decode(\"latin-1\")\n if accept != expected:\n raise ConnectionError(\"WS upgrade accept mismatch\")\n except Exception:\n try:\n s.close()\n except OSError:\n pass\n raise\n # Keep one socket mode across reader and writer threads. Reads wait\n # indefinitely in select; sends use their own absolute deadline.\n s.setblocking(False)\n return s, trailing\n\ndef mask_send(sock: socket.socket, payload: bytes, opcode: int = 1, deadline=None) -> None:\n mask = os.urandom(4)\n ln = len(payload)\n if ln < 126:\n hdr = bytes([0x80 | opcode, 0x80 | ln])\n elif ln < 65536:\n hdr = bytes([0x80 | opcode, 0x80 | 126]) + struct.pack(\"!H\", ln)\n else:\n hdr = bytes([0x80 | opcode, 0x80 | 127]) + struct.pack(\"!Q\", ln)\n view = memoryview(hdr + mask + bytes(b ^ mask[i % 4] for i, b in enumerate(payload)))\n while view:\n remaining = deadline - time.monotonic()\n if remaining <= 0:\n raise TimeoutError(\"socket write timed out\")\n _, writable, _ = select.select([], [sock], [], remaining)\n if not writable:\n raise TimeoutError(\"socket write timed out\")\n try:\n n = sock.send(view)\n except BlockingIOError:\n continue\n if not n:\n raise ConnectionError(\"socket closed during write\")\n view = view[n:]\n\ndef locked_send(sock: socket.socket, lock: threading.Lock, payload: bytes, opcode: int = 1, timeout: float = CONNECT_TIMEOUT_S) -> None:\n \"\"\"Serialized send sharing one monotonic budget across lock wait + send.\n\n A non-reading peer must never wedge pong writes or timeout cleanup: on\n expiry raise TimeoutError so the caller closes and exits without hanging.\n \"\"\"\n if timeout is None:\n timeout = CONNECT_TIMEOUT_S\n deadline = time.monotonic() + timeout\n if timeout <= 0:\n raise TimeoutError(\"send budget expired\")\n if not lock.acquire(timeout=timeout):\n raise TimeoutError(\"send lock timed out\")\n try:\n remaining = deadline - time.monotonic()\n if remaining <= 0:\n raise TimeoutError(\"send budget expired\")\n mask_send(sock, payload, opcode=opcode, deadline=deadline)\n finally:\n lock.release()\n\ndef _buffered_reader(sock: socket.socket, initial: bytes = b\"\"):\n buf = bytearray(initial)\n def recv(n):\n while len(buf) < n:\n select.select([sock], [], [])\n try:\n chunk = sock.recv(n - len(buf))\n except BlockingIOError:\n continue\n if not chunk:\n raise ConnectionError(\"eof\")\n buf.extend(chunk)\n out = bytes(buf[:n])\n del buf[:n]\n return out\n return recv\n\ndef read_frame(recv):\n hdr = recv(2)\n fin = bool(hdr[0] & 0x80)\n opcode = hdr[0] & 0x0F\n masked = bool(hdr[1] & 0x80)\n ln = hdr[1] & 0x7F\n if ln == 126:\n ln = struct.unpack(\"!H\", recv(2))[0]\n elif ln == 127:\n ln = struct.unpack(\"!Q\", recv(8))[0]\n mask = recv(4) if masked else b\"\"\n payload = recv(ln)\n if masked:\n payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))\n return fin, opcode, payload\n\ndef read_message(recv, send_pong):\n \"\"\"Next complete data message, assembling continuation frames.\n\n Control frames are handled inline (pong answered, close reported) so a\n fragmented message split around a ping still reassembles.\n \"\"\"\n opcode = None\n parts = []\n while True:\n fin, op, payload = read_frame(recv)\n if op == 0x8:\n return (\"close\", None, b\"\")\n if op == 0x9:\n send_pong(payload)\n continue\n if op == 0xA:\n continue\n if op in (0x1, 0x2):\n if opcode is not None:\n raise ConnectionError(\"data frame inside fragmented message\")\n if fin:\n return (\"data\", op, payload)\n opcode = op\n parts.append(payload)\n continue\n if op == 0x0:\n if opcode is None:\n raise ConnectionError(\"stray continuation frame\")\n parts.append(payload)\n if fin:\n return (\"data\", opcode, b\"\".join(parts))\n continue\n raise ConnectionError(f\"unsupported opcode {op}\")\n\ndef _is_daemon_response(payload: bytes, expect_id=None, known_ids=None) -> bool:\n \"\"\"True only for the matching JSON-RPC response/error, never a notification.\n\n The first-RPC timer must survive unrelated daemon notifications and foreign\n responses: only the response/error whose id matches the client's initialize\n (or, before it is known, any id the client already sent) proves the first\n RPC got an answer. Timeouts clear it via the deadline, never via data.\n \"\"\"\n try:\n text = payload.decode(\"utf-8\").strip()\n msg = json.loads(text)\n except Exception:\n return False\n if not isinstance(msg, dict):\n return False\n rid = msg.get(\"id\")\n if rid is None or \"method\" in msg or (\"result\" not in msg and \"error\" not in msg):\n return False\n if expect_id is not None:\n return rid == expect_id\n if known_ids:\n return rid in known_ids\n return False\n\ndef _bounded_stdout_write(data: bytes, timeout: float = IO_TIMEOUT_S) -> None:\n \"\"\"Write one daemon frame to stdout without hanging on a non-reading peer.\n\n Waits for writability under the mid-session write budget and writes via\n os.write in a loop, so a Desktop that stopped reading cannot hang the\n bridge and then exit 1 on dirty stdout.\n \"\"\"\n if timeout is None:\n timeout = IO_TIMEOUT_S\n if timeout <= 0:\n raise TimeoutError(\"stdout budget expired\")\n fd = sys.stdout.fileno()\n deadline = time.monotonic() + timeout\n was_blocking = os.get_blocking(fd)\n os.set_blocking(fd, False)\n try:\n view = memoryview(data)\n while view:\n remaining = deadline - time.monotonic()\n if remaining <= 0:\n raise TimeoutError(\"stdout write timed out\")\n _, writable, _ = select.select([], [fd], [], remaining)\n if not writable:\n raise TimeoutError(\"stdout write timed out\")\n try:\n n = os.write(fd, view)\n except BlockingIOError:\n continue\n view = view[n:]\n finally:\n os.set_blocking(fd, was_blocking)\n\ndef stdout_writer(sock: socket.socket, recv, send_pong, done: threading.Event, first_msg: threading.Event, output_forwarded: threading.Event, client_ids=None, init_state=None) -> None:\n try:\n while True:\n kind, opcode, payload = read_message(recv, send_pong)\n if kind == \"close\":\n log(\"daemon WS close\")\n return\n if opcode == 0x1:\n # Desktop StdioConnection expects newline-delimited JSON text\n if not payload.endswith(b\"\\n\"):\n payload += b\"\\n\"\n # Commit BEFORE writing: once a daemon byte is about to hit\n # stdout, the session is dirty and the fallback must never run,\n # even if the write itself blocks or fails.\n output_forwarded.set()\n _bounded_stdout_write(payload)\n if opcode == 0x1:\n expect = (init_state or {}).get(\"initialize_id\") if isinstance(init_state, dict) else None\n if _is_daemon_response(payload, expect, client_ids or ()):\n first_msg.set()\n except Exception as e:\n log(f\"stdout_writer exit: {e}\")\n finally:\n done.set()\n\ndef main() -> int:\n log(f\"start sock={SOCK}\")\n try:\n sock, pending = ws_connect(SOCK)\n except Exception as e:\n log(f\"connect failed: {e}\")\n sys.stderr.write(f\"codex-stdio-to-daemon-ws: {e}\\n\")\n return EXIT_PRE_STDIO\n log(\"connected\")\n connected_at = time.monotonic()\n first_deadline = connected_at + FIRST_MESSAGE_TIMEOUT_S\n send_lock = threading.Lock()\n done = threading.Event()\n first_msg = threading.Event()\n output_forwarded = threading.Event()\n client_ids = set()\n init_state = {\"initialize_id\": None}\n\n def _send_timeout() -> float:\n # Before the first matching daemon response the remaining first-RPC\n # budget bounds every send; after it each send gets the mid-session\n # write budget. Idle reads are never timed out.\n if not first_msg.is_set():\n return max(0.0, first_deadline - time.monotonic())\n return IO_TIMEOUT_S\n\n def _cleanup_timeout() -> float:\n # Timeout cleanup stays within the expired session budget: once the\n # first-RPC deadline has passed, close directly instead of opening\n # fresh windows that let a short budget take several timeout periods.\n if not first_msg.is_set():\n return max(0.0, first_deadline - time.monotonic())\n return CONNECT_TIMEOUT_S\n\n def send_pong(payload: bytes) -> None:\n locked_send(sock, send_lock, payload, opcode=0xA, timeout=_send_timeout())\n recv = _buffered_reader(sock, pending)\n t = threading.Thread(target=stdout_writer, args=(sock, recv, send_pong, done, first_msg, output_forwarded, client_ids, init_state), daemon=True)\n t.start()\n # Byte-precise commit point: the fallback may run only while zero stdin\n # bytes have left the pipe and no daemon payload was committed to stdout.\n # Reads use os.read into a userspace line buffer (buffered readline could\n # readahead past the commit point), and every byte read counts -- even a\n # blank line. first_msg tracks the matching daemon response/error, never\n # a notification.\n stdin_bytes = 0\n pending_in = b\"\"\n clean_eof = False\n try:\n fd = sys.stdin.fileno()\n while not done.is_set():\n if not first_msg.is_set() and time.monotonic() >= first_deadline:\n log(\"first daemon message timed out\")\n break\n ready, _, _ = select.select([fd], [], [], 0.5)\n if not ready:\n continue\n chunk = os.read(fd, 65536)\n if not chunk:\n clean_eof = True\n break\n stdin_bytes += len(chunk)\n pending_in += chunk\n while b\"\\n\" in pending_in:\n line, _, pending_in = pending_in.partition(b\"\\n\")\n line = line.strip()\n if not line:\n continue\n try:\n try:\n obj = json.loads(line.decode(\"utf-8\"))\n except Exception:\n obj = None\n if isinstance(obj, dict) and obj.get(\"id\") is not None:\n client_ids.add(obj.get(\"id\"))\n if obj.get(\"method\") == \"initialize\" and init_state[\"initialize_id\"] is None:\n init_state[\"initialize_id\"] = obj.get(\"id\")\n except Exception:\n pass\n try:\n locked_send(sock, send_lock, line, timeout=_send_timeout())\n except TimeoutError as e:\n log(f\"stdin forward timed out: {e}\")\n break\n except Exception as e:\n log(f\"stdin forward failed: {e}\")\n break\n else:\n continue\n break\n except Exception as e:\n log(f\"stdin loop exit: {e}\")\n # Flush any buffered EOF-tail data BEFORE the WS Close: sending Close\n # first would let the daemon discard the tail.\n if clean_eof:\n tail = pending_in.strip()\n if tail and not done.is_set():\n try:\n locked_send(sock, send_lock, tail, timeout=_cleanup_timeout())\n except Exception as e:\n log(f\"eof flush failed: {e}\")\n try:\n locked_send(sock, send_lock, b\"\", opcode=0x8, timeout=_cleanup_timeout())\n except Exception as e:\n log(f\"close send failed: {e}\")\n try:\n try:\n sock.shutdown(socket.SHUT_RDWR)\n except OSError:\n pass\n sock.close()\n except OSError:\n pass\n # Bound termination: wake the reader so no thread is left hung on recv.\n t.join(timeout=0.5)\n # A reader still exiting could commit output after this check. In that\n # case pristine stdio cannot be proven, so never permit fallback.\n if t.is_alive():\n output_forwarded.set()\n if clean_eof:\n log(\"exit\")\n return EXIT_SERVED\n # Reader died, first RPC timed out, post-upgrade I/O timed out, or stdin\n # broke: fail open only while stdio is still pristine -- no stdin byte\n # ever consumed AND no daemon payload ever committed to stdout. Committed\n # output forces a mid-session exit so Desktop reconnects instead of\n # falling back to stock Codex on a corrupted stream.\n rc = EXIT_MID_SESSION if (stdin_bytes or output_forwarded.is_set()) else EXIT_PRE_STDIO\n log(f\"exit rc={rc}\")\n return rc\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n";
27809
+
27810
+ __webpack_require__.d(__webpack_exports__, {
27811
+ }, {
27812
+ w: BRIDGE_SOURCE
27813
+ });
27814
+
27815
+
27816
+ },
27817
+ "./src/core/desktop-shim.js"(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
27818
+ /* import */ var node_child_process__rspack_import_0 = __webpack_require__("node:child_process");
27819
+ /* import */ var node_fs__rspack_import_1 = __webpack_require__("node:fs");
27820
+ /* import */ var node_os__rspack_import_2 = __webpack_require__("node:os");
27821
+ /* import */ var node_path__rspack_import_3 = __webpack_require__("node:path");
27822
+ /* import */ var _desktop_shim_bridge_js__rspack_import_4 = __webpack_require__("./src/core/desktop-shim-bridge.js");
27823
+
27824
+
27825
+
27826
+
27827
+
27828
+ /**
27829
+ * ChatGPT Desktop -> managed Codex daemon shim (CODEX_CLI_PATH bridge).
27830
+ *
27831
+ * Desktop injects `codex_app` overrides so CODEX_APP_SERVER_USE_LOCAL_DAEMON=1
27832
+ * alone cannot select the managed daemon, and stock `codex app-server proxy`
27833
+ * hangs for Desktop stdio. The winning path is a wrapper on CODEX_CLI_PATH
27834
+ * that rewrites Desktop's `codex ... app-server` spawn into the stdio<->WS
27835
+ * bridge (desktop-shim-bridge.js) onto the managed control socket.
27836
+ * Provenance: uploads/codex-desktop-to-daemon + codex-desktop-shared-daemon-env.sh.
27837
+ *
27838
+ * Fail-open is the hard rule: the wrapper execs the real standalone `codex`
27839
+ * for non-app-server spawns and whenever the bridge/python/socket side is
27840
+ * missing or broken. Never touches Desktop binaries, /tmp/codex-browser-use/,
27841
+ * or CODEX_APP_TOOLS_PIPE_PATH. No protocol change: gbot already speaks the
27842
+ * managed control socket.
27843
+ */ const SHIM_LABEL = "com.zackjackson.codex-desktop-shared-daemon";
27844
+ const WRAPPER_FILENAME = "codex-desktop-to-daemon";
27845
+ const BRIDGE_FILENAME = "codex-stdio-to-daemon-ws.py";
27846
+ const ENV_SCRIPT_FILENAME = "codex-desktop-shared-daemon-env.sh";
27847
+ const BRIDGE_LOG_FILENAME = "codex-stdio-to-daemon-ws.log";
27848
+ const WRAPPER_LOG_FILENAME = "codex-desktop-to-daemon.log";
27849
+ const ENV_LOG_FILENAME = "codex-desktop-shared-daemon-env.log";
27850
+ const STANDALONE_REAL_SUFFIX = (0,node_path__rspack_import_3.join)("packages", "standalone", "current", "bin", "codex");
27851
+ function codexHomeDir(env = process.env) {
27852
+ return env.CODEX_HOME || join(env.HOME || homedir(), ".codex");
27853
+ }
27854
+ function userHomeDir(env = process.env) {
27855
+ return env.HOME || (0,node_os__rspack_import_2.homedir)();
27856
+ }
27857
+ function defaultPaths({ env = process.env, home = userHomeDir(env) } = {}) {
27858
+ const codexHome = env.CODEX_HOME || (0,node_path__rspack_import_3.join)(home, ".codex");
27859
+ const binDir = (0,node_path__rspack_import_3.join)(codexHome, "bin");
27860
+ return {
27861
+ binDir,
27862
+ bridgeLogPath: (0,node_path__rspack_import_3.join)(binDir, BRIDGE_LOG_FILENAME),
27863
+ bridgePath: (0,node_path__rspack_import_3.join)(binDir, BRIDGE_FILENAME),
27864
+ codexHome,
27865
+ envLogPath: (0,node_path__rspack_import_3.join)(binDir, ENV_LOG_FILENAME),
27866
+ envScriptPath: (0,node_path__rspack_import_3.join)(binDir, ENV_SCRIPT_FILENAME),
27867
+ label: SHIM_LABEL,
27868
+ launchAgentsDir: (0,node_path__rspack_import_3.join)(home, "Library", "LaunchAgents"),
27869
+ plistPath: (0,node_path__rspack_import_3.join)(home, "Library", "LaunchAgents", `${SHIM_LABEL}.plist`),
27870
+ realPath: (0,node_path__rspack_import_3.join)(codexHome, STANDALONE_REAL_SUFFIX),
27871
+ socketPath: (0,node_path__rspack_import_3.join)(codexHome, "app-server-control", "app-server-control.sock"),
27872
+ wrapperLogPath: (0,node_path__rspack_import_3.join)(binDir, WRAPPER_LOG_FILENAME),
27873
+ wrapperPath: (0,node_path__rspack_import_3.join)(binDir, WRAPPER_FILENAME)
27874
+ };
27875
+ }
27876
+ /**
27877
+ * Pure passthrough decision, mirroring the wrapper's argv scan. True only for
27878
+ * a bare `app-server` spawn: Desktop's private-stdio case. `daemon`, `proxy`,
27879
+ * and schema generations always exec the real Codex.
27880
+ */ function shouldBridge(argv) {
27881
+ let appServer = false;
27882
+ let daemon = false;
27883
+ let proxy = false;
27884
+ let generate = false;
27885
+ for (const arg of argv ?? []){
27886
+ if (arg === "app-server") appServer = true;
27887
+ else if (arg === "daemon") daemon = true;
27888
+ else if (arg === "proxy") proxy = true;
27889
+ else if (arg === "generate-ts" || arg === "generate-json-schema") generate = true;
27890
+ }
27891
+ return appServer && !daemon && !proxy && !generate;
27892
+ }
27893
+ function renderWrapperScript({ realPath, bridgePath, wrapperLogPath, codexHome, bridgeLogPath }) {
27894
+ return `#!/bin/bash
27895
+ # Installed by \`gbot codex desktop-shim install\`. Rewrites ChatGPT Desktop's
27896
+ # \`codex ... app-server\` spawn into a stdio<->WebSocket bridge onto the managed
27897
+ # daemon's control socket. Reversible: \`gbot codex desktop-shim uninstall\`.
27898
+ # The daemon is kept up by the LaunchAgent login script, not here: this hot path
27899
+ # never starts the daemon itself, so a wedged daemon lock cannot hang
27900
+ # Desktop. Fail-open runs ONLY while stdio is still pristine (no stdin
27901
+ # consumed, no stdout written): an absent socket fails the preflight and a
27902
+ # pre-session bridge failure (exit 1) falls through
27903
+ # to the real standalone codex on pristine stdio. A mid-session bridge failure
27904
+ # (exit 2+) exits promptly so Desktop reconnects; the fallback never runs on
27905
+ # half-consumed stdin or dirty stdout. Bridge exit 0 means it served the session. Never touches
27906
+ # Desktop binaries or its private tool pipe.
27907
+ set -u
27908
+ REAL="\${CODEX_DESKTOP_WRAPPER_REAL:-${realPath}}"
27909
+ BRIDGE="\${CODEX_DESKTOP_BRIDGE:-${bridgePath}}"
27910
+ export CODEX_HOME="\${CODEX_HOME:-${codexHome}}"
27911
+ SOCK="\${CODEX_APP_SERVER_SOCK:-$CODEX_HOME/app-server-control/app-server-control.sock}"
27912
+ BRIDGE_LOG="\${CODEX_STDIO_BRIDGE_LOG:-${bridgeLogPath}}"
27913
+ LOG="\${CODEX_DESKTOP_WRAPPER_LOG:-${wrapperLogPath}}"
27914
+ export CODEX_APP_SERVER_SOCK="$SOCK"
27915
+ export CODEX_STDIO_BRIDGE_LOG="$BRIDGE_LOG"
27916
+ ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
27917
+ echo "$(ts) argv: $*" >>"$LOG" 2>/dev/null || true
27918
+
27919
+ if [[ ! -x "$REAL" ]]; then
27920
+ FALLBACK="$(command -v codex 2>/dev/null || true)"
27921
+ if [[ -n "$FALLBACK" && -x "$FALLBACK" ]]; then
27922
+ if [[ "$FALLBACK" -ef "$0" ]] 2>/dev/null || { [[ -n "\${CODEX_CLI_PATH:-}" ]] && [[ "$FALLBACK" -ef "\${CODEX_CLI_PATH}" ]] 2>/dev/null; }; then
27923
+ echo "$(ts) refusing self fallback $FALLBACK" >>"$LOG" 2>/dev/null || true
27924
+ else
27925
+ REAL="$FALLBACK"
27926
+ fi
27927
+ fi
27928
+ fi
27929
+
27930
+ has_app_server=0; has_daemon=0; has_proxy=0; has_generate=0
27931
+ for a in "$@"; do
27932
+ case "$a" in
27933
+ app-server) has_app_server=1 ;;
27934
+ daemon) has_daemon=1 ;;
27935
+ proxy) has_proxy=1 ;;
27936
+ generate-ts|generate-json-schema) has_generate=1 ;;
27937
+ esac
27938
+ done
27939
+
27940
+ # Preflight the daemon socket with a short deadline: a refused/absent socket
27941
+ # means starting the bridge could never succeed, so skip straight to real codex.
27942
+ preflight() {
27943
+ python3 -c 'import os,socket,sys; s=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM); s.settimeout(float(os.environ.get("CODEX_DESKTOP_PREFLIGHT_TIMEOUT","3"))); s.connect(sys.argv[1]); s.close()' "$SOCK" 2>/dev/null
27944
+ }
27945
+
27946
+ if [[ "$has_app_server" -eq 1 && "$has_daemon" -eq 0 && "$has_proxy" -eq 0 && "$has_generate" -eq 0 ]]; then
27947
+ if [[ -f "$BRIDGE" ]] && command -v python3 >/dev/null 2>&1; then
27948
+ if preflight; then
27949
+ echo "$(ts) rewrite -> stdio-ws bridge" >>"$LOG" 2>/dev/null || true
27950
+ python3 "$BRIDGE"
27951
+ rc=$?
27952
+ case "$rc" in
27953
+ 0)
27954
+ echo "$(ts) bridge served session" >>"$LOG" 2>/dev/null || true
27955
+ exit 0
27956
+ ;;
27957
+ 1)
27958
+ echo "$(ts) bridge failed before session start, falling through to real codex" >>"$LOG" 2>/dev/null || true
27959
+ ;;
27960
+ *)
27961
+ echo "$(ts) bridge failed mid-session (rc=$rc); exiting so Desktop reconnects" >>"$LOG" 2>/dev/null || true
27962
+ exit "$rc"
27963
+ ;;
27964
+ esac
27965
+ else
27966
+ echo "$(ts) daemon socket unreachable, passthrough to real codex" >>"$LOG" 2>/dev/null || true
27967
+ fi
27968
+ else
27969
+ echo "$(ts) bridge unavailable, passthrough to real codex" >>"$LOG" 2>/dev/null || true
27970
+ fi
27971
+ fi
27972
+ if [[ -x "$REAL" ]]; then
27973
+ exec "$REAL" "$@"
27974
+ fi
27975
+ echo "codex-desktop-to-daemon: no runnable codex found" >&2
27976
+ exit 1
27977
+ `;
27978
+ }
27979
+ function renderEnvScript({ wrapperPath, realPath, envLogPath, codexHome }) {
27980
+ return `#!/bin/bash
27981
+ # Installed by \`gbot codex desktop-shim install\`. Re-applies the GUI-domain env
27982
+ # so ChatGPT.app inherits CODEX_CLI_PATH after login, then best-effort starts the
27983
+ # managed daemon (this login script — never Desktop's spawn hot path — owns
27984
+ # daemon upkeep). No -e: every step is best-effort so login never breaks.
27985
+ # Revert: \`gbot codex desktop-shim uninstall\`.
27986
+ set -u
27987
+ WRAPPER="${wrapperPath}"
27988
+ REAL="${realPath}"
27989
+ LOG="${envLogPath}"
27990
+ CODEX_HOME_DIR="${codexHome}"
27991
+ # One CODEX_HOME for the GUI domain and this script's own daemon start, so the
27992
+ # daemon that is kept up is the one the wrapper bridges to.
27993
+ export CODEX_HOME="$CODEX_HOME_DIR"
27994
+ ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
27995
+ {
27996
+ echo "$(ts) start"
27997
+ if [[ ! -x "$WRAPPER" ]]; then
27998
+ echo "$(ts) missing wrapper: $WRAPPER"
27999
+ exit 0
28000
+ fi
28001
+ # GUI domain setenv so ChatGPT.app inherits CODEX_CLI_PATH after login
28002
+ launchctl setenv CODEX_CLI_PATH "$WRAPPER"
28003
+ launchctl setenv CODEX_HOME "$CODEX_HOME_DIR"
28004
+ launchctl setenv CODEX_APP_SERVER_USE_LOCAL_DAEMON 1
28005
+ launchctl unsetenv CODEX_APP_SERVER_WS_URL 2>/dev/null || true
28006
+ echo "$(ts) CODEX_CLI_PATH=$(launchctl getenv CODEX_CLI_PATH)"
28007
+ if [[ -x "$REAL" ]]; then
28008
+ "$REAL" app-server daemon start >/dev/null 2>&1 || true
28009
+ echo "$(ts) daemon start attempted"
28010
+ fi
28011
+ echo "$(ts) done"
28012
+ } >>"$LOG" 2>&1
28013
+ `;
28014
+ }
28015
+ function renderPlist({ envScriptPath, label = SHIM_LABEL }) {
28016
+ return `<?xml version="1.0" encoding="UTF-8"?>
28017
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
28018
+ <plist version="1.0">
28019
+ <dict>
28020
+ \t<key>Label</key>
28021
+ \t<string>${label}</string>
28022
+ \t<key>ProgramArguments</key>
28023
+ \t<array>
28024
+ \t\t<string>/bin/bash</string>
28025
+ \t\t<string>${envScriptPath}</string>
28026
+ \t</array>
28027
+ \t<key>RunAtLoad</key>
28028
+ \t<true/>
28029
+ \t<key>LimitLoadToSessionType</key>
28030
+ \t<string>Aqua</string>
28031
+ </dict>
28032
+ </plist>
28033
+ `;
28034
+ }
28035
+ const defaultRunner = (file, args)=>{
28036
+ try {
28037
+ return (0,node_child_process__rspack_import_0.spawnSync)(file, args, {
28038
+ encoding: "utf8",
28039
+ timeout: 15000
28040
+ });
28041
+ } catch (error) {
28042
+ return {
28043
+ error,
28044
+ status: null
28045
+ };
28046
+ }
28047
+ };
28048
+ function runBestEffort(runner, warnings, what, file, args) {
28049
+ const out = runner(file, args);
28050
+ if (out && (out.error || typeof out.status === "number" && out.status !== 0)) {
28051
+ const detail = out.error && out.error.message || out.stderr && String(out.stderr).trim() || `exit ${out.status}`;
28052
+ warnings.push(`${what} failed (${detail}); continuing`);
28053
+ }
28054
+ return out;
28055
+ }
28056
+ function writeExecutable(path, content) {
28057
+ (0,node_fs__rspack_import_1.writeFileSync)(path, content, {
28058
+ mode: 493
28059
+ });
28060
+ (0,node_fs__rspack_import_1.chmodSync)(path, 493);
28061
+ }
28062
+ function isExecutable(path) {
28063
+ try {
28064
+ (0,node_fs__rspack_import_1.statSync)(path);
28065
+ } catch {
28066
+ return false;
28067
+ }
28068
+ try {
28069
+ // X_OK without throwing: accessSync is clearer; stat mode bit check is sync-safe.
28070
+ return ((0,node_fs__rspack_import_1.statSync)(path).mode & 73) !== 0;
28071
+ } catch {
28072
+ return false;
28073
+ }
28074
+ }
28075
+ function fileExists(path) {
28076
+ try {
28077
+ (0,node_fs__rspack_import_1.statSync)(path);
28078
+ return true;
28079
+ } catch {
28080
+ return false;
28081
+ }
28082
+ }
28083
+ /**
28084
+ * Copy the wrapper + bridge + login env script out of the package into durable
28085
+ * ~/.codex paths (never a git checkout), then persist CODEX_CLI_PATH for the
28086
+ * macOS Aqua session via LaunchAgent. Idempotent.
28087
+ */ function installDesktopShim({ env = process.env, home = userHomeDir(env), platform = process.platform, runner = defaultRunner } = {}) {
28088
+ const paths = defaultPaths({
28089
+ env,
28090
+ home
28091
+ });
28092
+ const warnings = [];
28093
+ (0,node_fs__rspack_import_1.mkdirSync)(paths.binDir, {
28094
+ recursive: true
28095
+ });
28096
+ writeExecutable(paths.wrapperPath, renderWrapperScript(paths));
28097
+ (0,node_fs__rspack_import_1.writeFileSync)(paths.bridgePath, _desktop_shim_bridge_js__rspack_import_4/* .BRIDGE_SOURCE.endsWith */.w.endsWith("\n") ? _desktop_shim_bridge_js__rspack_import_4/* .BRIDGE_SOURCE */.w : `${_desktop_shim_bridge_js__rspack_import_4/* .BRIDGE_SOURCE */.w}\n`, {
28098
+ mode: 420
28099
+ });
28100
+ writeExecutable(paths.envScriptPath, renderEnvScript(paths));
28101
+ let persisted = false;
28102
+ if (platform === "darwin") {
28103
+ (0,node_fs__rspack_import_1.mkdirSync)(paths.launchAgentsDir, {
28104
+ recursive: true
28105
+ });
28106
+ (0,node_fs__rspack_import_1.writeFileSync)(paths.plistPath, renderPlist({
28107
+ envScriptPath: paths.envScriptPath
28108
+ }), {
28109
+ mode: 420
28110
+ });
28111
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
28112
+ if (uid === null || uid === undefined) {
28113
+ warnings.push("could not determine uid; LaunchAgent file written but not bootstrapped — log out/in or bootstrap it manually");
28114
+ } else {
28115
+ const target = `gui/${uid}/${paths.label}`;
28116
+ runBestEffort(runner, warnings, "launchctl bootout (previous agent)", "launchctl", [
28117
+ "bootout",
28118
+ target
28119
+ ]);
28120
+ runBestEffort(runner, warnings, "launchctl bootstrap", "launchctl", [
28121
+ "bootstrap",
28122
+ `gui/${uid}`,
28123
+ paths.plistPath
28124
+ ]);
28125
+ }
28126
+ runBestEffort(runner, warnings, "launchctl setenv CODEX_CLI_PATH", "launchctl", [
28127
+ "setenv",
28128
+ "CODEX_CLI_PATH",
28129
+ paths.wrapperPath
28130
+ ]);
28131
+ runBestEffort(runner, warnings, "launchctl setenv CODEX_APP_SERVER_USE_LOCAL_DAEMON", "launchctl", [
28132
+ "setenv",
28133
+ "CODEX_APP_SERVER_USE_LOCAL_DAEMON",
28134
+ "1"
28135
+ ]);
28136
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_APP_SERVER_WS_URL", "launchctl", [
28137
+ "unsetenv",
28138
+ "CODEX_APP_SERVER_WS_URL"
28139
+ ]);
28140
+ persisted = true;
28141
+ } else {
28142
+ warnings.push("LaunchAgent persistence is macOS-only; wrapper and bridge installed — export CODEX_CLI_PATH manually");
28143
+ }
28144
+ if (isExecutable(paths.realPath)) {
28145
+ runBestEffort(runner, warnings, "codex app-server daemon start", paths.realPath, [
28146
+ "app-server",
28147
+ "daemon",
28148
+ "start"
28149
+ ]);
28150
+ } else {
28151
+ warnings.push(`managed daemon not started: no executable at ${paths.realPath}`);
28152
+ }
28153
+ return {
28154
+ action: "install",
28155
+ bridgePath: paths.bridgePath,
28156
+ envScriptPath: paths.envScriptPath,
28157
+ exitCode: 0,
28158
+ persisted,
28159
+ plistPath: platform === "darwin" ? paths.plistPath : null,
28160
+ realPath: paths.realPath,
28161
+ warnings,
28162
+ wrapperPath: paths.wrapperPath
28163
+ };
28164
+ }
28165
+ /** Remove what install wrote and unset the GUI env; Desktop/Codex fall back to stock. */ function uninstallDesktopShim({ env = process.env, home = userHomeDir(env), platform = process.platform, runner = defaultRunner } = {}) {
28166
+ const paths = defaultPaths({
28167
+ env,
28168
+ home
28169
+ });
28170
+ const warnings = [];
28171
+ const removed = [];
28172
+ for (const path of [
28173
+ paths.wrapperPath,
28174
+ paths.bridgePath,
28175
+ paths.envScriptPath
28176
+ ]){
28177
+ const existed = fileExists(path);
28178
+ try {
28179
+ if ((0,node_fs__rspack_import_1.rmSync)(path, {
28180
+ force: true
28181
+ }) === undefined && fileExists(path)) warnings.push(`could not remove ${path}`);
28182
+ else if (existed && !fileExists(path)) removed.push(path);
28183
+ } catch (error) {
28184
+ warnings.push(`could not remove ${path} (${error instanceof Error ? error.message : String(error)})`);
28185
+ }
28186
+ }
28187
+ let plistRemoved = false;
28188
+ if (platform === "darwin") {
28189
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
28190
+ if (uid !== null && uid !== undefined) {
28191
+ runBestEffort(runner, warnings, "launchctl bootout", "launchctl", [
28192
+ "bootout",
28193
+ `gui/${uid}/${paths.label}`
28194
+ ]);
28195
+ }
28196
+ try {
28197
+ (0,node_fs__rspack_import_1.rmSync)(paths.plistPath, {
28198
+ force: true
28199
+ });
28200
+ plistRemoved = !fileExists(paths.plistPath);
28201
+ } catch (error) {
28202
+ warnings.push(`could not remove ${paths.plistPath} (${error instanceof Error ? error.message : String(error)})`);
28203
+ }
28204
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_CLI_PATH", "launchctl", [
28205
+ "unsetenv",
28206
+ "CODEX_CLI_PATH"
28207
+ ]);
28208
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_HOME", "launchctl", [
28209
+ "unsetenv",
28210
+ "CODEX_HOME"
28211
+ ]);
28212
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_APP_SERVER_USE_LOCAL_DAEMON", "launchctl", [
28213
+ "unsetenv",
28214
+ "CODEX_APP_SERVER_USE_LOCAL_DAEMON"
28215
+ ]);
28216
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_APP_SERVER_WS_URL", "launchctl", [
28217
+ "unsetenv",
28218
+ "CODEX_APP_SERVER_WS_URL"
28219
+ ]);
28220
+ }
28221
+ return {
28222
+ action: "uninstall",
28223
+ exitCode: 0,
28224
+ plistPath: platform === "darwin" ? paths.plistPath : null,
28225
+ plistRemoved,
28226
+ removed,
28227
+ warnings,
28228
+ wrapperPath: paths.wrapperPath
28229
+ };
28230
+ }
28231
+ function socketStateOf(path) {
28232
+ try {
28233
+ return (0,node_fs__rspack_import_1.statSync)(path).isSocket() ? "socket" : "not-a-socket";
28234
+ } catch (error) {
28235
+ return error && (error.code === "EACCES" || error.code === "EPERM") ? "permission-denied" : "absent";
28236
+ }
28237
+ }
28238
+ /**
28239
+ * Inspect the shim without touching Desktop, pipes, or sockets beyond a stat.
28240
+ * On macOS the Desktop-facing CODEX_CLI_PATH lives in launchd's GUI domain,
28241
+ * which a shell-spawned `gbot` does not inherit — so status queries
28242
+ * `launchctl getenv` there (via the injectable runner) and reports the caller
28243
+ * environment separately. Elsewhere the process env is the whole story.
28244
+ */ function desktopShimStatus({ env = process.env, home = userHomeDir(env), platform = process.platform, runner = defaultRunner } = {}) {
28245
+ const paths = defaultPaths({
28246
+ env,
28247
+ home
28248
+ });
28249
+ const warnings = [];
28250
+ const wrapperPresent = fileExists(paths.wrapperPath);
28251
+ const bridgePresent = fileExists(paths.bridgePath);
28252
+ const envScriptPresent = fileExists(paths.envScriptPath);
28253
+ const plistPresent = platform === "darwin" ? fileExists(paths.plistPath) : false;
28254
+ const cliPath = env.CODEX_CLI_PATH || null;
28255
+ let guiCliPath = null;
28256
+ if (platform === "darwin") {
28257
+ try {
28258
+ const out = runner("launchctl", [
28259
+ "getenv",
28260
+ "CODEX_CLI_PATH"
28261
+ ]);
28262
+ const value = out && typeof out.stdout === "string" ? out.stdout.trim() : "";
28263
+ guiCliPath = value || null;
28264
+ if (out && (out.error || typeof out.status === "number" && out.status !== 0)) {
28265
+ warnings.push("launchctl getenv CODEX_CLI_PATH failed; GUI-domain state unknown");
28266
+ guiCliPath = null;
28267
+ }
28268
+ } catch (error) {
28269
+ warnings.push(`launchctl getenv CODEX_CLI_PATH failed (${error instanceof Error ? error.message : String(error)})`);
28270
+ guiCliPath = null;
28271
+ }
28272
+ }
28273
+ // Desktop-facing value: the GUI domain on macOS, the caller env elsewhere.
28274
+ const desktopCliPath = platform === "darwin" ? guiCliPath : cliPath;
28275
+ const installed = wrapperPresent && isExecutable(paths.wrapperPath) && bridgePresent;
28276
+ // Effective socket, resolved exactly like gbot's own client (CODEX_APP_SERVER_SOCK
28277
+ // wins, else the install-time CODEX_HOME) so status and send/queue/status never
28278
+ // disagree. Inlined here so codex-bridge can reuse desktopShimStatus without a cycle.
28279
+ const socketPath = env.CODEX_APP_SERVER_SOCK || (0,node_path__rspack_import_3.join)(paths.codexHome, "app-server-control", "app-server-control.sock");
28280
+ return {
28281
+ action: "status",
28282
+ bridgePath: paths.bridgePath,
28283
+ bridgePresent,
28284
+ cliPath,
28285
+ codexHome: paths.codexHome,
28286
+ desktopCliPath,
28287
+ envScriptPath: paths.envScriptPath,
28288
+ envScriptPresent,
28289
+ exitCode: installed ? 0 : 1,
28290
+ guiCliPath,
28291
+ installed,
28292
+ persisted: platform === "darwin" ? plistPresent : null,
28293
+ platform,
28294
+ plistPath: platform === "darwin" ? paths.plistPath : null,
28295
+ plistPresent,
28296
+ realPath: paths.realPath,
28297
+ socketPath,
28298
+ socketSource: env.CODEX_APP_SERVER_SOCK ? "CODEX_APP_SERVER_SOCK" : "CODEX_HOME",
28299
+ socketState: socketStateOf(socketPath),
28300
+ warnings,
28301
+ wrapperExecutable: wrapperPresent && isExecutable(paths.wrapperPath),
28302
+ wrapperPath: paths.wrapperPath,
28303
+ wrapperPointsAtShim: desktopCliPath === paths.wrapperPath,
28304
+ wrapperPresent
28305
+ };
28306
+ }
28307
+
28308
+ __webpack_require__.d(__webpack_exports__, {
28309
+ B0: () => (installDesktopShim),
28310
+ em: () => (desktopShimStatus),
28311
+ gM: () => (uninstallDesktopShim)
28312
+ });
28313
+
28314
+
27477
28315
  },
27478
28316
  "./src/core/format.js"(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
27479
28317
  /* import */ var _transcript_js__rspack_import_0 = __webpack_require__("./src/core/transcript.js");
@@ -27546,6 +28384,7 @@ function formatCodexStatus(s) {
27546
28384
  const lines = [
27547
28385
  "socket: " + s.socketPath + " (" + s.socketState + ")"
27548
28386
  ];
28387
+ if (s.desktopShimConfigured) lines.push("desktop shim: configured (managed attachment unverified)");
27549
28388
  if (!s.reachable) return lines.concat("reachable: no (" + s.mode + ")", s.message).join("\n");
27550
28389
  if (s.mode !== "daemon") {
27551
28390
  return lines.concat("reachable: yes, but unusable (" + s.mode + ")", s.message).join("\n");
@@ -27553,7 +28392,7 @@ function formatCodexStatus(s) {
27553
28392
  lines.push("reachable: yes (daemon)");
27554
28393
  const cli = s.cliVersion ?? (s.cliVersionProbe === "ok" ? "unknown" : "unknown, probe " + s.cliVersionProbe);
27555
28394
  lines.push("daemon version: " + (s.daemonVersion ?? "unknown") + " cli version: " + cli + " pinned schema: " + s.pinnedVersion + " (" + s.schema.compatibility + ")");
27556
- lines.push("desktop attached: unknown (not observable from the socket)");
28395
+ lines.push(s.desktopAttached === "private-stdio" ? "desktop attached: private-stdio (Desktop-bundled app-server process observed; managed attachment unverified)" : "desktop attached: unknown (not observable from the socket)");
27557
28396
  if (s.versionMismatch) {
27558
28397
  lines.push("warning: daemon and CLI versions differ; `codex app-server daemon restart` picks up the installed CLI");
27559
28398
  }
@@ -27568,12 +28407,37 @@ function formatCodexThread(t) {
27568
28407
  const preview = t.preview ? "\n " + stripTerminalControls(String(t.preview).replace(/\s+/g, " ")).slice(0, 200) : "";
27569
28408
  return stripTerminalControls(t.id) + " " + stripTerminalControls(t.status) + title + "\n " + stripTerminalControls(t.cwd ?? "") + preview;
27570
28409
  }
28410
+ /** Shell-safe single-quoting for copy-pasteable export lines (spaces, quotes, $). */ function shellQuote(value) {
28411
+ return "'" + String(value).replace(/'/g, "'\\''") + "'";
28412
+ }
28413
+ function formatDesktopShimStatus(s) {
28414
+ const mark = (ok)=>ok ? "yes" : "no";
28415
+ const cliLine = s.platform === "darwin" ? "CODEX_CLI_PATH: GUI domain " + (s.guiCliPath ?? "(unset)") + " / this shell " + (s.cliPath ?? "(unset)") + (s.wrapperPointsAtShim ? " (Desktop-facing value points at shim)" : "") : "CODEX_CLI_PATH: " + (s.cliPath ?? "(unset)") + (s.wrapperPointsAtShim ? " (points at shim)" : "");
28416
+ const lines = [
28417
+ "wrapper: " + s.wrapperPath + " (" + (s.wrapperExecutable ? "executable" : s.wrapperPresent ? "present, not executable" : "absent") + ")",
28418
+ "bridge: " + s.bridgePath + " (" + mark(s.bridgePresent) + ")",
28419
+ "login env: " + s.envScriptPath + " (" + mark(s.envScriptPresent) + ")",
28420
+ s.platform === "darwin" ? "LaunchAgent: " + s.plistPath + " (" + mark(s.plistPresent) + ")" : "LaunchAgent: n/a (macOS-only)",
28421
+ cliLine,
28422
+ "daemon socket: " + s.socketPath + " (" + s.socketState + ", from " + (s.socketSource ?? "CODEX_HOME") + ")"
28423
+ ];
28424
+ if (!s.installed) {
28425
+ lines.push("shim: not installed — run `gbot codex desktop-shim install` (Desktop keeps stock behavior until then)");
28426
+ } else if (!s.wrapperPointsAtShim) {
28427
+ lines.push(s.platform === "darwin" ? "shim: installed but CODEX_CLI_PATH does not point at it — reinstall or relaunch ChatGPT.app after login" : "shim: installed but CODEX_CLI_PATH does not point at it — export CODEX_CLI_PATH=" + shellQuote(s.wrapperPath));
28428
+ } else {
28429
+ lines.push("shim: active — Desktop app-server spawns bridge onto the managed daemon");
28430
+ }
28431
+ for (const warning of s.warnings ?? [])lines.push("warning: " + String(warning));
28432
+ return lines.join("\n");
28433
+ }
27571
28434
 
27572
28435
  __webpack_require__.d(__webpack_exports__, {
27573
28436
  P2: () => (formatCodexStatus),
27574
28437
  bM: () => (formatRecord),
27575
28438
  e_: () => (formatCodexQueue),
27576
28439
  eh: () => (summarize),
28440
+ fX: () => (formatDesktopShimStatus),
27577
28441
  nz: () => (formatCodexThread),
27578
28442
  oZ: () => (formatTranscript)
27579
28443
  });
@@ -28657,6 +29521,8 @@ __webpack_require__.d(__webpack_exports__, {
28657
29521
  * never *.cursorvm.com, so a CURSOR_ACCESS_TOKEN cannot be pointed at a box host.
28658
29522
  * Local/dev gateways: http(s)://127.0.0.1|localhost|::1 when GROK_BOT_ALLOW_LOCAL_GATEWAY=1.
28659
29523
  * Escape hatch: GROK_BOT_ALLOW_ANY_GATEWAY=1 (unsafe; disables host checks; warns once).
29524
+ * Test mode (GROK_BOT_TEST=1 or NODE_ENV=test): loopback only, for gateway and backend
29525
+ * alike, and the escape hatches are ignored — a test can never reach a live thread.
28660
29526
  */ function truthyEnv(name) {
28661
29527
  const v = (process.env[name] || "").trim().toLowerCase();
28662
29528
  return v === "1" || v === "true" || v === "yes";
@@ -28667,6 +29533,9 @@ function allowAnyGateway() {
28667
29533
  function allowLocalGateway() {
28668
29534
  return truthyEnv("GROK_BOT_ALLOW_LOCAL_GATEWAY");
28669
29535
  }
29536
+ function testMode() {
29537
+ return truthyEnv("GROK_BOT_TEST") || process.env.NODE_ENV === "test";
29538
+ }
28670
29539
  const warned = new Set();
28671
29540
  function warnOnce(key, message) {
28672
29541
  if (warned.has(key)) return;
@@ -28706,6 +29575,12 @@ function isCursorGatewayHostname(hostname) {
28706
29575
  if (parsed.username || parsed.password) {
28707
29576
  throw new Error("Rejected " + label + ": userinfo is not allowed.");
28708
29577
  }
29578
+ if (testMode()) {
29579
+ if (!isLocalHostname(parsed.hostname) || parsed.protocol !== "http:" && parsed.protocol !== "https:") {
29580
+ throw new Error("Rejected " + label + " host \"" + parsed.hostname + "\": test mode (GROK_BOT_TEST / NODE_ENV=test) only allows http(s) loopback gateways.");
29581
+ }
29582
+ return String(rawUrl).replace(/\/$/, "");
29583
+ }
28709
29584
  if (allowAnyGateway()) {
28710
29585
  warnOnce("ALLOW_ANY", "warning: GROK_BOT_ALLOW_ANY_GATEWAY is set; credential host checks are disabled.");
28711
29586
  return String(rawUrl).replace(/\/$/, "");
@@ -28821,32 +29696,33 @@ __webpack_require__.r = (exports) => {
28821
29696
  var __webpack_exports__ = {};
28822
29697
  /* import */ var node_worker_threads__rspack_import_0 = __webpack_require__("node:worker_threads");
28823
29698
  /* import */ var react__rspack_import_1 = __webpack_require__("./node_modules/react/react.react-server.js");
28824
- /* import */ var _agent_bundle_runtime_flight_server__rspack_import_27 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/flight/server.js");
28825
- /* import */ var _agent_bundle_runtime__rspack_import_25 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/49.js");
28826
- /* import */ var _agent_bundle_runtime__rspack_import_26 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/736.js");
29699
+ /* import */ var _agent_bundle_runtime_flight_server__rspack_import_28 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/flight/server.js");
29700
+ /* import */ var _agent_bundle_runtime__rspack_import_26 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/49.js");
29701
+ /* import */ var _agent_bundle_runtime__rspack_import_27 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/736.js");
28827
29702
  /* import */ var node_url__rspack_import_2 = __webpack_require__("node:url");
28828
29703
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_create_tsx__rspack_import_3 = __webpack_require__("./src/cli/bots/create.tsx");
28829
29704
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_delete_tsx__rspack_import_4 = __webpack_require__("./src/cli/bots/delete.tsx");
28830
29705
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_get_tsx__rspack_import_5 = __webpack_require__("./src/cli/bots/get.tsx");
28831
29706
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_list_tsx__rspack_import_6 = __webpack_require__("./src/cli/bots/list.tsx");
28832
29707
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_update_tsx__rspack_import_7 = __webpack_require__("./src/cli/bots/update.tsx");
28833
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_8 = __webpack_require__("./src/cli/codex/list-threads.tsx");
28834
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_9 = __webpack_require__("./src/cli/codex/queue.tsx");
28835
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_10 = __webpack_require__("./src/cli/codex/send.tsx");
28836
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_11 = __webpack_require__("./src/cli/codex/status.tsx");
28837
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_12 = __webpack_require__("./src/cli/doctor.tsx");
28838
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_13 = __webpack_require__("./src/cli/groups/add.tsx");
28839
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_14 = __webpack_require__("./src/cli/groups/create.tsx");
28840
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_15 = __webpack_require__("./src/cli/groups/delete.tsx");
28841
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_16 = __webpack_require__("./src/cli/groups/get.tsx");
28842
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_17 = __webpack_require__("./src/cli/groups/list.tsx");
28843
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_18 = __webpack_require__("./src/cli/groups/members.tsx");
28844
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_19 = __webpack_require__("./src/cli/groups/remove.tsx");
28845
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_20 = __webpack_require__("./src/cli/groups/set.tsx");
28846
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_21 = __webpack_require__("./src/cli/groups/update.tsx");
28847
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_22 = __webpack_require__("./src/cli/history.tsx");
28848
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_23 = __webpack_require__("./src/cli/send.tsx");
28849
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_24 = __webpack_require__("./src/cli/thread.tsx");
29708
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_desktop_shim_tsx__rspack_import_8 = __webpack_require__("./src/cli/codex/desktop-shim.tsx");
29709
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_9 = __webpack_require__("./src/cli/codex/list-threads.tsx");
29710
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_10 = __webpack_require__("./src/cli/codex/queue.tsx");
29711
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_11 = __webpack_require__("./src/cli/codex/send.tsx");
29712
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_12 = __webpack_require__("./src/cli/codex/status.tsx");
29713
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_13 = __webpack_require__("./src/cli/doctor.tsx");
29714
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_14 = __webpack_require__("./src/cli/groups/add.tsx");
29715
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_15 = __webpack_require__("./src/cli/groups/create.tsx");
29716
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_16 = __webpack_require__("./src/cli/groups/delete.tsx");
29717
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_17 = __webpack_require__("./src/cli/groups/get.tsx");
29718
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_18 = __webpack_require__("./src/cli/groups/list.tsx");
29719
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_19 = __webpack_require__("./src/cli/groups/members.tsx");
29720
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_20 = __webpack_require__("./src/cli/groups/remove.tsx");
29721
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_21 = __webpack_require__("./src/cli/groups/set.tsx");
29722
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_22 = __webpack_require__("./src/cli/groups/update.tsx");
29723
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_23 = __webpack_require__("./src/cli/history.tsx");
29724
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_24 = __webpack_require__("./src/cli/send.tsx");
29725
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_25 = __webpack_require__("./src/cli/thread.tsx");
28850
29726
 
28851
29727
 
28852
29728
 
@@ -28863,40 +29739,42 @@ const route3 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok
28863
29739
 
28864
29740
  const route4 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_update_tsx__rspack_import_7, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_update_tsx__rspack_import_7);
28865
29741
 
28866
- const route5 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_8, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_8);
29742
+ const route5 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_desktop_shim_tsx__rspack_import_8, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_desktop_shim_tsx__rspack_import_8);
29743
+
29744
+ const route6 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_9, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_9);
28867
29745
 
28868
- const route6 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_9, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_9);
29746
+ const route7 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_10, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_10);
28869
29747
 
28870
- const route7 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_10, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_10);
29748
+ const route8 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_11, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_11);
28871
29749
 
28872
- const route8 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_11, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_11);
29750
+ const route9 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_12, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_12);
28873
29751
 
28874
- const route9 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_12, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_12);
29752
+ const route10 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_13, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_13);
28875
29753
 
28876
- const route10 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_13, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_13);
29754
+ const route11 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_14, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_14);
28877
29755
 
28878
- const route11 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_14, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_14);
29756
+ const route12 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_15, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_15);
28879
29757
 
28880
- const route12 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_15, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_15);
29758
+ const route13 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_16, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_16);
28881
29759
 
28882
- const route13 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_16, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_16);
29760
+ const route14 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_17, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_17);
28883
29761
 
28884
- const route14 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_17, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_17);
29762
+ const route15 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_18, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_18);
28885
29763
 
28886
- const route15 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_18, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_18);
29764
+ const route16 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_19, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_19);
28887
29765
 
28888
- const route16 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_19, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_19);
29766
+ const route17 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_20, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_20);
28889
29767
 
28890
- const route17 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_20, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_20);
29768
+ const route18 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_21, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_21);
28891
29769
 
28892
- const route18 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_21, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_21);
29770
+ const route19 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_22, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_22);
28893
29771
 
28894
- const route19 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_22, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_22);
29772
+ const route20 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_23, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_23);
28895
29773
 
28896
- const route20 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_23, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_23);
29774
+ const route21 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_24, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_24);
28897
29775
 
28898
- const route21 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_24, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_24);
28899
- const pluginRoot = (0,_agent_bundle_runtime__rspack_import_25/* .resolvePluginRoot */.E7)({
29776
+ const route22 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_25, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_25);
29777
+ const pluginRoot = (0,_agent_bundle_runtime__rspack_import_26/* .resolvePluginRoot */.E7)({
28900
29778
  fallback: (0,node_url__rspack_import_2.fileURLToPath)(new URL('..', import.meta.url)),
28901
29779
  stateAnchor: 'user-data'
28902
29780
  });
@@ -28944,107 +29822,113 @@ const routes = Object.freeze({
28944
29822
  name: "bots update",
28945
29823
  module: route4
28946
29824
  }),
29825
+ "cli:codex/desktop-shim": Object.freeze({
29826
+ id: "cli:codex/desktop-shim",
29827
+ kind: "cli",
29828
+ name: "codex desktop-shim",
29829
+ module: route5
29830
+ }),
28947
29831
  "cli:codex/list-threads": Object.freeze({
28948
29832
  id: "cli:codex/list-threads",
28949
29833
  kind: "cli",
28950
29834
  name: "codex list-threads",
28951
- module: route5
29835
+ module: route6
28952
29836
  }),
28953
29837
  "cli:codex/queue": Object.freeze({
28954
29838
  id: "cli:codex/queue",
28955
29839
  kind: "cli",
28956
29840
  name: "codex queue",
28957
- module: route6
29841
+ module: route7
28958
29842
  }),
28959
29843
  "cli:codex/send": Object.freeze({
28960
29844
  id: "cli:codex/send",
28961
29845
  kind: "cli",
28962
29846
  name: "codex send",
28963
- module: route7
29847
+ module: route8
28964
29848
  }),
28965
29849
  "cli:codex/status": Object.freeze({
28966
29850
  id: "cli:codex/status",
28967
29851
  kind: "cli",
28968
29852
  name: "codex status",
28969
- module: route8
29853
+ module: route9
28970
29854
  }),
28971
29855
  "cli:doctor": Object.freeze({
28972
29856
  id: "cli:doctor",
28973
29857
  kind: "cli",
28974
29858
  name: "doctor",
28975
- module: route9
29859
+ module: route10
28976
29860
  }),
28977
29861
  "cli:groups/add": Object.freeze({
28978
29862
  id: "cli:groups/add",
28979
29863
  kind: "cli",
28980
29864
  name: "groups add",
28981
- module: route10
29865
+ module: route11
28982
29866
  }),
28983
29867
  "cli:groups/create": Object.freeze({
28984
29868
  id: "cli:groups/create",
28985
29869
  kind: "cli",
28986
29870
  name: "groups create",
28987
- module: route11
29871
+ module: route12
28988
29872
  }),
28989
29873
  "cli:groups/delete": Object.freeze({
28990
29874
  id: "cli:groups/delete",
28991
29875
  kind: "cli",
28992
29876
  name: "groups delete",
28993
- module: route12
29877
+ module: route13
28994
29878
  }),
28995
29879
  "cli:groups/get": Object.freeze({
28996
29880
  id: "cli:groups/get",
28997
29881
  kind: "cli",
28998
29882
  name: "groups get",
28999
- module: route13
29883
+ module: route14
29000
29884
  }),
29001
29885
  "cli:groups/list": Object.freeze({
29002
29886
  id: "cli:groups/list",
29003
29887
  kind: "cli",
29004
29888
  name: "groups list",
29005
- module: route14
29889
+ module: route15
29006
29890
  }),
29007
29891
  "cli:groups/members": Object.freeze({
29008
29892
  id: "cli:groups/members",
29009
29893
  kind: "cli",
29010
29894
  name: "groups members",
29011
- module: route15
29895
+ module: route16
29012
29896
  }),
29013
29897
  "cli:groups/remove": Object.freeze({
29014
29898
  id: "cli:groups/remove",
29015
29899
  kind: "cli",
29016
29900
  name: "groups remove",
29017
- module: route16
29901
+ module: route17
29018
29902
  }),
29019
29903
  "cli:groups/set": Object.freeze({
29020
29904
  id: "cli:groups/set",
29021
29905
  kind: "cli",
29022
29906
  name: "groups set",
29023
- module: route17
29907
+ module: route18
29024
29908
  }),
29025
29909
  "cli:groups/update": Object.freeze({
29026
29910
  id: "cli:groups/update",
29027
29911
  kind: "cli",
29028
29912
  name: "groups update",
29029
- module: route18
29913
+ module: route19
29030
29914
  }),
29031
29915
  "cli:history": Object.freeze({
29032
29916
  id: "cli:history",
29033
29917
  kind: "cli",
29034
29918
  name: "history",
29035
- module: route19
29919
+ module: route20
29036
29920
  }),
29037
29921
  "cli:send": Object.freeze({
29038
29922
  id: "cli:send",
29039
29923
  kind: "cli",
29040
29924
  name: "send",
29041
- module: route20
29925
+ module: route21
29042
29926
  }),
29043
29927
  "cli:thread": Object.freeze({
29044
29928
  id: "cli:thread",
29045
29929
  kind: "cli",
29046
29930
  name: "thread",
29047
- module: route21
29931
+ module: route22
29048
29932
  })
29049
29933
  });
29050
29934
  const requests = new Map();
@@ -29079,18 +29963,18 @@ const render = async (message)=>{
29079
29963
  };
29080
29964
  try {
29081
29965
  const cwd = process.cwd();
29082
- await (0,_agent_bundle_runtime__rspack_import_26/* .runAgentRequest */.iC)({
29966
+ await (0,_agent_bundle_runtime__rspack_import_27/* .runAgentRequest */.iC)({
29083
29967
  capabilities: {
29084
- command: (0,_agent_bundle_runtime__rspack_import_26/* .unavailable */.hU)(),
29085
- filesystem: (0,_agent_bundle_runtime__rspack_import_26/* .unavailable */.hU)(),
29086
- network: (0,_agent_bundle_runtime__rspack_import_26/* .unavailable */.hU)(),
29087
- projectRoot: (0,_agent_bundle_runtime__rspack_import_26/* .available */.qC)({
29968
+ command: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)(),
29969
+ filesystem: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)(),
29970
+ network: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)(),
29971
+ projectRoot: (0,_agent_bundle_runtime__rspack_import_27/* .available */.qC)({
29088
29972
  root: cwd
29089
29973
  }, 'derived')
29090
29974
  },
29091
- host: (0,_agent_bundle_runtime__rspack_import_26/* .unavailable */.hU)('unsupported-surface'),
29975
+ host: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)('unsupported-surface'),
29092
29976
  invocation: message.request,
29093
- lineage: (0,_agent_bundle_runtime__rspack_import_26/* .unavailable */.hU)('unsupported-surface'),
29977
+ lineage: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)('unsupported-surface'),
29094
29978
  plugin: pluginRoot.identity,
29095
29979
  progress: {
29096
29980
  report: async (update)=>{
@@ -29103,8 +29987,8 @@ const render = async (message)=>{
29103
29987
  },
29104
29988
  process: processHit,
29105
29989
  signal: controller.signal,
29106
- terminal: message.terminal === undefined ? (0,_agent_bundle_runtime__rspack_import_26/* .unavailable */.hU)('not-provided') : (0,_agent_bundle_runtime__rspack_import_26/* .available */.qC)(message.terminal, 'native'),
29107
- workspace: (0,_agent_bundle_runtime__rspack_import_26/* .available */.qC)({
29990
+ terminal: message.terminal === undefined ? (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)('not-provided') : (0,_agent_bundle_runtime__rspack_import_27/* .available */.qC)(message.terminal, 'native'),
29991
+ workspace: (0,_agent_bundle_runtime__rspack_import_27/* .available */.qC)({
29108
29992
  root: cwd
29109
29993
  }, 'derived')
29110
29994
  }, async ()=>{
@@ -29113,7 +29997,7 @@ const render = async (message)=>{
29113
29997
  type: 'observed-render-start'
29114
29998
  });
29115
29999
  const renderStartedAt = performance.now();
29116
- const flight = (0,_agent_bundle_runtime_flight_server__rspack_import_27/* .renderAgentFlight */.y)(composeLayouts(observedRoute, {
30000
+ const flight = (0,_agent_bundle_runtime_flight_server__rspack_import_28/* .renderAgentFlight */.y)(composeLayouts(observedRoute, {
29117
30001
  ...message.props,
29118
30002
  signal: controller.signal
29119
30003
  }, controller.signal), {