tide-commander 0.84.3 → 0.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{W as s}from"./main-Cl7abmzo.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react-uS-d4TUT.js";import"./vendor-three-DJ4p3FLF.js";class f extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{f as LocalNotificationsWeb};
1
+ import{W as s}from"./main-hADDQllW.js";import"./modulepreload-polyfill-B5Qt9EMX.js";import"./vendor-react-uS-d4TUT.js";import"./vendor-three-DJ4p3FLF.js";class f extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{f as LocalNotificationsWeb};
package/dist/index.html CHANGED
@@ -22,11 +22,11 @@
22
22
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
23
23
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
24
24
  <title>Tide Commander</title>
25
- <script type="module" crossorigin src="/assets/main-Cl7abmzo.js"></script>
25
+ <script type="module" crossorigin src="/assets/main-hADDQllW.js"></script>
26
26
  <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-uS-d4TUT.js">
28
28
  <link rel="modulepreload" crossorigin href="/assets/vendor-three-DJ4p3FLF.js">
29
- <link rel="stylesheet" crossorigin href="/assets/main-C6IAMrFB.css">
29
+ <link rel="stylesheet" crossorigin href="/assets/main-BB4dFHqm.css">
30
30
  </head>
31
31
  <body>
32
32
  <div id="app"></div>
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "Erforderlich wenn der Server AUTH_TOKEN gesetzt hat",
54
54
  "saveAndReconnect": "Speichern und neu verbinden",
55
55
  "showToken": "Token anzeigen",
56
- "hideToken": "Token ausblenden"
56
+ "hideToken": "Token ausblenden",
57
+ "codexBinaryPath": "Codex-Binärpfad",
58
+ "codexBinaryPathPlaceholder": "Leer lassen für automatische Erkennung",
59
+ "codexBinaryPathHint": "Pfad zur nativen Codex-Binärdatei. Umgeht den Node.js-Wrapper für bessere Sitzungspersistenz."
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "Charaktergröße",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "Required if server has AUTH_TOKEN set",
54
54
  "saveAndReconnect": "Save and reconnect",
55
55
  "showToken": "Show token",
56
- "hideToken": "Hide token"
56
+ "hideToken": "Hide token",
57
+ "codexBinaryPath": "Codex Binary Path",
58
+ "codexBinaryPathPlaceholder": "Leave empty for auto-detect",
59
+ "codexBinaryPathHint": "Path to the native Codex binary. Bypasses the Node.js wrapper for better session persistence."
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "Character Size",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "Requerido si el servidor tiene AUTH_TOKEN configurado",
54
54
  "saveAndReconnect": "Guardar y reconectar",
55
55
  "showToken": "Mostrar token",
56
- "hideToken": "Ocultar token"
56
+ "hideToken": "Ocultar token",
57
+ "codexBinaryPath": "Ruta del binario Codex",
58
+ "codexBinaryPathPlaceholder": "Dejar vacío para detección automática",
59
+ "codexBinaryPathHint": "Ruta al binario nativo de Codex. Omite el wrapper de Node.js para mejor persistencia de sesión."
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "Tamano del personaje",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "Requis si le serveur a AUTH_TOKEN défini",
54
54
  "saveAndReconnect": "Enregistrer et reconnecter",
55
55
  "showToken": "Afficher le jeton",
56
- "hideToken": "Masquer le jeton"
56
+ "hideToken": "Masquer le jeton",
57
+ "codexBinaryPath": "Chemin du binaire Codex",
58
+ "codexBinaryPathPlaceholder": "Laisser vide pour la détection automatique",
59
+ "codexBinaryPathHint": "Chemin vers le binaire natif Codex. Contourne le wrapper Node.js pour une meilleure persistance de session."
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "Taille du personnage",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "यदि सर्वर पर AUTH_TOKEN सेट है तो आवश्यक",
54
54
  "saveAndReconnect": "सहेजें और पुनः कनेक्ट करें",
55
55
  "showToken": "टोकन दिखाएं",
