@node9/proxy 2.6.2 → 2.7.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/dist/cli.js CHANGED
@@ -2561,8 +2561,7 @@ function extractSessionLevelFindings(calls, ctx) {
2561
2561
  occurrenceCount: 1,
2562
2562
  loopCount: verdict.count,
2563
2563
  loopKind: "loop",
2564
- commandPreview: previewArgs(call.args, DEDUPE_PREVIEW_LEN),
2565
- costUsd: verdict.count * COST_PER_LOOP_ITER_USD
2564
+ commandPreview: previewArgs(call.args, DEDUPE_PREVIEW_LEN)
2566
2565
  });
2567
2566
  }
2568
2567
  return out;
@@ -2632,7 +2631,7 @@ function* stringValues(obj, depth = 0) {
2632
2631
  }
2633
2632
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
2634
2633
  }
2635
- var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto3, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
2634
+ var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto3, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
2636
2635
  var init_dist = __esm({
2637
2636
  "packages/policy-engine/dist/index.mjs"() {
2638
2637
  "use strict";
@@ -4439,7 +4438,6 @@ var init_dist = __esm({
4439
4438
  longOutputRedactions: 1
4440
4439
  };
4441
4440
  LOOP_THRESHOLD_FOR_WASTE = 3;
4442
- COST_PER_LOOP_ITER_USD = 6e-3;
4443
4441
  DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
4444
4442
  SENSITIVE_PATH_RE = /\.aws\/(credentials|config)\b|\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)\b|\.env(\.|$|\b)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
4445
4443
  FILE_TOOLS = /* @__PURE__ */ new Set([
@@ -11962,6 +11960,31 @@ function agentColorName(agent) {
11962
11960
  return "cyan";
11963
11961
  }
11964
11962
  }
11963
+ function computeLoopWaste(loops, perSession) {
11964
+ const rate = /* @__PURE__ */ new Map();
11965
+ for (const s of perSession) {
11966
+ if (!(s.toolCalls > 0) || !(s.costUSD > 0)) continue;
11967
+ const r = s.costUSD / s.toolCalls;
11968
+ if (!Number.isFinite(r) || r <= 0 || r > MAX_PLAUSIBLE_RATE_USD) continue;
11969
+ rate.set(s.sessionId, r);
11970
+ }
11971
+ let usd = 0;
11972
+ let pricedIterations = 0;
11973
+ let unpricedIterations = 0;
11974
+ for (const l of loops) {
11975
+ if (l.kind === "long-iteration") continue;
11976
+ const wasted = Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE);
11977
+ if (!Number.isFinite(wasted) || wasted === 0) continue;
11978
+ const r = rate.get(l.sessionId);
11979
+ if (r === void 0) {
11980
+ unpricedIterations += wasted;
11981
+ continue;
11982
+ }
11983
+ usd += wasted * r;
11984
+ pricedIterations += wasted;
11985
+ }
11986
+ return { usd, pricedIterations, unpricedIterations };
11987
+ }
11965
11988
  function buildScanSummary(agents) {
11966
11989
  const stats = {
11967
11990
  sessions: 0,
@@ -12022,8 +12045,11 @@ function buildScanSummary(agents) {
12022
12045
  costUSD: a.scan.totalCostUSD
12023
12046
  })).filter((s) => s.sessions > 0 || s.findings > 0);
12024
12047
  const sections = buildSections(allFindings);
12025
- const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
12026
- const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
12048
+ const loopWaste = computeLoopWaste(
12049
+ allLoops,
12050
+ agents.flatMap((a) => a.scan.perSession)
12051
+ );
12052
+ const loopWastedUSD = loopWaste.usd;
12027
12053
  return {
12028
12054
  stats,
12029
12055
  byVerdict,
@@ -12031,7 +12057,8 @@ function buildScanSummary(agents) {
12031
12057
  sections,
12032
12058
  leaks: allLeaks,
12033
12059
  loops: allLoops,
12034
- loopWastedUSD
12060
+ loopWastedUSD,
12061
+ loopWaste
12035
12062
  };
12036
12063
  }
12037
12064
  function buildSections(findings) {
@@ -12138,7 +12165,7 @@ function fullCommandOf(input) {
12138
12165
  const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
12139
12166
  return String(raw).replace(/\s+/g, " ").trim();
12140
12167
  }
12141
- var AGENT_SHORT, AGENT_LONG;
12168
+ var AGENT_SHORT, AGENT_LONG, MAX_PLAUSIBLE_RATE_USD;
12142
12169
  var init_scan_summary = __esm({
12143
12170
  "src/scan-summary.ts"() {
12144
12171
  "use strict";
@@ -12161,6 +12188,7 @@ var init_scan_summary = __esm({
12161
12188
  copilot: "GitHub Copilot",
12162
12189
  shell: "Shell"
12163
12190
  };
12191
+ MAX_PLAUSIBLE_RATE_USD = 50;
12164
12192
  }
12165
12193
  });
12166
12194
 
@@ -12368,7 +12396,7 @@ function topRulesByVerdict(sections, verdict, n) {
12368
12396
  }
12369
12397
  return matched.sort((a, b) => b.count - a.count).slice(0, n);
12370
12398
  }
12371
- function computeLoopWaste(loops, totalToolCalls) {
12399
+ function computeLoopWaste2(loops, totalToolCalls) {
12372
12400
  const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
12373
12401
  const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
12374
12402
  return { wastedCalls, wastePct };
@@ -13954,6 +13982,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
13954
13982
  result.sessions++;
13955
13983
  onProgress?.(result.filesScanned);
13956
13984
  const sessionId = file.replace(/\.jsonl$/, "");
13985
+ const session = { sessionId, costUSD: 0, toolCalls: 0 };
13957
13986
  let raw;
13958
13987
  try {
13959
13988
  raw = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
@@ -14041,7 +14070,9 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14041
14070
  if (usage && model) {
14042
14071
  const p = claudeModelPrice(model);
14043
14072
  if (p) {
14044
- result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
14073
+ const rowCost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
14074
+ result.totalCostUSD += rowCost;
14075
+ session.costUSD += rowCost;
14045
14076
  }
14046
14077
  }
14047
14078
  const content = entry.message?.content;
@@ -14049,6 +14080,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14049
14080
  for (const block of content) {
14050
14081
  if (block.type !== "tool_use") continue;
14051
14082
  result.totalToolCalls++;
14083
+ session.toolCalls++;
14052
14084
  const toolName = block.name ?? "";
14053
14085
  const toolNameLower = toolName.toLowerCase();
14054
14086
  const input = block.input ?? {};
@@ -14160,6 +14192,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14160
14192
  if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
14161
14193
  result.sessionsWithEarlySecrets++;
14162
14194
  }
14195
+ result.perSession.push(session);
14163
14196
  }
14164
14197
  function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
14165
14198
  const projPath = import_path28.default.join(projectsDir, proj);
@@ -14204,7 +14237,8 @@ function emptyClaudeScan() {
14204
14237
  totalCostUSD: 0,
14205
14238
  firstDate: null,
14206
14239
  lastDate: null,
14207
- sessionsWithEarlySecrets: 0
14240
+ sessionsWithEarlySecrets: 0,
14241
+ perSession: []
14208
14242
  };
14209
14243
  }
14210
14244
  function scanClaudeHistory(startDate, onProgress, onLine) {
@@ -14246,7 +14280,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
14246
14280
  totalCostUSD: 0,
14247
14281
  firstDate: null,
14248
14282
  lastDate: null,
14249
- sessionsWithEarlySecrets: 0
14283
+ sessionsWithEarlySecrets: 0,
14284
+ perSession: []
14250
14285
  };
14251
14286
  const dedup = emptyScanDedup();
14252
14287
  if (!import_fs26.default.existsSync(tmpDir)) return result;
@@ -14484,7 +14519,8 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
14484
14519
  // transcripts carry no token/model data
14485
14520
  firstDate: null,
14486
14521
  lastDate: null,
14487
- sessionsWithEarlySecrets: 0
14522
+ sessionsWithEarlySecrets: 0,
14523
+ perSession: []
14488
14524
  };
14489
14525
  const dedup = emptyScanDedup();
14490
14526
  const brainDirs = antigravityBrainDirs();
@@ -14680,7 +14716,8 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
14680
14716
  // event logs carry no token/cost rollup
14681
14717
  firstDate: null,
14682
14718
  lastDate: null,
14683
- sessionsWithEarlySecrets: 0
14719
+ sessionsWithEarlySecrets: 0,
14720
+ perSession: []
14684
14721
  };
14685
14722
  const dedup = emptyScanDedup();
14686
14723
  if (!import_fs26.default.existsSync(sessionDir)) return result;
@@ -14862,7 +14899,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
14862
14899
  totalCostUSD: 0,
14863
14900
  firstDate: null,
14864
14901
  lastDate: null,
14865
- sessionsWithEarlySecrets: 0
14902
+ sessionsWithEarlySecrets: 0,
14903
+ perSession: []
14866
14904
  };
14867
14905
  const dedup = emptyScanDedup();
14868
14906
  if (!import_fs26.default.existsSync(sessionsBase)) return result;
@@ -15141,7 +15179,8 @@ function mergeScans(a, b) {
15141
15179
  totalCostUSD: a.totalCostUSD + b.totalCostUSD,
15142
15180
  firstDate: dates.length ? dates.sort()[0] : null,
15143
15181
  lastDate: lastDates.length ? lastDates.sort().at(-1) : null,
15144
- sessionsWithEarlySecrets: a.sessionsWithEarlySecrets + b.sessionsWithEarlySecrets
15182
+ sessionsWithEarlySecrets: a.sessionsWithEarlySecrets + b.sessionsWithEarlySecrets,
15183
+ perSession: [...a.perSession, ...b.perSession]
15145
15184
  };
15146
15185
  }
15147
15186
  function verdictIcon(verdict) {
@@ -15225,9 +15264,9 @@ function renderCompactScorecard(input) {
15225
15264
  const realLoops = scan.loopFindings.filter((l) => l.kind !== "long-iteration");
15226
15265
  const longIterations = scan.loopFindings.filter((l) => l.kind === "long-iteration");
15227
15266
  if (realLoops.length > 0) {
15228
- const { wastePct, wastedCalls } = computeLoopWaste(realLoops, scan.totalToolCalls);
15267
+ const { wastePct, wastedCalls } = computeLoopWaste2(realLoops, scan.totalToolCalls);
15229
15268
  const wasteParts = [];
15230
- if (wastedCalls > 0) wasteParts.push(`${wastedCalls} wasted calls (${wastePct}% of all calls)`);
15269
+ if (wastedCalls > 0) wasteParts.push(`${wastedCalls} repeat calls (${wastePct}% of all calls)`);
15231
15270
  if (summary.loopWastedUSD > 0) wasteParts.push("~" + fmtCost(summary.loopWastedUSD));
15232
15271
  const wasteSummary = wasteParts.length ? `(${wasteParts.join(" \xB7 ")})` : "";
15233
15272
  console.log(
@@ -15305,10 +15344,10 @@ function renderNarrativeScorecard(input) {
15305
15344
  });
15306
15345
  }
15307
15346
  if (scan.loopFindings.length > 0) {
15308
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15347
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15309
15348
  const cost = summary.loopWastedUSD > 0 ? `, ~${fmtCost(summary.loopWastedUSD)} wasted` : "";
15310
15349
  medium.push({
15311
- label: `${scan.loopFindings.length} agent loops (${wastedCalls} wasted calls, ${wastePct}% of calls${cost})`,
15350
+ label: `${scan.loopFindings.length} agent loops (${wastedCalls} repeat calls, ${wastePct}% of calls${cost})`,
15312
15351
  count: scan.loopFindings.length
15313
15352
  });
15314
15353
  }
@@ -15420,13 +15459,13 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15420
15459
  );
15421
15460
  }
15422
15461
  if (scan.loopFindings.length > 0) {
15423
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15462
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15424
15463
  const byTool = /* @__PURE__ */ new Map();
15425
15464
  for (const f of scan.loopFindings) {
15426
15465
  byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
15427
15466
  }
15428
15467
  const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
15429
- const wasteSuffix = wastedCalls > 0 ? `, ${wastedCalls} wasted calls \xB7 ${wastePct}% of calls` : "";
15468
+ const wasteSuffix = wastedCalls > 0 ? `, ${wastedCalls} repeat calls \xB7 ${wastePct}% of calls` : "";
15430
15469
  const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
15431
15470
  topLines.push(
15432
15471
  mkLine(
@@ -15518,7 +15557,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15518
15557
  console.log("");
15519
15558
  }
15520
15559
  if (scan.loopFindings.length > 0) {
15521
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15560
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15522
15561
  const byTool = /* @__PURE__ */ new Map();
15523
15562
  let totalRepeats = 0;
15524
15563
  for (const f of scan.loopFindings) {
@@ -15548,7 +15587,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15548
15587
  loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, import_chalk6.default.bold], [target, import_chalk6.default.dim]));
15549
15588
  }
15550
15589
  }
