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.
package/dist/bin/gbot.mjs CHANGED
@@ -11456,6 +11456,140 @@ __webpack_require__.d(__webpack_exports__, {
11456
11456
  });
11457
11457
 
11458
11458
 
11459
+ },
11460
+ "./src/cli/codex/desktop-shim.tsx"(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
11461
+ __webpack_require__.r(__webpack_exports__);
11462
+ /* import */ var react_jsx_runtime__rspack_import_0 = __webpack_require__("./node_modules/react/jsx-runtime.js");
11463
+ /* import */ var _agent_bundle_runtime__rspack_import_3 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/506.js");
11464
+ /* import */ var zod__rspack_import_2 = __webpack_require__("./node_modules/zod/v4/classic/schemas.js");
11465
+ /* import */ var _core_desktop_shim_js__rspack_import_1 = __webpack_require__("./src/core/desktop-shim.js");
11466
+ /* import */ var _core_format_js__rspack_import_4 = __webpack_require__("./src/core/format.js");
11467
+
11468
+
11469
+
11470
+
11471
+
11472
+ const config = {
11473
+ 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.',
11474
+ exitCode: 'result',
11475
+ inputJsonSchema: {
11476
+ additionalProperties: false,
11477
+ properties: {
11478
+ action: {
11479
+ description: 'install | uninstall | status',
11480
+ enum: [
11481
+ 'install',
11482
+ 'uninstall',
11483
+ 'status'
11484
+ ],
11485
+ type: 'string'
11486
+ }
11487
+ },
11488
+ required: [
11489
+ 'action'
11490
+ ],
11491
+ type: 'object'
11492
+ },
11493
+ positionals: [
11494
+ 'action'
11495
+ ]
11496
+ };
11497
+ const inputSchema = zod__rspack_import_2/* .object */.Ikc({
11498
+ action: zod__rspack_import_2/* ["enum"] */.k5n([
11499
+ 'install',
11500
+ 'uninstall',
11501
+ 'status'
11502
+ ])
11503
+ }).strict();
11504
+ const resultSchema = zod__rspack_import_2/* .object */.Ikc({
11505
+ exitCode: zod__rspack_import_2/* .union */.KCZ([
11506
+ zod__rspack_import_2/* .literal */.euz(0),
11507
+ zod__rspack_import_2/* .literal */.euz(1)
11508
+ ])
11509
+ }).passthrough();
11510
+ const formatWarnings = (warnings)=>warnings.length === 0 ? '' : `\nwarnings:\n${warnings.map((w)=>` - ${String(w)}`).join('\n')}`;
11511
+ async function codexDesktopShim({ input }) {
11512
+ switch(input.action){
11513
+ case 'install':
11514
+ {
11515
+ let out;
11516
+ try {
11517
+ out = (0,_core_desktop_shim_js__rspack_import_1/* .installDesktopShim */.B0)();
11518
+ } catch (error) {
11519
+ const message = error instanceof Error ? error.message : String(error);
11520
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
11521
+ value: {
11522
+ action: 'install',
11523
+ error: message,
11524
+ exitCode: 1
11525
+ },
11526
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
11527
+ children: `Desktop shim install failed: ${message}`
11528
+ })
11529
+ });
11530
+ }
11531
+ 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);
11532
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
11533
+ value: out,
11534
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
11535
+ children: text
11536
+ })
11537
+ });
11538
+ }
11539
+ case 'uninstall':
11540
+ {
11541
+ let out;
11542
+ try {
11543
+ out = (0,_core_desktop_shim_js__rspack_import_1/* .uninstallDesktopShim */.gM)();
11544
+ } catch (error) {
11545
+ const message = error instanceof Error ? error.message : String(error);
11546
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
11547
+ value: {
11548
+ action: 'uninstall',
11549
+ error: message,
11550
+ exitCode: 1
11551
+ },
11552
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
11553
+ children: `Desktop shim uninstall failed: ${message}`
11554
+ })
11555
+ });
11556
+ }
11557
+ const removed = out.removed.length === 0 ? '(nothing installed)' : out.removed.join(', ');
11558
+ 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);
11559
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
11560
+ value: out,
11561
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
11562
+ children: text
11563
+ })
11564
+ });
11565
+ }
11566
+ case 'status':
11567
+ {
11568
+ const out = (0,_core_desktop_shim_js__rspack_import_1/* .desktopShimStatus */.em)();
11569
+ return /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Result */.g6.Result, {
11570
+ value: out,
11571
+ children: /*#__PURE__*/ (0,react_jsx_runtime__rspack_import_0.jsx)(_agent_bundle_runtime__rspack_import_3/* .Agent.Text */.g6.Text, {
11572
+ children: (0,_core_format_js__rspack_import_4/* .formatDesktopShimStatus */.fX)(out)
11573
+ })
11574
+ });
11575
+ }
11576
+ default:
11577
+ {
11578
+ const _exhaustive = input.action;
11579
+ throw new Error(`unknown desktop-shim action: ${String(_exhaustive)}`);
11580
+ }
11581
+ }
11582
+ }
11583
+
11584
+ __webpack_require__.d(__webpack_exports__, {
11585
+ "default": () => (codexDesktopShim)
11586
+ }, {
11587
+ config: config,
11588
+ inputSchema: inputSchema,
11589
+ resultSchema: resultSchema
11590
+ });
11591
+
11592
+
11459
11593
  },
11460
11594
  "./src/cli/codex/list-threads.tsx"(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
11461
11595
  __webpack_require__.r(__webpack_exports__);
@@ -82746,9 +82880,9 @@ __webpack_require__.d(__webpack_exports__, {
82746
82880
  __webpack_require__.a(__webpack_module__, async function (__rspack_load_async_deps, __rspack_async_done) { try {
82747
82881
  /* import */ var agent_bundle_launch_env_layer__rspack_import_0 = __webpack_require__("./.agent-bundle-virtual/bin-gbot-0.mjs");
82748
82882
  /* import */ var agent_bundle_cli_entry__rspack_import_1 = __webpack_require__("./node_modules/agent-bundle/dist/cli-entry.js");
82749
- /* import */ var _agent_bundle_runtime__rspack_import_26 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/49.js");
82750
- /* import */ var _agent_bundle_runtime__rspack_import_27 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/736.js");
82751
- /* import */ var _agent_bundle_runtime__rspack_import_28 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/index.js");
82883
+ /* import */ var _agent_bundle_runtime__rspack_import_27 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/49.js");
82884
+ /* import */ var _agent_bundle_runtime__rspack_import_28 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/736.js");
82885
+ /* import */ var _agent_bundle_runtime__rspack_import_29 = __webpack_require__("./node_modules/@agent-bundle/runtime/dist/index.js");
82752
82886
  /* import */ var node_url__rspack_import_2 = __webpack_require__("node:url");
82753
82887
  /* import */ var node_worker_threads__rspack_import_3 = __webpack_require__("node:worker_threads");
82754
82888
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_create_tsx__rspack_import_4 = __webpack_require__("./src/cli/bots/create.tsx");
@@ -82756,23 +82890,24 @@ __webpack_require__.a(__webpack_module__, async function (__rspack_load_async_de
82756
82890
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_get_tsx__rspack_import_6 = __webpack_require__("./src/cli/bots/get.tsx");
82757
82891
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_list_tsx__rspack_import_7 = __webpack_require__("./src/cli/bots/list.tsx");
82758
82892
  /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_update_tsx__rspack_import_8 = __webpack_require__("./src/cli/bots/update.tsx");
82759
- /* 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");
82760
- /* 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");
82761
- /* 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");
82762
- /* 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");
82763
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_13 = __webpack_require__("./src/cli/doctor.tsx");
82764
- /* 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");
82765
- /* 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");
82766
- /* 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");
82767
- /* 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");
82768
- /* 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");
82769
- /* 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");
82770
- /* 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");
82771
- /* 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");
82772
- /* 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");
82773
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_23 = __webpack_require__("./src/cli/history.tsx");
82774
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_24 = __webpack_require__("./src/cli/send.tsx");
82775
- /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_25 = __webpack_require__("./src/cli/thread.tsx");
82893
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_desktop_shim_tsx__rspack_import_9 = __webpack_require__("./src/cli/codex/desktop-shim.tsx");
82894
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_10 = __webpack_require__("./src/cli/codex/list-threads.tsx");
82895
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_11 = __webpack_require__("./src/cli/codex/queue.tsx");
82896
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_12 = __webpack_require__("./src/cli/codex/send.tsx");
82897
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_13 = __webpack_require__("./src/cli/codex/status.tsx");
82898
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_14 = __webpack_require__("./src/cli/doctor.tsx");
82899
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_15 = __webpack_require__("./src/cli/groups/add.tsx");
82900
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_16 = __webpack_require__("./src/cli/groups/create.tsx");
82901
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_17 = __webpack_require__("./src/cli/groups/delete.tsx");
82902
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_18 = __webpack_require__("./src/cli/groups/get.tsx");
82903
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_19 = __webpack_require__("./src/cli/groups/list.tsx");
82904
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_20 = __webpack_require__("./src/cli/groups/members.tsx");
82905
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_21 = __webpack_require__("./src/cli/groups/remove.tsx");
82906
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_22 = __webpack_require__("./src/cli/groups/set.tsx");
82907
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_23 = __webpack_require__("./src/cli/groups/update.tsx");
82908
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_24 = __webpack_require__("./src/cli/history.tsx");
82909
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_25 = __webpack_require__("./src/cli/send.tsx");
82910
+ /* import */ var _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_26 = __webpack_require__("./src/cli/thread.tsx");
82776
82911
 
82777
82912
 
82778
82913
 
@@ -82789,40 +82924,42 @@ const route3 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok
82789
82924
 
82790
82925
  const route4 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_update_tsx__rspack_import_8, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_bots_update_tsx__rspack_import_8);
82791
82926
 
82792
- const route5 = 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);
82927
+ const route5 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_desktop_shim_tsx__rspack_import_9, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_desktop_shim_tsx__rspack_import_9);
82928
+
82929
+ const route6 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_10, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_list_threads_tsx__rspack_import_10);
82793
82930
 
82794
- const route6 = 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);
82931
+ const route7 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_11, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_queue_tsx__rspack_import_11);
82795
82932
 
82796
- const route7 = 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);
82933
+ const route8 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_12, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_send_tsx__rspack_import_12);
82797
82934
 