56
- "hideToken": "टोकन छुपाएं"
56
+ "hideToken": "टोकन छुपाएं",
57
+ "codexBinaryPath": "Codex बाइनरी पथ",
58
+ "codexBinaryPathPlaceholder": "स्वतः पहचान के लिए खाली छोड़ें",
59
+ "codexBinaryPathHint": "नेटिव Codex बाइनरी का पथ। बेहतर सत्र स्थिरता के लिए Node.js रैपर को बायपास करता है।"
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "कैरेक्टर आकार",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "Richiesto se il server ha AUTH_TOKEN impostato",
54
54
  "saveAndReconnect": "Salva e riconnetti",
55
55
  "showToken": "Mostra token",
56
- "hideToken": "Nascondi token"
56
+ "hideToken": "Nascondi token",
57
+ "codexBinaryPath": "Percorso binario Codex",
58
+ "codexBinaryPathPlaceholder": "Lasciare vuoto per il rilevamento automatico",
59
+ "codexBinaryPathHint": "Percorso al binario nativo Codex. Bypassa il wrapper Node.js per una migliore persistenza della sessione."
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "Dimensione personaggio",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "サーバーにAUTH_TOKENが設定されている場合は必須",
54
54
  "saveAndReconnect": "保存して再接続",
55
55
  "showToken": "トークンを表示",
56
- "hideToken": "トークンを非表示"
56
+ "hideToken": "トークンを非表示",
57
+ "codexBinaryPath": "Codexバイナリパス",
58
+ "codexBinaryPathPlaceholder": "空欄で自動検出",
59
+ "codexBinaryPathHint": "ネイティブCodexバイナリへのパス。Node.jsラッパーをバイパスしてセッションの永続性を向上させます。"
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "キャラクターサイズ",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "Obrigatorio se o servidor tiver AUTH_TOKEN definido",
54
54
  "saveAndReconnect": "Salvar e reconectar",
55
55
  "showToken": "Mostrar token",
56
- "hideToken": "Ocultar token"
56
+ "hideToken": "Ocultar token",
57
+ "codexBinaryPath": "Caminho do binário Codex",
58
+ "codexBinaryPathPlaceholder": "Deixe vazio para detecção automática",
59
+ "codexBinaryPathHint": "Caminho para o binário nativo do Codex. Ignora o wrapper Node.js para melhor persistência de sessão."
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "Tamanho do Personagem",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "Требуется, если на сервере установлен AUTH_TOKEN",
54
54
  "saveAndReconnect": "Сохранить и переподключиться",
55
55
  "showToken": "Показать токен",
56
- "hideToken": "Скрыть токен"
56
+ "hideToken": "Скрыть токен",
57
+ "codexBinaryPath": "Путь к бинарному файлу Codex",
58
+ "codexBinaryPathPlaceholder": "Оставьте пустым для автоопределения",
59
+ "codexBinaryPathHint": "Путь к нативному бинарному файлу Codex. Обходит обёртку Node.js для лучшей сохранности сессий."
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "Размер персонажа",
@@ -53,7 +53,10 @@
53
53
  "tokenRequired": "如果服务器设置了 AUTH_TOKEN 则必填",
54
54
  "saveAndReconnect": "保存并重新连接",
55
55
  "showToken": "显示令牌",
56
- "hideToken": "隐藏令牌"
56
+ "hideToken": "隐藏令牌",
57
+ "codexBinaryPath": "Codex 二进制路径",
58
+ "codexBinaryPathPlaceholder": "留空以自动检测",
59
+ "codexBinaryPathHint": "原生 Codex 二进制文件的路径。绕过 Node.js 包装器以获得更好的会话持久性。"
57
60
  },
