tide-commander 1.160.0 → 1.162.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/CHANGELOG.md +26 -0
- package/dist/assets/{BossLogsModal-COg0RBw_.js → BossLogsModal-DrYF13xp.js} +1 -1
- package/dist/assets/{BossSpawnModal-Bvkk2uFi.js → BossSpawnModal-2ZgbW8Iy.js} +1 -1
- package/dist/assets/{ControlsModal-BWcWGs2x.js → ControlsModal-CRdNNTEo.js} +1 -1
- package/dist/assets/{DockerLogsModal-C4tJ59Pf.js → DockerLogsModal-BGPZP9_V.js} +1 -1
- package/dist/assets/{EmbeddedEditor-DiDm5EvF.js → EmbeddedEditor-B99bT-bl.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-GkYs6wUs.js → GmailOAuthSetup-DmMCxdUW.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-KKwp2gEQ.js → GoogleOAuthSetup-BZqit-mW.js} +1 -1
- package/dist/assets/{IframeModal-D0iTkxZB.js → IframeModal-vMY7XxHP.js} +1 -1
- package/dist/assets/{IntegrationsPanel-CCKoizJa.js → IntegrationsPanel-DVmqPRTs.js} +2 -2
- package/dist/assets/{LogViewerModal-CSGEkyhZ.js → LogViewerModal-rYC6IVPV.js} +1 -1
- package/dist/assets/{MonitoringModal-Be6uEd7K.js → MonitoringModal-C-QXpZkE.js} +1 -1
- package/dist/assets/{PM2LogsModal-DZG7Tcw7.js → PM2LogsModal-C3A_ohFv.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-CigBbBB_.js → RestoreArchivedAreaModal-Dx94NzSz.js} +1 -1
- package/dist/assets/{Scene2DCanvas-sJig3vJ4.js → Scene2DCanvas-BxOqJ0IB.js} +1 -1
- package/dist/assets/{SceneManager-BG_0UVJc.js → SceneManager-CxcXfq_u.js} +1 -1
- package/dist/assets/{SkillsPanel-BnAf8uyn.js → SkillsPanel-Fu5sfG7Q.js} +1 -1
- package/dist/assets/{SlackMultiInstanceSetup-DGuexHRz.js → SlackMultiInstanceSetup-jnGMesoq.js} +1 -1
- package/dist/assets/{SpawnModal-FjDJZlIm.js → SpawnModal-CGYg4uTC.js} +1 -1
- package/dist/assets/{StatisticsModal-CbbdwSF0.js → StatisticsModal-BOV6Juts.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-Dn2uuOcT.js → SubordinateAssignmentModal-Bhgj9FvT.js} +1 -1
- package/dist/assets/{TriggerManagerPanel-Cj9pqf-r.js → TriggerManagerPanel-Qkw4ajmL.js} +1 -1
- package/dist/assets/{WorkflowEditorPanel-nucrx_0v.js → WorkflowEditorPanel-DTIBRGz4.js} +1 -1
- package/dist/assets/{area-logos-C93KZk3h.js → area-logos-uM2FM0I2.js} +1 -1
- package/dist/assets/{index-Gjc7NXlZ.js → index-3y5Ajiyr.js} +1 -1
- package/dist/assets/{index-iX9jPnEp.js → index-5tz2e3op.js} +1 -1
- package/dist/assets/{index-ojmo-Cva.js → index-B03BKiDN.js} +1 -1
- package/dist/assets/index-BFJWNGpT.js +19 -0
- package/dist/assets/{index-BkbKu66S.js → index-BvD1v4JH.js} +1 -1
- package/dist/assets/{index-D7YoMagO.js → index-BvOgm4yp.js} +1 -1
- package/dist/assets/{index-CmmI2kZf.js → index-CpQeaxZ7.js} +2 -2
- package/dist/assets/index-CrBXvs1w.css +1 -0
- package/dist/assets/{index-fh53j_4E.js → index-DzQxMm67.js} +1 -1
- package/dist/assets/index-cA0kPNUq.js +2 -0
- package/dist/assets/{main-BZcOdtO_.css → main-B-jhtDyZ.css} +1 -1
- package/dist/assets/main-rX2ghMIV.js +297 -0
- package/dist/assets/{web-8gl2noUE.js → web-ClQyiX7L.js} +1 -1
- package/dist/assets/{web-M9cLwTrs.js → web-NCIGsXXA.js} +1 -1
- package/dist/assets/{web-D0uiHhnA.js → web-ml-7D2g7.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/index.html +2 -2
- package/dist/locales/en/terminal.json +4 -0
- package/dist/locales/es/terminal.json +1 -0
- package/dist/src/packages/server/claude/backend.js +4 -0
- package/dist/src/packages/server/claude/session-loader.js +116 -0
- package/dist/src/packages/server/codex/generated-image.js +35 -0
- package/dist/src/packages/server/codex/json-event-parser.js +122 -0
- package/dist/src/packages/server/index.js +4 -0
- package/dist/src/packages/server/routes/agents.js +9 -6
- package/dist/src/packages/server/services/claude-credentials-service.js +172 -13
- package/dist/src/packages/server/services/claude-usage-service.js +5 -1
- package/dist/src/packages/server/services/codex-usage-service.js +93 -0
- package/package.json +1 -1
- package/dist/assets/index-BIxFr4P2.css +0 -1
- package/dist/assets/index-DIOhr3im.js +0 -19
- package/dist/assets/index-DqM0QltS.js +0 -2
- package/dist/assets/main-BFW0rGXU.js +0 -297
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{dh as a}from"./main-rX2ghMIV.js";import{ImpactStyle as i,NotificationType as r}from"./index-CpQeaxZ7.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{dh as t}from"./main-rX2ghMIV.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class o extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{o as AppWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{dh as s}from"./main-rX2ghMIV.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l 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{l as LocalNotificationsWeb};
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.
|
|
1
|
+
{"version":"1.162.0"}
|
package/dist/index.html
CHANGED
|
@@ -22,10 +22,10 @@
|
|
|
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-rX2ghMIV.js"></script>
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
|
|
28
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
28
|
+
<link rel="stylesheet" crossorigin href="/assets/main-B-jhtDyZ.css">
|
|
29
29
|
</head>
|
|
30
30
|
<body>
|
|
31
31
|
<div id="app"></div>
|
|
@@ -1270,6 +1270,7 @@
|
|
|
1270
1270
|
"usage": {
|
|
1271
1271
|
"title": "Claude Usage",
|
|
1272
1272
|
"titleGrok": "Grok Usage",
|
|
1273
|
+
"titleCodex": "Codex Usage",
|
|
1273
1274
|
"session": "Session",
|
|
1274
1275
|
"tokensUsed": "Tokens used",
|
|
1275
1276
|
"taskCount": "Tasks sent",
|
|
@@ -1285,8 +1286,10 @@
|
|
|
1285
1286
|
"currentSession": "Current session",
|
|
1286
1287
|
"currentWeekAll": "Current week (all models)",
|
|
1287
1288
|
"currentWeekOpus": "Current week (Opus only)",
|
|
1289
|
+
"currentWeekFable": "Current week (Fable)",
|
|
1288
1290
|
"currentWeekSonnet": "Current week (Sonnet only)",
|
|
1289
1291
|
"weeklyLimit": "Weekly limit",
|
|
1292
|
+
"dailyLimit": "Daily limit",
|
|
1290
1293
|
"monthlyLimit": "Monthly limit",
|
|
1291
1294
|
"periodLimit": "Plan period limit",
|
|
1292
1295
|
"onDemandLimit": "Pay-as-you-go limit",
|
|
@@ -1327,6 +1330,7 @@
|
|
|
1327
1330
|
"gaugeSession": "Session",
|
|
1328
1331
|
"gaugeWeek": "Week",
|
|
1329
1332
|
"gaugeWeekOpus": "Opus wk",
|
|
1333
|
+
"gaugeWeekFable": "Fable wk",
|
|
1330
1334
|
"gaugeNoSession": "no active session",
|
|
1331
1335
|
"providerTitle": "{{provider}} Accounts",
|
|
1332
1336
|
"providerHelp": "Named copies of {{dir}}/auth.json. Switch the active {{provider}} account when you hit rate limits — same as renaming the file manually."
|
|
@@ -1137,6 +1137,7 @@
|
|
|
1137
1137
|
"gaugeSession": "Sesión",
|
|
1138
1138
|
"gaugeWeek": "Semana",
|
|
1139
1139
|
"gaugeWeekOpus": "Opus sem",
|
|
1140
|
+
"gaugeWeekFable": "Fable sem",
|
|
1140
1141
|
"gaugeNoSession": "sin sesión activa",
|
|
1141
1142
|
"providerTitle": "Cuentas de {{provider}}",
|
|
1142
1143
|
"providerHelp": "Copias nombradas de {{dir}}/auth.json. Cambia la cuenta activa de {{provider}} cuando llegues a los límites — igual que renombrar el archivo a mano."
|
|
@@ -348,6 +348,10 @@ export class ClaudeBackend {
|
|
|
348
348
|
toolName,
|
|
349
349
|
toolOutput: content,
|
|
350
350
|
toolUseId: block.tool_use_id, // Preserve for subagent correlation
|
|
351
|
+
// The matching tool_start uses the tool_use id as its live-output
|
|
352
|
+
// uuid. Reuse it for the result so clients can enrich that card in
|
|
353
|
+
// place instead of waiting for a session-history reconstruction.
|
|
354
|
+
uuid: block.tool_use_id,
|
|
351
355
|
};
|
|
352
356
|
// Extract subagent stats from Task/Agent tool completion metadata
|
|
353
357
|
if ((toolName === 'Task' || toolName === 'Agent') && event.tool_use_result) {
|
|
@@ -11,6 +11,7 @@ import * as os from 'os';
|
|
|
11
11
|
import * as readline from 'readline';
|
|
12
12
|
import Database from 'better-sqlite3';
|
|
13
13
|
import { createLogger } from '../utils/logger.js';
|
|
14
|
+
import { materializeCodexGeneratedImage } from '../codex/generated-image.js';
|
|
14
15
|
const log = createLogger('Session');
|
|
15
16
|
// Claude's project directory
|
|
16
17
|
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
@@ -348,6 +349,9 @@ function normalizeCodexWebSearchToolInput(payload) {
|
|
|
348
349
|
? actionQueriesRaw
|
|
349
350
|
: undefined;
|
|
350
351
|
return {
|
|
352
|
+
query: typeof payload.query === 'string'
|
|
353
|
+
? payload.query
|
|
354
|
+
: typeof action.query === 'string' ? action.query : undefined,
|
|
351
355
|
actionType: typeof action.type === 'string' ? action.type : undefined,
|
|
352
356
|
actionQuery: typeof action.query === 'string' ? action.query : undefined,
|
|
353
357
|
actionQueries,
|
|
@@ -355,6 +359,40 @@ function normalizeCodexWebSearchToolInput(payload) {
|
|
|
355
359
|
status: typeof payload.status === 'string' ? payload.status : undefined,
|
|
356
360
|
};
|
|
357
361
|
}
|
|
362
|
+
function normalizeCodexMcpToolCall(payload) {
|
|
363
|
+
if (!isObject(payload.invocation))
|
|
364
|
+
return null;
|
|
365
|
+
const invocation = payload.invocation;
|
|
366
|
+
const server = typeof invocation.server === 'string' ? invocation.server : 'mcp';
|
|
367
|
+
const tool = typeof invocation.tool === 'string' ? invocation.tool : 'tool';
|
|
368
|
+
const args = isObject(invocation.arguments) ? invocation.arguments : {};
|
|
369
|
+
return {
|
|
370
|
+
toolName: `mcp__${server}__${tool}`,
|
|
371
|
+
toolInput: { ...args, server },
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function normalizeCodexMcpToolOutput(result) {
|
|
375
|
+
if (!isObject(result))
|
|
376
|
+
return result === undefined ? '' : String(result);
|
|
377
|
+
const envelope = isObject(result.Ok) ? result.Ok : isObject(result.Err) ? result.Err : result;
|
|
378
|
+
if (Array.isArray(envelope.content)) {
|
|
379
|
+
const texts = envelope.content
|
|
380
|
+
.filter(isObject)
|
|
381
|
+
.map((entry) => typeof entry.text === 'string' ? entry.text : undefined)
|
|
382
|
+
.filter((text) => !!text);
|
|
383
|
+
if (texts.length > 0) {
|
|
384
|
+
const output = texts.join('\n');
|
|
385
|
+
return output.length > 4000 ? `${output.slice(0, 4000)}...` : output;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
const output = JSON.stringify(result);
|
|
390
|
+
return output.length > 4000 ? `${output.slice(0, 4000)}...` : output;
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return String(result);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
358
396
|
function normalizeCodexEventFallbackText(eventType, payload) {
|
|
359
397
|
let serialized = '';
|
|
360
398
|
try {
|
|
@@ -1043,6 +1081,84 @@ function parseCodexEntryMessages(entry, messages, toolUseIdToName, patchDiffsByC
|
|
|
1043
1081
|
if (payload.type === 'token_count') {
|
|
1044
1082
|
return;
|
|
1045
1083
|
}
|
|
1084
|
+
if (payload.type === 'mcp_tool_call_begin' || payload.type === 'mcp_tool_call_end') {
|
|
1085
|
+
const callId = typeof payload.call_id === 'string' ? payload.call_id : `${entry.timestamp}-mcp`;
|
|
1086
|
+
const normalized = normalizeCodexMcpToolCall(payload);
|
|
1087
|
+
const priorToolName = toolUseIdToName.get(callId);
|
|
1088
|
+
if (!normalized && !priorToolName)
|
|
1089
|
+
return;
|
|
1090
|
+
const toolName = normalized?.toolName ?? priorToolName;
|
|
1091
|
+
const alreadyStarted = toolUseIdToName.has(callId);
|
|
1092
|
+
if (!alreadyStarted && normalized) {
|
|
1093
|
+
toolUseIdToName.set(callId, toolName);
|
|
1094
|
+
messages.push({
|
|
1095
|
+
type: 'tool_use',
|
|
1096
|
+
content: JSON.stringify(normalized.toolInput, null, 2),
|
|
1097
|
+
timestamp: entry.timestamp,
|
|
1098
|
+
uuid: `${entry.timestamp}-tool-use-mcp`,
|
|
1099
|
+
toolName,
|
|
1100
|
+
toolInput: normalized.toolInput,
|
|
1101
|
+
toolUseId: callId,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
if (payload.type === 'mcp_tool_call_end') {
|
|
1105
|
+
messages.push({
|
|
1106
|
+
type: 'tool_result',
|
|
1107
|
+
content: normalizeCodexMcpToolOutput(payload.result),
|
|
1108
|
+
timestamp: entry.timestamp,
|
|
1109
|
+
uuid: `${entry.timestamp}-tool-result-mcp`,
|
|
1110
|
+
toolName,
|
|
1111
|
+
toolUseId: callId,
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if (payload.type === 'web_search_begin' || payload.type === 'web_search_end') {
|
|
1117
|
+
const toolName = 'web_search';
|
|
1118
|
+
const toolInput = normalizeCodexWebSearchToolInput(payload);
|
|
1119
|
+
const callId = typeof payload.call_id === 'string' ? payload.call_id : `${entry.timestamp}-web-search`;
|
|
1120
|
+
const alreadyStarted = toolUseIdToName.has(callId);
|
|
1121
|
+
if (!alreadyStarted) {
|
|
1122
|
+
toolUseIdToName.set(callId, toolName);
|
|
1123
|
+
messages.push({
|
|
1124
|
+
type: 'tool_use',
|
|
1125
|
+
content: JSON.stringify(toolInput, null, 2),
|
|
1126
|
+
timestamp: entry.timestamp,
|
|
1127
|
+
uuid: `${entry.timestamp}-tool-use-web-search`,
|
|
1128
|
+
toolName,
|
|
1129
|
+
toolInput,
|
|
1130
|
+
toolUseId: callId,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
if (payload.type === 'web_search_end') {
|
|
1134
|
+
messages.push({
|
|
1135
|
+
type: 'tool_result',
|
|
1136
|
+
content: JSON.stringify(toolInput, null, 2),
|
|
1137
|
+
timestamp: entry.timestamp,
|
|
1138
|
+
uuid: `${entry.timestamp}-tool-result-web-search`,
|
|
1139
|
+
toolName,
|
|
1140
|
+
toolUseId: callId,
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (payload.type === 'image_generation_begin') {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (payload.type === 'image_generation_end') {
|
|
1149
|
+
const imagePath = materializeCodexGeneratedImage(payload.result);
|
|
1150
|
+
messages.push({
|
|
1151
|
+
type: 'assistant',
|
|
1152
|
+
content: imagePath
|
|
1153
|
+
? `[Image: ${imagePath}]`
|
|
1154
|
+
: '[codex-event] Image generation completed, but the PNG result was invalid.',
|
|
1155
|
+
timestamp: entry.timestamp,
|
|
1156
|
+
uuid: typeof payload.call_id === 'string'
|
|
1157
|
+
? payload.call_id
|
|
1158
|
+
: `${entry.timestamp}-assistant-generated-image`,
|
|
1159
|
+
});
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1046
1162
|
// agent_reasoning: Skip in history (consistent with Claude thinking behavior)
|
|
1047
1163
|
if (payload.type === 'agent_reasoning') {
|
|
1048
1164
|
return;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
const GENERATED_IMAGES_DIR = path.join(os.tmpdir(), 'tide-commander-uploads');
|
|
6
|
+
const MAX_GENERATED_IMAGE_BYTES = 50 * 1024 * 1024;
|
|
7
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
8
|
+
/**
|
|
9
|
+
* Materialize Codex's image_generation_end base64 result in the directory
|
|
10
|
+
* already exposed by Tide Commander at /uploads.
|
|
11
|
+
*/
|
|
12
|
+
export function materializeCodexGeneratedImage(result) {
|
|
13
|
+
if (typeof result !== 'string')
|
|
14
|
+
return null;
|
|
15
|
+
const encoded = result.startsWith('data:image/png;base64,')
|
|
16
|
+
? result.slice('data:image/png;base64,'.length)
|
|
17
|
+
: result;
|
|
18
|
+
if (!encoded || encoded.length > Math.ceil(MAX_GENERATED_IMAGE_BYTES * 4 / 3) + 4)
|
|
19
|
+
return null;
|
|
20
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encoded))
|
|
21
|
+
return null;
|
|
22
|
+
const image = Buffer.from(encoded, 'base64');
|
|
23
|
+
if (image.length === 0 ||
|
|
24
|
+
image.length > MAX_GENERATED_IMAGE_BYTES ||
|
|
25
|
+
image.length < PNG_SIGNATURE.length ||
|
|
26
|
+
!image.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const digest = createHash('sha256').update(image).digest('hex').slice(0, 16);
|
|
30
|
+
const imagePath = path.join(GENERATED_IMAGES_DIR, `codex-generated-${digest}.png`);
|
|
31
|
+
fs.mkdirSync(GENERATED_IMAGES_DIR, { recursive: true });
|
|
32
|
+
if (!fs.existsSync(imagePath))
|
|
33
|
+
fs.writeFileSync(imagePath, image, { mode: 0o600 });
|
|
34
|
+
return imagePath;
|
|
35
|
+
}
|
|
@@ -2,6 +2,7 @@ import { execFileSync } from 'child_process';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { createLogger } from '../utils/logger.js';
|
|
5
|
+
import { materializeCodexGeneratedImage } from './generated-image.js';
|
|
5
6
|
const log = createLogger('CodexParser');
|
|
6
7
|
let hasLoggedTurnAbortedLiveWarning = false;
|
|
7
8
|
const MAX_DIFF_FILE_BYTES = 256 * 1024;
|
|
@@ -141,6 +142,9 @@ function parseResponsePayload(payload) {
|
|
|
141
142
|
name: asString(payload.name),
|
|
142
143
|
input: asString(payload.input),
|
|
143
144
|
output: asString(payload.output),
|
|
145
|
+
query: asString(payload.query),
|
|
146
|
+
invocation: isObject(payload.invocation) ? payload.invocation : undefined,
|
|
147
|
+
result: payload.result,
|
|
144
148
|
summary: parseSummaryArray(payload.summary),
|
|
145
149
|
};
|
|
146
150
|
}
|
|
@@ -150,6 +154,7 @@ function parseResponsePayload(payload) {
|
|
|
150
154
|
*/
|
|
151
155
|
export class CodexJsonEventParser {
|
|
152
156
|
activeToolByItemId = new Map();
|
|
157
|
+
activeEventToolCalls = new Map();
|
|
153
158
|
lastAgentMessageText;
|
|
154
159
|
lastErrorText;
|
|
155
160
|
lastModelUsageSnapshot;
|
|
@@ -246,6 +251,38 @@ export class CodexJsonEventParser {
|
|
|
246
251
|
};
|
|
247
252
|
return [];
|
|
248
253
|
}
|
|
254
|
+
// Newer Codex builds expose MCP and web activity as event_msg progress
|
|
255
|
+
// events instead of response_item entries. Normalize them into the same
|
|
256
|
+
// compact tool rows; otherwise Guake receives a multi-kilobyte raw JSON
|
|
257
|
+
// fallback for every completed call.
|
|
258
|
+
if (payloadType === 'mcp_tool_call_begin' || payloadType === 'mcp_tool_call_end') {
|
|
259
|
+
return this.parseMcpToolCallEvent(payload, payloadType === 'mcp_tool_call_end');
|
|
260
|
+
}
|
|
261
|
+
if (payloadType === 'web_search_begin' || payloadType === 'web_search_end') {
|
|
262
|
+
return this.parseWebSearchEvent(payload, payloadType === 'web_search_end');
|
|
263
|
+
}
|
|
264
|
+
// Codex returns generated PNGs inline as base64. Persist the bytes in the
|
|
265
|
+
// existing uploads directory and let Guake's normal image-reference
|
|
266
|
+
// renderer show a clickable thumbnail instead of dumping the JSON/base64.
|
|
267
|
+
if (payloadType === 'image_generation_begin') {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
if (payloadType === 'image_generation_end') {
|
|
271
|
+
const imagePath = materializeCodexGeneratedImage(payload.result);
|
|
272
|
+
if (!imagePath) {
|
|
273
|
+
return [{
|
|
274
|
+
type: 'text',
|
|
275
|
+
text: '[codex-event] Image generation completed, but the PNG result was invalid.',
|
|
276
|
+
isStreaming: false,
|
|
277
|
+
}];
|
|
278
|
+
}
|
|
279
|
+
return [{
|
|
280
|
+
type: 'text',
|
|
281
|
+
text: `[Image: ${imagePath}]`,
|
|
282
|
+
isStreaming: false,
|
|
283
|
+
uuid: asString(payload.call_id) || undefined,
|
|
284
|
+
}];
|
|
285
|
+
}
|
|
249
286
|
// agent_reasoning: Map to thinking event (stable uuid when item id present)
|
|
250
287
|
if (payloadType === 'agent_reasoning') {
|
|
251
288
|
const text = asString(payload.text);
|
|
@@ -300,6 +337,91 @@ export class CodexJsonEventParser {
|
|
|
300
337
|
}
|
|
301
338
|
return [this.buildUnknownEventFallback(`Unhandled Codex event_msg type: ${payloadType}`, payload)];
|
|
302
339
|
}
|
|
340
|
+
parseMcpToolCallEvent(payload, completed) {
|
|
341
|
+
const invocation = payload.invocation;
|
|
342
|
+
const callId = payload.call_id;
|
|
343
|
+
const active = callId ? this.activeEventToolCalls.get(callId) : undefined;
|
|
344
|
+
if (!invocation && !active)
|
|
345
|
+
return [];
|
|
346
|
+
const server = invocation ? (asString(invocation.server) || 'mcp') : 'mcp';
|
|
347
|
+
const tool = invocation ? (asString(invocation.tool) || 'tool') : 'tool';
|
|
348
|
+
const toolName = active?.toolName ?? `mcp__${server}__${tool}`;
|
|
349
|
+
const args = invocation && isObject(invocation.arguments) ? invocation.arguments : {};
|
|
350
|
+
const toolInput = active?.toolInput ?? { ...args, server };
|
|
351
|
+
const hadStart = !!active;
|
|
352
|
+
if (!completed) {
|
|
353
|
+
if (callId)
|
|
354
|
+
this.activeEventToolCalls.set(callId, { toolName, toolInput });
|
|
355
|
+
return [{ type: 'tool_start', toolName, toolInput, uuid: callId, toolUseId: callId }];
|
|
356
|
+
}
|
|
357
|
+
if (callId)
|
|
358
|
+
this.activeEventToolCalls.delete(callId);
|
|
359
|
+
const result = {
|
|
360
|
+
type: 'tool_result',
|
|
361
|
+
toolName,
|
|
362
|
+
toolOutput: this.extractMcpToolOutput(payload.result),
|
|
363
|
+
uuid: callId,
|
|
364
|
+
toolUseId: callId,
|
|
365
|
+
};
|
|
366
|
+
return hadStart
|
|
367
|
+
? [result]
|
|
368
|
+
: [{ type: 'tool_start', toolName, toolInput, uuid: callId, toolUseId: callId }, result];
|
|
369
|
+
}
|
|
370
|
+
parseWebSearchEvent(payload, completed) {
|
|
371
|
+
const callId = payload.call_id;
|
|
372
|
+
const toolName = 'web_search';
|
|
373
|
+
const active = callId ? this.activeEventToolCalls.get(callId) : undefined;
|
|
374
|
+
const currentInput = {
|
|
375
|
+
query: payload.query || payload.action?.query,
|
|
376
|
+
actionType: payload.action?.type,
|
|
377
|
+
actionQuery: payload.action?.query,
|
|
378
|
+
actionQueries: payload.action?.queries,
|
|
379
|
+
actionUrl: payload.action?.url,
|
|
380
|
+
};
|
|
381
|
+
const toolInput = active?.toolInput ?? currentInput;
|
|
382
|
+
const hadStart = !!active;
|
|
383
|
+
if (!completed) {
|
|
384
|
+
if (callId)
|
|
385
|
+
this.activeEventToolCalls.set(callId, { toolName, toolInput });
|
|
386
|
+
return [{ type: 'tool_start', toolName, toolInput, uuid: callId, toolUseId: callId }];
|
|
387
|
+
}
|
|
388
|
+
if (callId)
|
|
389
|
+
this.activeEventToolCalls.delete(callId);
|
|
390
|
+
const result = {
|
|
391
|
+
type: 'tool_result',
|
|
392
|
+
toolName,
|
|
393
|
+
toolOutput: JSON.stringify(toolInput),
|
|
394
|
+
uuid: callId,
|
|
395
|
+
toolUseId: callId,
|
|
396
|
+
};
|
|
397
|
+
return hadStart
|
|
398
|
+
? [result]
|
|
399
|
+
: [{ type: 'tool_start', toolName, toolInput, uuid: callId, toolUseId: callId }, result];
|
|
400
|
+
}
|
|
401
|
+
extractMcpToolOutput(result) {
|
|
402
|
+
if (!isObject(result))
|
|
403
|
+
return result === undefined ? '' : String(result);
|
|
404
|
+
const envelope = isObject(result.Ok) ? result.Ok : isObject(result.Err) ? result.Err : result;
|
|
405
|
+
if (Array.isArray(envelope.content)) {
|
|
406
|
+
const texts = envelope.content
|
|
407
|
+
.filter(isObject)
|
|
408
|
+
.map((entry) => asString(entry.text))
|
|
409
|
+
.filter((text) => !!text);
|
|
410
|
+
if (texts.length > 0)
|
|
411
|
+
return this.truncateToolOutput(texts.join('\n'));
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
return this.truncateToolOutput(JSON.stringify(result));
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return String(result);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
truncateToolOutput(output) {
|
|
421
|
+
return output.length > MAX_FALLBACK_EVENT_TEXT
|
|
422
|
+
? `${output.slice(0, MAX_FALLBACK_EVENT_TEXT)}...`
|
|
423
|
+
: output;
|
|
424
|
+
}
|
|
303
425
|
parseResponseItem(payload) {
|
|
304
426
|
if (!payload?.type)
|
|
305
427
|
return [];
|
|
@@ -19,6 +19,7 @@ import { initBackupService, shutdownBackupService } from './services/backup-serv
|
|
|
19
19
|
import { initAutoUpdateService, shutdownAutoUpdateService } from './services/auto-update-service.js';
|
|
20
20
|
import { initAttachmentJanitor, shutdownAttachmentJanitor } from './services/attachment-janitor.js';
|
|
21
21
|
import { stopAllAgentTerminals, sweepAllAgentTtyds } from './services/agent-terminal-service.js';
|
|
22
|
+
import { initClaudeCredentialKeepAlive, shutdownClaudeCredentialKeepAlive, } from './services/claude-credentials-service.js';
|
|
22
23
|
// Configuration
|
|
23
24
|
const PORT = process.env.PORT || 6200;
|
|
24
25
|
const HOST = process.env.HOST || (process.env.LISTEN_ALL_INTERFACES ? '::' : '127.0.0.1');
|
|
@@ -154,6 +155,8 @@ async function main() {
|
|
|
154
155
|
initAutoUpdateService();
|
|
155
156
|
// Start hourly sweeper for the trigger-attachment temp dir.
|
|
156
157
|
initAttachmentJanitor();
|
|
158
|
+
// Keep every saved Claude OAuth account warm while Tide Commander is on.
|
|
159
|
+
initClaudeCredentialKeepAlive();
|
|
157
160
|
logger.server.log(`Data directory: ${getDataDir()}`);
|
|
158
161
|
logger.server.log(`Log file: ${getLogFilePath()}`);
|
|
159
162
|
// Create Express app and HTTP server
|
|
@@ -218,6 +221,7 @@ async function main() {
|
|
|
218
221
|
shutdownBackupService();
|
|
219
222
|
shutdownAutoUpdateService();
|
|
220
223
|
shutdownAttachmentJanitor();
|
|
224
|
+
shutdownClaudeCredentialKeepAlive();
|
|
221
225
|
triggerService.shutdown();
|
|
222
226
|
autoCollapseService.shutdown();
|
|
223
227
|
workflowService.shutdown();
|
|
@@ -23,6 +23,7 @@ import { markInstructionsDirtyForAll } from '../services/instruction-refresh.js'
|
|
|
23
23
|
import { startAgentTerminal, stopAgentTerminal } from '../services/agent-terminal-service.js';
|
|
24
24
|
import { buildClaudeUsageByAgentSummary, buildClaudeUsageByDaySummary, buildClaudeUsageSnapshot } from '../services/claude-usage-service.js';
|
|
25
25
|
import { buildGrokUsageSnapshot } from '../services/grok-usage-service.js';
|
|
26
|
+
import { buildCodexUsageSnapshot } from '../services/codex-usage-service.js';
|
|
26
27
|
import { getBackupStatus, setBackupEnabled } from '../services/backup-service.js';
|
|
27
28
|
const log = createLogger('Routes');
|
|
28
29
|
const router = Router();
|
|
@@ -949,7 +950,7 @@ router.delete('/:id', (req, res) => {
|
|
|
949
950
|
}
|
|
950
951
|
res.status(204).end();
|
|
951
952
|
});
|
|
952
|
-
// GET /api/agents/:id/usage -
|
|
953
|
+
// GET /api/agents/:id/usage - provider usage snapshot for a single agent
|
|
953
954
|
//
|
|
954
955
|
// Claude: local agent stats + Anthropic OAuth rate-limit gauges (CLI `/usage`).
|
|
955
956
|
// Grok: local agent stats + CLI chat-proxy billing/credit gauges (CLI `/usage`).
|
|
@@ -960,17 +961,19 @@ router.get('/:id/usage', async (req, res) => {
|
|
|
960
961
|
return;
|
|
961
962
|
}
|
|
962
963
|
const provider = agent.provider ?? 'claude';
|
|
963
|
-
if (provider !== 'claude' && provider !== 'grok') {
|
|
964
|
+
if (provider !== 'claude' && provider !== 'grok' && provider !== 'codex') {
|
|
964
965
|
res.status(400).json({
|
|
965
|
-
error: 'Usage data is only available for Claude and Grok agents',
|
|
966
|
+
error: 'Usage data is only available for Claude, Codex, and Grok agents',
|
|
966
967
|
provider,
|
|
967
968
|
});
|
|
968
969
|
return;
|
|
969
970
|
}
|
|
970
971
|
try {
|
|
971
|
-
const snapshot = provider === '
|
|
972
|
-
? await
|
|
973
|
-
:
|
|
972
|
+
const snapshot = provider === 'codex'
|
|
973
|
+
? await buildCodexUsageSnapshot(agent)
|
|
974
|
+
: provider === 'grok'
|
|
975
|
+
? await buildGrokUsageSnapshot(agent)
|
|
976
|
+
: await buildClaudeUsageSnapshot(agent);
|
|
974
977
|
res.json(snapshot);
|
|
975
978
|
}
|
|
976
979
|
catch (err) {
|