@skein-code/cli 0.3.11 → 0.3.12

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
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
220
220
  // package.json
221
221
  var package_default = {
222
222
  name: "@skein-code/cli",
223
- version: "0.3.11",
223
+ version: "0.3.12",
224
224
  description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
225
225
  type: "module",
226
226
  license: "MIT",
@@ -236,9 +236,9 @@ var package_default = {
236
236
  },
237
237
  skein: {
238
238
  releaseNotes: [
239
- "Large tool results now use a language-aware dynamic token budget with status-preserving head and tail receipts",
240
- "Complete captured output can be read back through redacted, expiring, session-scoped local artifacts",
241
- "Shell and MCP source truncation is explicit, and session deletion removes retained output"
239
+ "Each model request records privacy-safe token partitions and actual-versus-estimated provenance",
240
+ "Local retrieval now selects focused, standard, broad, or maximum evidence budgets",
241
+ "Terminal and JSON diagnostics expose budget decisions without storing prompt or source content"
242
242
  ]
243
243
  },
244
244
  bin: {
@@ -2484,6 +2484,112 @@ function shouldUseProviderEnvironmentKey(provider, baseUrl) {
2484
2484
  return baseUrl.replace(/\/+$/u, "") === official[provider];
2485
2485
  }
2486
2486
 
2487
+ // src/context/budget.ts
2488
+ var tierTokens = {
2489
+ focused: 2e3,
2490
+ standard: 4e3,
2491
+ broad: 8e3,
2492
+ maximum: 12e3
2493
+ };
2494
+ function selectContextBudget(query, config, options = {}) {
2495
+ if (options.trivial) return noContextBudget("trivial turn; retrieval skipped");
2496
+ const configuredCeiling = positiveFloor(options.maxTokens ?? config.context.maxTokens);
2497
+ const configuredTopK = positiveFloor(options.topK ?? config.context.topK);
2498
+ if (configuredCeiling === 0 || configuredTopK === 0) {
2499
+ return noContextBudget("retrieval disabled by the configured context ceiling");
2500
+ }
2501
+ const normalized = query.trim().toLocaleLowerCase();
2502
+ const explicitPaths = countExplicitPaths(query);
2503
+ const breadthSignals = countMatches(normalized, [
2504
+ /\b(?:cross[- ]module|cross[- ]package|across modules|across packages|entire repository|whole repository|whole codebase|all modules|all packages|end[- ]to[- ]end)\b/gu,
2505
+ /跨模块|跨包|全仓库|全代码库|整个(?:项目|仓库|代码库)|所有(?:模块|包)|端到端/gu
2506
+ ]);
2507
+ const exhaustiveSignals = countMatches(normalized, [
2508
+ /\b(?:exhaustive|comprehensive|repository[- ]wide|codebase[- ]wide|all call sites|all usages|architecture migration|security audit)\b/gu,
2509
+ /全面|完整审计|全量|全局迁移|所有调用|所有引用|架构迁移|安全审计/gu
2510
+ ]);
2511
+ const coordinationSignals = countMatches(normalized, [
2512
+ /\b(?:and|then|also|plus|including|migrate|redesign|architecture|integration|workflow)\b/gu,
2513
+ /并且|然后|同时|以及|包括|迁移|重设计|架构|集成|工作流/gu
2514
+ ]);
2515
+ let tier;
2516
+ let reason;
2517
+ if (breadthSignals > 0 && exhaustiveSignals > 0 && (coordinationSignals >= 2 || normalized.length >= 180)) {
2518
+ tier = "maximum";
2519
+ reason = "explicit repository-wide work with exhaustive, multi-part scope";
2520
+ } else if (breadthSignals > 0 || exhaustiveSignals >= 2) {
2521
+ tier = "broad";
2522
+ reason = "cross-module or repository-wide evidence requested";
2523
+ } else if (explicitPaths > 0 && coordinationSignals < 2) {
2524
+ tier = "focused";
2525
+ reason = "one or more explicit paths bound the evidence surface";
2526
+ } else if ((options.intent === "explain" || options.intent === "review" || options.intent === "test") && normalized.length <= 160 && coordinationSignals < 2) {
2527
+ tier = "focused";
2528
+ reason = `short ${options.intent} request with a narrow evidence surface`;
2529
+ } else {
2530
+ tier = "standard";
2531
+ reason = "ordinary implementation, debugging, or refactoring request";
2532
+ }
2533
+ const requestedTokens = tierTokens[tier];
2534
+ const budgetTokens = Math.min(configuredCeiling, requestedTokens);
2535
+ const baseBudgetTokens = Math.min(budgetTokens, tierTokens.focused);
2536
+ const incrementalBudgetTokens = Math.max(0, budgetTokens - baseBudgetTokens);
2537
+ const topKByTier = {
2538
+ focused: 4,
2539
+ standard: 8,
2540
+ broad: 12,
2541
+ maximum: 16
2542
+ };
2543
+ const topK = Math.min(configuredTopK, topKByTier[tier]);
2544
+ const capped = budgetTokens < requestedTokens ? `; capped by configuration at ${budgetTokens} tokens` : "";
2545
+ return {
2546
+ tier,
2547
+ budgetTokens,
2548
+ baseBudgetTokens,
2549
+ incrementalBudgetTokens,
2550
+ topK,
2551
+ reason: `${reason}${capped}`
2552
+ };
2553
+ }
2554
+ function emptyPackedContext(decision, engine = "local") {
2555
+ return {
2556
+ text: "",
2557
+ hits: [],
2558
+ estimatedTokens: 0,
2559
+ engine,
2560
+ truncated: false,
2561
+ budgetTier: decision.tier,
2562
+ budgetTokens: decision.budgetTokens,
2563
+ baseBudgetTokens: decision.baseBudgetTokens,
2564
+ incrementalBudgetTokens: decision.incrementalBudgetTokens,
2565
+ budgetReason: decision.reason,
2566
+ candidateHits: 0,
2567
+ selectedHits: 0,
2568
+ duplicateHits: 0,
2569
+ incrementalEvidenceTokens: 0
2570
+ };
2571
+ }
2572
+ function noContextBudget(reason) {
2573
+ return {
2574
+ tier: "none",
2575
+ budgetTokens: 0,
2576
+ baseBudgetTokens: 0,
2577
+ incrementalBudgetTokens: 0,
2578
+ topK: 0,
2579
+ reason
2580
+ };
2581
+ }
2582
+ function positiveFloor(value) {
2583
+ return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
2584
+ }
2585
+ function countMatches(value, patterns) {
2586
+ return patterns.reduce((total, pattern) => total + [...value.matchAll(pattern)].length, 0);
2587
+ }
2588
+ function countExplicitPaths(value) {
2589
+ const matches = value.match(/(?:^|[\s'"`(])@?(?:\.{0,2}[/\\]|[A-Za-z]:[/\\]|[\w.-]+[/\\])[\w@%+.,()[\]{}=-]+(?:[/\\][\w@%+.,()[\]{}=-]+)*(?:\.\w+)?/gu);
2590
+ return matches?.length ?? 0;
2591
+ }
2592
+
2487
2593
  // src/context/local-index.ts
2488
2594
  import { createHash as createHash5 } from "node:crypto";
2489
2595
  import { lstat as lstat8, readFile as readFile3, stat as stat3 } from "node:fs/promises";
@@ -2604,6 +2710,58 @@ async function pathExists(path) {
2604
2710
  }
2605
2711
  }
2606
2712
 
2713
+ // src/utils/tokens.ts
2714
+ function estimateTokens(value) {
2715
+ let tokens2 = 0;
2716
+ for (const character of value) tokens2 += estimatedTokenCost(character);
2717
+ return Math.ceil(tokens2 - Number.EPSILON * Math.max(1, value.length) * 32);
2718
+ }
2719
+ function estimatedTokenCost(character) {
2720
+ const codePoint = character.codePointAt(0) ?? 0;
2721
+ if (codePoint <= 127) return 0.25;
2722
+ if (isEmojiOrSupplementarySymbol(character, codePoint)) return 2;
2723
+ if (/^[\p{P}\p{S}]$/u.test(character)) return 0.5;
2724
+ if (isCjkCharacter(codePoint)) return 1;
2725
+ return 2;
2726
+ }
2727
+ function sliceStartByTokens(value, budget) {
2728
+ if (budget <= 0) return "";
2729
+ let used = 0;
2730
+ let end = 0;
2731
+ for (const character of value) {
2732
+ const cost = estimatedTokenCost(character);
2733
+ if (used + cost > budget) break;
2734
+ used += cost;
2735
+ end += character.length;
2736
+ }
2737
+ return value.slice(0, end);
2738
+ }
2739
+ function sliceEndByTokens(value, budget) {
2740
+ if (budget <= 0) return "";
2741
+ let used = 0;
2742
+ let start = value.length;
2743
+ while (start > 0) {
2744
+ let next = start - 1;
2745
+ const code = value.charCodeAt(next);
2746
+ if (code >= 56320 && code <= 57343 && next > 0) {
2747
+ const previous = value.charCodeAt(next - 1);
2748
+ if (previous >= 55296 && previous <= 56319) next -= 1;
2749
+ }
2750
+ const character = value.slice(next, start);
2751
+ const cost = estimatedTokenCost(character);
2752
+ if (used + cost > budget) break;
2753
+ used += cost;
2754
+ start = next;
2755
+ }
2756
+ return value.slice(start);
2757
+ }
2758
+ function isCjkCharacter(codePoint) {
2759
+ return codePoint >= 11904 && codePoint <= 40959 || codePoint >= 44032 && codePoint <= 55215 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 131072 && codePoint <= 195103 || codePoint >= 12352 && codePoint <= 12543;
2760
+ }
2761
+ function isEmojiOrSupplementarySymbol(character, codePoint) {
2762
+ return codePoint > 65535 || new RegExp("\\p{Extended_Pictographic}", "u").test(character);
2763
+ }
2764
+
2607
2765
  // src/context/local-index.ts
2608
2766
  var contentHashSchema = z3.string().regex(/^[a-f\d]{64}$/u);
2609
2767
  var indexedChunkSchema = z3.object({
@@ -3045,17 +3203,31 @@ function packContextHits(hits, roots, maxTokens, engine) {
3045
3203
  const uniquePaths = new Set(hits.map((hit) => hit.path)).size;
3046
3204
  let estimatedTokens = 0;
3047
3205
  let truncated = false;
3206
+ let duplicateHits = 0;
3207
+ let perFileLimitHits = 0;
3048
3208
  for (const hit of hits) {
3049
3209
  const count = perFile.get(hit.path) ?? 0;
3050
- if (uniquePaths > 1 && count >= 2) continue;
3051
- if (selected.some((candidate) => hasSubstantialOverlap(candidate, hit))) continue;
3210
+ if (uniquePaths > 1 && count >= 2) {
3211
+ perFileLimitHits += 1;
3212
+ continue;
3213
+ }
3214
+ if (selected.some((candidate) => hasSubstantialOverlap(candidate, hit))) {
3215
+ duplicateHits += 1;
3216
+ continue;
3217
+ }
3052
3218
  const tokens2 = estimateTokens(hit.content);
3053
3219
  if (estimatedTokens + tokens2 > maxTokens) {
3054
- const remainingChars = Math.max(0, (maxTokens - estimatedTokens) * 4);
3055
- if (remainingChars >= 32) {
3056
- selected.push({ ...hit, content: hit.content.slice(0, remainingChars) });
3220
+ const remainingTokens = Math.max(0, maxTokens - estimatedTokens);
3221
+ const content = sliceStartByTokens(hit.content, remainingTokens);
3222
+ if (content.length >= 32) {
3223
+ const returnedLines = Math.max(1, content.split("\n").length);
3224
+ selected.push({
3225
+ ...hit,
3226
+ content,
3227
+ endLine: Math.min(hit.endLine, hit.startLine + returnedLines - 1)
3228
+ });
3057
3229
  perFile.set(hit.path, count + 1);
3058
- estimatedTokens = maxTokens;
3230
+ estimatedTokens += estimateTokens(content);
3059
3231
  }
3060
3232
  truncated = true;
3061
3233
  break;
@@ -3071,7 +3243,16 @@ function packContextHits(hits, roots, maxTokens, engine) {
3071
3243
  ${hit.content}
3072
3244
  </code>`;
3073
3245
  }).join("\n\n");
3074
- return { text, hits: selected, estimatedTokens, engine, truncated };
3246
+ return {
3247
+ text,
3248
+ hits: selected,
3249
+ estimatedTokens,
3250
+ engine,
3251
+ truncated,
3252
+ candidateHits: hits.length,
3253
+ selectedHits: selected.length,
3254
+ duplicateHits: duplicateHits + perFileLimitHits
3255
+ };
3075
3256
  }
3076
3257
  function cloneHits(hits) {
3077
3258
  return hits.map((hit) => ({ ...hit }));
@@ -3082,9 +3263,6 @@ function hashContent(content) {
3082
3263
  function createGeneration(files) {
3083
3264
  return createHash5("sha256").update(files.slice().sort((left, right) => left.absolutePath.localeCompare(right.absolutePath)).map((file) => `${file.absolutePath}\0${file.contentHash}`).join("\n"), "utf8").digest("hex").slice(0, 16);
3084
3265
  }
3085
- function estimateTokens(content) {
3086
- return Math.ceil(content.length / 4);
3087
- }
3088
3266
  function hasSubstantialOverlap(left, right) {
3089
3267
  if (left.path !== right.path) return false;
3090
3268
  const overlap = Math.max(0, Math.min(left.endLine, right.endLine) - Math.max(left.startLine, right.startLine) + 1);
@@ -3205,15 +3383,28 @@ var ContextEngine = class {
3205
3383
  config;
3206
3384
  local;
3207
3385
  degradation;
3208
- async pack(query) {
3386
+ async pack(query, options = {}) {
3387
+ const decision = selectContextBudget(query, this.config, options);
3388
+ if (decision.budgetTokens === 0 || decision.topK === 0) {
3389
+ this.degradation = void 0;
3390
+ return emptyPackedContext(decision);
3391
+ }
3209
3392
  try {
3210
3393
  const packed = await this.local.pack(
3211
3394
  query,
3212
- this.config.context.topK,
3213
- this.config.context.maxTokens
3395
+ decision.topK,
3396
+ decision.budgetTokens
3214
3397
  );
3215
3398
  this.degradation = void 0;
3216
- return packed;
3399
+ return {
3400
+ ...packed,
3401
+ budgetTier: decision.tier,
3402
+ budgetTokens: decision.budgetTokens,
3403
+ baseBudgetTokens: decision.baseBudgetTokens,
3404
+ incrementalBudgetTokens: decision.incrementalBudgetTokens,
3405
+ budgetReason: decision.reason,
3406
+ incrementalEvidenceTokens: Math.max(0, packed.estimatedTokens - decision.baseBudgetTokens)
3407
+ };
3217
3408
  } catch (error) {
3218
3409
  const detail = error instanceof Error ? error.message : String(error);
3219
3410
  this.degradation = {
@@ -3223,11 +3414,7 @@ var ContextEngine = class {
3223
3414
  };
3224
3415
  const degradation = this.lastDegradation();
3225
3416
  return {
3226
- text: "",
3227
- hits: [],
3228
- estimatedTokens: 0,
3229
- engine: "local",
3230
- truncated: false,
3417
+ ...emptyPackedContext(decision),
3231
3418
  ...degradation ? { degradation } : {}
3232
3419
  };
3233
3420
  }
@@ -3324,7 +3511,7 @@ var ContextManager = class {
3324
3511
  status(session, modelContextTokens) {
3325
3512
  const active = activeMessages(session);
3326
3513
  const activeTokens = estimateMessages(active);
3327
- const summaryTokens = estimateTokens2(session.contextSummary ?? "");
3514
+ const summaryTokens = estimateTokens(session.contextSummary ?? "");
3328
3515
  const toolTokenCount = toolTokens(active);
3329
3516
  const contextLimit = Math.max(
3330
3517
  8e3,
@@ -3351,11 +3538,11 @@ var ContextManager = class {
3351
3538
  const active = activeMessages(session);
3352
3539
  const cut = compactionCut(active);
3353
3540
  if (cut === 0) {
3354
- return { omittedMessages: 0, summaryTokens: estimateTokens2(session.contextSummary ?? "") };
3541
+ return { omittedMessages: 0, summaryTokens: estimateTokens(session.contextSummary ?? "") };
3355
3542
  }
3356
3543
  const older = active.slice(0, cut);
3357
3544
  if (!older.length) {
3358
- return { omittedMessages: 0, summaryTokens: estimateTokens2(session.contextSummary ?? "") };
3545
+ return { omittedMessages: 0, summaryTokens: estimateTokens(session.contextSummary ?? "") };
3359
3546
  }
3360
3547
  const transcript = older.map(formatMessageForSummary).join("\n\n").slice(-14e4);
3361
3548
  const response = await provider.complete([
@@ -3376,7 +3563,7 @@ ${transcript}`)
3376
3563
  session.contextCompactions = (session.contextCompactions ?? 0) + 1;
3377
3564
  return {
3378
3565
  omittedMessages: older.length,
3379
- summaryTokens: estimateTokens2(session.contextSummary)
3566
+ summaryTokens: estimateTokens(session.contextSummary)
3380
3567
  };
3381
3568
  }
3382
3569
  buildShortTermPrompt(session) {
@@ -3505,13 +3692,10 @@ function concise(value, max) {
3505
3692
  return normalized.length <= max ? normalized : `${normalized.slice(0, max - 1)}\u2026`;
3506
3693
  }
3507
3694
  function estimateMessages(messages) {
3508
- return messages.reduce((sum, message2) => sum + estimateTokens2(message2.content) + estimateTokens2(JSON.stringify(message2.toolCalls ?? [])), 0);
3695
+ return messages.reduce((sum, message2) => sum + estimateTokens(message2.content) + estimateTokens(JSON.stringify(message2.toolCalls ?? [])), 0);
3509
3696
  }
3510
3697
  function toolTokens(messages) {
3511
- return messages.filter((message2) => message2.role === "tool").reduce((sum, message2) => sum + estimateTokens2(message2.content), 0);
3512
- }
3513
- function estimateTokens2(value) {
3514
- return Math.ceil(value.length / 4);
3698
+ return messages.filter((message2) => message2.role === "tool").reduce((sum, message2) => sum + estimateTokens(message2.content), 0);
3515
3699
  }
3516
3700
 
3517
3701
  // src/context/mentions.ts
@@ -5058,8 +5242,54 @@ var sessionSchema = z5.object({
5058
5242
  lastRun: lastRunSchema.optional(),
5059
5243
  usage: z5.object({
5060
5244
  inputTokens: z5.number().nonnegative(),
5061
- outputTokens: z5.number().nonnegative()
5062
- }).strict()
5245
+ outputTokens: z5.number().nonnegative(),
5246
+ source: z5.enum(["actual", "estimated", "mixed", "unknown"]).optional(),
5247
+ inputSource: z5.enum(["actual", "estimated", "mixed", "unknown"]).optional(),
5248
+ outputSource: z5.enum(["actual", "estimated", "mixed", "unknown"]).optional(),
5249
+ actualInputTokens: z5.number().nonnegative().optional(),
5250
+ actualOutputTokens: z5.number().nonnegative().optional(),
5251
+ estimatedInputTokens: z5.number().nonnegative().optional(),
5252
+ estimatedOutputTokens: z5.number().nonnegative().optional()
5253
+ }).strict(),
5254
+ tokenLedger: z5.array(z5.object({
5255
+ requestId: z5.string().uuid(),
5256
+ turn: z5.number().int().positive(),
5257
+ recordedAt: z5.string().datetime(),
5258
+ estimated: z5.object({
5259
+ stableTokens: z5.number().nonnegative(),
5260
+ dynamicTokens: z5.number().nonnegative(),
5261
+ conversationTokens: z5.number().nonnegative(),
5262
+ toolResultTokens: z5.number().nonnegative(),
5263
+ retrievedTokens: z5.number().nonnegative(),
5264
+ toolSchemaTokens: z5.number().nonnegative(),
5265
+ estimatedInputTokens: z5.number().nonnegative(),
5266
+ outputAllowanceTokens: z5.number().nonnegative(),
5267
+ outputTokens: z5.number().nonnegative()
5268
+ }).strict(),
5269
+ actual: z5.object({
5270
+ inputTokens: z5.number().nonnegative().optional(),
5271
+ outputTokens: z5.number().nonnegative().optional()
5272
+ }).strict(),
5273
+ inputSource: z5.enum(["actual", "estimated"]),
5274
+ outputSource: z5.enum(["actual", "estimated"]),
5275
+ tools: z5.object({
5276
+ loaded: z5.array(z5.string()),
5277
+ deferredCount: z5.number().int().nonnegative()
5278
+ }).strict(),
5279
+ retrieval: z5.object({
5280
+ engine: z5.string(),
5281
+ budgetTier: z5.enum(["none", "focused", "standard", "broad", "maximum"]).optional(),
5282
+ budgetTokens: z5.number().nonnegative().optional(),
5283
+ candidateHits: z5.number().int().nonnegative().optional(),
5284
+ selectedHits: z5.number().int().nonnegative().optional(),
5285
+ duplicateHits: z5.number().int().nonnegative().optional(),
5286
+ incrementalEvidenceTokens: z5.number().nonnegative().optional(),
5287
+ discarded: z5.array(z5.object({
5288
+ reason: z5.enum(["overlapping-span", "budget-cap"]),
5289
+ count: z5.number().int().positive()
5290
+ }).strict())
5291
+ }).strict()
5292
+ }).strict()).max(256).optional()
5063
5293
  }).strict();
5064
5294
  var SessionStore = class {
5065
5295
  workspace;
@@ -8331,11 +8561,7 @@ function dynamicToolOutputBudget(contextWindowTokens, activeContextTokens, remai
8331
8561
  return clamp2(budget, MIN_TOOL_OUTPUT_TOKENS, MAX_TOOL_OUTPUT_TOKENS);
8332
8562
  }
8333
8563
  function estimateToolOutputTokens(value) {
8334
- let tokens2 = 0;
8335
- for (const character of value) {
8336
- tokens2 += tokenCost(character);
8337
- }
8338
- return Math.max(1, Math.ceil(tokens2));
8564
+ return Math.max(1, estimateTokens(value));
8339
8565
  }
8340
8566
  function formatReceipt(options, metadata, preview) {
8341
8567
  const lines = [
@@ -8399,35 +8625,6 @@ function sanitizeToolOutput(value) {
8399
8625
  const text = stripAnsi(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, "");
8400
8626
  return { text, sanitized: text !== value };
8401
8627
  }
8402
- function sliceStartByTokens(value, budget) {
8403
- let used = 0;
8404
- let end = 0;
8405
- for (const character of value) {
8406
- const cost = tokenCost(character);
8407
- if (used + cost > budget) break;
8408
- used += cost;
8409
- end += character.length;
8410
- }
8411
- return value.slice(0, end);
8412
- }
8413
- function sliceEndByTokens(value, budget) {
8414
- let used = 0;
8415
- let start = value.length;
8416
- while (start > 0) {
8417
- let next = start - 1;
8418
- const code = value.charCodeAt(next);
8419
- if (code >= 56320 && code <= 57343 && next > 0) {
8420
- const previous = value.charCodeAt(next - 1);
8421
- if (previous >= 55296 && previous <= 56319) next -= 1;
8422
- }
8423
- const character = value.slice(next, start);
8424
- const cost = tokenCost(character);
8425
- if (used + cost > budget) break;
8426
- used += cost;
8427
- start = next;
8428
- }
8429
- return value.slice(start);
8430
- }
8431
8628
  function boundedPreview(value, budget) {
8432
8629
  if (budget <= 0) return "";
8433
8630
  if (estimateToolOutputTokens(value) <= budget) return value;
@@ -8452,15 +8649,6 @@ ${tail}`;
8452
8649
  }
8453
8650
  return best || sliceStartByTokens(value, budget);
8454
8651
  }
8455
- function tokenCost(character) {
8456
- const code = character.codePointAt(0) ?? 0;
8457
- if (isCjkCharacter(character) || code > 127) return 2;
8458
- return /[A-Z0-9\p{P}\p{S}]/u.test(character) ? 0.5 : 0.25;
8459
- }
8460
- function isCjkCharacter(character) {
8461
- const code = character.codePointAt(0) ?? 0;
8462
- return code >= 11904 && code <= 40959 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 131072 && code <= 195103;
8463
- }
8464
8652
  function clamp2(value, minimum, maximum) {
8465
8653
  return Math.max(minimum, Math.min(maximum, value));
8466
8654
  }
@@ -8468,7 +8656,7 @@ function boundedInlineByTokens(value, maxTokens) {
8468
8656
  const normalized = value.replace(/[\r\n\t]+/gu, " ").trim();
8469
8657
  if (estimateToolOutputTokens(normalized) <= maxTokens) return normalized;
8470
8658
  const suffix = "\u2026";
8471
- return `${sliceStartByTokens(normalized, Math.max(0, maxTokens - tokenCost(suffix)))}${suffix}`;
8659
+ return `${sliceStartByTokens(normalized, Math.max(0, maxTokens - estimatedTokenCost(suffix)))}${suffix}`;
8472
8660
  }
8473
8661
 
8474
8662
  // src/agent/rules.ts
@@ -8543,9 +8731,6 @@ import { readFile as readFile13, stat as stat9 } from "node:fs/promises";
8543
8731
  var MAX_SOURCE_CHARS = 6e4;
8544
8732
  var MAX_PINNED_CHARS = 16e4;
8545
8733
  var MAX_CONTEXT_SOURCES = 32;
8546
- function estimateTokens3(value) {
8547
- return Math.ceil(value.length / 4);
8548
- }
8549
8734
  function sources(session) {
8550
8735
  return session.contextSources ?? (session.contextSources = []);
8551
8736
  }
@@ -8553,7 +8738,7 @@ async function pinContextSource(session, workspace, requested) {
8553
8738
  const resolved = await workspace.resolvePath(requested, { expect: "file" });
8554
8739
  const info = await stat9(resolved);
8555
8740
  const alias = workspace.display(resolved);
8556
- const tokens2 = estimateTokens3((await readFile13(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
8741
+ const tokens2 = estimateTokens((await readFile13(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
8557
8742
  const list2 = sources(session);
8558
8743
  const existing = list2.find((source2) => source2.path === alias);
8559
8744
  if (existing) {
@@ -8605,7 +8790,7 @@ async function resolvePinnedContent(session, workspace) {
8605
8790
  const safe = await workspace.resolvePath(source.path, { expect: "file" });
8606
8791
  const raw = await readFile13(safe, "utf8");
8607
8792
  const capped = raw.slice(0, Math.min(MAX_SOURCE_CHARS, remaining));
8608
- source.tokens = estimateTokens3(capped);
8793
+ source.tokens = estimateTokens(capped);
8609
8794
  resolved.push({
8610
8795
  path: source.path,
8611
8796
  content: capped,
@@ -8771,13 +8956,20 @@ var AgentRunner = class {
8771
8956
  this.session.title = titleFromInput(request);
8772
8957
  }
8773
8958
  this.contextManager.startTurn(this.session, request);
8774
- this.session.messages.push(message("user", request));
8959
+ const userMessage = message("user", request);
8960
+ this.session.messages.push(userMessage);
8775
8961
  await this.persist();
8776
8962
  const trivialTurn = isTrivialTurn(request);
8777
- const packed = await this.packContext(request);
8963
+ const turnDirective = buildTurnDirective(request, {
8964
+ agents: Boolean(this.config.agents?.enabled)
8965
+ });
8966
+ const packed = trivialTurn ? emptyPackedContext(selectContextBudget(request, this.config, {
8967
+ intent: turnDirective.intent,
8968
+ trivial: true
8969
+ })) : await this.packContext(request, { intent: turnDirective.intent });
8778
8970
  if (!trivialTurn) await emit({ type: "context", packed });
8779
8971
  const mentions = await this.packMentions(request);
8780
- const retrievedContext = buildRetrievedContext(
8972
+ const retrievedContext = trivialTurn && !mentions.length ? "" : buildRetrievedContext(
8781
8973
  packed,
8782
8974
  mentions,
8783
8975
  this.workspace.primaryRoot,
@@ -8803,9 +8995,6 @@ var AgentRunner = class {
8803
8995
  scope: augmentation.memoryScope ?? "session"
8804
8996
  });
8805
8997
  }
8806
- const turnDirective = buildTurnDirective(request, {
8807
- agents: Boolean(this.config.agents?.enabled)
8808
- });
8809
8998
  const contractEnabled = shouldUseTaskContract(
8810
8999
  request,
8811
9000
  turnDirective.intent,
@@ -8829,21 +9018,6 @@ var AgentRunner = class {
8829
9018
  ...augmentation.skills?.length ? [`skills:${augmentation.skills.length}`] : [],
8830
9019
  ...augmentation.memoryCount ? [`memory:${augmentation.memoryCount}`] : []
8831
9020
  ];
8832
- if (!trivialTurn) await emit({
8833
- type: "prompt",
8834
- intent: turnDirective.intent,
8835
- sections: promptSections,
8836
- estimatedTokens: Math.ceil([
8837
- turnDirective.text,
8838
- buildSessionStatePrompt(this.session),
8839
- this.contextManager.buildShortTermPrompt(this.session),
8840
- options.turnInstructions ?? "",
8841
- augmentation.text,
8842
- retrievedContext,
8843
- pinnedContext,
8844
- workspaceRules
8845
- ].join("\n").length / 4)
8846
- });
8847
9021
  let verificationAttempted = false;
8848
9022
  const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
8849
9023
  const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
@@ -8858,16 +9032,17 @@ var AgentRunner = class {
8858
9032
  }
8859
9033
  this.applySteering();
8860
9034
  await emit({ type: "thinking", turn });
9035
+ const dynamicPrompt = [
9036
+ buildSessionStatePrompt(this.session),
9037
+ turnDirective.text,
9038
+ this.contextManager.buildShortTermPrompt(this.session),
9039
+ pinnedContext,
9040
+ options.turnInstructions ?? "",
9041
+ augmentation.text
9042
+ ].filter(Boolean).join("\n\n");
8861
9043
  const messages = packConversation(
8862
9044
  stableSystemPrompt,
8863
- [
8864
- buildSessionStatePrompt(this.session),
8865
- turnDirective.text,
8866
- this.contextManager.buildShortTermPrompt(this.session),
8867
- pinnedContext,
8868
- options.turnInstructions ?? "",
8869
- augmentation.text
8870
- ].filter(Boolean).join("\n\n"),
9045
+ dynamicPrompt,
8871
9046
  retrievedContext,
8872
9047
  activeMessages(this.session),
8873
9048
  contextBudget
@@ -8887,6 +9062,23 @@ var AgentRunner = class {
8887
9062
  this.config.model.maxTokens ?? 8192,
8888
9063
  availableTokens - estimatedInputTokens
8889
9064
  ));
9065
+ const breakdown = promptTokenBreakdown(
9066
+ messages,
9067
+ stableSystemPrompt,
9068
+ dynamicPrompt,
9069
+ retrievedContext,
9070
+ visibleTools,
9071
+ maxOutputTokens
9072
+ );
9073
+ if (!trivialTurn) {
9074
+ await emit({
9075
+ type: "prompt",
9076
+ intent: turnDirective.intent,
9077
+ sections: promptSections,
9078
+ estimatedTokens: breakdown.estimatedInputTokens,
9079
+ breakdown
9080
+ });
9081
+ }
8890
9082
  const assistantId = randomUUID12();
8891
9083
  const response = await this.completeModel(
8892
9084
  messages,
@@ -8902,17 +9094,49 @@ var AgentRunner = class {
8902
9094
  assistantMessage.id = assistantId;
8903
9095
  this.session.messages.push(assistantMessage);
8904
9096
  if (response.content) await emit({ type: "assistant", id: assistantId, content: response.content });
8905
- const inputTokens = response.usage?.inputTokens ?? estimatedInputTokens;
8906
- const outputTokens = response.usage?.outputTokens ?? estimateResponseTokens(response);
8907
- this.session.usage.inputTokens += inputTokens;
8908
- this.session.usage.outputTokens += outputTokens;
8909
- if (inputTokens || outputTokens) {
8910
- await emit({
8911
- type: "usage",
8912
- inputTokens: this.session.usage.inputTokens,
8913
- outputTokens: this.session.usage.outputTokens
8914
- });
8915
- }
9097
+ const turnUsage = recordTokenUsage(
9098
+ this.session,
9099
+ response.usage,
9100
+ estimatedInputTokens,
9101
+ estimateResponseTokens(response)
9102
+ );
9103
+ const { inputTokens, outputTokens } = turnUsage;
9104
+ const actualInputTokens = validTokenCount(response.usage?.inputTokens);
9105
+ const actualOutputTokens = validTokenCount(response.usage?.outputTokens);
9106
+ const receipt = recordTokenLedger(this.session, {
9107
+ requestId: userMessage.id,
9108
+ turn,
9109
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
9110
+ estimated: {
9111
+ ...breakdown,
9112
+ outputTokens: estimateResponseTokens(response)
9113
+ },
9114
+ actual: {
9115
+ ...actualInputTokens === void 0 ? {} : { inputTokens: actualInputTokens },
9116
+ ...actualOutputTokens === void 0 ? {} : { outputTokens: actualOutputTokens }
9117
+ },
9118
+ inputSource: actualInputTokens === void 0 ? "estimated" : "actual",
9119
+ outputSource: actualOutputTokens === void 0 ? "estimated" : "actual",
9120
+ tools: { loaded: visibleTools.map((tool) => tool.name), deferredCount: 0 },
9121
+ retrieval: tokenRetrievalReceipt(packed)
9122
+ });
9123
+ await emit({
9124
+ type: "usage",
9125
+ inputTokens: this.session.usage.inputTokens,
9126
+ outputTokens: this.session.usage.outputTokens,
9127
+ source: this.session.usage.source ?? "unknown",
9128
+ inputSource: this.session.usage.inputSource ?? "unknown",
9129
+ outputSource: this.session.usage.outputSource ?? "unknown",
9130
+ actual: {
9131
+ inputTokens: this.session.usage.actualInputTokens ?? 0,
9132
+ outputTokens: this.session.usage.actualOutputTokens ?? 0
9133
+ },
9134
+ estimated: {
9135
+ inputTokens: this.session.usage.estimatedInputTokens ?? 0,
9136
+ outputTokens: this.session.usage.estimatedOutputTokens ?? 0
9137
+ },
9138
+ receipt
9139
+ });
8916
9140
  await this.persist();
8917
9141
  if (this.session.usage.inputTokens + this.session.usage.outputTokens >= this.config.agent.maxSessionTokens) {
8918
9142
  for (const call of response.toolCalls) {
@@ -9291,8 +9515,8 @@ ${completeContent}`;
9291
9515
  toolCalls: toolCalls.map((call) => ({ id: call.id, name: call.name }))
9292
9516
  }, signal);
9293
9517
  }
9294
- async packContext(input2) {
9295
- return this.contextEngine.pack(input2);
9518
+ async packContext(input2, options) {
9519
+ return this.contextEngine.pack(input2, options);
9296
9520
  }
9297
9521
  async packMentions(input2) {
9298
9522
  try {
@@ -9410,14 +9634,14 @@ function packConversation(systemPrompt, dynamicPrompt, retrievedContext, history
9410
9634
  const system = message("system", systemPrompt);
9411
9635
  const dynamic = dynamicPrompt ? message("system", dynamicPrompt) : void 0;
9412
9636
  const context = retrievedContext ? message("system", retrievedContext) : void 0;
9413
- const reserved = estimateTokens4(system.content) + estimateTokens4(dynamic?.content ?? "") + estimateTokens4(context?.content ?? "");
9637
+ const reserved = estimateTokens(system.content) + estimateTokens(dynamic?.content ?? "") + estimateTokens(context?.content ?? "");
9414
9638
  const budget = Math.max(4e3, tokenBudget - reserved);
9415
9639
  const groups = groupMessages(clearOldToolResults(history));
9416
9640
  const selected = [];
9417
9641
  let used = 0;
9418
9642
  for (let index = groups.length - 1; index >= 0; index -= 1) {
9419
9643
  const group = groups[index] ?? [];
9420
- const cost = group.reduce((sum, item) => sum + estimateTokens4(item.content) + estimateTokens4(JSON.stringify(item.toolCalls ?? [])), 0);
9644
+ const cost = group.reduce((sum, item) => sum + estimateTokens(item.content) + estimateTokens(JSON.stringify(item.toolCalls ?? [])), 0);
9421
9645
  if (selected.length && used + cost > budget) break;
9422
9646
  selected.unshift(group);
9423
9647
  used += cost;
@@ -9455,17 +9679,114 @@ function groupMessages(messages) {
9455
9679
  }
9456
9680
  return groups;
9457
9681
  }
9458
- function estimateTokens4(input2) {
9459
- return Math.ceil(input2.length / 4);
9460
- }
9461
9682
  function estimateMessages2(messages) {
9462
- return messages.reduce((total, item) => total + estimateTokens4(item.content) + estimateTokens4(JSON.stringify(item.toolCalls ?? [])), 0);
9683
+ return messages.reduce((total, item) => total + estimateTokens(item.content) + estimateTokens(JSON.stringify(item.toolCalls ?? [])), 0);
9463
9684
  }
9464
9685
  function estimateToolDefinitions(tools) {
9465
- return estimateTokens4(JSON.stringify(tools));
9686
+ return estimateTokens(JSON.stringify(tools));
9466
9687
  }
9467
9688
  function estimateResponseTokens(response) {
9468
- return estimateTokens4(response.content) + estimateTokens4(JSON.stringify(response.toolCalls));
9689
+ return estimateTokens(response.content) + estimateTokens(JSON.stringify(response.toolCalls));
9690
+ }
9691
+ function promptTokenBreakdown(messages, stablePrompt, dynamicPrompt, retrievedContext, tools, outputAllowanceTokens) {
9692
+ const stableTokens = estimateTokens(stablePrompt);
9693
+ const dynamicTokens = estimateTokens(dynamicPrompt);
9694
+ const retrievedTokens = estimateTokens(retrievedContext);
9695
+ const messageTokens = estimateMessages2(messages);
9696
+ const toolSchemaTokens = estimateToolDefinitions(tools);
9697
+ const toolResultTokens = messages.filter((message2) => message2.role === "tool").reduce((total, message2) => total + estimateTokens(message2.content), 0);
9698
+ return {
9699
+ stableTokens,
9700
+ dynamicTokens,
9701
+ conversationTokens: Math.max(0, messageTokens - stableTokens - dynamicTokens - retrievedTokens - toolResultTokens),
9702
+ toolResultTokens,
9703
+ retrievedTokens,
9704
+ toolSchemaTokens,
9705
+ estimatedInputTokens: messageTokens + toolSchemaTokens,
9706
+ outputAllowanceTokens
9707
+ };
9708
+ }
9709
+ function tokenRetrievalReceipt(packed) {
9710
+ const discarded = [];
9711
+ if ((packed.duplicateHits ?? 0) > 0) {
9712
+ discarded.push({ reason: "overlapping-span", count: packed.duplicateHits ?? 0 });
9713
+ }
9714
+ if (packed.truncated) discarded.push({ reason: "budget-cap", count: 1 });
9715
+ return {
9716
+ engine: packed.engine,
9717
+ ...packed.budgetTier ? { budgetTier: packed.budgetTier } : {},
9718
+ ...packed.budgetTokens === void 0 ? {} : { budgetTokens: packed.budgetTokens },
9719
+ ...packed.candidateHits === void 0 ? {} : { candidateHits: packed.candidateHits },
9720
+ ...packed.selectedHits === void 0 ? {} : { selectedHits: packed.selectedHits },
9721
+ ...packed.duplicateHits === void 0 ? {} : { duplicateHits: packed.duplicateHits },
9722
+ ...packed.incrementalEvidenceTokens === void 0 ? {} : { incrementalEvidenceTokens: packed.incrementalEvidenceTokens },
9723
+ discarded
9724
+ };
9725
+ }
9726
+ function recordTokenLedger(session, entry) {
9727
+ const ledger = session.tokenLedger ?? (session.tokenLedger = []);
9728
+ ledger.push(entry);
9729
+ if (ledger.length > 256) ledger.splice(0, ledger.length - 256);
9730
+ return entry;
9731
+ }
9732
+ function recordTokenUsage(session, providerUsage, estimatedInputTokens, estimatedOutputTokens) {
9733
+ const inputActual = validTokenCount(providerUsage?.inputTokens);
9734
+ const outputActual = validTokenCount(providerUsage?.outputTokens);
9735
+ const priorInputSource = existingMeasurementSource(session, "input");
9736
+ const priorOutputSource = existingMeasurementSource(session, "output");
9737
+ const inputTokens = inputActual ?? estimatedInputTokens;
9738
+ const outputTokens = outputActual ?? estimatedOutputTokens;
9739
+ session.usage.inputTokens += inputTokens;
9740
+ session.usage.outputTokens += outputTokens;
9741
+ if (inputActual !== void 0) {
9742
+ session.usage.actualInputTokens = (session.usage.actualInputTokens ?? 0) + inputActual;
9743
+ } else {
9744
+ session.usage.estimatedInputTokens = (session.usage.estimatedInputTokens ?? 0) + inputTokens;
9745
+ }
9746
+ if (outputActual !== void 0) {
9747
+ session.usage.actualOutputTokens = (session.usage.actualOutputTokens ?? 0) + outputActual;
9748
+ } else {
9749
+ session.usage.estimatedOutputTokens = (session.usage.estimatedOutputTokens ?? 0) + outputTokens;
9750
+ }
9751
+ session.usage.inputSource = mergeMeasurementSource(
9752
+ priorInputSource,
9753
+ inputActual === void 0 ? "estimated" : "actual",
9754
+ session.usage.inputTokens
9755
+ );
9756
+ session.usage.outputSource = mergeMeasurementSource(
9757
+ priorOutputSource,
9758
+ outputActual === void 0 ? "estimated" : "actual",
9759
+ session.usage.outputTokens
9760
+ );
9761
+ session.usage.source = combineMeasurementSources(
9762
+ session.usage.inputSource,
9763
+ session.usage.outputSource,
9764
+ session.usage.inputTokens,
9765
+ session.usage.outputTokens
9766
+ );
9767
+ return { inputTokens, outputTokens };
9768
+ }
9769
+ function validTokenCount(value) {
9770
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
9771
+ }
9772
+ function existingMeasurementSource(session, channel) {
9773
+ const source = channel === "input" ? session.usage.inputSource : session.usage.outputSource;
9774
+ if (source) return source;
9775
+ const total = channel === "input" ? session.usage.inputTokens : session.usage.outputTokens;
9776
+ return total > 0 ? "unknown" : void 0;
9777
+ }
9778
+ function mergeMeasurementSource(previous, current, total) {
9779
+ if (total === 0 && !previous) return current;
9780
+ if (!previous) return current;
9781
+ return previous === current ? current : "mixed";
9782
+ }
9783
+ function combineMeasurementSources(input2, output2, inputTokens, outputTokens) {
9784
+ const active = [
9785
+ ...inputTokens > 0 ? [input2] : [],
9786
+ ...outputTokens > 0 ? [output2] : []
9787
+ ];
9788
+ if (!active.length) return "unknown";
9789
+ return active.every((source) => source === active[0]) ? active[0] ?? "unknown" : "mixed";
9469
9790
  }
9470
9791
  function uniqueCategories(categories) {
9471
9792
  return [...new Set(categories)];
@@ -12136,9 +12457,10 @@ var HeadlessReporter = class {
12136
12457
  }
12137
12458
  if (!this.options.quiet && !this.options.compact) {
12138
12459
  const usage = session.usage.inputTokens + session.usage.outputTokens;
12460
+ const usageLabel = tokenUsageLabel(session.usage);
12139
12461
  process.stderr.write(this.paint.dim(
12140
12462
  `
12141
- ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.separator} ${usage.toLocaleString()} tokens ${this.glyphs.separator} session ${session.id.slice(0, 8)}
12463
+ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.separator} ${usage.toLocaleString()} tokens (${usageLabel}) ${this.glyphs.separator} session ${session.id.slice(0, 8)}
12142
12464
  `
12143
12465
  ));
12144
12466
  }
@@ -12162,16 +12484,19 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
12162
12484
  if (!compact2) process.stderr.write(this.paint.dim(`${this.glyphs.meta} reasoning ${this.glyphs.separator} turn ${event.turn}
12163
12485
  `));
12164
12486
  break;
12165
- case "context":
12487
+ case "context": {
12488
+ const budget = event.packed.budgetTokens === void 0 ? "" : ` ${this.glyphs.separator} ${event.packed.budgetTier ?? "adaptive"} ${event.packed.budgetTokens} budget`;
12166
12489
  process.stderr.write(this.paint.cyan(
12167
- `${this.glyphs.meta} context ${this.glyphs.separator} ${event.packed.engine} ${this.glyphs.separator} ${event.packed.hits.length} spans ${this.glyphs.separator} ~${event.packed.estimatedTokens} tokens${event.packed.degradation ? ` ${this.glyphs.separator} ${event.packed.degradation.summary}` : ""}
12490
+ `${this.glyphs.meta} context ${this.glyphs.separator} ${event.packed.engine} ${this.glyphs.separator} ${event.packed.hits.length} spans ${this.glyphs.separator} ~${event.packed.estimatedTokens} tokens${budget}${event.packed.degradation ? ` ${this.glyphs.separator} ${event.packed.degradation.summary}` : ""}
12168
12491
  `
12169
12492
  ));
12170
12493
  break;
12494
+ }
12171
12495
  case "prompt":
12172
12496
  if (!compact2) {
12497
+ const partition = event.breakdown ? ` ${this.glyphs.separator} stable ${event.breakdown.stableTokens} ${this.glyphs.separator} dynamic ${event.breakdown.dynamicTokens} ${this.glyphs.separator} history ${event.breakdown.conversationTokens} ${this.glyphs.separator} tool results ${event.breakdown.toolResultTokens} ${this.glyphs.separator} retrieved ${event.breakdown.retrievedTokens} ${this.glyphs.separator} tools ${event.breakdown.toolSchemaTokens} ${this.glyphs.separator} output cap ${event.breakdown.outputAllowanceTokens}` : "";
12173
12498
  process.stderr.write(this.paint.dim(
12174
- `${this.glyphs.meta} prompt ${this.glyphs.separator} ${event.intent} ${this.glyphs.separator} ${event.sections.join(" + ")} ${this.glyphs.separator} ~${event.estimatedTokens} tokens
12499
+ `${this.glyphs.meta} prompt ${this.glyphs.separator} ${event.intent} ${this.glyphs.separator} ~${event.estimatedTokens} estimated tokens${partition}
12175
12500
  `
12176
12501
  ));
12177
12502
  }
@@ -12303,9 +12628,14 @@ function sessionSummary(session) {
12303
12628
  ...session.taskContract ? { taskContract: session.taskContract } : {},
12304
12629
  changedFiles: session.changedFiles,
12305
12630
  ...session.lastRun ? { lastRun: session.lastRun } : {},
12631
+ ...session.tokenLedger?.length ? { tokenLedger: session.tokenLedger } : {},
12306
12632
  usage: session.usage
12307
12633
  };
12308
12634
  }
12635
+ function tokenUsageLabel(usage) {
12636
+ if (usage.source) return usage.source;
12637
+ return usage.inputTokens + usage.outputTokens > 0 ? "unknown source" : "no usage";
12638
+ }
12309
12639
  function formatToolDetail(call, paint = chalk2, glyphs = resolveCliGlyphs()) {
12310
12640
  const environment = call.name === "shell" && typeof call.arguments.env === "object" && call.arguments.env !== null && !Array.isArray(call.arguments.env) ? Object.keys(call.arguments.env).sort() : [];
12311
12641
  const environmentDetail = environment.length ? ` ${glyphs.separator} env ${environment.join(", ")}` : "";
@@ -13030,10 +13360,11 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
13030
13360
  width,
13031
13361
  glyph: glyphs.context,
13032
13362
  label: "context",
13033
- detail: `${sanitizeInlineTerminalText(item.engine)} ${glyphs.separator} ${item.hits} spans ${glyphs.separator} ~${formatTokens(item.tokens)}${item.truncated ? ` ${glyphs.separator} truncated` : ""}`,
13363
+ detail: `${sanitizeInlineTerminalText(item.engine)} ${glyphs.separator} ${item.hits} spans ${glyphs.separator} ~${formatTokens(item.tokens)} estimated${item.budgetTokens === void 0 ? "" : ` ${glyphs.separator} ${item.budgetTier ?? "adaptive"} ${formatTokens(item.budgetTokens)} budget`}${item.truncated ? ` ${glyphs.separator} truncated` : ""}`,
13034
13364
  labelColor: theme.accent
13035
13365
  }
13036
13366
  ),
13367
+ !compact2 && item.budgetReason ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`${glyphs.branchLast} ${sanitizeInlineTerminalText(item.budgetReason)}`, innerWidth) }) : null,
13037
13368
  spans.slice(0, spanLimit).map((span, spanIndex) => {
13038
13369
  const lines = span.startLine === span.endLine ? `${span.startLine}` : `${span.startLine}-${span.endLine}`;
13039
13370
  const location = `${compactDisplayPath(sanitizeInlineTerminalText(span.path), 44)}:${lines}`;
@@ -13055,13 +13386,14 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
13055
13386
  ] }, item.id);
13056
13387
  }
13057
13388
  if (item.kind === "prompt") {
13389
+ const detail = item.breakdown ? `~${formatTokens(item.tokens)} estimated ${glyphs.separator} stable ${formatTokens(item.breakdown.stableTokens)} ${glyphs.separator} dynamic ${formatTokens(item.breakdown.dynamicTokens)} ${glyphs.separator} history ${formatTokens(item.breakdown.conversationTokens)} ${glyphs.separator} tool results ${formatTokens(item.breakdown.toolResultTokens)} ${glyphs.separator} retrieved ${formatTokens(item.breakdown.retrievedTokens)} ${glyphs.separator} tools ${formatTokens(item.breakdown.toolSchemaTokens)} ${glyphs.separator} output cap ${formatTokens(item.breakdown.outputAllowanceTokens)}` : `${item.sections.map(sanitizeInlineTerminalText).join(` ${glyphs.separator} `)} ${glyphs.separator} ~${formatTokens(item.tokens)} estimated`;
13058
13390
  return /* @__PURE__ */ jsx(
13059
13391
  MetaRow,
13060
13392
  {
13061
13393
  width,
13062
13394
  glyph: glyphs.pending,
13063
13395
  label: `prompt/${sanitizeInlineTerminalText(item.intent)}`,
13064
- detail: `${item.sections.map(sanitizeInlineTerminalText).join(` ${glyphs.separator} `)} ${glyphs.separator} ~${formatTokens(item.tokens)}`
13396
+ detail
13065
13397
  },
13066
13398
  item.id
13067
13399
  );
@@ -15176,6 +15508,9 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
15176
15508
  engine: event.packed.engine,
15177
15509
  hits: event.packed.hits.length,
15178
15510
  tokens: event.packed.estimatedTokens,
15511
+ ...event.packed.budgetTier ? { budgetTier: event.packed.budgetTier } : {},
15512
+ ...event.packed.budgetTokens !== void 0 ? { budgetTokens: event.packed.budgetTokens } : {},
15513
+ ...event.packed.budgetReason ? { budgetReason: event.packed.budgetReason } : {},
15179
15514
  truncated: event.packed.truncated,
15180
15515
  spans: event.packed.hits.slice(0, 5).map((hit) => ({
15181
15516
  path: relative8(runner.workspace.primaryRoot, hit.path) || hit.path,
@@ -15189,7 +15524,14 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
15189
15524
  setActivity({ label: "Assembling relevant context", startedAt: Date.now() });
15190
15525
  break;
15191
15526
  case "prompt":
15192
- append({ id: nextId(), kind: "prompt", intent: event.intent, sections: event.sections, tokens: event.estimatedTokens });
15527
+ append({
15528
+ id: nextId(),
15529
+ kind: "prompt",
15530
+ intent: event.intent,
15531
+ sections: event.sections,
15532
+ tokens: event.estimatedTokens,
15533
+ ...event.breakdown ? { breakdown: event.breakdown } : {}
15534
+ });
15193
15535
  setActivity({ label: "Preparing the model prompt", startedAt: Date.now() });
15194
15536
  break;
15195
15537
  case "assistant_delta":
@@ -15736,7 +16078,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
15736
16078
  { label: config.agents?.enabled ? `${config.agents.maxConcurrent} concurrent` : "disabled", detail: "expert delegation" },
15737
16079
  {
15738
16080
  label: `${usage.inputTokens.toLocaleString()} in ${separator} ${usage.outputTokens.toLocaleString()} out`,
15739
- detail: `session tokens${separator}${(usage.inputTokens + usage.outputTokens).toLocaleString()} total`
16081
+ detail: `session tokens${separator}${(usage.inputTokens + usage.outputTokens).toLocaleString()} total${separator}${usage.source ?? "unknown source"}`
15740
16082
  },
15741
16083
  {
15742
16084
  label: `${Math.round(status.pressure * 100)}% context pressure`,
@@ -18871,7 +19213,7 @@ program.command("context").description("Pack task-oriented context under a token
18871
19213
 
18872
19214
  `);
18873
19215
  process.stderr.write(chalk4.dim(
18874
- `${cliGlyphs.meta} ${packed.engine} ${cliGlyphs.separator} ${packed.hits.length} spans ${cliGlyphs.separator} ~${packed.estimatedTokens} tokens${packed.truncated ? ` ${cliGlyphs.separator} capped` : ""}${packed.degradation ? ` ${cliGlyphs.separator} ${packed.degradation.summary}` : ""}
19216
+ `${cliGlyphs.meta} ${packed.engine} ${cliGlyphs.separator} ${packed.hits.length} spans ${cliGlyphs.separator} ~${packed.estimatedTokens} estimated tokens${packed.budgetTokens === void 0 ? "" : ` ${cliGlyphs.separator} ${packed.budgetTier ?? "adaptive"} ${packed.budgetTokens} budget`}${packed.truncated ? ` ${cliGlyphs.separator} capped` : ""}${packed.degradation ? ` ${cliGlyphs.separator} ${packed.degradation.summary}` : ""}
18875
19217
  `
18876
19218
  ));
18877
19219
  });