agent-tank 0.9.9 → 0.9.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-tank",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Monitor and query usage limits for LLM CLI tools (Claude, Antigravity, Codex) via HTTP API",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -134,14 +134,6 @@ class BaseAgent {
134
134
  return;
135
135
  }
136
136
 
137
- if (/rate.?limited|rate_limit_error/i.test(cleanOutput)) {
138
- logger.agent(this.name, 'Rate limited, preserving last known usage data');
139
- this.error = 'Rate limited — using cached data';
140
- this.lastUpdated = new Date().toISOString();
141
- // Don't overwrite this.usage — keep the last known good data
142
- return;
143
- }
144
-
145
137
  // Check for session/auth errors — preserve cached data if available
146
138
  const sessionErrorMatch = cleanOutput.match(/session.?expired|session.?error|invalid.?session|authentication.?error|auth.?failed|Unable to (?:load|fetch)|Error loading|could not (?:load|fetch)|Failed to load usage|not authenticated|login required|sign.?in required/i);
147
139
  if (sessionErrorMatch) {
@@ -152,12 +144,7 @@ class BaseAgent {
152
144
  return;
153
145
  }
154
146
 
155
- const parsed = this.parseOutput(output);
156
-
157
- // Only update usage if we got meaningful data (not all-null)
158
- const hasData = parsed && Object.values(parsed).some(v => v !== null && v !== undefined && (typeof v !== 'object' || (Array.isArray(v) ? v.length > 0 : Object.keys(v).length > 0)));
159
- if (hasData) { this.usage = parsed; this.lastUpdated = new Date().toISOString(); } else if (this.usage) { logger.agent(this.name, 'Parse returned no data, preserving last known usage'); this.error = 'Failed to parse — using cached data'; } else { this.usage = parsed; this.lastUpdated = new Date().toISOString(); }
160
- logger.agent(this.name, 'Parsed usage:', logger.json(this.usage));
147
+ this._applyParsedUsage(this.parseOutput(output), cleanOutput);
161
148
  } catch (err) {
162
149
  if (this.isStopping() && err.message === 'Agent stopping') {
163
150
  logger.agent(this.name, 'Refresh cancelled during shutdown');
@@ -171,6 +158,34 @@ class BaseAgent {
171
158
  }
172
159
  }
173
160
 
161
+ // Decide what to do with freshly parsed usage, given the raw (cleaned) output.
162
+ // Prefers fresh data; treats rate-limit notices as an error only when no
163
+ // usable data came back (a partial rate limit — e.g. Claude's per-model /
164
+ // Sonnet breakdown — still yields session/weekly data, and the rate-limited
165
+ // section simply parses to null and is omitted by the formatter).
166
+ _applyParsedUsage(parsed, cleanOutput) {
167
+ const hasData = parsed && Object.values(parsed).some(v =>
168
+ v !== null && v !== undefined &&
169
+ (typeof v !== 'object' || (Array.isArray(v) ? v.length > 0 : Object.keys(v).length > 0)));
170
+
171
+ if (hasData) {
172
+ this.usage = parsed;
173
+ this.lastUpdated = new Date().toISOString();
174
+ } else if (/rate.?limited|rate_limit_error/i.test(cleanOutput)) {
175
+ logger.agent(this.name, 'Rate limited with no usable data, preserving last known usage');
176
+ this.error = this.usage ? 'Rate limited — using cached data' : 'Rate limited';
177
+ this.lastUpdated = new Date().toISOString();
178
+ return;
179
+ } else if (this.usage) {
180
+ logger.agent(this.name, 'Parse returned no data, preserving last known usage');
181
+ this.error = 'Failed to parse — using cached data';
182
+ } else {
183
+ this.usage = parsed;
184
+ this.lastUpdated = new Date().toISOString();
185
+ }
186
+ logger.agent(this.name, 'Parsed usage:', logger.json(this.usage));
187
+ }
188
+
174
189
  async runCommand() {
175
190
  if (this.isStopping()) {
176
191
  throw this._createStopError();
@@ -250,8 +250,20 @@ function addPaceData(sectionData, cycleType) {
250
250
  function parsePtyOutput(clean) {
251
251
  const usage = { session: null, weeklyAll: null, weeklySonnet: null };
252
252
 
253
- // Parse session
254
- const sessionData = parseUsageSection(extractSection(clean, 'Current\\s*session', 'Current\\s*week'));
253
+ // Parse session. The "Current session" label is frequently corrupted while
254
+ // Claude's /usage dialog is still rendering (e.g. "Curretsession"), even
255
+ // though the bar, "N% used", and reset time are intact. When the labeled
256
+ // extraction fails, fall back to the region before the weekly section — the
257
+ // session block is the only "N% used" that appears there.
258
+ let sessionData = parseUsageSection(extractSection(clean, 'Current\\s*session', 'Current\\s*week'));
259
+ if (!sessionData) {
260
+ const beforeWeekly = clean.split(/Current\s*week/i);
261
+ // length > 1 confirms the weekly section exists, i.e. this is real /usage
262
+ // output and beforeWeekly[0] is the session region (not unrelated text).
263
+ if (beforeWeekly.length > 1) {
264
+ sessionData = parseUsageSection(beforeWeekly[0]);
265
+ }
266
+ }
255
267
  if (sessionData) {
256
268
  usage.session = { label: 'Current session', ...sessionData };
257
269
  addPaceData(usage.session, 'session');