82798
- const route8 = 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);
82935
+ const route9 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_13, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_codex_status_tsx__rspack_import_13);
82799
82936
 
82800
- const route9 = 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);
82937
+ const route10 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_14, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_doctor_tsx__rspack_import_14);
82801
82938
 
82802
- const route10 = 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);
82939
+ const route11 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_15, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_add_tsx__rspack_import_15);
82803
82940
 
82804
- const route11 = 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);
82941
+ const route12 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_16, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_create_tsx__rspack_import_16);
82805
82942
 
82806
- const route12 = 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);
82943
+ const route13 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_17, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_delete_tsx__rspack_import_17);
82807
82944
 
82808
- const route13 = 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);
82945
+ const route14 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_18, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_get_tsx__rspack_import_18);
82809
82946
 
82810
- const route14 = 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);
82947
+ const route15 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_19, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_list_tsx__rspack_import_19);
82811
82948
 
82812
- const route15 = 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);
82949
+ const route16 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_20, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_members_tsx__rspack_import_20);
82813
82950
 
82814
- const route16 = 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);
82951
+ const route17 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_21, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_remove_tsx__rspack_import_21);
82815
82952
 
82816
- const route17 = 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);
82953
+ const route18 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_22, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_set_tsx__rspack_import_22);
82817
82954
 
82818
- const route18 = 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);
82955
+ const route19 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_23, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_groups_update_tsx__rspack_import_23);
82819
82956
 
82820
- const route19 = 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);
82957
+ const route20 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_24, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_history_tsx__rspack_import_24);
82821
82958
 
82822
- const route20 = 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);
82959
+ const route21 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_25, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_send_tsx__rspack_import_25);
82823
82960
 