15551
- const wasteSuffix = wastedCalls > 0 ? ` \xB7 ${wastedCalls} wasted calls (${wastePct}% of all calls)` : "";
15590
+ const wasteSuffix = wastedCalls > 0 ? ` \xB7 ${wastedCalls} repeat calls (${wastePct}% of all calls)` : "";
15552
15591
  const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} pattern${scan.loopFindings.length !== 1 ? "s" : ""}${wasteSuffix}`;
15553
15592
  for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
15554
15593
  console.log("");
@@ -15883,7 +15922,7 @@ function registerScanCommand(program2) {
15883
15922
  );
15884
15923
  }
15885
15924
  if (scan.loopFindings.length > 0) {
15886
- const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15925
+ const { wastePct } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15887
15926
  const wasteSuffix = wastePct > 0 ? import_chalk6.default.dim(` (${wastePct}% of calls)`) : "";
15888
15927
  cardParts.push(
15889
15928
  import_chalk6.default.yellow("\u{1F501} ") + import_chalk6.default.yellow.bold(String(scan.loopFindings.length)) + import_chalk6.default.dim(" loops") + wasteSuffix
@@ -21056,7 +21095,8 @@ data: ${JSON.stringify(item.data)}
21056
21095
  totalCostUSD: 0,
21057
21096
  firstDate: null,
21058
21097
  lastDate: null,
21059
- sessionsWithEarlySecrets: 0
21098
+ sessionsWithEarlySecrets: 0,
21099
+ perSession: []
21060
21100
  };
21061
21101
  let claude = EMPTY_SCAN;
21062
21102
  let gemini = EMPTY_SCAN;
package/dist/cli.mjs CHANGED
@@ -2571,8 +2571,7 @@ function extractSessionLevelFindings(calls, ctx) {
2571
2571
  occurrenceCount: 1,
2572
2572
  loopCount: verdict.count,
2573
2573
  loopKind: "loop",
2574
- commandPreview: previewArgs(call.args, DEDUPE_PREVIEW_LEN),
2575
- costUsd: verdict.count * COST_PER_LOOP_ITER_USD
2574
+ commandPreview: previewArgs(call.args, DEDUPE_PREVIEW_LEN)
2576
2575
  });
2577
2576
  }
2578
2577
  return out;
@@ -2642,7 +2641,7 @@ function* stringValues(obj, depth = 0) {
2642
2641
  }
2643
2642
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
2644
2643
  }
2645
- var MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
2644
+ var MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
2646
2645
  var init_dist = __esm({
2647
2646
  "packages/policy-engine/dist/index.mjs"() {
2648
2647
  "use strict";
@@ -4443,7 +4442,6 @@ var init_dist = __esm({
4443
4442
  longOutputRedactions: 1
4444
4443
  };
4445
4444
  LOOP_THRESHOLD_FOR_WASTE = 3;
4446
- COST_PER_LOOP_ITER_USD = 6e-3;
4447
4445
  DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
4448
4446
  SENSITIVE_PATH_RE = /\.aws\/(credentials|config)\b|\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)\b|\.env(\.|$|\b)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
4449
4447
  FILE_TOOLS = /* @__PURE__ */ new Set([
@@ -11963,6 +11961,31 @@ function agentColorName(agent) {
11963
11961
  return "cyan";
11964
11962
  }
11965
11963
  }
11964
+ function computeLoopWaste(loops, perSession) {
11965
+ const rate = /* @__PURE__ */ new Map();
11966
+ for (const s of perSession) {
11967
+ if (!(s.toolCalls > 0) || !(s.costUSD > 0)) continue;
11968
+ const r = s.costUSD / s.toolCalls;
11969
+ if (!Number.isFinite(r) || r <= 0 || r > MAX_PLAUSIBLE_RATE_USD) continue;
11970
+ rate.set(s.sessionId, r);
11971
+ }
11972
+ let usd = 0;
11973
+ let pricedIterations = 0;
11974
+ let unpricedIterations = 0;
11975
+ for (const l of loops) {
11976
+ if (l.kind === "long-iteration") continue;
11977
+ const wasted = Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE);
11978
+ if (!Number.isFinite(wasted) || wasted === 0) continue;
11979
+ const r = rate.get(l.sessionId);
11980
+ if (r === void 0) {
11981
+ unpricedIterations += wasted;
11982
+ continue;
11983
+ }
11984
+ usd += wasted * r;
11985
+ pricedIterations += wasted;
11986
+ }
11987
+ return { usd, pricedIterations, unpricedIterations };
11988
+ }
11966
11989
  function buildScanSummary(agents) {
11967
11990
  const stats = {
11968
11991
  sessions: 0,
@@ -12023,8 +12046,11 @@ function buildScanSummary(agents) {
12023
12046
  costUSD: a.scan.totalCostUSD
12024
12047
  })).filter((s) => s.sessions > 0 || s.findings > 0);
12025
12048
  const sections = buildSections(allFindings);
12026
- const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
12027
- const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
12049
+ const loopWaste = computeLoopWaste(
12050
+ allLoops,
12051
+ agents.flatMap((a) => a.scan.perSession)
12052
+ );
12053
+ const loopWastedUSD = loopWaste.usd;
12028
12054
  return {
12029
12055
  stats,
12030
12056
  byVerdict,
@@ -12032,7 +12058,8 @@ function buildScanSummary(agents) {
12032
12058
  sections,
12033
12059
  leaks: allLeaks,
12034
12060
  loops: allLoops,
12035
- loopWastedUSD
12061
+ loopWastedUSD,
12062
+ loopWaste
12036
12063
  };
12037
12064
  }
12038
12065
  function buildSections(findings) {
@@ -12139,7 +12166,7 @@ function fullCommandOf(input) {
12139
12166
  const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
12140
12167
  return String(raw).replace(/\s+/g, " ").trim();
12141
12168
  }
12142
- var AGENT_SHORT, AGENT_LONG;
12169
+ var AGENT_SHORT, AGENT_LONG, MAX_PLAUSIBLE_RATE_USD;
12143
12170
  var init_scan_summary = __esm({
12144
12171
  "src/scan-summary.ts"() {
12145
12172
  "use strict";
@@ -12162,6 +12189,7 @@ var init_scan_summary = __esm({
12162
12189
  copilot: "GitHub Copilot",
12163
12190
  shell: "Shell"
12164
12191
  };
12192
+ MAX_PLAUSIBLE_RATE_USD = 50;
12165
12193
  }
12166
12194
  });
12167
12195
 
@@ -12370,7 +12398,7 @@ function topRulesByVerdict(sections, verdict, n) {
12370
12398
  }
12371
12399
  return matched.sort((a, b) => b.count - a.count).slice(0, n);
12372
12400
  }
12373
- function computeLoopWaste(loops, totalToolCalls) {
12401
+ function computeLoopWaste2(loops, totalToolCalls) {
12374
12402
  const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
12375
12403
  const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
12376
12404
  return { wastedCalls, wastePct };
@@ -13959,6 +13987,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
13959
13987
  result.sessions++;
13960
13988
  onProgress?.(result.filesScanned);
13961
13989
  const sessionId = file.replace(/\.jsonl$/, "");
13990
+ const session = { sessionId, costUSD: 0, toolCalls: 0 };
13962
13991
  let raw;
13963
13992
  try {
13964
13993
  raw = fs27.readFileSync(path29.join(projPath, file), "utf-8");
@@ -14046,7 +14075,9 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14046
14075
  if (usage && model) {
14047
14076
  const p = claudeModelPrice(model);
14048
14077
  if (p) {
14049
- result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
14078
+ const rowCost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
14079
+ result.totalCostUSD += rowCost;
14080
+ session.costUSD += rowCost;
14050
14081
  }
14051
14082
  }
14052
14083
  const content = entry.message?.content;
@@ -14054,6 +14085,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14054
14085
  for (const block of content) {
14055
14086
  if (block.type !== "tool_use") continue;
14056
14087
  result.totalToolCalls++;
14088
+ session.toolCalls++;
14057
14089
  const toolName = block.name ?? "";
14058
14090
  const toolNameLower = toolName.toLowerCase();
14059
14091
  const input = block.input ?? {};
@@ -14165,6 +14197,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14165
14197
  if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
14166
14198
  result.sessionsWithEarlySecrets++;
14167
14199
  }
14200
+ result.perSession.push(session);
14168
14201
  }
14169
14202
  function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
14170
14203
  const projPath = path29.join(projectsDir, proj);
@@ -14209,7 +14242,8 @@ function emptyClaudeScan() {
14209
14242
  totalCostUSD: 0,
14210
14243
  firstDate: null,
14211
14244
  lastDate: null,
14212
- sessionsWithEarlySecrets: 0
14245
+ sessionsWithEarlySecrets: 0,
14246
+ perSession: []
14213
14247
  };
14214
14248
  }
14215
14249
  function scanClaudeHistory(startDate, onProgress, onLine) {
@@ -14251,7 +14285,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
14251
14285
  totalCostUSD: 0,
14252
14286
  firstDate: null,
14253
14287
  lastDate: null,
14254
- sessionsWithEarlySecrets: 0
14288
+ sessionsWithEarlySecrets: 0,
14289
+ perSession: []
14255
14290
  };
14256
14291
  const dedup = emptyScanDedup();
14257
14292
  if (!fs27.existsSync(tmpDir)) return result;
@@ -14489,7 +14524,8 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
14489
14524
  // transcripts carry no token/model data
14490
14525
  firstDate: null,
14491
14526
  lastDate: null,
14492
- sessionsWithEarlySecrets: 0
14527
+ sessionsWithEarlySecrets: 0,
14528
+ perSession: []
14493
14529
  };
14494
14530
  const dedup = emptyScanDedup();
14495
14531
  const brainDirs = antigravityBrainDirs();
@@ -14685,7 +14721,8 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
14685
14721
  // event logs carry no token/cost rollup
14686
14722
  firstDate: null,
14687
14723
  lastDate: null,
14688
- sessionsWithEarlySecrets: 0
14724
+ sessionsWithEarlySecrets: 0,
14725
+ perSession: []
14689
14726
  };
14690
14727
  const dedup = emptyScanDedup();
14691
14728
  if (!fs27.existsSync(sessionDir)) return result;
@@ -14867,7 +14904,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
14867
14904
  totalCostUSD: 0,
14868
14905
  firstDate: null,
14869
14906
  lastDate: null,
14870
- sessionsWithEarlySecrets: 0
14907
+ sessionsWithEarlySecrets: 0,
14908
+ perSession: []
14871
14909
  };
14872
14910
  const dedup = emptyScanDedup();
14873
14911
  if (!fs27.existsSync(sessionsBase)) return result;
@@ -15146,7 +15184,8 @@ function mergeScans(a, b) {
15146
15184
  totalCostUSD: a.totalCostUSD + b.totalCostUSD,
15147
15185
  firstDate: dates.length ? dates.sort()[0] : null,
15148
15186
  lastDate: lastDates.length ? lastDates.sort().at(-1) : null,
15149
- sessionsWithEarlySecrets: a.sessionsWithEarlySecrets + b.sessionsWithEarlySecrets
15187
+ sessionsWithEarlySecrets: a.sessionsWithEarlySecrets + b.sessionsWithEarlySecrets,
15188
+ perSession: [...a.perSession, ...b.perSession]
15150
15189
  };
15151
15190
  }
15152
15191
  function verdictIcon(verdict) {
@@ -15230,9 +15269,9 @@ function renderCompactScorecard(input) {
15230
15269
  const realLoops = scan.loopFindings.filter((l) => l.kind !== "long-iteration");
15231
15270
  const longIterations = scan.loopFindings.filter((l) => l.kind === "long-iteration");
15232
15271
  if (realLoops.length > 0) {
15233
- const { wastePct, wastedCalls } = computeLoopWaste(realLoops, scan.totalToolCalls);
15272
+ const { wastePct, wastedCalls } = computeLoopWaste2(realLoops, scan.totalToolCalls);
15234
15273
  const wasteParts = [];
15235
- if (wastedCalls > 0) wasteParts.push(`${wastedCalls} wasted calls (${wastePct}% of all calls)`);
15274
+ if (wastedCalls > 0) wasteParts.push(`${wastedCalls} repeat calls (${wastePct}% of all calls)`);
15236
15275
  if (summary.loopWastedUSD > 0) wasteParts.push("~" + fmtCost(summary.loopWastedUSD));
15237
15276
  const wasteSummary = wasteParts.length ? `(${wasteParts.join(" \xB7 ")})` : "";
15238
15277
  console.log(
@@ -15310,10 +15349,10 @@ function renderNarrativeScorecard(input) {
15310
15349
  });
15311
15350
  }
15312
15351
  if (scan.loopFindings.length > 0) {
15313
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15352
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15314
15353
  const cost = summary.loopWastedUSD > 0 ? `, ~${fmtCost(summary.loopWastedUSD)} wasted` : "";
15315
15354
  medium.push({
15316
- label: `${scan.loopFindings.length} agent loops (${wastedCalls} wasted calls, ${wastePct}% of calls${cost})`,
15355
+ label: `${scan.loopFindings.length} agent loops (${wastedCalls} repeat calls, ${wastePct}% of calls${cost})`,
15317
15356
  count: scan.loopFindings.length
15318
15357
  });
15319
15358
  }
@@ -15425,13 +15464,13 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15425
15464
  );
15426
15465
  }
15427
15466
  if (scan.loopFindings.length > 0) {
15428
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15467
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15429
15468
  const byTool = /* @__PURE__ */ new Map();
15430
15469
  for (const f of scan.loopFindings) {
15431
15470
  byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
15432
15471
  }
15433
15472
  const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
15434
- const wasteSuffix = wastedCalls > 0 ? `, ${wastedCalls} wasted calls \xB7 ${wastePct}% of calls` : "";
15473
+ const wasteSuffix = wastedCalls > 0 ? `, ${wastedCalls} repeat calls \xB7 ${wastePct}% of calls` : "";
15435
15474
  const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
15436
15475
  topLines.push(
15437
15476
  mkLine(
@@ -15523,7 +15562,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15523
15562
  console.log("");
15524
15563
  }
15525
15564
  if (scan.loopFindings.length > 0) {
15526
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15565
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15527
15566
  const byTool = /* @__PURE__ */ new Map();
15528
15567
  let totalRepeats = 0;
15529
15568
  for (const f of scan.loopFindings) {
@@ -15553,7 +15592,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15553
15592
  loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, chalk6.bold], [target, chalk6.dim]));
15554
15593
  }
15555
15594
  }
15556
- const wasteSuffix = wastedCalls > 0 ? ` \xB7 ${wastedCalls} wasted calls (${wastePct}% of all calls)` : "";
15595
+ const wasteSuffix = wastedCalls > 0 ? ` \xB7 ${wastedCalls} repeat calls (${wastePct}% of all calls)` : "";
15557
15596
  const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} pattern${scan.loopFindings.length !== 1 ? "s" : ""}${wasteSuffix}`;