58
61
  "scene": {
59
62
  "characterSize": "角色大小",
@@ -42,6 +42,7 @@ export class RunnerProcessLifecycle {
42
42
  const executable = this.backend.getExecutablePath();
43
43
  log.log(`🚀 Spawning: ${executable} ${args.join(' ')}`);
44
44
  const isWindows = process.platform === 'win32';
45
+ const extraEnv = this.backend.getExtraEnv?.() ?? {};
45
46
  const childProcess = spawn(executable, args, {
46
47
  cwd: workingDir,
47
48
  env: {
@@ -49,6 +50,7 @@ export class RunnerProcessLifecycle {
49
50
  LANG: 'en_US.UTF-8',
50
51
  LC_ALL: 'en_US.UTF-8',
51
52
  TIDE_SERVER: `http://localhost:${process.env.TIDE_PORT || process.env.PORT || 5174}`,
53
+ ...extraEnv,
52
54
  },
53
55
  shell: isWindows ? true : false,
54
56
  detached: isWindows ? false : true,
@@ -155,6 +155,34 @@ function normalizeCodexFunctionToolCall(rawToolName, rawToolInput) {
155
155
  toolInput: rawToolInput,
156
156
  };
157
157
  }
158
+ function normalizeCodexWebSearchToolInput(payload) {
159
+ const action = isObject(payload.action) ? payload.action : {};
160
+ const actionQueriesRaw = action.queries;
161
+ const actionQueries = Array.isArray(actionQueriesRaw) && actionQueriesRaw.every((q) => typeof q === 'string')
162
+ ? actionQueriesRaw
163
+ : undefined;
164
+ return {
165
+ actionType: typeof action.type === 'string' ? action.type : undefined,
166
+ actionQuery: typeof action.query === 'string' ? action.query : undefined,
167
+ actionQueries,
168
+ actionUrl: typeof action.url === 'string' ? action.url : undefined,
169
+ status: typeof payload.status === 'string' ? payload.status : undefined,
170
+ };
171
+ }
172
+ function normalizeCodexEventFallbackText(eventType, payload) {
173
+ let serialized = '';
174
+ try {
175
+ serialized = JSON.stringify(payload, null, 2);
176
+ }
177
+ catch {
178
+ serialized = String(payload);
179
+ }
180
+ const MAX_LEN = 4000;
181
+ if (serialized.length > MAX_LEN) {
182
+ serialized = `${serialized.slice(0, MAX_LEN)}...`;
183
+ }
184
+ return `[codex-event] ${eventType}\n${serialized}`;
185
+ }
158
186
  function findCodexSessionFile(sessionId) {
159
187
  const cached = codexSessionFileById.get(sessionId);
160
188
  if (cached && fs.existsSync(cached)) {
@@ -445,6 +473,37 @@ function parseCodexEntryMessages(entry, messages, toolUseIdToName) {
445
473
  });
446
474
  return;
447
475
  }
476
+ // token_count: Skip in history (token accounting shown via turn.completed)
477
+ if (payload.type === 'token_count') {
478
+ return;
479
+ }
480
+ // agent_reasoning: Skip in history (consistent with Claude thinking behavior)
481
+ if (payload.type === 'agent_reasoning') {
482
+ return;
483
+ }
484
+ // turn_aborted: Skip in history (interruption is visible from agent stopping)
485
+ if (payload.type === 'turn_aborted') {
486
+ return;
487
+ }
488
+ // task_complete: Show the final agent message as formatted text
489
+ if (payload.type === 'task_complete' && typeof payload.last_agent_message === 'string') {
490
+ messages.push({
491
+ type: 'assistant',
492
+ content: payload.last_agent_message,
493
+ timestamp: entry.timestamp,
494
+ uuid: `${entry.timestamp}-assistant-task-complete`,
495
+ });
496
+ return;
497
+ }
498
+ // Unknown event_msg types: keep fallback
499
+ const payloadType = typeof payload.type === 'string' ? payload.type : 'unknown';
500
+ messages.push({
501
+ type: 'assistant',
502
+ content: normalizeCodexEventFallbackText(`event_msg.${payloadType}`, payload),
503
+ timestamp: entry.timestamp,
504
+ uuid: `${entry.timestamp}-assistant-codex-event`,
505
+ });
506
+ return;
448
507
  }
449
508
  if (entry.type !== 'response_item' || !isObject(entry.payload)) {
450
509
  return;
@@ -494,6 +553,32 @@ function parseCodexEntryMessages(entry, messages, toolUseIdToName) {
494
553
  });
495
554
  return;
496
555
  }
556
+ if (payloadType === 'web_search_call') {
557
+ const toolName = 'web_search';
558
+ const toolInput = normalizeCodexWebSearchToolInput(payload);
559
+ const status = typeof payload.status === 'string' ? payload.status : undefined;
560
+ const toolUseId = typeof payload.call_id === 'string' ? payload.call_id : `${entry.timestamp}-web-search`;
561
+ messages.push({
562
+ type: 'tool_use',
563
+ content: JSON.stringify(toolInput, null, 2),
564
+ timestamp: entry.timestamp,
565
+ uuid: `${entry.timestamp}-tool-use-web-search`,
566
+ toolName,
567
+ toolInput,
568
+ toolUseId,
569
+ });
570
+ if (status === 'completed') {
571
+ messages.push({
572
+ type: 'tool_result',
573
+ content: JSON.stringify(toolInput, null, 2),
574
+ timestamp: entry.timestamp,
575
+ uuid: `${entry.timestamp}-tool-result-web-search`,
576
+ toolName,
577
+ toolUseId,
578
+ });
579
+ }
580
+ return;
581
+ }
497
582
  if (payloadType === 'function_call_output') {
498
583
  const toolUseId = typeof payload.call_id === 'string' ? payload.call_id : undefined;
499
584
  const toolName = toolUseId ? (toolUseIdToName.get(toolUseId) || 'unknown') : 'unknown';
@@ -506,7 +591,80 @@ function parseCodexEntryMessages(entry, messages, toolUseIdToName) {
506
591
  toolName,
507
592
  toolUseId,
508
593
  });
594
+ return;
595
+ }
596
+ // reasoning: Skip in history (consistent with Claude thinking behavior)
597
+ if (payloadType === 'reasoning') {
598
+ return;
599
+ }
600
+ // custom_tool_call: Map to tool_use (normalize apply_patch -> Edit)
601
+ if (payloadType === 'custom_tool_call') {
602
+ const rawToolName = typeof payload.name === 'string' ? payload.name : 'unknown';
603
+ const callId = typeof payload.call_id === 'string' ? payload.call_id : undefined;
604
+ const rawInput = typeof payload.input === 'string' ? payload.input : '';
605
+ // Normalize: apply_patch -> Edit for consistent UI rendering
606
+ const toolName = rawToolName === 'apply_patch' ? 'Edit' : rawToolName;
607
+ const toolInput = {};
608
+ if (rawToolName === 'apply_patch' && rawInput) {
609
+ // Extract file path from patch for clickable display
610
+ const fileMatch = rawInput.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);
611
+ if (fileMatch) {
612
+ toolInput.file_path = fileMatch[1].trim();
613
+ }
614
+ toolInput.old_string = '';
615
+ toolInput.new_string = rawInput;
616
+ }
617
+ else {
618
+ toolInput.input = rawInput;
619
+ }
620
+ if (callId) {
621
+ toolUseIdToName.set(callId, toolName);
622
+ }
623
+ messages.push({
624
+ type: 'tool_use',
625
+ content: JSON.stringify(toolInput, null, 2),
626
+ timestamp: entry.timestamp,
627
+ uuid: `${entry.timestamp}-tool-use-custom`,
628
+ toolName,
629
+ toolInput,
630
+ toolUseId: callId,
631
+ });
632
+ return;
633
+ }
634
+ // custom_tool_call_output: Map to tool_result
635
+ if (payloadType === 'custom_tool_call_output') {
636
+ const callId = typeof payload.call_id === 'string' ? payload.call_id : undefined;
637
+ const toolName = callId ? (toolUseIdToName.get(callId) || 'unknown') : 'unknown';
638
+ let content = normalizeTextContent(payload.output);
639
+ // Parse apply_patch JSON output wrapper to extract meaningful text
640
+ if (content) {
641
+ try {
642
+ const parsed = JSON.parse(content);
643
+ if (isObject(parsed) && typeof parsed.output === 'string') {
644
+ content = parsed.output;
645
+ }
646
+ }
647
+ catch {
648
+ // Use raw content as-is
649
+ }
650
+ }
651
+ messages.push({
652
+ type: 'tool_result',
653
+ content,
654
+ timestamp: entry.timestamp,
655
+ uuid: `${entry.timestamp}-tool-result-custom`,
656
+ toolName,
657
+ toolUseId: callId,
658
+ });
659
+ return;
509
660
  }
