@link-assistant/hive-mind 2.7.2 → 2.7.3

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,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.7.3
4
+
5
+ ### Patch Changes
6
+
7
+ - e6c67c0: Reset Anthropic cost accounting between fresh auto-restart working sessions while preserving cumulative totals for true session resumes.
8
+
3
9
  ## 2.7.2
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.7.2",
3
+ "version": "2.7.3",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -29,12 +29,11 @@
29
29
  * shown next to the full-session public estimate covers the same scope. This
30
30
  * module is the single source of truth for that running total:
31
31
  *
32
- * - Each `solve` process seeds the accumulator once from
33
- * `--previous-anthropic-cost` (0 for the first run; the carried-forward
34
- * total for an auto-resumed run).
35
- * - Every finished Claude process adds its own `total_cost_usd` via
36
- * `addAnthropicRunCost`, which also covers the in-process auto-merge loop
37
- * (each iteration is a separate Claude process in the same node process).
32
+ * - Each `executeClaude` call starts a scope. Fresh sessions reset it; true
33
+ * resumes retain it or seed it from `--previous-anthropic-cost` when the
34
+ * resume crosses a process boundary.
35
+ * - Every finished Claude process in that logical session adds its own
36
+ * `total_cost_usd` via `addAnthropicRunCost`.
38
37
  * - The display and the cross-process spawn both read the cumulative total,
39
38
  * so "Calculated by Anthropic" tracks the full session.
40
39
  *
@@ -43,12 +42,12 @@
43
42
  * future model. See docs/case-studies/issue-1886/ for the full analysis.
44
43
  */
45
44
 
46
- // Module-level singleton: the cumulative Anthropic cost for the current logical
47
- // session (this node process plus everything seeded from prior processes).
45
+ // Module-level singleton: the cumulative Anthropic cost for the active logical
46
+ // session (including anything seeded by a true resume from a prior process).
48
47
  let cumulativeAnthropicCostUSD = 0;
49
- // Seeding must happen exactly once per node process. The auto-merge loop calls
50
- // runClaude (and therefore the seed helper) repeatedly within a single process;
51
- // re-seeding from the same CLI flag each time would wipe out accumulation.
48
+ // Seeding must happen exactly once per active scope. Resume retries may call the
49
+ // seed helper repeatedly; re-seeding from the same CLI flag would wipe out the
50
+ // costs already accumulated in this process.
52
51
  let seeded = false;
53
52
 
54
53
  /**
@@ -61,10 +60,34 @@ const toCostAmount = value => {
61
60
  return Number.isFinite(n) && n > 0 ? n : 0;
62
61
  };
63
62
 
63
+ /**
64
+ * Start accounting for one logical Claude session.
65
+ *
66
+ * A fresh execution owns a new transcript and therefore a new cost scope. A
67
+ * true `--resume` execution continues the active scope (or restores it from
68
+ * `--previous-anthropic-cost` when the resume happens in a child process).
69
+ * Calling this at the public `executeClaude` boundary keeps retries inside one
70
+ * scope while preventing watch-mode and mergeability auto-restarts from
71
+ * inheriting the preceding working session's cost.
72
+ *
73
+ * @param {Object} [options]
74
+ * @param {boolean|string} [options.resume=false] - whether this execution resumes an existing Claude session
75
+ * @param {number|string|null} [options.previousAnthropicCost=0] - cumulative cost restored by a resumed child process
76
+ * @returns {number} the cumulative cost at the start of this execution
77
+ */
78
+ export const beginAnthropicCostScope = ({ resume = false, previousAnthropicCost = 0 } = {}) => {
79
+ if (!resume) {
80
+ cumulativeAnthropicCostUSD = 0;
81
+ seeded = true;
82
+ return cumulativeAnthropicCostUSD;
83
+ }
84
+ return seedCumulativeAnthropicCost(previousAnthropicCost);
85
+ };
86
+
64
87
  /**
65
88
  * Seed the accumulator from the carried-forward previous-run cost, exactly once
66
- * per node process. Subsequent calls are no-ops so the in-process auto-merge
67
- * loop does not reset the running total.
89
+ * per active scope. Subsequent calls are no-ops so resume retries do not reset
90
+ * the running total.
68
91
  * @param {number|string|null|undefined} previousAnthropicCostUSD
69
92
  * @returns {number} the cumulative total after seeding
70
93
  */
@@ -98,8 +121,8 @@ export const getCumulativeAnthropicCost = () => cumulativeAnthropicCostUSD;
98
121
  export const hasCumulativeAnthropicCost = () => cumulativeAnthropicCostUSD > 0;
99
122
 
100
123
  /**
101
- * Reset the accumulator. Intended for tests production code seeds once and
102
- * accumulates for the lifetime of the process.
124
+ * Reset the accumulator. Intended for tests; production code starts scopes via
125
+ * `beginAnthropicCostScope`.
103
126
  */