15558
15597
  for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
15559
15598
  console.log("");
@@ -15888,7 +15927,7 @@ function registerScanCommand(program2) {
15888
15927
  );
15889
15928
  }
15890
15929
  if (scan.loopFindings.length > 0) {
15891
- const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15930
+ const { wastePct } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15892
15931
  const wasteSuffix = wastePct > 0 ? chalk6.dim(` (${wastePct}% of calls)`) : "";
15893
15932
  cardParts.push(
15894
15933
  chalk6.yellow("\u{1F501} ") + chalk6.yellow.bold(String(scan.loopFindings.length)) + chalk6.dim(" loops") + wasteSuffix
@@ -21057,7 +21096,8 @@ data: ${JSON.stringify(item.data)}
21057
21096
  totalCostUSD: 0,
21058
21097
  firstDate: null,
21059
21098
  lastDate: null,
21060
- sessionsWithEarlySecrets: 0
21099
+ sessionsWithEarlySecrets: 0,
21100
+ perSession: []
21061
21101
  };
21062
21102
  let claude = EMPTY_SCAN;
21063
21103
  let gemini = EMPTY_SCAN;
@@ -5047,6 +5047,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
5047
5047
  result.sessions++;
5048
5048
  onProgress?.(result.filesScanned);
5049
5049
  const sessionId = file.replace(/\.jsonl$/, "");
5050
+ const session = { sessionId, costUSD: 0, toolCalls: 0 };
5050
5051
  let raw;
5051
5052
  try {
5052
5053
  raw = fs7.readFileSync(path10.join(projPath, file), "utf-8");
@@ -5134,7 +5135,9 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
5134
5135
  if (usage && model) {
5135
5136
  const p = claudeModelPrice2(model);
5136
5137
  if (p) {
5137
- result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
5138
+ const rowCost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
5139
+ result.totalCostUSD += rowCost;
5140
+ session.costUSD += rowCost;
5138
5141
  }
5139
5142
  }
5140
5143
  const content = entry.message?.content;
@@ -5142,6 +5145,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
5142
5145
  for (const block of content) {
5143
5146
  if (block.type !== "tool_use") continue;
5144
5147
  result.totalToolCalls++;
5148
+ session.toolCalls++;
5145
5149
  const toolName = block.name ?? "";
5146
5150
  const toolNameLower = toolName.toLowerCase();
5147
5151
  const input = block.input ?? {};
@@ -5253,6 +5257,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
5253
5257
  if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
5254
5258
  result.sessionsWithEarlySecrets++;
5255
5259
  }
5260
+ result.perSession.push(session);
5256
5261
  }
5257
5262
  async function processClaudeProjectAsync(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
5258
5263
  const projPath = path10.join(projectsDir, proj);
@@ -5301,7 +5306,8 @@ function emptyClaudeScan() {
5301
5306
  totalCostUSD: 0,
5302
5307
  firstDate: null,
5303
5308
  lastDate: null,
5304
- sessionsWithEarlySecrets: 0
5309
+ sessionsWithEarlySecrets: 0,
5310
+ perSession: []
5305
5311
  };
5306
5312
  }
5307
5313
  async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
@@ -5343,7 +5349,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
5343
5349
  totalCostUSD: 0,
5344
5350
  firstDate: null,
5345
5351
  lastDate: null,
5346
- sessionsWithEarlySecrets: 0
5352
+ sessionsWithEarlySecrets: 0,
5353
+ perSession: []
5347
5354
  };
