@node9/proxy 2.6.2 → 2.7.1

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
@@ -243,8 +243,8 @@ function sanitizeConfig(raw) {
243
243
  }
244
244
  }
245
245
  const lines = result.error.issues.map((issue) => {
246
- const path73 = issue.path.length > 0 ? issue.path.join(".") : "root";
247
- return ` \u2022 ${path73}: ${issue.message}`;
246
+ const path74 = issue.path.length > 0 ? issue.path.join(".") : "root";
247
+ return ` \u2022 ${path74}: ${issue.message}`;
248
248
  });
249
249
  return {
250
250
  sanitized,
@@ -624,9 +624,9 @@ function matchesPattern(text, patterns) {
624
624
  const withoutDotSlash = text.replace(/^\.\//, "");
625
625
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
626
626
  }
627
- function getNestedValue(obj, path73) {
627
+ function getNestedValue(obj, path74) {
628
628
  if (!obj || typeof obj !== "object") return null;
629
- const segments = path73.split(".");
629
+ const segments = path74.split(".");
630
630
  for (const seg of segments) {
631
631
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
632
632
  }
@@ -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([
@@ -5425,13 +5423,13 @@ function getConfig(cwd) {
5425
5423
  }
5426
5424
  if (Array.isArray(mc.jailPaths)) {
5427
5425
  for (const jp of mc.jailPaths) {
5428
- const path73 = typeof jp?.path === "string" ? jp.path.trim() : "";
5429
- if (!path73) continue;
5426
+ const path74 = typeof jp?.path === "string" ? jp.path.trim() : "";
5427
+ if (!path74) continue;
5430
5428
  const verdict = jp?.verdict === "review" ? "review" : "block";
5431
- for (const r of pathRules(path73, verdict, "org-managed jail")) {
5429
+ for (const r of pathRules(path74, verdict, "org-managed jail")) {
5432
5430
  mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
5433
5431
  }
5434
- mergedPolicy.managedJailPaths.push({ path: path73, verdict });
5432
+ mergedPolicy.managedJailPaths.push({ path: path74, verdict });
5435
5433
  }
5436
5434
  }
5437
5435
  if (Array.isArray(mc.trustedHosts)) {
@@ -11403,14 +11401,15 @@ var init_keyed_guard = __esm({
11403
11401
  function normalizeModel(raw) {
11404
11402
  return raw.replace(/-\d{8}$/, "").toLowerCase();
11405
11403
  }
11406
- function readCache() {
11404
+ function readCache(opts) {
11407
11405
  try {
11408
11406
  const raw = JSON.parse(import_fs17.default.readFileSync(CACHE_FILE(), "utf-8"));
11409
11407
  if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
11410
11408
  return null;
11411
11409
  }
11412
11410
  const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
11413
- if (ageMs < 0 || ageMs > TTL_MS) return null;
11411
+ if (!Number.isFinite(ageMs) || ageMs < 0) return null;
11412
+ if (opts.requireFresh && ageMs > TTL_MS) return null;
11414
11413
  return raw.prices;
11415
11414
  } catch {
11416
11415
  return null;
@@ -11476,7 +11475,7 @@ async function fetchLiteLLMPricing() {
11476
11475
  }
11477
11476
  async function ensurePricingLoaded() {
11478
11477
  if (memCache !== null && Date.now() - memCacheAt < TTL_MS) return;
11479
- const fromDisk = readCache();
11478
+ const fromDisk = readCache({ requireFresh: true });
11480
11479
  if (fromDisk && Object.keys(fromDisk).length > 0) {
11481
11480
  memCache = fromDisk;
11482
11481
  memCacheAt = Date.now();
@@ -11501,7 +11500,7 @@ function pricingFor(model) {
11501
11500
  if (cached !== void 0) return cached;
11502
11501
  if (memCache === null && !diskChecked) {
11503
11502
  diskChecked = true;
11504
- const disk = readCache();
11503
+ const disk = readCache({ requireFresh: false });
11505
11504
  if (disk && Object.keys(disk).length > 0) {
11506
11505
  memCache = disk;
11507
11506
  memCacheAt = Date.now();
@@ -11962,6 +11961,31 @@ function agentColorName(agent) {
11962
11961
  return "cyan";
11963
11962
  }
11964
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
+ }
11965
11989
  function buildScanSummary(agents) {
11966
11990
  const stats = {
11967
11991
  sessions: 0,
@@ -12022,8 +12046,11 @@ function buildScanSummary(agents) {
12022
12046
  costUSD: a.scan.totalCostUSD
12023
12047
  })).filter((s) => s.sessions > 0 || s.findings > 0);
12024
12048
  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;
12049
+ const loopWaste = computeLoopWaste(
12050
+ allLoops,
12051
+ agents.flatMap((a) => a.scan.perSession)
12052
+ );
12053
+ const loopWastedUSD = loopWaste.usd;
12027
12054
  return {
12028
12055
  stats,
12029
12056
  byVerdict,
@@ -12031,7 +12058,8 @@ function buildScanSummary(agents) {
12031
12058
  sections,
12032
12059
  leaks: allLeaks,
12033
12060
  loops: allLoops,
12034
- loopWastedUSD
12061
+ loopWastedUSD,
12062
+ loopWaste
12035
12063
  };
12036
12064
  }
12037
12065
  function buildSections(findings) {
@@ -12138,7 +12166,7 @@ function fullCommandOf(input) {
12138
12166
  const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
12139
12167
  return String(raw).replace(/\s+/g, " ").trim();
12140
12168
  }
12141
- var AGENT_SHORT, AGENT_LONG;
12169
+ var AGENT_SHORT, AGENT_LONG, MAX_PLAUSIBLE_RATE_USD;
12142
12170
  var init_scan_summary = __esm({
12143
12171
  "src/scan-summary.ts"() {
12144
12172
  "use strict";
@@ -12161,6 +12189,7 @@ var init_scan_summary = __esm({
12161
12189
  copilot: "GitHub Copilot",
12162
12190
  shell: "Shell"
12163
12191
  };
12192
+ MAX_PLAUSIBLE_RATE_USD = 50;
12164
12193
  }
12165
12194
  });
12166
12195
 
@@ -12368,7 +12397,7 @@ function topRulesByVerdict(sections, verdict, n) {
12368
12397
  }
12369
12398
  return matched.sort((a, b) => b.count - a.count).slice(0, n);
12370
12399
  }
12371
- function computeLoopWaste(loops, totalToolCalls) {
12400
+ function computeLoopWaste2(loops, totalToolCalls) {
12372
12401
  const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
12373
12402
  const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
12374
12403
  return { wastedCalls, wastePct };
@@ -12478,6 +12507,38 @@ var init_scan_json = __esm({
12478
12507
  }
12479
12508
  });
12480
12509
 
12510
+ // src/session-files.ts
12511
+ function listSessionFiles(dir, maxDepth = 6) {
12512
+ const out = [];
12513
+ const walk = (d, rel, depth) => {
12514
+ if (depth > maxDepth) return;
12515
+ let entries;
12516
+ try {
12517
+ entries = fs22.readdirSync(d, { withFileTypes: true });
12518
+ } catch {
12519
+ return;
12520
+ }
12521
+ for (const e of entries) {
12522
+ const childRel = rel ? path24.join(rel, e.name) : e.name;
12523
+ if (e.isDirectory()) walk(path24.join(d, e.name), childRel, depth + 1);
12524
+ else if (e.name.endsWith(".jsonl")) out.push(childRel);
12525
+ }
12526
+ };
12527
+ walk(dir, "", 0);
12528
+ return out;
12529
+ }
12530
+ function sessionIdOf(relPath) {
12531
+ return path24.basename(relPath).replace(/\.jsonl$/, "");
12532
+ }
12533
+ var fs22, path24;
12534
+ var init_session_files = __esm({
12535
+ "src/session-files.ts"() {
12536
+ "use strict";
12537
+ fs22 = __toESM(require("fs"));
12538
+ path24 = __toESM(require("path"));
12539
+ }
12540
+ });
12541
+
12481
12542
  // src/cli/render/scan-history.ts
12482
12543
  function defaultHistoryPath() {
12483
12544
  return import_path23.default.join(import_os20.default.homedir(), ".node9", "scan-history.json");
@@ -12841,6 +12902,7 @@ var init_costSync = __esm({
12841
12902
  init_cost_codex();
12842
12903
  init_cost_gemini();
12843
12904
  init_cost_copilot();
12905
+ init_session_files();
12844
12906
  SYNC_INTERVAL_MS = 10 * 60 * 1e3;
12845
12907
  claudeSource = {
12846
12908
  id: "claude",
@@ -12866,7 +12928,7 @@ var init_costSync = __esm({
12866
12928
  }
12867
12929
  let files;
12868
12930
  try {
12869
- files = import_fs23.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
12931
+ files = listSessionFiles(dirPath);
12870
12932
  } catch {
12871
12933
  continue;
12872
12934
  }
@@ -13920,7 +13982,7 @@ function countScanFiles() {
13920
13982
  const dp = import_path28.default.join(mp, day);
13921
13983
  try {
13922
13984
  if (!import_fs26.default.statSync(dp).isDirectory()) continue;
13923
- total += import_fs26.default.readdirSync(dp).filter((f) => f.endsWith(".jsonl")).length;
13985
+ total += listSessionFiles(dp).length;
13924
13986
  } catch {
13925
13987
  continue;
13926
13988
  }
@@ -13953,7 +14015,8 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
13953
14015
  result.filesScanned++;
13954
14016
  result.sessions++;
13955
14017
  onProgress?.(result.filesScanned);
13956
- const sessionId = file.replace(/\.jsonl$/, "");
14018
+ const sessionId = sessionIdOf(file);
14019
+ const session = { sessionId, costUSD: 0, toolCalls: 0 };
13957
14020
  let raw;
13958
14021
  try {
13959
14022
  raw = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
@@ -14041,7 +14104,9 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14041
14104
  if (usage && model) {
14042
14105
  const p = claudeModelPrice(model);
14043
14106
  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;
14107
+ 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;
14108
+ result.totalCostUSD += rowCost;
14109
+ session.costUSD += rowCost;
14045
14110
  }
14046
14111
  }
14047
14112
  const content = entry.message?.content;
@@ -14049,6 +14114,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14049
14114
  for (const block of content) {
14050
14115
  if (block.type !== "tool_use") continue;
14051
14116
  result.totalToolCalls++;
14117
+ session.toolCalls++;
14052
14118
  const toolName = block.name ?? "";
14053
14119
  const toolNameLower = toolName.toLowerCase();
14054
14120
  const input = block.input ?? {};
@@ -14160,6 +14226,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
14160
14226
  if (firstDlpTs !== null && (firstEditTs === null || firstDlpTs < firstEditTs)) {
14161
14227
  result.sessionsWithEarlySecrets++;
14162
14228
  }
14229
+ result.perSession.push(session);
14163
14230
  }
14164
14231
  function processClaudeProject(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
14165
14232
  const projPath = import_path28.default.join(projectsDir, proj);
@@ -14174,7 +14241,7 @@ function processClaudeProject(proj, projectsDir, ruleSources, startDate, result,
14174
14241
  );
14175
14242
  let files;
14176
14243
  try {
14177
- files = import_fs26.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
14244
+ files = listSessionFiles(projPath);
14178
14245
  } catch {
14179
14246
  return;
14180
14247
  }
@@ -14204,7 +14271,8 @@ function emptyClaudeScan() {
14204
14271
  totalCostUSD: 0,
14205
14272
  firstDate: null,
14206
14273
  lastDate: null,
14207
- sessionsWithEarlySecrets: 0
14274
+ sessionsWithEarlySecrets: 0,
14275
+ perSession: []
14208
14276
  };
14209
14277
  }
14210
14278
  function scanClaudeHistory(startDate, onProgress, onLine) {
@@ -14246,7 +14314,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
14246
14314
  totalCostUSD: 0,
14247
14315
  firstDate: null,
14248
14316
  lastDate: null,
14249
- sessionsWithEarlySecrets: 0
14317
+ sessionsWithEarlySecrets: 0,
14318
+ perSession: []
14250
14319
  };
14251
14320
  const dedup = emptyScanDedup();
14252
14321
  if (!import_fs26.default.existsSync(tmpDir)) return result;
@@ -14484,7 +14553,8 @@ function scanAntigravityHistory(startDate, onProgress, onLine) {
14484
14553
  // transcripts carry no token/model data
14485
14554
  firstDate: null,
14486
14555
  lastDate: null,
14487
- sessionsWithEarlySecrets: 0
14556
+ sessionsWithEarlySecrets: 0,
14557
+ perSession: []
14488
14558
  };
14489
14559
  const dedup = emptyScanDedup();
14490
14560
  const brainDirs = antigravityBrainDirs();
@@ -14680,7 +14750,8 @@ function scanCopilotHistory(startDate, onProgress, onLine) {
14680
14750
  // event logs carry no token/cost rollup
14681
14751
  firstDate: null,
14682
14752
  lastDate: null,
14683
- sessionsWithEarlySecrets: 0
14753
+ sessionsWithEarlySecrets: 0,
14754
+ perSession: []
14684
14755
  };
14685
14756
  const dedup = emptyScanDedup();
14686
14757
  if (!import_fs26.default.existsSync(sessionDir)) return result;
@@ -14862,7 +14933,8 @@ function scanCodexHistory(startDate, onProgress, onLine) {
14862
14933
  totalCostUSD: 0,
14863
14934
  firstDate: null,
14864
14935
  lastDate: null,
14865
- sessionsWithEarlySecrets: 0
14936
+ sessionsWithEarlySecrets: 0,
14937
+ perSession: []
14866
14938
  };
14867
14939
  const dedup = emptyScanDedup();
14868
14940
  if (!import_fs26.default.existsSync(sessionsBase)) return result;
@@ -15141,7 +15213,8 @@ function mergeScans(a, b) {
15141
15213
  totalCostUSD: a.totalCostUSD + b.totalCostUSD,
15142
15214
  firstDate: dates.length ? dates.sort()[0] : null,
15143
15215
  lastDate: lastDates.length ? lastDates.sort().at(-1) : null,
15144
- sessionsWithEarlySecrets: a.sessionsWithEarlySecrets + b.sessionsWithEarlySecrets
15216
+ sessionsWithEarlySecrets: a.sessionsWithEarlySecrets + b.sessionsWithEarlySecrets,
15217
+ perSession: [...a.perSession, ...b.perSession]
15145
15218
  };
15146
15219
  }
15147
15220
  function verdictIcon(verdict) {
@@ -15225,9 +15298,9 @@ function renderCompactScorecard(input) {
15225
15298
  const realLoops = scan.loopFindings.filter((l) => l.kind !== "long-iteration");
15226
15299
  const longIterations = scan.loopFindings.filter((l) => l.kind === "long-iteration");
15227
15300
  if (realLoops.length > 0) {
15228
- const { wastePct, wastedCalls } = computeLoopWaste(realLoops, scan.totalToolCalls);
15301
+ const { wastePct, wastedCalls } = computeLoopWaste2(realLoops, scan.totalToolCalls);
15229
15302
  const wasteParts = [];
15230
- if (wastedCalls > 0) wasteParts.push(`${wastedCalls} wasted calls (${wastePct}% of all calls)`);
15303
+ if (wastedCalls > 0) wasteParts.push(`${wastedCalls} repeat calls (${wastePct}% of all calls)`);
15231
15304
  if (summary.loopWastedUSD > 0) wasteParts.push("~" + fmtCost(summary.loopWastedUSD));
15232
15305
  const wasteSummary = wasteParts.length ? `(${wasteParts.join(" \xB7 ")})` : "";
15233
15306
  console.log(
@@ -15305,10 +15378,10 @@ function renderNarrativeScorecard(input) {
15305
15378
  });
15306
15379
  }
15307
15380
  if (scan.loopFindings.length > 0) {
15308
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15381
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15309
15382
  const cost = summary.loopWastedUSD > 0 ? `, ~${fmtCost(summary.loopWastedUSD)} wasted` : "";
15310
15383
  medium.push({
15311
- label: `${scan.loopFindings.length} agent loops (${wastedCalls} wasted calls, ${wastePct}% of calls${cost})`,
15384
+ label: `${scan.loopFindings.length} agent loops (${wastedCalls} repeat calls, ${wastePct}% of calls${cost})`,
15312
15385
  count: scan.loopFindings.length
15313
15386
  });
15314
15387
  }
@@ -15420,13 +15493,13 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15420
15493
  );
15421
15494
  }
15422
15495
  if (scan.loopFindings.length > 0) {
15423
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15496
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15424
15497
  const byTool = /* @__PURE__ */ new Map();
15425
15498
  for (const f of scan.loopFindings) {
15426
15499
  byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
15427
15500
  }
15428
15501
  const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
15429
- const wasteSuffix = wastedCalls > 0 ? `, ${wastedCalls} wasted calls \xB7 ${wastePct}% of calls` : "";
15502
+ const wasteSuffix = wastedCalls > 0 ? `, ${wastedCalls} repeat calls \xB7 ${wastePct}% of calls` : "";
15430
15503
  const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
15431
15504
  topLines.push(
15432
15505
  mkLine(
@@ -15518,7 +15591,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15518
15591
  console.log("");
15519
15592
  }
15520
15593
  if (scan.loopFindings.length > 0) {
15521
- const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15594
+ const { wastePct, wastedCalls } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15522
15595
  const byTool = /* @__PURE__ */ new Map();
15523
15596
  let totalRepeats = 0;
15524
15597
  for (const f of scan.loopFindings) {
@@ -15548,7 +15621,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
15548
15621
  loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, import_chalk6.default.bold], [target, import_chalk6.default.dim]));
15549
15622
  }
15550
15623
  }
15551
- const wasteSuffix = wastedCalls > 0 ? ` \xB7 ${wastedCalls} wasted calls (${wastePct}% of all calls)` : "";
15624
+ const wasteSuffix = wastedCalls > 0 ? ` \xB7 ${wastedCalls} repeat calls (${wastePct}% of all calls)` : "";
15552
15625
  const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} pattern${scan.loopFindings.length !== 1 ? "s" : ""}${wasteSuffix}`;
15553
15626
  for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
15554
15627
  console.log("");
@@ -15883,7 +15956,7 @@ function registerScanCommand(program2) {
15883
15956
  );
15884
15957
  }
15885
15958
  if (scan.loopFindings.length > 0) {
15886
- const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
15959
+ const { wastePct } = computeLoopWaste2(scan.loopFindings, scan.totalToolCalls);
15887
15960
  const wasteSuffix = wastePct > 0 ? import_chalk6.default.dim(` (${wastePct}% of calls)`) : "";
15888
15961
  cardParts.push(
15889
15962
  import_chalk6.default.yellow("\u{1F501} ") + import_chalk6.default.yellow.bold(String(scan.loopFindings.length)) + import_chalk6.default.dim(" loops") + wasteSuffix
@@ -16189,6 +16262,7 @@ var init_scan = __esm({
16189
16262
  init_protection();
16190
16263
  import_string_width2 = __toESM(require("string-width"));
16191
16264
  init_scan_json();
16265
+ init_session_files();
16192
16266
  init_scan_history();
16193
16267
  toolInspectionMap = DEFAULT_CONFIG.policy.toolInspection;
16194
16268
  CODE_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -18857,28 +18931,28 @@ var init_ship2 = __esm({
18857
18931
 
18858
18932
  // src/machine-id.ts
18859
18933
  function getMachineId(homeDir2 = os34.homedir()) {
18860
- const file = path37.join(homeDir2, ".node9", "machine-id");
18934
+ const file = path38.join(homeDir2, ".node9", "machine-id");
18861
18935
  try {
18862
- const existing = fs38.readFileSync(file, "utf-8").trim();
18936
+ const existing = fs39.readFileSync(file, "utf-8").trim();
18863
18937
  if (UUID_RE.test(existing)) return existing;
18864
18938
  } catch {
18865
18939
  }
18866
18940
  const id = crypto5.randomUUID();
18867
18941
  try {
18868
- fs38.mkdirSync(path37.dirname(file), { recursive: true });
18869
- fs38.writeFileSync(file, id + "\n", { mode: 384 });
18942
+ fs39.mkdirSync(path38.dirname(file), { recursive: true });
18943
+ fs39.writeFileSync(file, id + "\n", { mode: 384 });
18870
18944
  } catch {
18871
18945
  }
18872
18946
  return id;
18873
18947
  }
18874
- var crypto5, fs38, os34, path37, UUID_RE;
18948
+ var crypto5, fs39, os34, path38, UUID_RE;
18875
18949
  var init_machine_id = __esm({
18876
18950
  "src/machine-id.ts"() {
18877
18951
  "use strict";
18878
18952
  crypto5 = __toESM(require("crypto"));
18879
- fs38 = __toESM(require("fs"));
18953
+ fs39 = __toESM(require("fs"));
18880
18954
  os34 = __toESM(require("os"));
18881
- path37 = __toESM(require("path"));
18955
+ path38 = __toESM(require("path"));
18882
18956
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
18883
18957
  }
18884
18958
  });
@@ -21056,7 +21130,8 @@ data: ${JSON.stringify(item.data)}
21056
21130
  totalCostUSD: 0,
21057
21131
  firstDate: null,
21058
21132
  lastDate: null,
21059
- sessionsWithEarlySecrets: 0
21133
+ sessionsWithEarlySecrets: 0,
21134
+ perSession: []
21060
21135
  };
21061
21136
  let claude = EMPTY_SCAN;
21062
21137
  let gemini = EMPTY_SCAN;
@@ -23118,14 +23193,14 @@ var require_util = __commonJS({
23118
23193
  }
23119
23194
  const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80;
23120
23195
  let origin = url.origin != null ? url.origin : `${url.protocol || ""}//${url.hostname || ""}:${port}`;
23121
- let path73 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
23196
+ let path74 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
23122
23197
  if (origin[origin.length - 1] === "/") {
23123
23198
  origin = origin.slice(0, origin.length - 1);
23124
23199
  }
23125
- if (path73 && path73[0] !== "/") {
23126
- path73 = `/${path73}`;
23200
+ if (path74 && path74[0] !== "/") {
23201
+ path74 = `/${path74}`;
23127
23202
  }
23128
- return new URL(`${origin}${path73}`);
23203
+ return new URL(`${origin}${path74}`);
23129
23204
  }
23130
23205
  if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
23131
23206
  throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`.");
@@ -23946,9 +24021,9 @@ var require_diagnostics = __commonJS({
23946
24021
  "undici:client:sendHeaders",
23947
24022
  (evt) => {
23948
24023
  const {
23949
- request: { method, path: path73, origin }
24024
+ request: { method, path: path74, origin }
23950
24025
  } = evt;
23951
- debugLog("sending request to %s %s%s", method, origin, path73);
24026
+ debugLog("sending request to %s %s%s", method, origin, path74);
23952
24027
  }
23953
24028
  );
23954
24029
  }
@@ -23966,14 +24041,14 @@ var require_diagnostics = __commonJS({
23966
24041
  "undici:request:headers",
23967
24042
  (evt) => {
23968
24043
  const {
23969
- request: { method, path: path73, origin },
24044
+ request: { method, path: path74, origin },
23970
24045
  response: { statusCode }
23971
24046
  } = evt;
23972
24047
  debugLog(
23973
24048
  "received response to %s %s%s - HTTP %d",
23974
24049
  method,
23975
24050
  origin,
23976
- path73,
24051
+ path74,
23977
24052
  statusCode
23978
24053
  );
23979
24054
  }
@@ -23982,23 +24057,23 @@ var require_diagnostics = __commonJS({
23982
24057
  "undici:request:trailers",
23983
24058
  (evt) => {
23984
24059
  const {
23985
- request: { method, path: path73, origin }
24060
+ request: { method, path: path74, origin }
23986
24061
  } = evt;
23987
- debugLog("trailers received from %s %s%s", method, origin, path73);
24062
+ debugLog("trailers received from %s %s%s", method, origin, path74);
23988
24063
  }
23989
24064
  );
23990
24065
  diagnosticsChannel.subscribe(
23991
24066
  "undici:request:error",
23992
24067
  (evt) => {
23993
24068
  const {
23994
- request: { method, path: path73, origin },
24069
+ request: { method, path: path74, origin },
23995
24070
  error
23996
24071
  } = evt;
23997
24072
  debugLog(
23998
24073
  "request to %s %s%s errored - %s",
23999
24074
  method,
24000
24075
  origin,
24001
- path73,
24076
+ path74,
24002
24077
  error.message
24003
24078
  );
24004
24079
  }
@@ -24101,7 +24176,7 @@ var require_request = __commonJS({
24101
24176
  var kHandler = /* @__PURE__ */ Symbol("handler");
24102
24177
  var Request = class {
24103
24178
  constructor(origin, {
24104
- path: path73,
24179
+ path: path74,
24105
24180
  method,
24106
24181
  body,
24107
24182
  headers,
@@ -24118,11 +24193,11 @@ var require_request = __commonJS({
24118
24193
  maxRedirections,
24119
24194
  typeOfService
24120
24195
  }, handler) {
24121
- if (typeof path73 !== "string") {
24196
+ if (typeof path74 !== "string") {
24122
24197
  throw new InvalidArgumentError("path must be a string");
24123
- } else if (path73[0] !== "/" && !(path73.startsWith("http://") || path73.startsWith("https://")) && method !== "CONNECT") {
24198
+ } else if (path74[0] !== "/" && !(path74.startsWith("http://") || path74.startsWith("https://")) && method !== "CONNECT") {
24124
24199
  throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
24125
- } else if (invalidPathRegex.test(path73)) {
24200
+ } else if (invalidPathRegex.test(path74)) {
24126
24201
  throw new InvalidArgumentError("invalid request path");
24127
24202
  }
24128
24203
  if (typeof method !== "string") {
@@ -24197,7 +24272,7 @@ var require_request = __commonJS({
24197
24272
  this.completed = false;
24198
24273
  this.aborted = false;
24199
24274
  this.upgrade = upgrade || null;
24200
- this.path = query ? serializePathWithQuery(path73, query) : path73;
24275
+ this.path = query ? serializePathWithQuery(path74, query) : path74;
24201
24276
  this.origin = origin;
24202
24277
  this.protocol = getProtocolFromUrlString(origin);
24203
24278
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
@@ -29236,7 +29311,7 @@ var require_client_h1 = __commonJS({
29236
29311
  return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT";
29237
29312
  }
29238
29313
  function writeH1(client, request2) {
29239
- const { method, path: path73, host, upgrade, blocking, reset } = request2;
29314
+ const { method, path: path74, host, upgrade, blocking, reset } = request2;
29240
29315
  let { body, headers, contentLength } = request2;
29241
29316
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH";
29242
29317
  if (util.isFormDataLike(body)) {
@@ -29305,7 +29380,7 @@ var require_client_h1 = __commonJS({
29305
29380
  if (socket.setTypeOfService) {
29306
29381
  socket.setTypeOfService(request2.typeOfService);
29307
29382
  }
29308
- let header = `${method} ${path73} HTTP/1.1\r
29383
+ let header = `${method} ${path74} HTTP/1.1\r
29309
29384
  `;
29310
29385
  if (typeof host === "string") {
29311
29386
  header += `host: ${host}\r
@@ -29958,7 +30033,7 @@ var require_client_h2 = __commonJS({
29958
30033
  function writeH2(client, request2) {
29959
30034
  const requestTimeout = request2.bodyTimeout ?? client[kBodyTimeout];
29960
30035
  const session = client[kHTTP2Session];
29961
- const { method, path: path73, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request2;
30036
+ const { method, path: path74, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request2;
29962
30037
  let { body } = request2;
29963
30038
  if (upgrade != null && upgrade !== "websocket") {
29964
30039
  util.errorRequest(client, request2, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`));
@@ -30026,7 +30101,7 @@ var require_client_h2 = __commonJS({
30026
30101
  }
30027
30102
  headers[HTTP2_HEADER_METHOD] = "CONNECT";
30028
30103
  headers[HTTP2_HEADER_PROTOCOL] = "websocket";
30029
- headers[HTTP2_HEADER_PATH] = path73;
30104
+ headers[HTTP2_HEADER_PATH] = path74;
30030
30105
  if (protocol === "ws:" || protocol === "wss:") {
30031
30106
  headers[HTTP2_HEADER_SCHEME] = protocol === "ws:" ? "http" : "https";
30032
30107
  } else {
@@ -30067,7 +30142,7 @@ var require_client_h2 = __commonJS({
30067
30142
  stream.setTimeout(requestTimeout);
30068
30143
  return true;
30069
30144
  }
30070
- headers[HTTP2_HEADER_PATH] = path73;
30145
+ headers[HTTP2_HEADER_PATH] = path74;
30071
30146
  headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https";
30072
30147
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
30073
30148
  if (body && typeof body.read === "function") {
@@ -32369,10 +32444,10 @@ var require_proxy_agent = __commonJS({
32369
32444
  };
32370
32445
  const {
32371
32446
  origin,
32372
- path: path73 = "/",
32447
+ path: path74 = "/",
32373
32448
  headers = {}
32374
32449
  } = opts;
32375
- opts.path = origin + path73;
32450
+ opts.path = origin + path74;
32376
32451
  if (!("host" in headers) && !("Host" in headers)) {
32377
32452
  const { host } = new URL(origin);
32378
32453
  headers.host = host;
@@ -34435,20 +34510,20 @@ var require_mock_utils = __commonJS({
34435
34510
  }
34436
34511
  return normalizedQp;
34437
34512
  }
34438
- function safeUrl(path73) {
34439
- if (typeof path73 !== "string") {
34440
- return path73;
34513
+ function safeUrl(path74) {
34514
+ if (typeof path74 !== "string") {
34515
+ return path74;
34441
34516
  }
34442
- const pathSegments = path73.split("?", 3);
34517
+ const pathSegments = path74.split("?", 3);
34443
34518
  if (pathSegments.length !== 2) {
34444
- return path73;
34519
+ return path74;
34445
34520
  }
34446
34521
  const qp = new URLSearchParams(pathSegments.pop());
34447
34522
  qp.sort();
34448
34523
  return [...pathSegments, qp.toString()].join("?");
34449
34524
  }
34450
- function matchKey(mockDispatch2, { path: path73, method, body, headers }) {
34451
- const pathMatch = matchValue(mockDispatch2.path, path73);
34525
+ function matchKey(mockDispatch2, { path: path74, method, body, headers }) {
34526
+ const pathMatch = matchValue(mockDispatch2.path, path74);
34452
34527
  const methodMatch = matchValue(mockDispatch2.method, method);
34453
34528
  const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
34454
34529
  const headersMatch = matchHeaders(mockDispatch2, headers);
@@ -34473,8 +34548,8 @@ var require_mock_utils = __commonJS({
34473
34548
  const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path;
34474
34549
  const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
34475
34550
  const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath);
34476
- let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path73, ignoreTrailingSlash }) => {
34477
- return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path73)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path73), resolvedPath);
34551
+ let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path74, ignoreTrailingSlash }) => {
34552
+ return ignoreTrailingSlash ? matchValue(removeTrailingSlash(safeUrl(path74)), resolvedPathWithoutTrailingSlash) : matchValue(safeUrl(path74), resolvedPath);
34478
34553
  });
34479
34554
  if (matchedMockDispatches.length === 0) {
34480
34555
  throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
@@ -34513,19 +34588,19 @@ var require_mock_utils = __commonJS({
34513
34588
  mockDispatches.splice(index, 1);
34514
34589
  }
34515
34590
  }
34516
- function removeTrailingSlash(path73) {
34517
- while (path73.endsWith("/")) {
34518
- path73 = path73.slice(0, -1);
34591
+ function removeTrailingSlash(path74) {
34592
+ while (path74.endsWith("/")) {
34593
+ path74 = path74.slice(0, -1);
34519
34594
  }
34520
- if (path73.length === 0) {
34521
- path73 = "/";
34595
+ if (path74.length === 0) {
34596
+ path74 = "/";
34522
34597
  }
34523
- return path73;
34598
+ return path74;
34524
34599
  }
34525
34600
  function buildKey(opts) {
34526
- const { path: path73, method, body, headers, query } = opts;
34601
+ const { path: path74, method, body, headers, query } = opts;
34527
34602
  return {
34528
- path: path73,
34603
+ path: path74,
34529
34604
  method,
34530
34605
  body,
34531
34606
  headers,
@@ -35215,10 +35290,10 @@ var require_pending_interceptors_formatter = __commonJS({
35215
35290
  }
35216
35291
  format(pendingInterceptors) {
35217
35292
  const withPrettyHeaders = pendingInterceptors.map(
35218
- ({ method, path: path73, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
35293
+ ({ method, path: path74, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
35219
35294
  Method: method,
35220
35295
  Origin: origin,
35221
- Path: path73,
35296
+ Path: path74,
35222
35297
  "Status code": statusCode,
35223
35298
  Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
35224
35299
  Invocations: timesInvoked,
@@ -35300,9 +35375,9 @@ var require_mock_agent = __commonJS({
35300
35375
  const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters];
35301
35376
  const dispatchOpts = { ...opts };
35302
35377
  if (acceptNonStandardSearchParameters && dispatchOpts.path) {
35303
- const [path73, searchParams] = dispatchOpts.path.split("?");
35378
+ const [path74, searchParams] = dispatchOpts.path.split("?");
35304
35379
  const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters);
35305
- dispatchOpts.path = `${path73}?${normalizedSearchParams}`;
35380
+ dispatchOpts.path = `${path74}?${normalizedSearchParams}`;
35306
35381
  }
35307
35382
  return this[kAgent].dispatch(dispatchOpts, handler);
35308
35383
  }
@@ -35703,12 +35778,12 @@ var require_snapshot_recorder = __commonJS({
35703
35778
  * @return {Promise<void>} - Resolves when snapshots are loaded
35704
35779
  */
35705
35780
  async loadSnapshots(filePath) {
35706
- const path73 = filePath || this.#snapshotPath;
35707
- if (!path73) {
35781
+ const path74 = filePath || this.#snapshotPath;
35782
+ if (!path74) {
35708
35783
  throw new InvalidArgumentError("Snapshot path is required");
35709
35784
  }
35710
35785
  try {
35711
- const data = await readFile(resolve2(path73), "utf8");
35786
+ const data = await readFile(resolve2(path74), "utf8");
35712
35787
  const parsed = JSON.parse(data);
35713
35788
  if (Array.isArray(parsed)) {
35714
35789
  this.#snapshots.clear();
@@ -35722,7 +35797,7 @@ var require_snapshot_recorder = __commonJS({
35722
35797
  if (error.code === "ENOENT") {
35723
35798
  this.#snapshots.clear();
35724
35799
  } else {
35725
- throw new UndiciError(`Failed to load snapshots from ${path73}`, { cause: error });
35800
+ throw new UndiciError(`Failed to load snapshots from ${path74}`, { cause: error });
35726
35801
  }
35727
35802
  }
35728
35803
  }
@@ -35733,11 +35808,11 @@ var require_snapshot_recorder = __commonJS({
35733
35808
  * @returns {Promise<void>} - Resolves when snapshots are saved
35734
35809
  */
35735
35810
  async saveSnapshots(filePath) {
35736
- const path73 = filePath || this.#snapshotPath;
35737
- if (!path73) {
35811
+ const path74 = filePath || this.#snapshotPath;
35812
+ if (!path74) {
35738
35813
  throw new InvalidArgumentError("Snapshot path is required");
35739
35814
  }
35740
- const resolvedPath = resolve2(path73);
35815
+ const resolvedPath = resolve2(path74);
35741
35816
  await mkdir(dirname3(resolvedPath), { recursive: true });
35742
35817
  const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
35743
35818
  hash,
@@ -36362,15 +36437,15 @@ var require_redirect_handler = __commonJS({
36362
36437
  return;
36363
36438
  }
36364
36439
  const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
36365
- const path73 = search ? `${pathname}${search}` : pathname;
36366
- const redirectUrlString = `${origin}${path73}`;
36440
+ const path74 = search ? `${pathname}${search}` : pathname;
36441
+ const redirectUrlString = `${origin}${path74}`;
36367
36442
  for (const historyUrl of this.history) {
36368
36443
  if (historyUrl.toString() === redirectUrlString) {
36369
36444
  throw new InvalidArgumentError(`Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects.`);
36370
36445
  }
36371
36446
  }
36372
36447
  this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
36373
- this.opts.path = path73;
36448
+ this.opts.path = path74;
36374
36449
  this.opts.origin = origin;
36375
36450
  this.opts.query = null;
36376
36451
  }
@@ -42577,11 +42652,11 @@ var require_fetch = __commonJS({
42577
42652
  function dispatch({ body }) {
42578
42653
  const url = requestCurrentURL(request2);
42579
42654
  const agent = fetchParams.controller.dispatcher;
42580
- const path73 = url.pathname + url.search;
42655
+ const path74 = url.pathname + url.search;
42581
42656
  const hasTrailingQuestionMark = url.search.length === 0 && url.href[url.href.length - url.hash.length - 1] === "?";
42582
42657
  return new Promise((resolve2, reject) => agent.dispatch(
42583
42658
  {
42584
- path: hasTrailingQuestionMark ? `${path73}?` : path73,
42659
+ path: hasTrailingQuestionMark ? `${path74}?` : path74,
42585
42660
  origin: url.origin,
42586
42661
  method: request2.method,
42587
42662
  body: agent.isMockActive ? request2.body && (request2.body.source || request2.body.stream) : body,
@@ -43512,9 +43587,9 @@ var require_util4 = __commonJS({
43512
43587
  }
43513
43588
  }
43514
43589
  }
43515
- function validateCookiePath(path73) {
43516
- for (let i = 0; i < path73.length; ++i) {
43517
- const code = path73.charCodeAt(i);
43590
+ function validateCookiePath(path74) {
43591
+ for (let i = 0; i < path74.length; ++i) {
43592
+ const code = path74.charCodeAt(i);
43518
43593
  if (code < 32 || // exclude CTLs (0-31)
43519
43594
  code === 127 || // DEL
43520
43595
  code === 59) {
@@ -46684,11 +46759,11 @@ var require_undici = __commonJS({
46684
46759
  if (typeof opts.path !== "string") {
46685
46760
  throw new InvalidArgumentError("invalid opts.path");
46686
46761
  }
46687
- let path73 = opts.path;
46762
+ let path74 = opts.path;
46688
46763
  if (!opts.path.startsWith("/")) {
46689
- path73 = `/${path73}`;
46764
+ path74 = `/${path74}`;
46690
46765
  }
46691
- url = new URL(util.parseOrigin(url).origin + path73);
46766
+ url = new URL(util.parseOrigin(url).origin + path74);
46692
46767
  } else {
46693
46768
  if (!opts) {
46694
46769
  opts = typeof url === "object" ? url : {};
@@ -48676,9 +48751,9 @@ async function runDeviceLogin(opts = {}) {
48676
48751
  }
48677
48752
 
48678
48753
  // src/cli/commands/logout.ts
48679
- var fs48 = __toESM(require("fs"));
48754
+ var fs49 = __toESM(require("fs"));
48680
48755
  var os44 = __toESM(require("os"));
48681
- var path46 = __toESM(require("path"));
48756
+ var path47 = __toESM(require("path"));
48682
48757
  var import_chalk12 = __toESM(require("chalk"));
48683
48758
  async function revokeSelf(creds) {
48684
48759
  const url = creds.apiUrl.replace(/\/$/, "") + "/machines/self/disconnect";
@@ -48696,10 +48771,10 @@ function registerLogoutCommand(program2) {
48696
48771
  "Disconnect this machine from the cloud (revokes its key; local enforcement keeps running)"
48697
48772
  ).action(async () => {
48698
48773
  const profile = process.env.NODE9_PROFILE || "default";
48699
- const credPath = path46.join(os44.homedir(), ".node9", "credentials.json");
48774
+ const credPath = path47.join(os44.homedir(), ".node9", "credentials.json");
48700
48775
  let all = {};
48701
48776
  try {
48702
- all = JSON.parse(fs48.readFileSync(credPath, "utf-8"));
48777
+ all = JSON.parse(fs49.readFileSync(credPath, "utf-8"));
48703
48778
  } catch {
48704
48779
  }
48705
48780
  const entry = all[profile];
@@ -48729,11 +48804,11 @@ function registerLogoutCommand(program2) {
48729
48804
  delete all[profile];
48730
48805
  if (Object.keys(all).length === 0) {
48731
48806
  try {
48732
- fs48.unlinkSync(credPath);
48807
+ fs49.unlinkSync(credPath);
48733
48808
  } catch {
48734
48809
  }
48735
48810
  } else {
48736
- fs48.writeFileSync(credPath, JSON.stringify(all, null, 2), { mode: 384 });
48811
+ fs49.writeFileSync(credPath, JSON.stringify(all, null, 2), { mode: 384 });
48737
48812
  }
48738
48813
  console.log(import_chalk12.default.green("\u2713 Local: credentials removed."));
48739
48814
  console.log(
@@ -50871,6 +50946,7 @@ init_costSync();
50871
50946
  init_litellm();
50872
50947
  init_cost_codex();
50873
50948
  init_decision();
50949
+ init_session_files();
50874
50950
  var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
50875
50951
  function buildTestTimestamps(allEntries) {
50876
50952
  const testTs = /* @__PURE__ */ new Set();
@@ -51010,7 +51086,7 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
51010
51086
  try {
51011
51087
  const stat = import_fs55.default.statSync(projPath);
51012
51088
  if (!stat.isDirectory()) return;
51013
- files = import_fs55.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
51089
+ files = listSessionFiles(projPath);
51014
51090
  } catch {
51015
51091
  return;
51016
51092
  }
@@ -55479,7 +55555,7 @@ function severityFromScore(score) {
55479
55555
  if (score >= 1) return "advisory";
55480
55556
  return null;
55481
55557
  }
55482
- function analyzeWorkflow(path73, content) {
55558
+ function analyzeWorkflow(path74, content) {
55483
55559
  let raw;
55484
55560
  try {
55485
55561
  raw = (0, import_yaml.parse)(content) ?? {};
@@ -55605,7 +55681,7 @@ function analyzeWorkflow(path73, content) {
55605
55681
  dimension: "workflows",
55606
55682
  severity,
55607
55683
  title,
55608
- file: path73,
55684
+ file: path74,
55609
55685
  signals,
55610
55686
  mitigations: mitigations.length ? mitigations : void 0,
55611
55687
  fix: head === "root" && privileged ? "Do not check out the untrusted PR head into the workspace root under a privileged trigger (pull_request_target/workflow_run) \u2014 check out the base ref, or isolate the head in a subdir (--add-dir). Add an actor gate and scope the agent tools." : head === "root" ? "This runs under `pull_request` (fork PRs get a read-only token), so the head checkout is low-risk today \u2014 keep it on `pull_request` (not `pull_request_target`) and keep the actor gate + scoped tools." : "Add/verify an actor gate, scope the agent tools to read-only, and env-deny secrets. See Anthropic\u2019s claude-code-action security doc."
@@ -55681,7 +55757,7 @@ function evalAgentJob(job, wf, raw, untrustedTrigger, reusable) {
55681
55757
  if (reusable && !loadedGun && SEVERITY_RANK2[severity] > SEVERITY_RANK2.medium) severity = "medium";
55682
55758
  return { severity, secrets, injectable, canReadEnv };
55683
55759
  }
55684
- function analyzeWorkflowSecrets(path73, content) {
55760
+ function analyzeWorkflowSecrets(path74, content) {
55685
55761
  let raw;
55686
55762
  try {
55687
55763
  raw = (0, import_yaml.parse)(content) ?? {};
@@ -55701,7 +55777,7 @@ function analyzeWorkflowSecrets(path73, content) {
55701
55777
  dimension: "data",
55702
55778
  severity: worst.severity,
55703
55779
  title: worst.severity === "advisory" ? "Secrets reachable by the agent \u2014 hardening" : "Exfiltratable secrets reachable by an injectable agent",
55704
- file: path73,
55780
+ file: path74,
55705
55781
  signals: [
55706
55782
  `agent can reach: ${worst.secrets.map((s) => s.name).join(", ")}`,
55707
55783
  worst.injectable ? "the agent is externally triggerable (untrusted trigger, no gate)" : "gated / not externally triggerable \u2014 latent risk only",
@@ -55728,7 +55804,7 @@ function hookCommands(hooks) {
55728
55804
  }
55729
55805
  return out;
55730
55806
  }
55731
- function analyzeAgentConfig(path73, content) {
55807
+ function analyzeAgentConfig(path74, content) {
55732
55808
  let cfg;
55733
55809
  try {
55734
55810
  cfg = JSON.parse(content);
@@ -55747,7 +55823,7 @@ function analyzeAgentConfig(path73, content) {
55747
55823
  dimension: "toolRules",
55748
55824
  severity: high ? "high" : "medium",
55749
55825
  title: high ? "Agent hook runs UNPINNED/remote third-party code on every action" : "Agent hook runs third-party code in the agent hot path",
55750
- file: path73,
55826
+ file: path74,
55751
55827
  signals: [
55752
55828
  `hook command: \`${cmd.slice(0, 120)}\``,
55753
55829
  remoteExec ? "fetch-and-run (curl|wget / pipe-to-shell) \u2014 unpinnable remote code execution on every contributor" : unpinned ? "unpinned \u2014 a compromised/yanked package = code execution on every contributor" : "pinned, but still a standing supply-chain dependency in the agent hot path"
@@ -55767,7 +55843,7 @@ function analyzeAgentConfig(path73, content) {
55767
55843
  dimension: "toolRules",
55768
55844
  severity: hasBackstop ? "medium" : "high",
55769
55845
  title: hasBackstop ? "Committed agent config pre-authorizes broad tools" : "Committed agent config pre-authorizes broad tools with no deny backstop",
55770
- file: path73,
55846
+ file: path74,
55771
55847
  signals: [
55772
55848
  `broad allow(s): ${broad.slice(0, 5).join(", ")}`,
55773
55849
  hasBackstop ? "a `deny` list backstops the broad allow" : "no `deny` entry covers Bash/Write/Edit \u2014 every contributor is pre-authorized for catastrophic tools"
@@ -55780,16 +55856,16 @@ function analyzeAgentConfig(path73, content) {
55780
55856
 
55781
55857
  // src/ci-check/mcp.ts
55782
55858
  init_dist();
55783
- function analyzeMcp(path73, content) {
55859
+ function analyzeMcp(path74, content) {
55784
55860
  let cfg;
55785
55861
  try {
55786
55862
  cfg = JSON.parse(content);
55787
55863
  } catch {
55788
55864
  return [];
55789
55865
  }
55790
- return analyzeMcpServers(cfg.mcpServers ?? {}, path73);
55866
+ return analyzeMcpServers(cfg.mcpServers ?? {}, path74);
55791
55867
  }
55792
- function analyzeMcpServers(servers, path73) {
55868
+ function analyzeMcpServers(servers, path74) {
55793
55869
  const findings = [];
55794
55870
  for (const [name, srv] of Object.entries(servers ?? {})) {
55795
55871
  if (!srv || srv.disabled) continue;
@@ -55800,7 +55876,7 @@ function analyzeMcpServers(servers, path73) {
55800
55876
  dimension: "mcp",
55801
55877
  severity: "medium",
55802
55878
  title: `MCP server "${name}" runs an unpinned executable`,
55803
- file: path73,
55879
+ file: path74,
55804
55880
  signals: [`\`${argv.slice(0, 120)}\` \u2014 unversioned/@latest npx`],
55805
55881
  fix: "Pin the MCP server package to an exact version so a PR (or a registry compromise) can\u2019t swap the toolchain."
55806
55882
  });
@@ -55814,7 +55890,7 @@ function analyzeMcpServers(servers, path73) {
55814
55890
  dimension: "mcp",
55815
55891
  severity: "high",
55816
55892
  title: `MCP server "${name}" has an inline credential`,
55817
- file: path73,
55893
+ file: path74,
55818
55894
  signals: [
55819
55895
  `env.${k} matches ${hit.patternName} \u2014 agent-reachable secret committed to the repo`
55820
55896
  ],
@@ -55828,7 +55904,7 @@ function analyzeMcpServers(servers, path73) {
55828
55904
 
55829
55905
  // src/ci-check/codex.ts
55830
55906
  var import_smol_toml5 = require("smol-toml");
55831
- function analyzeCodexConfig(path73, content) {
55907
+ function analyzeCodexConfig(path74, content) {
55832
55908
  let cfg;
55833
55909
  try {
55834
55910
  cfg = (0, import_smol_toml5.parse)(content);
@@ -55836,7 +55912,7 @@ function analyzeCodexConfig(path73, content) {
55836
55912
  return [];
55837
55913
  }
55838
55914
  const findings = [];
55839
- findings.push(...analyzeMcpServers(cfg.mcp_servers ?? {}, path73));
55915
+ findings.push(...analyzeMcpServers(cfg.mcp_servers ?? {}, path74));
55840
55916
  const sandbox = typeof cfg.sandbox_mode === "string" ? cfg.sandbox_mode : "";
55841
55917
  const approval = typeof cfg.approval_policy === "string" ? cfg.approval_policy : "";
55842
55918
  const fullAccess = /danger-full-access/i.test(sandbox);
@@ -55851,7 +55927,7 @@ function analyzeCodexConfig(path73, content) {
55851
55927
  dimension: "toolRules",
55852
55928
  severity: fullAccess ? "high" : "medium",
55853
55929
  title: fullAccess ? "Codex config grants a full-access sandbox" : "Codex config never requires approval",
55854
- file: path73,
55930
+ file: path74,
55855
55931
  signals,
55856
55932
  fix: 'Commit a least-privilege Codex config: prefer `sandbox_mode = "read-only"` (or `"workspace-write"`) and `approval_policy = "on-request"`/`"on-failure"`. A repo-committed config applies to every contributor who runs Codex here.'
55857
55933
  });
@@ -55907,10 +55983,10 @@ function decodeSuspiciousBase64(text) {
55907
55983
  }
55908
55984
  return out;
55909
55985
  }
55910
- function mk(severity, title, signals, fix, path73) {
55911
- return { check: "CI-6", dimension: "instructions", severity, title, file: path73, signals, fix };
55986
+ function mk(severity, title, signals, fix, path74) {
55987
+ return { check: "CI-6", dimension: "instructions", severity, title, file: path74, signals, fix };
55912
55988
  }
55913
- function analyzeInstructionFile(path73, content) {
55989
+ function analyzeInstructionFile(path74, content) {
55914
55990
  const findings = [];
55915
55991
  const decoded = decodeSuspiciousBase64(content);
55916
55992
  if (TAG_CHARS.test(content))
@@ -55922,7 +55998,7 @@ function analyzeInstructionFile(path73, content) {
55922
55998
  "contains Unicode tag characters (U+E0000\u2013E007F) \u2014 an invisible instruction-smuggling channel with no legitimate use in text"
55923
55999
  ],
55924
56000
  "Remove the tag characters. Instruction files must be plain, reviewable text.",
55925
- path73
56001
+ path74
55926
56002
  )
55927
56003
  );
55928
56004
  if (BIDI_OVERRIDE.test(content))
@@ -55934,7 +56010,7 @@ function analyzeInstructionFile(path73, content) {
55934
56010
  "contains a bidi override (U+202D/U+202E) \u2014 a Trojan-Source technique that visually reorders text so a human reads something different from what the agent parses"
55935
56011
  ],
55936
56012
  "Remove the bidi override characters.",
55937
- path73
56013
+ path74
55938
56014
  )
55939
56015
  );
55940
56016
  else if (BIDI_EMBED_ISOLATE.test(content))
@@ -55946,7 +56022,7 @@ function analyzeInstructionFile(path73, content) {
55946
56022
  "contains bidi embed/isolate characters (U+202A\u2013202C / U+2066\u20132069) \u2014 legitimate in right-to-left text, but confirm they are not being used to hide or reorder instructions"
55947
56023
  ],
55948
56024
  "Confirm the bidi marks are legitimate RTL formatting; remove otherwise.",
55949
- path73
56025
+ path74
55950
56026
  )
55951
56027
  );
55952
56028
  const zw = suspiciousZeroWidth(content);
@@ -55960,7 +56036,7 @@ function analyzeInstructionFile(path73, content) {
55960
56036
  revealed ? "a zero-width character conceals a prompt-override directive that only appears once the hidden characters are stripped" : "a zero-width character splits a visible Latin word \u2014 a concealment technique (hides text from human review while the agent reads it as contiguous)"
55961
56037
  ],
55962
56038
  "Remove the zero-width characters. Instruction files must be plain, reviewable text.",
55963
- path73
56039
+ path74
55964
56040
  )
55965
56041
  );
55966
56042
  }
@@ -55976,7 +56052,7 @@ function analyzeInstructionFile(path73, content) {
55976
56052
  `contains a prompt-override / role-impersonation directive (\`${m[0].slice(0, 60).trim()}\`)${ovEnc ? " \u2014 concealed in a base64 blob" : ""}`
55977
56053
  ],
55978
56054
  "Remove the override text. An instruction file should not tell the agent to ignore its own rules.",
55979
- path73
56055
+ path74
55980
56056
  )
55981
56057
  );
55982
56058
  }
@@ -55988,7 +56064,7 @@ function analyzeInstructionFile(path73, content) {
55988
56064
  "Instruction directs the agent to fetch and run remote code",
55989
56065
  [`\`${fo[0].slice(0, 70).trim()}\` \u2014 fetch-and-obey, outside an install/setup section`],
55990
56066
  "Do not instruct the agent to pipe remote content into a shell; pin and vendor scripts instead.",
55991
- path73
56067
+ path74
55992
56068
  )
55993
56069
  );
55994
56070
  }
@@ -56000,7 +56076,7 @@ function analyzeInstructionFile(path73, content) {
56000
56076
  "Instruction points the agent at credential material",
56001
56077
  [`references \`${sp[0].slice(0, 50).trim()}\` \u2014 directs the agent toward secrets`],
56002
56078
  "Do not reference credential files or paths in agent instructions.",
56003
- path73
56079
+ path74
56004
56080
  )
56005
56081
  );
56006
56082
  }
@@ -56012,7 +56088,7 @@ function analyzeInstructionFile(path73, content) {
56012
56088
  "Instruction directs the agent to send data to an external endpoint",
56013
56089
  [`\`${ex[0].slice(0, 70).trim()}\` \u2014 possible exfiltration directive`],
56014
56090
  "Remove external post/upload directives from agent instructions.",
56015
- path73
56091
+ path74
56016
56092
  )
56017
56093
  );
56018
56094
  }