tide-commander 0.84.2 → 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.
- package/README.md +4 -1
- package/dist/assets/main-BB4dFHqm.css +1 -0
- package/dist/assets/{main-BSaZHBzs.js → main-hADDQllW.js} +97 -97
- package/dist/assets/{web-GtGat3e7.js → web-C9K1Th3w.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/locales/de/config.json +4 -1
- package/dist/locales/en/config.json +4 -1
- package/dist/locales/es/config.json +4 -1
- package/dist/locales/fr/config.json +4 -1
- package/dist/locales/hi/config.json +4 -1
- package/dist/locales/it/config.json +4 -1
- package/dist/locales/ja/config.json +4 -1
- package/dist/locales/pt/config.json +4 -1
- package/dist/locales/ru/config.json +4 -1
- package/dist/locales/zh-CN/config.json +4 -1
- package/dist/src/packages/server/claude/runner/process-lifecycle.js +2 -0
- package/dist/src/packages/server/claude/session-loader.js +158 -0
- package/dist/src/packages/server/cli.js +29 -9
- package/dist/src/packages/server/codex/backend.js +26 -2
- package/dist/src/packages/server/codex/json-event-parser.js +224 -4
- package/dist/src/packages/server/data/builtin-skills/full-notifications.js +6 -12
- package/dist/src/packages/server/routes/agents.js +29 -1
- package/dist/src/packages/server/routes/config.js +1 -1
- package/dist/src/packages/server/services/system-prompt-service.js +55 -0
- package/package.json +1 -1
- package/dist/assets/main-C6IAMrFB.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as s}from"./main-
|
|
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-
|
|
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-
|
|
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 = [];
|
|
@@ -251,12 +251,18 @@ function installLocalCert(host) {
|
|
|
251
251
|
fs.mkdirSync(TLS_DIR, { recursive: true });
|
|
252
252
|
const mkcertBin = findSystemMkcert();
|
|
253
253
|
if (!mkcertBin) {
|
|
254
|
-
throw new Error('mkcert (Go binary) is required for
|
|
254
|
+
throw new Error('mkcert (Go binary) is required for HTTPS but was not found in PATH.\n'
|
|
255
255
|
+ 'Install it from: https://github.com/FiloSottile/mkcert\n'
|
|
256
256
|
+ 'Note: the npm "mkcert" package is not the same tool.');
|
|
257
257
|
}
|
|
258
|
+
const dim = '\x1b[2m';
|
|
259
|
+
const cyan = '\x1b[36m';
|
|
260
|
+
const reset = '\x1b[0m';
|
|
261
|
+
console.log(`\n${cyan}Installing local CA with mkcert...${reset}`);
|
|
262
|
+
console.log(`${dim}Running: ${mkcertBin} -install${reset}`);
|
|
263
|
+
console.log(`${dim}This may require your password to trust the local CA.${reset}\n`);
|
|
258
264
|
try {
|
|
259
|
-
|
|
265
|
+
execSync(`"${mkcertBin}" -install`, { stdio: 'inherit' });
|
|
260
266
|
}
|
|
261
267
|
catch {
|
|
262
268
|
throw new Error(`mkcert -install failed. You may need to run: sudo ${mkcertBin} -install`);
|
|
@@ -266,14 +272,18 @@ function installLocalCert(host) {
|
|
|
266
272
|
hostArgs.push(host);
|
|
267
273
|
}
|
|
268
274
|
const mkcertGenCmd = `"${mkcertBin}" -cert-file "${DEFAULT_TLS_CERT_FILE}" -key-file "${DEFAULT_TLS_KEY_FILE}" ${hostArgs.join(' ')}`;
|
|
269
|
-
|
|
275
|
+
console.log(`${dim}Running: ${mkcertBin} ${hostArgs.join(' ')}${reset}`);
|
|
276
|
+
try {
|
|
277
|
+
execSync(mkcertGenCmd, { stdio: 'inherit' });
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
throw new Error(`Failed to generate TLS certificates with mkcert`);
|
|
281
|
+
}
|
|
270
282
|
ensureFileExists(DEFAULT_TLS_CERT_FILE, 'TLS cert');
|
|
271
283
|
ensureFileExists(DEFAULT_TLS_KEY_FILE, 'TLS key');
|
|
284
|
+
console.log(`${cyan}Local certificates generated at ${TLS_DIR}${reset}\n`);
|
|
272
285
|
return { keyPath: DEFAULT_TLS_KEY_FILE, certPath: DEFAULT_TLS_CERT_FILE };
|
|
273
286
|
}
|
|
274
|
-
function spawnSyncOrThrow(command) {
|
|
275
|
-
execSync(command, { stdio: 'ignore' });
|
|
276
|
-
}
|
|
277
287
|
function readPidFile() {
|
|
278
288
|
try {
|
|
279
289
|
const raw = fs.readFileSync(PID_FILE, 'utf8').trim();
|
|
@@ -585,15 +595,25 @@ async function main() {
|
|
|
585
595
|
if (shouldEnableHttps) {
|
|
586
596
|
process.env.HTTPS = '1';
|
|
587
597
|
}
|
|
598
|
+
if (options.tlsKey && options.tlsCert) {
|
|
599
|
+
process.env.TLS_KEY_PATH = resolveFromCwd(options.tlsKey);
|
|
600
|
+
process.env.TLS_CERT_PATH = resolveFromCwd(options.tlsCert);
|
|
601
|
+
}
|
|
588
602
|
if (options.installLocalCert) {
|
|
589
603
|
const host = process.env.HOST || 'localhost';
|
|
590
604
|
const generated = installLocalCert(host);
|
|
591
605
|
process.env.TLS_KEY_PATH = generated.keyPath;
|
|
592
606
|
process.env.TLS_CERT_PATH = generated.certPath;
|
|
593
607
|
}
|
|
594
|
-
if (
|
|
595
|
-
|
|
596
|
-
|
|
608
|
+
if (process.env.HTTPS === '1' && !process.env.TLS_KEY_PATH && !process.env.TLS_CERT_PATH) {
|
|
609
|
+
const defaultKeyExists = fs.existsSync(DEFAULT_TLS_KEY_FILE);
|
|
610
|
+
const defaultCertExists = fs.existsSync(DEFAULT_TLS_CERT_FILE);
|
|
611
|
+
if (!defaultKeyExists || !defaultCertExists) {
|
|
612
|
+
const host = process.env.HOST || 'localhost';
|
|
613
|
+
const generated = installLocalCert(host);
|
|
614
|
+
process.env.TLS_KEY_PATH = generated.keyPath;
|
|
615
|
+
process.env.TLS_CERT_PATH = generated.certPath;
|
|
616
|
+
}
|
|
597
617
|
}
|
|
598
618
|
if (process.env.HTTPS === '1') {
|
|
599
619
|
const tlsKeyPath = resolveFromCwd(process.env.TLS_KEY_PATH || DEFAULT_TLS_KEY_FILE);
|
|
@@ -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
|
}
|