82824
- const route21 = 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);
82825
- const pluginRoot = (0,_agent_bundle_runtime__rspack_import_26/* .resolvePluginRoot */.E7)({
82961
+ const route22 = Object.assign({}, Reflect.get(_home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_26, 'default'), _home_runner_work_grok_bot_cli_grok_bot_cli_src_cli_thread_tsx__rspack_import_26);
82962
+ const pluginRoot = (0,_agent_bundle_runtime__rspack_import_27/* .resolvePluginRoot */.E7)({
82826
82963
  fallback: (0,node_url__rspack_import_2.fileURLToPath)(new URL('..', import.meta.url)),
82827
82964
  stateAnchor: 'user-data'
82828
82965
  });
@@ -82847,56 +82984,59 @@ const routes = Object.freeze({
82847
82984
  "cli:bots/update": Object.freeze({
82848
82985
  module: route4
82849
82986
  }),
82850
- "cli:codex/list-threads": Object.freeze({
82987
+ "cli:codex/desktop-shim": Object.freeze({
82851
82988
  module: route5
82852
82989
  }),
82853
- "cli:codex/queue": Object.freeze({
82990
+ "cli:codex/list-threads": Object.freeze({
82854
82991
  module: route6
82855
82992
  }),
82856
- "cli:codex/send": Object.freeze({
82993
+ "cli:codex/queue": Object.freeze({
82857
82994
  module: route7
82858
82995
  }),
82859
- "cli:codex/status": Object.freeze({
82996
+ "cli:codex/send": Object.freeze({
82860
82997
  module: route8
82861
82998
  }),
82862
- "cli:doctor": Object.freeze({
82999
+ "cli:codex/status": Object.freeze({
82863
83000
  module: route9
82864
83001
  }),
82865
- "cli:groups/add": Object.freeze({
83002
+ "cli:doctor": Object.freeze({
82866
83003
  module: route10
82867
83004
  }),
82868
- "cli:groups/create": Object.freeze({
83005
+ "cli:groups/add": Object.freeze({
82869
83006
  module: route11
82870
83007
  }),
82871
- "cli:groups/delete": Object.freeze({
83008
+ "cli:groups/create": Object.freeze({
82872
83009
  module: route12
82873
83010
  }),
82874
- "cli:groups/get": Object.freeze({
83011
+ "cli:groups/delete": Object.freeze({
82875
83012
  module: route13
82876
83013
  }),
82877
- "cli:groups/list": Object.freeze({
83014
+ "cli:groups/get": Object.freeze({
82878
83015
  module: route14
82879
83016
  }),
82880
- "cli:groups/members": Object.freeze({
83017
+ "cli:groups/list": Object.freeze({
82881
83018
  module: route15
82882
83019
  }),
82883
- "cli:groups/remove": Object.freeze({
83020
+ "cli:groups/members": Object.freeze({
82884
83021
  module: route16
82885
83022
  }),
82886
- "cli:groups/set": Object.freeze({
83023
+ "cli:groups/remove": Object.freeze({
82887
83024
  module: route17
82888
83025
  }),
82889
- "cli:groups/update": Object.freeze({
83026
+ "cli:groups/set": Object.freeze({
82890
83027
  module: route18
82891
83028
  }),
82892
- "cli:history": Object.freeze({
83029
+ "cli:groups/update": Object.freeze({
82893
83030
  module: route19
82894
83031
  }),
82895
- "cli:send": Object.freeze({
83032
+ "cli:history": Object.freeze({
82896
83033
  module: route20
82897
83034
  }),
82898
- "cli:thread": Object.freeze({
83035
+ "cli:send": Object.freeze({
82899
83036
  module: route21
83037
+ }),
83038
+ "cli:thread": Object.freeze({
83039
+ module: route22
82900
83040
  })
82901
83041
  });
82902
83042
  const commands = Object.freeze([
@@ -83196,6 +83336,33 @@ const commands = Object.freeze([
83196
83336
  "rendered": true,
83197
83337
  "routeId": "cli:bots/update"
83198
83338
  },
83339
+ {
83340
+ "aliases": [],
83341
+ "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.",
83342
+ "exitCode": "result",
83343
+ "options": [
83344
+ {
83345
+ "choices": [
83346
+ "install",
83347
+ "uninstall",
83348
+ "status"
83349
+ ],
83350
+ "description": "install | uninstall | status",
83351
+ "key": "action",
83352
+ "kind": "enum",
83353
+ "option": "action",
83354
+ "positional": 0,
83355
+ "repeated": false,
83356
+ "required": true
83357
+ }
83358
+ ],
83359
+ "path": [
83360
+ "codex",
83361
+ "desktop-shim"
83362
+ ],
83363
+ "rendered": true,
83364
+ "routeId": "cli:codex/desktop-shim"
83365
+ },
83199
83366
  {
83200
83367
  "aliases": [],
83201
83368
  "description": "List Codex daemon-managed threads.",
@@ -84161,27 +84328,27 @@ const execute = async (command, input, context)=>{
84161
84328
  instanceId: processLifetime.instanceId,
84162
84329
  pid: processLifetime.pid
84163
84330
  };
84164
- const result = await (0,_agent_bundle_runtime__rspack_import_27/* .runAgentRequest */.iC)({
84331
+ const result = await (0,_agent_bundle_runtime__rspack_import_28/* .runAgentRequest */.iC)({
84165
84332
  capabilities: {
84166
- command: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)(),
84167
- filesystem: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)(),
84168
- network: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)(),
84169
- projectRoot: (0,_agent_bundle_runtime__rspack_import_27/* .available */.qC)({
84333
+ command: (0,_agent_bundle_runtime__rspack_import_28/* .unavailable */.hU)(),
84334
+ filesystem: (0,_agent_bundle_runtime__rspack_import_28/* .unavailable */.hU)(),
84335
+ network: (0,_agent_bundle_runtime__rspack_import_28/* .unavailable */.hU)(),
84336
+ projectRoot: (0,_agent_bundle_runtime__rspack_import_28/* .available */.qC)({
84170
84337
  root: cwd
84171
84338
  }, 'derived')
84172
84339
  },
84173
- host: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)('unsupported-surface'),
84340
+ host: (0,_agent_bundle_runtime__rspack_import_28/* .unavailable */.hU)('unsupported-surface'),
84174
84341
  invocation: {
84175
84342
  kind: 'cli',
84176
84343
  operationId: command.routeId,
84177
84344
  surface: command.path.join(' ')
84178
84345
  },
84179
- lineage: (0,_agent_bundle_runtime__rspack_import_27/* .unavailable */.hU)('unsupported-surface'),
84346
+ lineage: (0,_agent_bundle_runtime__rspack_import_28/* .unavailable */.hU)('unsupported-surface'),
84180
84347
  plugin: pluginRoot.identity,
84181
84348
  process: processHit,
84182
84349
  signal: context.signal,
84183
- terminal: (0,_agent_bundle_runtime__rspack_import_27/* .available */.qC)(context.terminal, 'native'),
84184
- workspace: (0,_agent_bundle_runtime__rspack_import_27/* .available */.qC)({
84350
+ terminal: (0,_agent_bundle_runtime__rspack_import_28/* .available */.qC)(context.terminal, 'native'),
84351
+ workspace: (0,_agent_bundle_runtime__rspack_import_28/* .available */.qC)({
84185
84352
  root: cwd
84186
84353
  }, 'derived')
84187
84354
  }, async ()=>route.module.default({
@@ -84288,7 +84455,7 @@ const openRenderedSession = ({ invocation, limits, props, request, routeId, sign
84288
84455
  return stream;
84289
84456
  }
84290
84457
  });
84291
- const dispatcher = (0,_agent_bundle_runtime__rspack_import_28/* .createAgentRenderDispatcher */.jK)(host);
84458
+ const dispatcher = (0,_agent_bundle_runtime__rspack_import_29/* .createAgentRenderDispatcher */.jK)(host);
84292
84459
  return Object.freeze({
84293
84460
  close: async ()=>{
84294
84461
  await worker.terminate();
@@ -84336,7 +84503,7 @@ if (import.meta.main) {
84336
84503
  execute,
84337
84504
  name: "gbot",
84338
84505
  render,
84339
- version: "0.4.1"
84506
+ version: "0.4.5"
84340
84507
  });
84341
84508
  }
84342
84509
 
@@ -84574,6 +84741,8 @@ __webpack_require__.d(__webpack_exports__, {
84574
84741
  /* import */ var node_child_process__rspack_import_5 = __webpack_require__("node:child_process");
84575
84742
  /* import */ var node_module__rspack_import_6 = __webpack_require__("node:module");
84576
84743
  /* import */ var _codex_contract_js__rspack_import_7 = __webpack_require__("./src/core/codex/contract.js");
84744
+ /* import */ var _desktop_shim_js__rspack_import_8 = __webpack_require__("./src/core/desktop-shim.js");
84745
+
84577
84746
 
84578
84747
 
84579
84748
 
@@ -84601,6 +84770,9 @@ const textDecoder = new TextDecoder("utf-8", {
84601
84770
  });
84602
84771
  const pkg = (0,node_module__rspack_import_6.createRequire)(import.meta.url)("../../package.json");
84603
84772
  function codexSocketPath(env = process.env) {
84773
+ // Explicit socket wins so status/send/queue agree with a shim-installed or
84774
+ // operator-exported CODEX_APP_SERVER_SOCK; otherwise derive from CODEX_HOME.
84775
+ if (env.CODEX_APP_SERVER_SOCK) return env.CODEX_APP_SERVER_SOCK;
84604
84776
  const home = env.CODEX_HOME || (0,node_path__rspack_import_4.join)((0,node_os__rspack_import_3.homedir)(), ".codex");
84605
84777
  return (0,node_path__rspack_import_4.join)(home, "app-server-control", "app-server-control.sock");
84606
84778
  }
@@ -84617,7 +84789,15 @@ function codexSocketPath(env = process.env) {
84617
84789
  /** Single-line fields (ids, names, paths, cursors, header tokens): no line breaks or tabs survive. */ function singleLine(text) {
84618
84790
  return stripTerminalControls(text).replace(/[\t\n\r\u2028\u2029]+/g, " ");
84619
84791
  }
84620
- function unreachableMessage(path) {
84792
+ function unreachableMessage(path, desktopAttached = "unknown") {
84793
+ if (desktopAttached === "private-stdio") {
84794
+ return [
84795
+ "ChatGPT Desktop is running its private stdio app-server, which external clients cannot reach",
84796
+ "(" + UPSTREAM_DESKTOP_ISSUES.join(", ") + ").",
84797
+ "No Codex app-server control socket at " + path + ": start a managed standalone daemon with `codex app-server daemon start`.",
84798
+ "gbot codex targets daemon-managed threads only."
84799
+ ].join("\n");
84800
+ }
84621
84801
  return [
84622
84802
  "No Codex app-server control socket at " + path + ".",
84623
84803
  "Either no daemon is running (start one with `codex app-server daemon start`),",
@@ -84820,6 +85000,43 @@ function isObject(value) {
84820
85000
  const sendJson = (obj)=>write(0x1, Buffer.from(JSON.stringify(obj)));
84821
85001
  const client = {
84822
85002
  refused,
85003
+ // Server-initiated requests are only *answered* once our own turn/start
85004
+ // is in flight, and then only when they name our own thread/turn:
85005
+ // anything earlier — or naming another thread or Desktop turn — belongs
85006
+ // to another client and must never be rejected on their behalf. Early
85007
+ // and foreign requests are still recorded; the connection closes right
85008
+ // after, so a pending request simply dies with it instead of denying
85009
+ // someone's approval.
85010
+ answerServerRequests: false,
85011
+ expectedThreadId: null,
85012
+ expectedTurnId: null,
85013
+ // Approvals that name a turn while our own turn id is still unknown
85014
+ // (arriving before — or in the same socket batch as — the turn/start
85015
+ // acknowledgment) wait here instead of being classified foreign: once
85016
+ // the acknowledgment supplies the turn id, matching ones are answered
85017
+ // and the rest stay silent.
85018
+ deferred: [],
85019
+ // Adopt the acknowledged turn id, answering deferred requests that
85020
+ // prove to be ours. Runs inside the connection closure so it can send.
85021
+ _adoptTurn (turnId) {
85022
+ client.expectedTurnId = turnId;
85023
+ for (const entry of client.deferred.splice(0)){
85024
+ const params = entry.params && typeof entry.params === "object" && !Array.isArray(entry.params) ? entry.params : null;
85025
+ const threadId = params ? params.threadId ?? params.thread_id : null;
85026
+ const namedTurnId = params ? params.turnId ?? params.turn_id ?? (params.turn && params.turn.id) : null;
85027
+ if (threadId === client.expectedThreadId && namedTurnId === turnId) {
85028
+ entry.answered = true;
85029
+ sendJson({
85030
+ jsonrpc: "2.0",
85031
+ id: entry.id,
85032
+ error: {
85033
+ code: -32601,
85034
+ message: "gbot codex does not answer " + entry.method + "; configure approval_policy on the daemon"
85035
+ }
85036
+ });
85037
+ }
85038
+ }
85039
+ },
84823
85040
  request (method, params) {
84824
85041
  const id = nextId++;
84825
85042
  return new Promise((res, rej)=>{
@@ -84883,11 +85100,37 @@ function isObject(value) {
84883
85100
  };
84884
85101
  const onMessage = (msg)=>{
84885
85102
  if (msg.id != null && msg.method) {
84886
- refused.push({
85103
+ let answered = client.answerServerRequests;
85104
+ let defer = false;
85105
+ // Approval ownership: only answer requests for the turn gbot itself
85106
+ // started. A request naming another thread — or naming a turn that is
85107
+ // not ours — is recorded foreign-silent, never rejected. A request
85108
+ // naming a turn while our turn id is still unknown is deferred, not
85109
+ // classified: the turn/start acknowledgment decides it.
85110
+ if (answered) {
85111
+ const params = msg.params && typeof msg.params === "object" && !Array.isArray(msg.params) ? msg.params : null;
85112
+ if (params) {
85113
+ const threadId = params.threadId ?? params.thread_id;
85114
+ const turnId = params.turnId ?? params.turn_id ?? (params.turn && params.turn.id);
85115
+ if (threadId !== client.expectedThreadId || turnId == null) answered = false;
85116
+ else if (client.expectedTurnId == null) {
85117
+ answered = false;
85118
+ defer = true;
85119
+ } else if (turnId !== client.expectedTurnId) answered = false;
85120
+ } else answered = false;
85121
+ }
85122
+ const entry = {
84887
85123
  id: msg.id,
84888
85124
  method: msg.method,
84889
- params: msg.params
84890
- });
85125
+ params: msg.params,
85126
+ answered
85127
+ };
85128
+ refused.push(entry);
85129
+ if (defer) {
85130
+ client.deferred.push(entry);
85131
+ return;
85132
+ }
85133
+ if (!answered) return;
84891
85134
  sendJson({
84892
85135
  jsonrpc: "2.0",
84893
85136
  id: msg.id,
@@ -85091,32 +85334,98 @@ const CODEX_VERSION_PROBE_TIMEOUT_MS = 3000;
85091
85334
  probe: "error"
85092
85335
  };
85093
85336
  }
85337
+ /** Bounded `ps` snapshot for Desktop detection: process names only, never pipes or sockets. */ const DESKTOP_PROCESS_LIST_TIMEOUT_MS = 3000;
85338
+ function listDesktopProcesses(timeoutMs = DESKTOP_PROCESS_LIST_TIMEOUT_MS) {
85339
+ if (process.platform === "win32") return "";
85340
+ try {
85341
+ const out = (0,node_child_process__rspack_import_5.spawnSync)("ps", [
85342
+ "-eo",
85343
+ "args"
85344
+ ], {
85345
+ encoding: "utf8",
85346
+ timeout: timeoutMs
85347
+ });
85348
+ if (out.error || out.status !== 0 || typeof out.stdout !== "string") return "";
85349
+ return out.stdout;
85350
+ } catch {
85351
+ return "";
85352
+ }
85353
+ }
85354
+ /**
85355
+ * Pure Desktop private-stdio detector. Returns `"private-stdio"` when the process list
85356
+ * shows Desktop's bundled `.../ChatGPT.app/.../Resources/codex` running `app-server`
85357
+ * (the `codex_app` override corroborates, the Resources path alone suffices); `"unknown"`
85358
+ * otherwise. Never `"detached"`: absence of a match means "not observed", not "not running".
85359
+ * Windows stays `"unknown"` (unsupported route, unchanged).
85360
+ *
85361
+ * @param {{ platform?: string, listProcesses?: string | string[] | null }} [opts]
85362
+ */ function detectDesktopPrivateAppServer({ platform = process.platform, listProcesses = "" } = {}) {
85363
+ if (platform === "win32") return "unknown";
85364
+ const lines = Array.isArray(listProcesses) ? listProcesses : String(listProcesses ?? "").split(/\r?\n/);
85365
+ for (const line of lines){
85366
+ if (!/app-server/.test(line)) continue;
85367
+ // Desktop's bundled binary, Mac or Windows-style separators with optional .exe.
85368
+ if (/ChatGPT\.app[\\/].*Resources[\\/]codex(\.exe)?(["'\s]|$)/.test(line)) return "private-stdio";
85369
+ // Same app-server line owned by ChatGPT.app carrying the app-tools override.
85370
+ if (/ChatGPT\.app/.test(line) && /(codex_app|mcp_servers\.codex_app|codex-app-tools)/.test(line)) return "private-stdio";
85371
+ }
85372
+ return "unknown";
85373
+ }
85094
85374
  /**
85095
85375
  * Status contract: `reachable` is endpoint reachability only. `schema.compatibility` is
85096
85376
  * `exact` when the daemon reports the pinned version, otherwise `unverified` (methods
85097
- * usually survive upgrades) or `unknown`. Whether ChatGPT Desktop owns any thread is not
85098
- * observable from the socket, so `desktopAttached` is always `"unknown"`.
85099
- */ async function codexStatus(env = process.env) {
85377
+ * usually survive upgrades) or `unknown`. `desktopShimConfigured` records an installed
85378
+ * wrapper selected by Desktop-facing CODEX_CLI_PATH (the GUI domain on Darwin).
85379
+ * Configuration does not prove a running Desktop inherited it or connected.
85380
+ * `desktopAttached` is `"private-stdio"` when a Desktop-bundled app-server process
85381
+ * is visible, otherwise `"unknown"`. Managed attachment is not observable here.
85382
+ *
85383
+ * @param {NodeJS.ProcessEnv} [env]
85384
+ * @param {{ listProcesses?: string | string[], shim?: { installed?: boolean, wrapperPointsAtShim?: boolean } | null }} [opts]
85385
+ * injected process list and shim state for tests; defaults to a live `ps` snapshot and
85386
+ * `desktopShimStatus`. Never reads pipes or connects to Desktop.
85387
+ */ async function codexStatus(env = process.env, { listProcesses, shim } = {}) {
85100
85388
  const path = codexSocketPath(env);
85101
85389
  const cli = probeLocalCodexVersion();
85390
+ let desktopShimConfigured = Boolean(shim && shim.installed && shim.wrapperPointsAtShim);
85391
+ if (shim === undefined) {
85392
+ try {
85393
+ const live = (0,_desktop_shim_js__rspack_import_8/* .desktopShimStatus */.em)({
85394
+ env
85395
+ });
85396
+ desktopShimConfigured = Boolean(live.installed && live.wrapperPointsAtShim);
85397
+ } catch {
85398
+ desktopShimConfigured = false;
85399
+ }
85400
+ }
85401
+ const desktopAttached = detectDesktopPrivateAppServer({
85402
+ platform: process.platform,
85403
+ listProcesses: listProcesses ?? listDesktopProcesses()
85404
+ });
85102
85405
  const base = {
85103
85406
  socketPath: path,
85104
85407
  socketState: socketState(path),
85105
85408
  pinnedVersion: PINNED_CODEX_VERSION,
85106
85409
  cliVersion: cli.version,
85107
85410
  cliVersionProbe: cli.probe,
85108
- desktopAttached: "unknown"
85411
+ desktopAttached,
85412
+ desktopShimConfigured
85109
85413
  };
85110
85414
  let session;
85111
85415
  try {
85112
85416
  session = await openSession(env);
85113
85417
  } catch (err) {
85114
- if (err instanceof CodexRouteError) return (0,_codex_contract_js__rspack_import_7/* .withStatusExitCode */.AQ)({
85115
- ...base,
85116
- reachable: false,
85117
- mode: err.mode,
85118
- message: err.message
85119
- });
85418
+ if (err instanceof CodexRouteError) {
85419
+ // Name the private-stdio case explicitly: Desktop is up but unreachable, so the
85420
+ // operator needs a managed standalone daemon, not a Desktop reconnect.
85421
+ const message = err.mode === "socket-absent" ? unreachableMessage(path, desktopAttached) : err.message;
85422
+ return (0,_codex_contract_js__rspack_import_7/* .withStatusExitCode */.AQ)({
85423
+ ...base,
85424
+ reachable: false,
85425
+ mode: err.mode,
85426
+ message
85427
+ });
85428
+ }
85120
85429
  // The endpoint answered; what it said does not match the pinned schema.
85121
85430
  if (err instanceof CodexProtocolError) return (0,_codex_contract_js__rspack_import_7/* .withStatusExitCode */.AQ)({
85122
85431
  ...base,
@@ -85502,6 +85811,16 @@ async function sendToCodexThreadInner(threadId, text, { env, envelope, whenBusy
85502
85811
  // the daemon exposes no compare-and-start request. Upgrade path: thread/queue/add + thread/queue/start once stable.
85503
85812
  // Scope refusals to this turn: server requests from earlier calls belong to another context.
85504
85813
  const seenRefused = client.refused.length;
85814
+ // Arm refusal replies only now that our own turn/start is in flight, scoped
85815
+ // to our own thread/turn: anything recorded earlier (resume, busy reject,
85816
+ // queue) — or naming another thread or Desktop turn — stays unanswered so
85817
+ // gbot never rejects another client's approval. Requests naming a turn
85818
+ // that arrives before the acknowledgment supplies our turn id wait in
85819
+ // client.deferred and are adopted only on a match. Requests missing
85820
+ // thread or turn IDs remain unanswered because ownership is unknown.
85821
+ client.expectedThreadId = threadId;
85822
+ client.expectedTurnId = null;
85823
+ client.answerServerRequests = true;
85505
85824
  let turn;
85506
85825
  try {
85507
85826
  turn = await client.request("turn/start", {
@@ -85536,7 +85855,17 @@ async function sendToCodexThreadInner(threadId, text, { env, envelope, whenBusy
85536
85855
  envelope
85537
85856
  });
85538
85857
  }
85539
- const freshRefused = client.refused.slice(seenRefused).filter((r)=>!r.params || r.params.threadId == null || r.params.threadId === threadId);
85858
+ client._adoptTurn(turnId);
85859
+ const freshRefused = client.refused.slice(seenRefused).filter((r)=>{
85860
+ if (!r.answered) return false;
85861
+ const params = r.params && typeof r.params === "object" && !Array.isArray(r.params) ? r.params : null;
85862
+ if (!params) return true;
85863
+ const refusedThreadId = params.threadId ?? params.thread_id;
85864
+ const refusedTurnId = params.turnId ?? params.turn_id ?? (params.turn && params.turn.id);
85865
+ if (refusedThreadId != null && refusedThreadId !== threadId) return false;
85866
+ if (refusedTurnId != null && refusedTurnId !== turnId) return false;
85867
+ return true;
85868
+ });
85540
85869
  if (freshRefused.length) {
85541
85870
  const methods = freshRefused.map((r)=>r.method).join(", ");
85542
85871
  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.", {
@@ -85772,6 +86101,548 @@ __webpack_require__.d(__webpack_exports__, {
85772
86101
  });
85773
86102
 
85774
86103
 
86104
+ },
86105
+ "./src/core/desktop-shim-bridge.js"(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
86106
+ // Stdio JSONL (Desktop) <-> WebSocket-over-unix bridge onto the managed
86107
+ // Codex app-server control socket. Vendored from uploads/codex-stdio-to-daemon-ws.py
86108
+ // (ChatGPT Desktop -> managed daemon shim) with fail-open deltas, marked below:
86109
+ // (1) ws_connect enforces one absolute monotonic deadline for connect + HTTP
86110
+ // upgrade (CODEX_BRIDGE_CONNECT_TIMEOUT, default 10s), requires HTTP/1.1 101
86111
+ // plus a matching Sec-WebSocket-Accept (a 200 fails even with a valid hash),
86112
+ // and retains bytes coalesced after the upgrade headers instead of discarding
86113
+ // the first data frame.
86114
+ // (2) read_message assembles continuation frames; control frames answer inline;
86115
+ // every socket send/lock shares one monotonic budget (remaining first-RPC
86116
+ // budget, else the mid-session write budget) so a non-reading peer cannot
86117
+ // wedge pong writes or timeout cleanup; timeouts close and exit. Reads wait
86118
+ // with no timeout after the handshake so healthy idle sessions survive.
86119
+ // The wrapper never starts the daemon on the spawn path, so no daemon lock can
86120
+ // block pre-stdin Desktop.
86121
+ // (3) A first-response deadline (CODEX_BRIDGE_FIRST_MESSAGE_TIMEOUT, default
86122
+ // 30s) bounds connect-to-matching-daemon-response covering the first RPC;
86123
+ // unrelated notifications and foreign responses never satisfy it, only the
86124
+ // matching response/error (or timeout) clears it. The preflight (3s) +
86125
+ // upgrade (10s) + first RPC (30s) budget stays far below a daemon-lock hang.
86126
+ // Exits are 0 served, 1 pre-stdio (wrapper falls back to real Codex on pristine
86127
+ // stdio+stdout), 2 mid-session (wrapper exits so Desktop reconnects).
86128
+ // (4) Byte-precise commit on input OR output, committed BEFORE the stdout
86129
+ // write: os.read into a userspace line buffer counts every stdin byte (even
86130
+ // an unforwarded blank line), and any daemon payload marks stdout used before
86131
+ // it is written via a bounded stdout write, so the fallback only ever runs
86132
+ // on truly pristine stdio+stdout and never spawns a second Codex after used
86133
+ // stdout. Buffered EOF-tail data flushes before the WS Close, which drains
86134
+ // via shutdown+close with a bounded join. The reader signals a done event so
86135
+ // WS close wakes the select loop; no path leaves a thread hung.
86136
+ // Desktop stdio hangs with stock `codex app-server proxy`, so this custom
86137
+ // bridge is required. Install writes it to the CODEX_HOME bin dir. Never reads
86138
+ // Desktop private pipes (CODEX_APP_TOOLS_PIPE_PATH, /tmp/codex-browser-use/).
86139
+ 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";
86140
+
86141
+ __webpack_require__.d(__webpack_exports__, {
86142
+ }, {
86143
+ w: BRIDGE_SOURCE
86144
+ });
86145
+
86146
+
86147
+ },
86148
+ "./src/core/desktop-shim.js"(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
86149
+ /* import */ var node_child_process__rspack_import_0 = __webpack_require__("node:child_process");
86150
+ /* import */ var node_fs__rspack_import_1 = __webpack_require__("node:fs");
86151
+ /* import */ var node_os__rspack_import_2 = __webpack_require__("node:os");
86152
+ /* import */ var node_path__rspack_import_3 = __webpack_require__("node:path");
86153
+ /* import */ var _desktop_shim_bridge_js__rspack_import_4 = __webpack_require__("./src/core/desktop-shim-bridge.js");
86154
+
86155
+
86156
+
86157
+
86158
+
86159
+ /**
86160
+ * ChatGPT Desktop -> managed Codex daemon shim (CODEX_CLI_PATH bridge).
86161
+ *
86162
+ * Desktop injects `codex_app` overrides so CODEX_APP_SERVER_USE_LOCAL_DAEMON=1
86163
+ * alone cannot select the managed daemon, and stock `codex app-server proxy`
86164
+ * hangs for Desktop stdio. The winning path is a wrapper on CODEX_CLI_PATH
86165
+ * that rewrites Desktop's `codex ... app-server` spawn into the stdio<->WS
86166
+ * bridge (desktop-shim-bridge.js) onto the managed control socket.
86167
+ * Provenance: uploads/codex-desktop-to-daemon + codex-desktop-shared-daemon-env.sh.
86168
+ *
86169
+ * Fail-open is the hard rule: the wrapper execs the real standalone `codex`
86170
+ * for non-app-server spawns and whenever the bridge/python/socket side is
86171
+ * missing or broken. Never touches Desktop binaries, /tmp/codex-browser-use/,
86172
+ * or CODEX_APP_TOOLS_PIPE_PATH. No protocol change: gbot already speaks the
86173
+ * managed control socket.
86174
+ */ const SHIM_LABEL = "com.zackjackson.codex-desktop-shared-daemon";
86175
+ const WRAPPER_FILENAME = "codex-desktop-to-daemon";
86176
+ const BRIDGE_FILENAME = "codex-stdio-to-daemon-ws.py";
86177
+ const ENV_SCRIPT_FILENAME = "codex-desktop-shared-daemon-env.sh";
86178
+ const BRIDGE_LOG_FILENAME = "codex-stdio-to-daemon-ws.log";
86179
+ const WRAPPER_LOG_FILENAME = "codex-desktop-to-daemon.log";
86180
+ const ENV_LOG_FILENAME = "codex-desktop-shared-daemon-env.log";
86181
+ const STANDALONE_REAL_SUFFIX = (0,node_path__rspack_import_3.join)("packages", "standalone", "current", "bin", "codex");
86182
+ function codexHomeDir(env = process.env) {
86183
+ return env.CODEX_HOME || join(env.HOME || homedir(), ".codex");
86184
+ }
86185
+ function userHomeDir(env = process.env) {
86186
+ return env.HOME || (0,node_os__rspack_import_2.homedir)();
86187
+ }
86188
+ function defaultPaths({ env = process.env, home = userHomeDir(env) } = {}) {
86189
+ const codexHome = env.CODEX_HOME || (0,node_path__rspack_import_3.join)(home, ".codex");
86190
+ const binDir = (0,node_path__rspack_import_3.join)(codexHome, "bin");
86191
+ return {
86192
+ binDir,
86193
+ bridgeLogPath: (0,node_path__rspack_import_3.join)(binDir, BRIDGE_LOG_FILENAME),
86194
+ bridgePath: (0,node_path__rspack_import_3.join)(binDir, BRIDGE_FILENAME),
86195
+ codexHome,
86196
+ envLogPath: (0,node_path__rspack_import_3.join)(binDir, ENV_LOG_FILENAME),
86197
+ envScriptPath: (0,node_path__rspack_import_3.join)(binDir, ENV_SCRIPT_FILENAME),
86198
+ label: SHIM_LABEL,
86199
+ launchAgentsDir: (0,node_path__rspack_import_3.join)(home, "Library", "LaunchAgents"),
86200
+ plistPath: (0,node_path__rspack_import_3.join)(home, "Library", "LaunchAgents", `${SHIM_LABEL}.plist`),
86201
+ realPath: (0,node_path__rspack_import_3.join)(codexHome, STANDALONE_REAL_SUFFIX),
86202
+ socketPath: (0,node_path__rspack_import_3.join)(codexHome, "app-server-control", "app-server-control.sock"),
86203
+ wrapperLogPath: (0,node_path__rspack_import_3.join)(binDir, WRAPPER_LOG_FILENAME),
86204
+ wrapperPath: (0,node_path__rspack_import_3.join)(binDir, WRAPPER_FILENAME)
86205
+ };
86206
+ }
86207
+ /**
86208
+ * Pure passthrough decision, mirroring the wrapper's argv scan. True only for
86209
+ * a bare `app-server` spawn: Desktop's private-stdio case. `daemon`, `proxy`,
86210
+ * and schema generations always exec the real Codex.
86211
+ */ function shouldBridge(argv) {
86212
+ let appServer = false;
86213
+ let daemon = false;
86214
+ let proxy = false;
86215
+ let generate = false;
86216
+ for (const arg of argv ?? []){
86217
+ if (arg === "app-server") appServer = true;
86218
+ else if (arg === "daemon") daemon = true;
86219
+ else if (arg === "proxy") proxy = true;
86220
+ else if (arg === "generate-ts" || arg === "generate-json-schema") generate = true;
86221
+ }
86222
+ return appServer && !daemon && !proxy && !generate;
86223
+ }
86224
+ function renderWrapperScript({ realPath, bridgePath, wrapperLogPath, codexHome, bridgeLogPath }) {
86225
+ return `#!/bin/bash
86226
+ # Installed by \`gbot codex desktop-shim install\`. Rewrites ChatGPT Desktop's
86227
+ # \`codex ... app-server\` spawn into a stdio<->WebSocket bridge onto the managed
86228
+ # daemon's control socket. Reversible: \`gbot codex desktop-shim uninstall\`.
86229
+ # The daemon is kept up by the LaunchAgent login script, not here: this hot path
86230
+ # never starts the daemon itself, so a wedged daemon lock cannot hang
86231
+ # Desktop. Fail-open runs ONLY while stdio is still pristine (no stdin
86232
+ # consumed, no stdout written): an absent socket fails the preflight and a
86233
+ # pre-session bridge failure (exit 1) falls through
86234
+ # to the real standalone codex on pristine stdio. A mid-session bridge failure
86235
+ # (exit 2+) exits promptly so Desktop reconnects; the fallback never runs on
86236
+ # half-consumed stdin or dirty stdout. Bridge exit 0 means it served the session. Never touches
86237
+ # Desktop binaries or its private tool pipe.
86238
+ set -u
86239
+ REAL="\${CODEX_DESKTOP_WRAPPER_REAL:-${realPath}}"
86240
+ BRIDGE="\${CODEX_DESKTOP_BRIDGE:-${bridgePath}}"
86241
+ export CODEX_HOME="\${CODEX_HOME:-${codexHome}}"
86242
+ SOCK="\${CODEX_APP_SERVER_SOCK:-$CODEX_HOME/app-server-control/app-server-control.sock}"
86243
+ BRIDGE_LOG="\${CODEX_STDIO_BRIDGE_LOG:-${bridgeLogPath}}"
86244
+ LOG="\${CODEX_DESKTOP_WRAPPER_LOG:-${wrapperLogPath}}"
86245
+ export CODEX_APP_SERVER_SOCK="$SOCK"
86246
+ export CODEX_STDIO_BRIDGE_LOG="$BRIDGE_LOG"
86247
+ ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
86248
+ echo "$(ts) argv: $*" >>"$LOG" 2>/dev/null || true
86249
+
86250
+ if [[ ! -x "$REAL" ]]; then
86251
+ FALLBACK="$(command -v codex 2>/dev/null || true)"
86252
+ if [[ -n "$FALLBACK" && -x "$FALLBACK" ]]; then
86253
+ if [[ "$FALLBACK" -ef "$0" ]] 2>/dev/null || { [[ -n "\${CODEX_CLI_PATH:-}" ]] && [[ "$FALLBACK" -ef "\${CODEX_CLI_PATH}" ]] 2>/dev/null; }; then
86254
+ echo "$(ts) refusing self fallback $FALLBACK" >>"$LOG" 2>/dev/null || true
86255
+ else
86256
+ REAL="$FALLBACK"
86257
+ fi
86258
+ fi
86259
+ fi
86260
+
86261
+ has_app_server=0; has_daemon=0; has_proxy=0; has_generate=0
86262
+ for a in "$@"; do
86263
+ case "$a" in
86264
+ app-server) has_app_server=1 ;;
86265
+ daemon) has_daemon=1 ;;
86266
+ proxy) has_proxy=1 ;;
86267
+ generate-ts|generate-json-schema) has_generate=1 ;;
86268
+ esac
86269
+ done
86270
+
86271
+ # Preflight the daemon socket with a short deadline: a refused/absent socket
86272
+ # means starting the bridge could never succeed, so skip straight to real codex.
86273
+ preflight() {
86274
+ 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
86275
+ }
86276
+
86277
+ if [[ "$has_app_server" -eq 1 && "$has_daemon" -eq 0 && "$has_proxy" -eq 0 && "$has_generate" -eq 0 ]]; then
86278
+ if [[ -f "$BRIDGE" ]] && command -v python3 >/dev/null 2>&1; then
86279
+ if preflight; then
86280
+ echo "$(ts) rewrite -> stdio-ws bridge" >>"$LOG" 2>/dev/null || true
86281
+ python3 "$BRIDGE"
86282
+ rc=$?
86283
+ case "$rc" in
86284
+ 0)
86285
+ echo "$(ts) bridge served session" >>"$LOG" 2>/dev/null || true
86286
+ exit 0
86287
+ ;;
86288
+ 1)
86289
+ echo "$(ts) bridge failed before session start, falling through to real codex" >>"$LOG" 2>/dev/null || true
86290
+ ;;
86291
+ *)
86292
+ echo "$(ts) bridge failed mid-session (rc=$rc); exiting so Desktop reconnects" >>"$LOG" 2>/dev/null || true
86293
+ exit "$rc"
86294
+ ;;
86295
+ esac
86296
+ else
86297
+ echo "$(ts) daemon socket unreachable, passthrough to real codex" >>"$LOG" 2>/dev/null || true
86298
+ fi
86299
+ else
86300
+ echo "$(ts) bridge unavailable, passthrough to real codex" >>"$LOG" 2>/dev/null || true
86301
+ fi
86302
+ fi
86303
+ if [[ -x "$REAL" ]]; then
86304
+ exec "$REAL" "$@"
86305
+ fi
86306
+ echo "codex-desktop-to-daemon: no runnable codex found" >&2
86307
+ exit 1
86308
+ `;
86309
+ }
86310
+ function renderEnvScript({ wrapperPath, realPath, envLogPath, codexHome }) {
86311
+ return `#!/bin/bash
86312
+ # Installed by \`gbot codex desktop-shim install\`. Re-applies the GUI-domain env
86313
+ # so ChatGPT.app inherits CODEX_CLI_PATH after login, then best-effort starts the
86314
+ # managed daemon (this login script — never Desktop's spawn hot path — owns
86315
+ # daemon upkeep). No -e: every step is best-effort so login never breaks.
86316
+ # Revert: \`gbot codex desktop-shim uninstall\`.
86317
+ set -u
86318
+ WRAPPER="${wrapperPath}"
86319
+ REAL="${realPath}"
86320
+ LOG="${envLogPath}"
86321
+ CODEX_HOME_DIR="${codexHome}"
86322
+ # One CODEX_HOME for the GUI domain and this script's own daemon start, so the
86323
+ # daemon that is kept up is the one the wrapper bridges to.
86324
+ export CODEX_HOME="$CODEX_HOME_DIR"
86325
+ ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
86326
+ {
86327
+ echo "$(ts) start"
86328
+ if [[ ! -x "$WRAPPER" ]]; then
86329
+ echo "$(ts) missing wrapper: $WRAPPER"
86330
+ exit 0
86331
+ fi
86332
+ # GUI domain setenv so ChatGPT.app inherits CODEX_CLI_PATH after login
86333
+ launchctl setenv CODEX_CLI_PATH "$WRAPPER"
86334
+ launchctl setenv CODEX_HOME "$CODEX_HOME_DIR"
86335
+ launchctl setenv CODEX_APP_SERVER_USE_LOCAL_DAEMON 1
86336
+ launchctl unsetenv CODEX_APP_SERVER_WS_URL 2>/dev/null || true
86337
+ echo "$(ts) CODEX_CLI_PATH=$(launchctl getenv CODEX_CLI_PATH)"
86338
+ if [[ -x "$REAL" ]]; then
86339
+ "$REAL" app-server daemon start >/dev/null 2>&1 || true
86340
+ echo "$(ts) daemon start attempted"
86341
+ fi
86342
+ echo "$(ts) done"
86343
+ } >>"$LOG" 2>&1
86344
+ `;
86345
+ }
86346
+ function renderPlist({ envScriptPath, label = SHIM_LABEL }) {
86347
+ return `<?xml version="1.0" encoding="UTF-8"?>
86348
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
86349
+ <plist version="1.0">
86350
+ <dict>
86351
+ \t<key>Label</key>
86352
+ \t<string>${label}</string>
86353
+ \t<key>ProgramArguments</key>
86354
+ \t<array>
86355
+ \t\t<string>/bin/bash</string>
86356
+ \t\t<string>${envScriptPath}</string>
86357
+ \t</array>
86358
+ \t<key>RunAtLoad</key>
86359
+ \t<true/>
86360
+ \t<key>LimitLoadToSessionType</key>
86361
+ \t<string>Aqua</string>
86362
+ </dict>
86363
+ </plist>
86364
+ `;
86365
+ }
86366
+ const defaultRunner = (file, args)=>{
86367
+ try {
86368
+ return (0,node_child_process__rspack_import_0.spawnSync)(file, args, {
86369
+ encoding: "utf8",
86370
+ timeout: 15000
86371
+ });
86372
+ } catch (error) {
86373
+ return {
86374
+ error,
86375
+ status: null
86376
+ };
86377
+ }
86378
+ };
86379
+ function runBestEffort(runner, warnings, what, file, args) {
86380
+ const out = runner(file, args);
86381
+ if (out && (out.error || typeof out.status === "number" && out.status !== 0)) {
86382
+ const detail = out.error && out.error.message || out.stderr && String(out.stderr).trim() || `exit ${out.status}`;
86383
+ warnings.push(`${what} failed (${detail}); continuing`);
86384
+ }
86385
+ return out;
86386
+ }
86387
+ function writeExecutable(path, content) {
86388
+ (0,node_fs__rspack_import_1.writeFileSync)(path, content, {
86389
+ mode: 493
86390
+ });
86391
+ (0,node_fs__rspack_import_1.chmodSync)(path, 493);
86392
+ }
86393
+ function isExecutable(path) {
86394
+ try {
86395
+ (0,node_fs__rspack_import_1.statSync)(path);
86396
+ } catch {
86397
+ return false;
86398
+ }
86399
+ try {
86400
+ // X_OK without throwing: accessSync is clearer; stat mode bit check is sync-safe.
86401
+ return ((0,node_fs__rspack_import_1.statSync)(path).mode & 73) !== 0;
86402
+ } catch {
86403
+ return false;
86404
+ }
86405
+ }
86406
+ function fileExists(path) {
86407
+ try {
86408
+ (0,node_fs__rspack_import_1.statSync)(path);
86409
+ return true;
86410
+ } catch {
86411
+ return false;
86412
+ }
86413
+ }
86414
+ /**
86415
+ * Copy the wrapper + bridge + login env script out of the package into durable
86416
+ * ~/.codex paths (never a git checkout), then persist CODEX_CLI_PATH for the
86417
+ * macOS Aqua session via LaunchAgent. Idempotent.
86418
+ */ function installDesktopShim({ env = process.env, home = userHomeDir(env), platform = process.platform, runner = defaultRunner } = {}) {
86419
+ const paths = defaultPaths({
86420
+ env,
86421
+ home
86422
+ });
86423
+ const warnings = [];
86424
+ (0,node_fs__rspack_import_1.mkdirSync)(paths.binDir, {
86425
+ recursive: true
86426
+ });
86427
+ writeExecutable(paths.wrapperPath, renderWrapperScript(paths));
86428
+ (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`, {
86429
+ mode: 420
86430
+ });
86431
+ writeExecutable(paths.envScriptPath, renderEnvScript(paths));
86432
+ let persisted = false;
86433
+ if (platform === "darwin") {
86434
+ (0,node_fs__rspack_import_1.mkdirSync)(paths.launchAgentsDir, {
86435
+ recursive: true
86436
+ });
86437
+ (0,node_fs__rspack_import_1.writeFileSync)(paths.plistPath, renderPlist({
86438
+ envScriptPath: paths.envScriptPath
86439
+ }), {
86440
+ mode: 420
86441
+ });
86442
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
86443
+ if (uid === null || uid === undefined) {
86444
+ warnings.push("could not determine uid; LaunchAgent file written but not bootstrapped — log out/in or bootstrap it manually");
86445
+ } else {
86446
+ const target = `gui/${uid}/${paths.label}`;
86447
+ runBestEffort(runner, warnings, "launchctl bootout (previous agent)", "launchctl", [
86448
+ "bootout",
86449
+ target
86450
+ ]);
86451
+ runBestEffort(runner, warnings, "launchctl bootstrap", "launchctl", [
86452
+ "bootstrap",
86453
+ `gui/${uid}`,
86454
+ paths.plistPath
86455
+ ]);
86456
+ }
86457
+ runBestEffort(runner, warnings, "launchctl setenv CODEX_CLI_PATH", "launchctl", [
86458
+ "setenv",
86459
+ "CODEX_CLI_PATH",
86460
+ paths.wrapperPath
86461
+ ]);
86462
+ runBestEffort(runner, warnings, "launchctl setenv CODEX_APP_SERVER_USE_LOCAL_DAEMON", "launchctl", [
86463
+ "setenv",
86464
+ "CODEX_APP_SERVER_USE_LOCAL_DAEMON",
86465
+ "1"
86466
+ ]);
86467
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_APP_SERVER_WS_URL", "launchctl", [
86468
+ "unsetenv",
86469
+ "CODEX_APP_SERVER_WS_URL"
86470
+ ]);
86471
+ persisted = true;
86472
+ } else {
86473
+ warnings.push("LaunchAgent persistence is macOS-only; wrapper and bridge installed — export CODEX_CLI_PATH manually");
86474
+ }
86475
+ if (isExecutable(paths.realPath)) {
86476
+ runBestEffort(runner, warnings, "codex app-server daemon start", paths.realPath, [
86477
+ "app-server",
86478
+ "daemon",
86479
+ "start"
86480
+ ]);
86481
+ } else {
86482
+ warnings.push(`managed daemon not started: no executable at ${paths.realPath}`);
86483
+ }
86484
+ return {
86485
+ action: "install",
86486
+ bridgePath: paths.bridgePath,
86487
+ envScriptPath: paths.envScriptPath,
86488
+ exitCode: 0,
86489
+ persisted,
86490
+ plistPath: platform === "darwin" ? paths.plistPath : null,
86491
+ realPath: paths.realPath,
86492
+ warnings,
86493
+ wrapperPath: paths.wrapperPath
86494
+ };
86495
+ }
86496
+ /** 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 } = {}) {
86497
+ const paths = defaultPaths({
86498
+ env,
86499
+ home
86500
+ });
86501
+ const warnings = [];
86502
+ const removed = [];
86503
+ for (const path of [
86504
+ paths.wrapperPath,
86505
+ paths.bridgePath,
86506
+ paths.envScriptPath
86507
+ ]){
86508
+ const existed = fileExists(path);
86509
+ try {
86510
+ if ((0,node_fs__rspack_import_1.rmSync)(path, {
86511
+ force: true
86512
+ }) === undefined && fileExists(path)) warnings.push(`could not remove ${path}`);
86513
+ else if (existed && !fileExists(path)) removed.push(path);
86514
+ } catch (error) {
86515
+ warnings.push(`could not remove ${path} (${error instanceof Error ? error.message : String(error)})`);
86516
+ }
86517
+ }
86518
+ let plistRemoved = false;
86519
+ if (platform === "darwin") {
86520
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
86521
+ if (uid !== null && uid !== undefined) {
86522
+ runBestEffort(runner, warnings, "launchctl bootout", "launchctl", [
86523
+ "bootout",
86524
+ `gui/${uid}/${paths.label}`
86525
+ ]);
86526
+ }
86527
+ try {
86528
+ (0,node_fs__rspack_import_1.rmSync)(paths.plistPath, {
86529
+ force: true
86530
+ });
86531
+ plistRemoved = !fileExists(paths.plistPath);
86532
+ } catch (error) {
86533
+ warnings.push(`could not remove ${paths.plistPath} (${error instanceof Error ? error.message : String(error)})`);
86534
+ }
86535
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_CLI_PATH", "launchctl", [
86536
+ "unsetenv",
86537
+ "CODEX_CLI_PATH"
86538
+ ]);
86539
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_HOME", "launchctl", [
86540
+ "unsetenv",
86541
+ "CODEX_HOME"
86542
+ ]);
86543
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_APP_SERVER_USE_LOCAL_DAEMON", "launchctl", [
86544
+ "unsetenv",
86545
+ "CODEX_APP_SERVER_USE_LOCAL_DAEMON"
86546
+ ]);
86547
+ runBestEffort(runner, warnings, "launchctl unsetenv CODEX_APP_SERVER_WS_URL", "launchctl", [
86548
+ "unsetenv",
86549
+ "CODEX_APP_SERVER_WS_URL"
86550
+ ]);
86551
+ }
86552
+ return {
86553
+ action: "uninstall",
86554
+ exitCode: 0,
86555
+ plistPath: platform === "darwin" ? paths.plistPath : null,
86556
+ plistRemoved,
86557
+ removed,
86558
+ warnings,
86559
+ wrapperPath: paths.wrapperPath
86560
+ };
86561
+ }
86562
+ function socketStateOf(path) {
86563
+ try {
86564
+ return (0,node_fs__rspack_import_1.statSync)(path).isSocket() ? "socket" : "not-a-socket";
86565
+ } catch (error) {
86566
+ return error && (error.code === "EACCES" || error.code === "EPERM") ? "permission-denied" : "absent";
86567
+ }
86568
+ }
86569
+ /**
86570
+ * Inspect the shim without touching Desktop, pipes, or sockets beyond a stat.
86571
+ * On macOS the Desktop-facing CODEX_CLI_PATH lives in launchd's GUI domain,
86572
+ * which a shell-spawned `gbot` does not inherit — so status queries
86573
+ * `launchctl getenv` there (via the injectable runner) and reports the caller
86574
+ * environment separately. Elsewhere the process env is the whole story.
86575
+ */ function desktopShimStatus({ env = process.env, home = userHomeDir(env), platform = process.platform, runner = defaultRunner } = {}) {
86576
+ const paths = defaultPaths({
86577
+ env,
86578
+ home
86579
+ });
86580
+ const warnings = [];
86581
+ const wrapperPresent = fileExists(paths.wrapperPath);
86582
+ const bridgePresent = fileExists(paths.bridgePath);
86583
+ const envScriptPresent = fileExists(paths.envScriptPath);
86584
+ const plistPresent = platform === "darwin" ? fileExists(paths.plistPath) : false;
86585
+ const cliPath = env.CODEX_CLI_PATH || null;
86586
+ let guiCliPath = null;
86587
+ if (platform === "darwin") {
86588
+ try {
86589
+ const out = runner("launchctl", [
86590
+ "getenv",
86591
+ "CODEX_CLI_PATH"
86592
+ ]);
86593
+ const value = out && typeof out.stdout === "string" ? out.stdout.trim() : "";
86594
+ guiCliPath = value || null;
86595
+ if (out && (out.error || typeof out.status === "number" && out.status !== 0)) {
86596
+ warnings.push("launchctl getenv CODEX_CLI_PATH failed; GUI-domain state unknown");
86597
+ guiCliPath = null;
86598
+ }
86599
+ } catch (error) {
86600
+ warnings.push(`launchctl getenv CODEX_CLI_PATH failed (${error instanceof Error ? error.message : String(error)})`);
86601
+ guiCliPath = null;
86602
+ }
86603
+ }
86604
+ // Desktop-facing value: the GUI domain on macOS, the caller env elsewhere.
86605
+ const desktopCliPath = platform === "darwin" ? guiCliPath : cliPath;
86606
+ const installed = wrapperPresent && isExecutable(paths.wrapperPath) && bridgePresent;
86607
+ // Effective socket, resolved exactly like gbot's own client (CODEX_APP_SERVER_SOCK
86608
+ // wins, else the install-time CODEX_HOME) so status and send/queue/status never
86609
+ // disagree. Inlined here so codex-bridge can reuse desktopShimStatus without a cycle.
86610
+ const socketPath = env.CODEX_APP_SERVER_SOCK || (0,node_path__rspack_import_3.join)(paths.codexHome, "app-server-control", "app-server-control.sock");
86611
+ return {
86612
+ action: "status",
86613
+ bridgePath: paths.bridgePath,
86614
+ bridgePresent,
86615
+ cliPath,
86616
+ codexHome: paths.codexHome,
86617
+ desktopCliPath,
86618
+ envScriptPath: paths.envScriptPath,
86619
+ envScriptPresent,
86620
+ exitCode: installed ? 0 : 1,
86621
+ guiCliPath,
86622
+ installed,
86623
+ persisted: platform === "darwin" ? plistPresent : null,
86624
+ platform,
86625
+ plistPath: platform === "darwin" ? paths.plistPath : null,
86626
+ plistPresent,
86627
+ realPath: paths.realPath,
86628
+ socketPath,
86629
+ socketSource: env.CODEX_APP_SERVER_SOCK ? "CODEX_APP_SERVER_SOCK" : "CODEX_HOME",
86630
+ socketState: socketStateOf(socketPath),
86631
+ warnings,
86632
+ wrapperExecutable: wrapperPresent && isExecutable(paths.wrapperPath),
86633
+ wrapperPath: paths.wrapperPath,
86634
+ wrapperPointsAtShim: desktopCliPath === paths.wrapperPath,
86635
+ wrapperPresent
86636
+ };
86637
+ }
86638
+
86639
+ __webpack_require__.d(__webpack_exports__, {
86640
+ B0: () => (installDesktopShim),
86641
+ em: () => (desktopShimStatus),
86642
+ gM: () => (uninstallDesktopShim)
86643
+ });
86644
+
86645
+
85775
86646
  },
85776
86647
  "./src/core/format.js"(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
85777
86648
  /* import */ var _transcript_js__rspack_import_0 = __webpack_require__("./src/core/transcript.js");
@@ -85844,6 +86715,7 @@ function formatCodexStatus(s) {
85844
86715
  const lines = [
85845
86716
  "socket: " + s.socketPath + " (" + s.socketState + ")"
85846
86717
  ];
86718
+ if (s.desktopShimConfigured) lines.push("desktop shim: configured (managed attachment unverified)");
85847
86719
  if (!s.reachable) return lines.concat("reachable: no (" + s.mode + ")", s.message).join("\n");
85848
86720
  if (s.mode !== "daemon") {
85849
86721
  return lines.concat("reachable: yes, but unusable (" + s.mode + ")", s.message).join("\n");
@@ -85851,7 +86723,7 @@ function formatCodexStatus(s) {
85851
86723
  lines.push("reachable: yes (daemon)");
85852
86724
  const cli = s.cliVersion ?? (s.cliVersionProbe === "ok" ? "unknown" : "unknown, probe " + s.cliVersionProbe);
85853
86725
  lines.push("daemon version: " + (s.daemonVersion ?? "unknown") + " cli version: " + cli + " pinned schema: " + s.pinnedVersion + " (" + s.schema.compatibility + ")");
85854
- lines.push("desktop attached: unknown (not observable from the socket)");
86726
+ 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)");
85855
86727
  if (s.versionMismatch) {
85856
86728
  lines.push("warning: daemon and CLI versions differ; `codex app-server daemon restart` picks up the installed CLI");
85857
86729
  }
@@ -85866,12 +86738,37 @@ function formatCodexThread(t) {
85866
86738
  const preview = t.preview ? "\n " + stripTerminalControls(String(t.preview).replace(/\s+/g, " ")).slice(0, 200) : "";
85867
86739
  return stripTerminalControls(t.id) + " " + stripTerminalControls(t.status) + title + "\n " + stripTerminalControls(t.cwd ?? "") + preview;
85868
86740
  }
86741
+ /** Shell-safe single-quoting for copy-pasteable export lines (spaces, quotes, $). */ function shellQuote(value) {
86742
+ return "'" + String(value).replace(/'/g, "'\\''") + "'";
86743
+ }
86744
+ function formatDesktopShimStatus(s) {
86745
+ const mark = (ok)=>ok ? "yes" : "no";
86746
+ 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)" : "");
86747
+ const lines = [
86748
+ "wrapper: " + s.wrapperPath + " (" + (s.wrapperExecutable ? "executable" : s.wrapperPresent ? "present, not executable" : "absent") + ")",
86749
+ "bridge: " + s.bridgePath + " (" + mark(s.bridgePresent) + ")",
86750
+ "login env: " + s.envScriptPath + " (" + mark(s.envScriptPresent) + ")",
86751
+ s.platform === "darwin" ? "LaunchAgent: " + s.plistPath + " (" + mark(s.plistPresent) + ")" : "LaunchAgent: n/a (macOS-only)",
86752
+ cliLine,
86753
+ "daemon socket: " + s.socketPath + " (" + s.socketState + ", from " + (s.socketSource ?? "CODEX_HOME") + ")"
86754
+ ];
86755
+ if (!s.installed) {
86756
+ lines.push("shim: not installed — run `gbot codex desktop-shim install` (Desktop keeps stock behavior until then)");
86757
+ } else if (!s.wrapperPointsAtShim) {
86758
+ 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));
86759
+ } else {
86760
+ lines.push("shim: active — Desktop app-server spawns bridge onto the managed daemon");
86761
+ }
86762
+ for (const warning of s.warnings ?? [])lines.push("warning: " + String(warning));
86763
+ return lines.join("\n");
86764
+ }
85869
86765
 
85870
86766
  __webpack_require__.d(__webpack_exports__, {
85871
86767
  P2: () => (formatCodexStatus),
85872
86768
  bM: () => (formatRecord),
85873
86769
  e_: () => (formatCodexQueue),
85874
86770
  eh: () => (summarize),
86771
+ fX: () => (formatDesktopShimStatus),
85875
86772
  nz: () => (formatCodexThread),
85876
86773
  oZ: () => (formatTranscript)
85877
86774
  });
@@ -86955,6 +87852,8 @@ __webpack_require__.d(__webpack_exports__, {
86955
87852
  * never *.cursorvm.com, so a CURSOR_ACCESS_TOKEN cannot be pointed at a box host.
86956
87853
  * Local/dev gateways: http(s)://127.0.0.1|localhost|::1 when GROK_BOT_ALLOW_LOCAL_GATEWAY=1.
86957
87854
  * Escape hatch: GROK_BOT_ALLOW_ANY_GATEWAY=1 (unsafe; disables host checks; warns once).
87855
+ * Test mode (GROK_BOT_TEST=1 or NODE_ENV=test): loopback only, for gateway and backend
87856
+ * alike, and the escape hatches are ignored — a test can never reach a live thread.
86958
87857
  */ function truthyEnv(name) {
86959
87858
  const v = (process.env[name] || "").trim().toLowerCase();
86960
87859
  return v === "1" || v === "true" || v === "yes";
@@ -86965,6 +87864,9 @@ function allowAnyGateway() {
86965
87864
  function allowLocalGateway() {
86966
87865
  return truthyEnv("GROK_BOT_ALLOW_LOCAL_GATEWAY");
86967
87866
  }
87867
+ function testMode() {
87868
+ return truthyEnv("GROK_BOT_TEST") || process.env.NODE_ENV === "test";
87869
+ }
86968
87870
  const warned = new Set();
86969
87871
  function warnOnce(key, message) {
86970
87872
  if (warned.has(key)) return;
@@ -87004,6 +87906,12 @@ function isCursorGatewayHostname(hostname) {
87004
87906
  if (parsed.username || parsed.password) {
87005
87907
  throw new Error("Rejected " + label + ": userinfo is not allowed.");
87006
87908
  }
87909
+ if (testMode()) {
87910
+ if (!isLocalHostname(parsed.hostname) || parsed.protocol !== "http:" && parsed.protocol !== "https:") {
87911
+ throw new Error("Rejected " + label + " host \"" + parsed.hostname + "\": test mode (GROK_BOT_TEST / NODE_ENV=test) only allows http(s) loopback gateways.");
87912
+ }
87913
+ return String(rawUrl).replace(/\/$/, "");
87914
+ }
87007
87915
  if (allowAnyGateway()) {
87008
87916
  warnOnce("ALLOW_ANY", "warning: GROK_BOT_ALLOW_ANY_GATEWAY is set; credential host checks are disabled.");
87009
87917
  return String(rawUrl).replace(/\/$/, "");