5348
5355
  const dedup = emptyScanDedup();
5349
5356
  if (!fs7.existsSync(tmpDir)) return result;
@@ -5570,7 +5577,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
5570
5577
  totalCostUSD: 0,
5571
5578
  firstDate: null,
5572
5579
  lastDate: null,
5573
- sessionsWithEarlySecrets: 0
5580
+ sessionsWithEarlySecrets: 0,
5581
+ perSession: []
5574
5582
  };
5575
5583
  const dedup = emptyScanDedup();
5576
5584
  if (!fs7.existsSync(sessionsBase)) return result;
@@ -7070,7 +7078,7 @@ function Cost({ audit }) {
7070
7078
  flexGrow: 1,
7071
7079
  flexBasis: 0,
7072
7080
  children: [
7073
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "COST" }),
7081
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "COST \xB7 API value" }),
7074
7082
  audit === null ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "loading\u2026" }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
7075
7083
  /* @__PURE__ */ jsxs3(Box3, { height: 1, children: [
7076
7084
  /* @__PURE__ */ jsx3(Box3, { width: LABEL_W2, children: /* @__PURE__ */ jsx3(Text3, { children: "Total" }) }),
package/dist/scan-ink.mjs CHANGED
@@ -45,7 +45,7 @@ var LABEL_W = 16;
45
45
  function CostPanel({ summary, width }) {
46
46
  const total = summary.stats.totalCostUSD;
47
47
  return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "round", borderColor: "gray", paddingX: 1, flexDirection: "column", width, children: [
48
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "COST" }),
48
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "COST \xB7 API value" }),
49
49
  /* @__PURE__ */ jsxs3(Box3, { children: [
50
50
  /* @__PURE__ */ jsx3(Box3, { width: LABEL_W, children: /* @__PURE__ */ jsx3(Text3, { children: "Total" }) }),
51
51
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: formatCost(total) })
@@ -1791,7 +1791,7 @@ function StaticScorecard({ input, rangeLabel, now }) {
1791
1791
  if (reviewCount > 0) parts.push(`${reviewCount} op${reviewCount !== 1 ? "s" : ""} flagged`);
1792
1792
  if (loopCount > 0)
1793
1793
  parts.push(
1794
- `${loopCount} loop${loopCount !== 1 ? "s" : ""}${wastedCalls > 0 ? ` \xB7 ${wastedCalls} wasted calls (${wastePct}% of all calls)` : ""}`
1794
+ `${loopCount} loop${loopCount !== 1 ? "s" : ""}${wastedCalls > 0 ? ` \xB7 ${wastedCalls} repeat calls (${wastePct}% of all calls)` : ""}`
1795
1795
  );
1796
1796
  return /* @__PURE__ */ jsxs11(Fragment2, { children: [
1797
1797
  /* @__PURE__ */ jsx11(SeverityBand, { label: `Medium (${parts.join(" \xB7 ")})`, width }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "2.6.2",
3
+ "version": "2.7.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",