104
127
  export const resetCumulativeAnthropicCost = () => {
105
128
  cumulativeAnthropicCostUSD = 0;
@@ -17,7 +17,7 @@ import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
17
17
  import Decimal from 'decimal.js-light';
18
18
  import { createEmptySubSessionUsage, accumulateModelUsage, mergeResultModelUsage, createSubAgentCallEntry, accumulateSubAgentUsage, getRawRequestInputTokens, displaySessionTokenUsage } from './claude.budget-stats.lib.mjs';
19
19
  import { buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand } from './claude.command-builder.lib.mjs';
20
- import { seedCumulativeAnthropicCost, addAnthropicRunCost } from './anthropic-cost-accumulator.lib.mjs'; // Issue #1886
20
+ import { beginAnthropicCostScope, seedCumulativeAnthropicCost, addAnthropicRunCost } from './anthropic-cost-accumulator.lib.mjs'; // Issues #1886, #2056
21
21
  import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs'; // Issue #942
22
22
  import { SESSION_FORCE_KILLED_MARKER, postTrackedComment } from './tool-comments.lib.mjs'; // Issue #1625
23
23
  import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
@@ -236,6 +236,8 @@ export const resolveThinkingSettings = async (argv, log) => {
236
236
  export const checkPlaywrightMcpAvailability = ensureClaudePlaywrightMcpServer;
237
237
  export const executeClaude = async params => {
238
238
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, setLogFile, getLogFile, formatAligned, getResourceSnapshot, claudePath, $ } = params;
239
+ // Issue #2056: reset fresh sessions while retaining issue #1886's true-resume accumulation.
240
+ beginAnthropicCostScope({ resume: argv.resume, previousAnthropicCost: argv.previousAnthropicCost });
239
241
  if (argv.promptSubagentsViaAgentCommander) {
240
242
  try {
241
243
  await $`which start-agent`;
@@ -304,11 +306,9 @@ export const executeClaude = async params => {
304
306
  }
305
307
  const escapedPrompt = prompt.replace(/"/g, '\\"').replace(/\$/g, '\\$');
306
308
  const escapedSystemPrompt = systemPrompt.replace(/"/g, '\\"').replace(/\$/g, '\\$');
307
-
308
309
  // Issue #1877: deploy the experimental HANDOFF.md Agent Skill so Claude loads
309
310
  // it natively from .claude/skills/handoff/SKILL.md (no-op unless --use-handoff).
310
311
  await deployHandoffSkill({ tempDir, argv, log, $ });
311
-
312
312
  return await withAgentsMdAsClaudeMd({ tempDir, branchName, argv, prompt, fs, path, $, log, formatAligned }, () =>
313
313
  executeClaudeCommand({
314
314
  tempDir,
@@ -1176,6 +1176,7 @@ export const executeClaudeCommand = async params => {
1176
1176
  // logs the failure and returns false; we fall through to the normal commandFailed return below
1177
1177
  // (the 400 is not a transient pattern, so it is not retried).
1178
1178
  if (commandFailed && retryableLastError.requiresFreshSession && (await tryThinkingBlockRecovery({ classified: retryableLastError, source: 'result', sessionId }))) {
1179
+ beginAnthropicCostScope({ resume: argv.resume, previousAnthropicCost: argv.previousAnthropicCost });
1179
1180
  return await executeWithRetry();
1180
1181
  }
1181
1182
  // Issues #1331, #1353, #1472/#1475: Unified transient error retry (exponential backoff, session preservation)
@@ -1376,6 +1377,7 @@ export const executeClaudeCommand = async params => {
1376
1377
  // Issue #1834: Corrupted extended-thinking blocks surfaced as a thrown exception. Same recovery
1377
1378
  // as the streamed-result path: resume the session first, then fall back to a fresh restart.
1378
1379
  if (retryableException.requiresFreshSession && (await tryThinkingBlockRecovery({ classified: retryableException, source: 'exception', sessionId }))) {
1380
+ beginAnthropicCostScope({ resume: argv.resume, previousAnthropicCost: argv.previousAnthropicCost });
1379
1381
  retryCount++;
1380
1382
  return await executeWithRetry();
1381
1383
  }
@@ -181,7 +181,11 @@ export const autoContinueWhenLimitResets = async (issueUrl, sessionId, argv, sho
181
181
  // cost folded in at the runClaude return, plus anything carried from prior
182
182
  // iterations via --previous-anthropic-cost.
183
183
  const carriedAnthropicCost = getCumulativeAnthropicCost();
184
- if (carriedAnthropicCost > 0) {
184
+ // Issue #2056: only a real resume reads the same transcript and belongs to
185
+ // the same cost scope. A configured auto-restart deliberately starts a
186
+ // fresh Claude session, so carrying the old cost would recreate the exact
187
+ // cross-session overcount reported in the issue.
188
+ if (!isRestart && carriedAnthropicCost > 0) {
185
189
  resumeArgs.push('--previous-anthropic-cost', String(carriedAnthropicCost));
186
190
  await log(`💰 Carrying forward cumulative Anthropic cost: $${carriedAnthropicCost.toFixed(6)} (issue #1886)`, { verbose: true });
187
191
  }