tide-commander 0.69.1 → 0.69.2

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/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-CkwIbXCi.js"></script>
25
+ <script type="module" crossorigin src="/assets/main-DkIjeEeR.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-CzElx71o.css">
29
+ <link rel="stylesheet" crossorigin href="/assets/main-BOFXGJQm.css">
30
30
  </head>
31
31
  <body>
32
32
  <div id="app"></div>
@@ -113,6 +113,11 @@ export function createRuntimeEventHandlers(deps) {
113
113
  agentService.updateAgent(agentId, { currentTool: undefined });
114
114
  break;
115
115
  case 'usage_snapshot': {
116
+ // Skip subagent events — their token counts reflect the subagent's context,
117
+ // not the parent's. Updating the parent's contextUsed here would corrupt it.
118
+ if (event.parentToolUseId) {
119
+ break;
120
+ }
116
121
  // Real-time context tracking from streaming usage data.
117
122
  // Context window usage = input tokens only (output tokens don't count toward the limit).
118
123
  // total_input = cache_read + cache_creation + input_tokens (the full prompt size).
@@ -151,6 +156,19 @@ export function createRuntimeEventHandlers(deps) {
151
156
  break;
152
157
  }
153
158
  case 'step_complete': {
159
+ // For subagent events, only accumulate cost (tokensUsed) but don't touch
160
+ // contextUsed/contextLimit/status — those belong to the subagent's context
161
+ // window, not the parent's.
162
+ if (event.parentToolUseId) {
163
+ const subTokensUsed = (event.tokens?.input || 0) + (event.tokens?.output || 0);
164
+ if (subTokensUsed > 0) {
165
+ agentService.updateAgent(agentId, {
166
+ tokensUsed: (agent.tokensUsed || 0) + subTokensUsed,
167
+ }, false);
168
+ }
169
+ log.log(`[step_complete] Subagent event for ${agentId} (parentToolUseId=${event.parentToolUseId}); skipping context update, added ${subTokensUsed} to tokensUsed`);
170
+ break;
171
+ }
154
172
  markStepCompleteReceived(agentId);
155
173
  const isClaudeProvider = (agent.provider ?? 'claude') === 'claude';
156
174
  const isCodexProvider = (agent.provider ?? 'claude') === 'codex';
@@ -162,64 +180,40 @@ export function createRuntimeEventHandlers(deps) {
162
180
  // We must check that it has actual data before using it, otherwise we'd
163
181
  // zero out contextUsed (since all fields would be undefined → 0).
164
182
  const hasModelUsageData = event.modelUsage && Object.keys(event.modelUsage).length > 0;
165
- if (hasModelUsageData && event.modelUsage) {
166
- const cacheRead = event.modelUsage.cacheReadInputTokens || 0;
167
- const cacheCreation = event.modelUsage.cacheCreationInputTokens || 0;
183
+ // For CLAUDE agents: the usage_snapshot handler (from the streaming assistant
184
+ // event) already set the authoritative per-turn contextUsed value. The
185
+ // step_complete's modelUsage/tokens may contain CUMULATIVE session-wide totals
186
+ // which would inflate the tracked context. Only extract contextLimit (window
187
+ // size) from modelUsage — never override contextUsed for Claude agents.
188
+ //
189
+ // For CODEX agents: there's no usage_snapshot, so step_complete is the only
190
+ // source of context estimation.
191
+ if (isClaudeProvider) {
192
+ // Extract contextLimit from modelUsage if available
193
+ if (hasModelUsageData && event.modelUsage?.contextWindow) {
194
+ contextLimit = event.modelUsage.contextWindow;
195
+ }
196
+ // Preserve contextUsed from usage_snapshot (set during streaming)
197
+ contextUsed = agent.contextUsed || 0;
198
+ log.log(`[step_complete] Claude agent ${agentId}: preserving usage_snapshot contextUsed=${contextUsed}, contextLimit=${contextLimit}`);
199
+ }
200
+ else if (hasModelUsageData && event.modelUsage) {
168
201
  const inputTokens = event.modelUsage.inputTokens || 0;
169
202
  const outputTokens = event.modelUsage.outputTokens || 0;
170
- // Only update contextLimit if the event actually carries contextWindow.
171
- // If undefined, keep the existing agent.contextLimit (which may have been
172
- // bumped by usage_snapshot or a previous step_complete).
173
203
  if (event.modelUsage.contextWindow) {
174
204
  contextLimit = event.modelUsage.contextWindow;
175
205
  }
176
- if (isCodexProvider) {
177
- const turnGrowthEstimate = estimateTokensFromText(agent.lastAssignedTask) + outputTokens;
178
- const rollingEstimate = updateCodexRollingContextEstimate(agentId, turnGrowthEstimate);
179
- const plausibleSnapshotLimit = contextLimit * CODEX_PLAUSIBLE_USAGE_MULTIPLIER;
180
- const hasPlausibleSnapshot = inputTokens > 0 && inputTokens <= plausibleSnapshotLimit;
181
- contextUsed = hasPlausibleSnapshot
182
- ? Math.max(rollingEstimate, inputTokens + outputTokens)
183
- : rollingEstimate;
184
- }
185
- else {
186
- // Context window = input side only (output tokens don't count toward the limit).
187
- // IMPORTANT: modelUsage token sums in result events are CUMULATIVE session-wide
188
- // totals, not per-request context fill. If the sum exceeds contextLimit, it's
189
- // clearly cumulative — preserve the usage_snapshot value which IS per-request.
190
- const modelUsageSum = cacheRead + cacheCreation + inputTokens;
191
- if (modelUsageSum > contextLimit) {
192
- // Cumulative session total detected. Prefer existing usage_snapshot value,
193
- // but if that's ALSO stale (exceeds limit), fall back to 0 rather than
194
- // propagating a bad value.
195
- const existing = agent.contextUsed || 0;
196
- contextUsed = existing <= contextLimit ? existing : 0;
197
- log.log(`[step_complete] modelUsage sum ${modelUsageSum} exceeds contextLimit ${contextLimit} for ${agentId} (cumulative); using contextUsed=${contextUsed}`);
198
- }
199
- else {
200
- contextUsed = modelUsageSum;
201
- }
202
- }
203
- log.log(`[step_complete] modelUsage data for ${agentId}: cacheRead=${cacheRead}, cacheCreation=${cacheCreation}, input=${inputTokens}, contextWindow=${event.modelUsage.contextWindow || 'none'}`);
206
+ const turnGrowthEstimate = estimateTokensFromText(agent.lastAssignedTask) + outputTokens;
207
+ const rollingEstimate = updateCodexRollingContextEstimate(agentId, turnGrowthEstimate);
208
+ const plausibleSnapshotLimit = contextLimit * CODEX_PLAUSIBLE_USAGE_MULTIPLIER;
209
+ const hasPlausibleSnapshot = inputTokens > 0 && inputTokens <= plausibleSnapshotLimit;
210
+ contextUsed = hasPlausibleSnapshot
211
+ ? Math.max(rollingEstimate, inputTokens + outputTokens)
212
+ : rollingEstimate;
213
+ log.log(`[step_complete] Codex modelUsage for ${agentId}: input=${inputTokens}, contextWindow=${event.modelUsage.contextWindow || 'none'}`);
204
214
  }
205
215
  else if (event.tokens) {
206
- if (isClaudeProvider) {
207
- const cacheRead = event.tokens.cacheRead || 0;
208
- const cacheCreation = event.tokens.cacheCreation || 0;
209
- const inputTokens = event.tokens.input || 0;
210
- // Context window = input side only.
211
- // Same cumulative guard as modelUsage path above.
212
- const tokenSum = cacheRead + cacheCreation + inputTokens;
213
- if (tokenSum > contextLimit) {
214
- const existing = agent.contextUsed || 0;
215
- contextUsed = existing <= contextLimit ? existing : 0;
216
- log.log(`[step_complete] tokens sum ${tokenSum} exceeds contextLimit ${contextLimit} for ${agentId} (cumulative); using contextUsed=${contextUsed}`);
217
- }
218
- else {
219
- contextUsed = tokenSum;
220
- }
221
- }
222
- else {
216
+ if (isCodexProvider) {
223
217
  const inputTokens = event.tokens.input || 0;
224
218
  const outputTokens = event.tokens.output || 0;
225
219
  const turnGrowthEstimate = estimateTokensFromText(agent.lastAssignedTask) + outputTokens;
@@ -231,14 +225,8 @@ export function createRuntimeEventHandlers(deps) {
231
225
  : rollingEstimate;
232
226
  contextLimit = agent.contextLimit || DEFAULT_CODEX_CONTEXT_WINDOW;
233
227
  }
228
+ // For Claude with tokens but no modelUsage — usage_snapshot already handled it
234
229
  }
235
- const hasZeroTokenUsage = !!event.tokens
236
- && (event.tokens.input || 0) === 0
237
- && (event.tokens.output || 0) === 0
238
- && (event.tokens.cacheRead || 0) === 0
239
- && (event.tokens.cacheCreation || 0) === 0;
240
- // Treat empty objects {} as "no model usage" (they have no meaningful data)
241
- const hasNoModelUsage = !hasModelUsageData;
242
230
  // For /context, /cost, /compact: the context_stats event already set the
243
231
  // authoritative contextUsed/contextLimit values. Don't overwrite them with
244
232
  // zero-token step_complete data from the local command.
@@ -255,21 +243,6 @@ export function createRuntimeEventHandlers(deps) {
255
243
  log.log(`[step_complete] Context command for ${agentId}; preserving context values from tracked fields`);
256
244
  }
257
245
  }
258
- else if (isClaudeProvider && hasZeroTokenUsage && hasNoModelUsage) {
259
- contextUsed = agent.contextUsed || 0;
260
- contextLimit = agent.contextLimit || 200000;
261
- log.log(`[step_complete] Claude empty usage detected for ${agentId}; preserving previous context`);
262
- }
263
- else if (isClaudeProvider && !hasModelUsageData && event.tokens) {
264
- // modelUsage was {} (empty) but we DO have event.tokens.
265
- // The usage_snapshot handler already set the correct contextUsed value
266
- // from the assistant event's usage data. Don't overwrite it.
267
- // The event.tokens here come from the result event, which for Claude
268
- // in --print mode may have different semantics. Prefer the last
269
- // usage_snapshot value which is the most accurate real-time reading.
270
- log.log(`[step_complete] Claude empty modelUsage but has tokens for ${agentId}; preserving usage_snapshot contextUsed=${agent.contextUsed}`);
271
- contextUsed = agent.contextUsed || 0;
272
- }
273
246
  // Don't clamp contextUsed to contextLimit - models can have up to 1M context.
274
247
  // The contextLimit comes from modelUsage.contextWindow which is authoritative.
275
248
  contextUsed = Math.max(0, contextUsed);
@@ -144,7 +144,8 @@ export function setupRuntimeListeners(ctx) {
144
144
  }
145
145
  }
146
146
  // Real-time context tracking: broadcast lightweight context_update on usage_snapshot and step_complete
147
- if ((event.type === 'usage_snapshot' && event.tokens) || event.type === 'step_complete') {
147
+ // Skip subagent events their token counts reflect the subagent's own context, not the parent's.
148
+ if (!event.parentToolUseId && ((event.type === 'usage_snapshot' && event.tokens) || event.type === 'step_complete')) {
148
149
  const agent = agentService.getAgent(agentId);
149
150
  if (agent) {
150
151
  ctx.broadcast({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "0.69.1",
3
+ "version": "0.69.2",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",