@inetafrica/open-claudia 3.0.5 → 3.0.6

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.0.6 — cache-bust burn is now a one-line query
4
+
5
+ - **Usage records carry a `coldStart` flag.** The restart→cache-bust burn (each bot restart re-bills the whole accumulated window as cache *writes* instead of ~13×-cheaper cache *reads*) was invisible to every token-count analysis — same token counts, different price class — and took a forensic reconstruction of the 22-day usage history to quantify (~8% of all spend, ~$12/day at its worst). Now the first usage record a process writes for each session is tagged `coldStart: true`: a cold start mid-conversation is the restart fingerprint, so the burn is one jq filter away instead of a modelling exercise. Records already carried `cacheReadTokens`/`cacheCreationTokens`; this adds the missing "was the cache necessarily cold?" dimension.
6
+ - **Agent-space / OpenClaw:** no new deps, no new env, no schema change (additive JSONL field); Docker image builds identically. `usageSessionColdStart` covered by new assertions in `test-usage-accounting.js`.
7
+
3
8
  ## v3.0.5 — the conflict handler owns the poll
4
9
 
5
10
  - **Hiccup heals no longer fight the 409 backoff.** During a 409 conflict streak the conflict handler pauses polling and retries on its own schedule — but a network hiccup (`ECONNRESET`) arriving mid-streak would schedule its own heal, which called `startPolling` straight back into the conflict and reset the streak clock. This was the live v2.15 burn mechanism: its own ghost long-poll after an ECONNRESET heal raised a 409, the old code exited on any 409, launchd respawned it (~1/hr, 208 lifetime startups), and every restart busted the prompt cache so the next turn re-billed the whole accumulated window uncached. Hiccup handling is now a no-op while a conflict streak is active — the conflict handler owns the poll lifecycle.
package/core/runner.js CHANGED
@@ -183,6 +183,7 @@ function logTurnUsage(state, usage, costUsd, announce, opts = {}) {
183
183
  billedContextTokens: parts.context,
184
184
  costUsd: typeof costUsd === "number" ? costUsd : 0,
185
185
  };
186
+ if (typeof opts.coldStart === "boolean") record.coldStart = opts.coldStart;
186
187
  if (opts.rawUsage && opts.rawUsage !== usage) {
187
188
  const raw = usageParts(opts.rawUsage, backend);
188
189
  record.rawInputTokens = raw.input;
@@ -1425,6 +1426,18 @@ async function persistForegroundTranscript(result, runContext, state = currentSt
1425
1426
  );
1426
1427
  }
1427
1428
 
1429
+ // Sessions this PROCESS has already recorded usage for. The first record per
1430
+ // session since boot is a cold start: the prompt cache is necessarily cold, so
1431
+ // the whole accumulated window re-bills as cache writes (~13x the read rate).
1432
+ // Restart burn is therefore one query away: coldStart=true mid-conversation.
1433
+ const warmUsageSessions = new Set();
1434
+ function usageSessionColdStart(sessionId, warmSet = warmUsageSessions) {
1435
+ if (!sessionId) return true;
1436
+ if (warmSet.has(sessionId)) return false;
1437
+ warmSet.add(sessionId);
1438
+ return true;
1439
+ }
1440
+
1428
1441
  async function persistForegroundUsage(result, runContext, state = currentState(), store = chatContext.getStore()) {
1429
1442
  if (!result.usage) return;
1430
1443
  const previousUsageBySession = structuredClone(state.usageBySession || {});
@@ -1468,6 +1481,7 @@ async function persistForegroundUsage(result, runContext, state = currentState()
1468
1481
  usageScope,
1469
1482
  liveContextTokens,
1470
1483
  rawUsage,
1484
+ coldStart: usageSessionColdStart(usageSessionId),
1471
1485
  strict: true,
1472
1486
  },
1473
1487
  );
@@ -2056,6 +2070,7 @@ module.exports = {
2056
2070
  effectiveCompactThreshold,
2057
2071
  usageParts,
2058
2072
  peakContextTokens,
2073
+ usageSessionColdStart,
2059
2074
  clampUsageDelta,
2060
2075
  normalizeCodexUsage,
2061
2076
  applyUsageToState,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -32,11 +32,11 @@ assert.doesNotMatch(setup, /Your Claude Code bot is connected|Description=Claude
32
32
 
33
33
  const pkg = JSON.parse(read("package.json"));
34
34
  assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
35
- // 3.0.5 approved by Sumeet 2026-07-12 ("no do it on the latest co0de and push
36
- // a new version" 409-conflict/heal/wedge guards so a token fight never
37
- // restarts the bot and busts the prompt cache; walker stays on per his call
38
- // that v2's walker was fine and v3's is identical).
39
- assert.strictEqual(pkg.version, "3.0.5", "release version must remain unchanged without explicit approval");
35
+ // 3.0.6 approved by Sumeet 2026-07-12 ("Yeh you can do that too" coldStart
36
+ // flag on usage records so restart cache-bust burn, measured at ~8% of all
37
+ // spend from the 22-day history, is a one-line query instead of a forensic
38
+ // reconstruction).
39
+ assert.strictEqual(pkg.version, "3.0.6", "release version must remain unchanged without explicit approval");
40
40
  for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
41
41
  assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
42
42
  }
@@ -10,6 +10,7 @@ const {
10
10
  applyUsageToState,
11
11
  shouldAutoCompact,
12
12
  peakContextTokens,
13
+ usageSessionColdStart,
13
14
  } = require("./core/runner");
14
15
  const stateApi = require("./core/state");
15
16
  const { normalizeUsageRecord, loadUsageHistory, USAGE_HISTORY_FILE } = require("./core/usage-log");
@@ -176,5 +177,17 @@ assert.strictEqual(peakContextTokens([
176
177
  { type: "usage", scope: "provider_call", usage: { input_tokens: 70, cached_input_tokens: 30, output_tokens: 5 } },
177
178
  ], "codex"), 70, "codex context counts input only, matching usageParts");
178
179
 
180
+ // usageSessionColdStart: the first usage record per session since process boot
181
+ // is a cold start (prompt cache empty — the restart-burn fingerprint); every
182
+ // later record for that session in the same process is warm.
183
+ {
184
+ const warm = new Set();
185
+ assert.strictEqual(usageSessionColdStart("sess-a", warm), true, "first record for a session is cold");
186
+ assert.strictEqual(usageSessionColdStart("sess-a", warm), false, "second record for the same session is warm");
187
+ assert.strictEqual(usageSessionColdStart("sess-b", warm), true, "a different session is cold on its first record");
188
+ assert.strictEqual(usageSessionColdStart(null, warm), true, "no session id → always treated cold");
189
+ assert.strictEqual(usageSessionColdStart(null, warm), true, "null never warms");
190
+ }
191
+
179
192
  fs.rmSync(configDir, { recursive: true, force: true });
180
193
  console.log("usage accounting OK");