661
+ const unknownPayloadType = typeof payloadType === 'string' ? payloadType : 'unknown';
662
+ messages.push({
663
+ type: 'assistant',
664
+ content: normalizeCodexEventFallbackText(`response_item.${unknownPayloadType}`, payload),
665
+ timestamp: entry.timestamp,
666
+ uuid: `${entry.timestamp}-assistant-codex-response-item`,
667
+ });
510
668
  }
511
669
  async function parseSessionMessages(resolved) {
512
670
  const messages = [];
@@ -3,7 +3,7 @@ import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import { CodexJsonEventParser } from './json-event-parser.js';
5
5
  import { TIDE_COMMANDER_APPENDED_PROMPT } from '../prompts/tide-commander.js';
6
- import { isEchoPromptEnabled } from '../services/system-prompt-service.js';
6
+ import { isEchoPromptEnabled, getCodexBinaryPath } from '../services/system-prompt-service.js';
7
7
  function shouldPassCodexModel(model) {
8
8
  if (!model)
9
9
  return false;
@@ -44,7 +44,7 @@ export class CodexBackend {
44
44
  buildArgs(config) {
45
45
  this.parser.setWorkingDirectory(config.workingDir);
46
46
  const prompt = buildCodexPrompt(config);
47
- const args = ['exec', '--json'];
47
+ const args = ['exec', '--experimental-json'];
48
48
  const codexConfig = config.codexConfig;
49
49
  const fullAuto = codexConfig?.fullAuto !== false;
50
50
  if (fullAuto) {
@@ -94,6 +94,15 @@ export class CodexBackend {
94
94
  return null;
95
95
  }
96
96
  getExecutablePath() {
97
+ // Priority: 1) CODEX_BINARY env var 2) Settings UI 3) auto-detect
98
+ const envBinary = process.env.CODEX_BINARY;
99
+ if (envBinary && fs.existsSync(envBinary)) {
100
+ return envBinary;
101
+ }
102
+ const settingsBinary = getCodexBinaryPath();
103
+ if (settingsBinary && fs.existsSync(settingsBinary)) {
104
+ return settingsBinary;
105
+ }
97
106
  return this.detectInstallation() || 'codex';
98
107
  }
99
108
  detectInstallation() {
@@ -116,6 +125,21 @@ export class CodexBackend {
116
125
  }
117
126
  return null;
118
127
  }
128
+ getExtraEnv() {
129
+ // When using the native binary directly, we need to add
130
+ // the vendor path directory to PATH so codex can find bundled tools like rg.
131
+ const envBinary = process.env.CODEX_BINARY || getCodexBinaryPath();
132
+ if (!envBinary)
133
+ return {};
134
+ const codexDir = path.dirname(envBinary); // .../codex/
135
+ const archRoot = path.dirname(codexDir); // .../x86_64-unknown-linux-musl/
136
+ const pathDir = path.join(archRoot, 'path');
137
+ if (fs.existsSync(pathDir)) {
138
+ const sep = process.platform === 'win32' ? ';' : ':';
139
+ return { PATH: pathDir + sep + (process.env.PATH || '') };
140
+ }
141
+ return {};
142
+ }
119
143
  requiresStdinInput() {
120
144
  return false;
121
145
  }