@skein-code/cli 0.3.10 → 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
@@ -4,7 +4,7 @@
4
4
  import { createInterface as createInterface2 } from "node:readline/promises";
5
5
  import { stdin as input, stdout as output } from "node:process";
6
6
  import { writeFile as writeFile3 } from "node:fs/promises";
7
- import { basename as basename12, resolve as resolve22 } from "node:path";
7
+ import { basename as basename12, resolve as resolve23 } from "node:path";
8
8
  import { Command, Option } from "commander";
9
9
  import chalk4 from "chalk";
10
10
 
@@ -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.10",
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
- "Complex executable requests now use a durable Task Contract with evidence-bound acceptance criteria",
240
- "Completion stays unverified while required acceptance or final verification remains unresolved",
241
- "Structured tool failure receipts add concise repair hints, retry budgets, cancellation handling, and identical-call circuit breaking"
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: {
@@ -990,16 +990,16 @@ async function hashRegularFile(path) {
990
990
  try {
991
991
  const info = await handle.stat();
992
992
  if (!info.isFile()) throw new Error(`Namespace entry is not a regular file: ${path}`);
993
- const hash = createHash2("sha256");
993
+ const hash2 = createHash2("sha256");
994
994
  const buffer = Buffer.allocUnsafe(64 * 1024);
995
995
  let position = 0;
996
996
  while (true) {
997
997
  const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
998
998
  if (!bytesRead) break;
999
- hash.update(buffer.subarray(0, bytesRead));
999
+ hash2.update(buffer.subarray(0, bytesRead));
1000
1000
  position += bytesRead;
1001
1001
  }
1002
- return { sha256: hash.digest("hex"), size: position };
1002
+ return { sha256: hash2.digest("hex"), size: position };
1003
1003
  } finally {
1004
1004
  await handle.close();
1005
1005
  }
@@ -1200,18 +1200,18 @@ var MemoryStore = class {
1200
1200
  input2.lastVerifiedAt ?? (explicit ? now : void 0),
1201
1201
  "Memory verification time"
1202
1202
  );
1203
- const hash = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
1203
+ const hash2 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
1204
1204
  database.exec("BEGIN IMMEDIATE");
1205
1205
  try {
1206
1206
  const existing = database.prepare(
1207
1207
  "SELECT * FROM memories WHERE content_hash = ?"
1208
- ).get(hash);
1208
+ ).get(hash2);
1209
1209
  const conflicting = conflictKey ? database.prepare(`
1210
1210
  SELECT * FROM memories
1211
1211
  WHERE scope = ? AND scope_key = ? AND conflict_key = ?
1212
1212
  AND status = 'active' AND content_hash <> ?
1213
1213
  ORDER BY updated_at DESC
1214
- `).all(input2.scope, scopeKey2, conflictKey, hash) : [];
1214
+ `).all(input2.scope, scopeKey2, conflictKey, hash2) : [];
1215
1215
  const supersedesId = normalizeOptional(input2.supersedesId, 80) ?? conflicting[0]?.id;
1216
1216
  let id;
1217
1217
  if (existing) {
@@ -1256,7 +1256,7 @@ var MemoryStore = class {
1256
1256
  importance,
1257
1257
  confidence,
1258
1258
  source,
1259
- hash,
1259
+ hash2,
1260
1260
  now,
1261
1261
  now,
1262
1262
  now,
@@ -1359,10 +1359,10 @@ var MemoryStore = class {
1359
1359
  const conflictKey = normalizeOptional(input2.conflictKey, 240);
1360
1360
  const candidateDefaultExpiry = !input2.expiresAt ? new Date(Date.now() + MEMORY_CANDIDATE_TTL_MS).toISOString() : void 0;
1361
1361
  const expiresAt = normalizeTimestamp(input2.expiresAt ?? candidateDefaultExpiry, "Memory expiration");
1362
- const hash = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
1362
+ const hash2 = createHash3("sha256").update(`${input2.scope}\0${scopeKey2}\0${content.toLocaleLowerCase()}`).digest("hex");
1363
1363
  const existing = database.prepare(
1364
1364
  "SELECT * FROM memory_candidates WHERE content_hash = ?"
1365
- ).get(hash);
1365
+ ).get(hash2);
1366
1366
  const now = (/* @__PURE__ */ new Date()).toISOString();
1367
1367
  if (existing) {
1368
1368
  if (existing.status === "approved" && existing.approved_memory_id) {
@@ -1409,7 +1409,7 @@ var MemoryStore = class {
1409
1409
  confidence,
1410
1410
  source,
1411
1411
  rationale,
1412
- hash,
1412
+ hash2,
1413
1413
  now,
1414
1414
  now,
1415
1415
  revision ?? null,
@@ -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
@@ -4755,7 +4939,7 @@ function executableNames(command2) {
4755
4939
  return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
4756
4940
  }
4757
4941
  function runProcess(command2, args, options) {
4758
- return new Promise((resolve23, reject) => {
4942
+ return new Promise((resolve24, reject) => {
4759
4943
  const started = Date.now();
4760
4944
  const environment = options.inheritEnv === false ? {} : { ...process.env };
4761
4945
  for (const name of options.unsetEnv ?? []) delete environment[name];
@@ -4776,6 +4960,8 @@ function runProcess(command2, args, options) {
4776
4960
  let stderr = "";
4777
4961
  let stdoutBytes = 0;
4778
4962
  let stderrBytes = 0;
4963
+ let stdoutObservedBytes = 0;
4964
+ let stderrObservedBytes = 0;
4779
4965
  let timedOut = false;
4780
4966
  let callbackError;
4781
4967
  const stdoutDecoder = new StringDecoder("utf8");
@@ -4798,12 +4984,14 @@ function runProcess(command2, args, options) {
4798
4984
  }
4799
4985
  };
4800
4986
  child.stdout.on("data", (chunk) => {
4987
+ stdoutObservedBytes += chunk.length;
4801
4988
  const appended = append(stdoutDecoder, chunk, stdoutBytes);
4802
4989
  stdout += appended.text;
4803
4990
  stdoutBytes = appended.usedBytes;
4804
4991
  notify(options.onStdout, stdoutCallbackDecoder, chunk);
4805
4992
  });
4806
4993
  child.stderr.on("data", (chunk) => {
4994
+ stderrObservedBytes += chunk.length;
4807
4995
  const appended = append(stderrDecoder, chunk, stderrBytes);
4808
4996
  stderr += appended.text;
4809
4997
  stderrBytes = appended.usedBytes;
@@ -4834,13 +5022,17 @@ function runProcess(command2, args, options) {
4834
5022
  reject(callbackError);
4835
5023
  return;
4836
5024
  }
4837
- resolve23({
5025
+ resolve24({
4838
5026
  command: [command2, ...args].join(" "),
4839
5027
  exitCode: code ?? (timedOut ? 124 : 1),
4840
5028
  stdout,
4841
5029
  stderr,
4842
5030
  timedOut,
4843
- durationMs: Date.now() - started
5031
+ durationMs: Date.now() - started,
5032
+ stdoutBytes: stdoutObservedBytes,
5033
+ stderrBytes: stderrObservedBytes,
5034
+ stdoutTruncated: stdoutObservedBytes > stdoutBytes,
5035
+ stderrTruncated: stderrObservedBytes > stderrBytes
4844
5036
  });
4845
5037
  });
4846
5038
  if (options.stdin) child.stdin.end(options.stdin);
@@ -4961,6 +5153,14 @@ var contextSourceSchema = z5.object({
4961
5153
  tokens: z5.number().int().nonnegative(),
4962
5154
  addedAt: z5.string()
4963
5155
  }).strict();
5156
+ var toolArtifactSchema = z5.object({
5157
+ toolCallId: z5.string().min(1).max(512).refine((value) => !/[\u0000-\u001f\u007f-\u009f]/u.test(value)),
5158
+ sha256: z5.string().regex(/^[a-f0-9]{64}$/u),
5159
+ bytes: z5.number().int().nonnegative().max(5 * 1024 * 1024),
5160
+ createdAt: z5.string().datetime(),
5161
+ expiresAt: z5.string().datetime(),
5162
+ redacted: z5.boolean()
5163
+ }).strict();
4964
5164
  var verificationEvidenceSchema = z5.object({
4965
5165
  toolCallId: z5.string(),
4966
5166
  tool: z5.enum(["shell", "git"]),
@@ -5037,12 +5237,59 @@ var sessionSchema = z5.object({
5037
5237
  compactedThroughMessageId: z5.string().optional(),
5038
5238
  workingMemory: workingMemorySchema.optional(),
5039
5239
  contextSources: z5.array(contextSourceSchema).max(64).optional(),
5240
+ toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
5040
5241
  taskContract: taskContractSchema.optional(),
5041
5242
  lastRun: lastRunSchema.optional(),
5042
5243
  usage: z5.object({
5043
5244
  inputTokens: z5.number().nonnegative(),
5044
- outputTokens: z5.number().nonnegative()
5045
- }).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()
5046
5293
  }).strict();
5047
5294
  var SessionStore = class {
5048
5295
  workspace;
@@ -5298,13 +5545,390 @@ async function exists2(path) {
5298
5545
  }
5299
5546
  }
5300
5547
 
5548
+ // src/session/tool-artifacts.ts
5549
+ import { createHash as createHash7 } from "node:crypto";
5550
+ import { lstat as lstat12, readFile as readFile7, readdir as readdir4, rm as rm2 } from "node:fs/promises";
5551
+ import { join as join11, resolve as resolve13 } from "node:path";
5552
+ import { z as z6 } from "zod";
5553
+ var MAX_ARTIFACT_BYTES = 5 * 1024 * 1024;
5554
+ var MAX_TOTAL_BYTES = 20 * 1024 * 1024;
5555
+ var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
5556
+ var sessionIdSchema2 = z6.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/);
5557
+ var toolCallIdSchema = z6.string().min(1).max(512).refine((value) => !/[\u0000-\u001f\u007f-\u009f]/u.test(value), {
5558
+ message: "Tool call id cannot contain control characters."
5559
+ });
5560
+ var artifactSchema = z6.object({
5561
+ version: z6.literal(1),
5562
+ sessionId: sessionIdSchema2,
5563
+ toolCallId: toolCallIdSchema,
5564
+ sha256: z6.string().regex(/^[a-f0-9]{64}$/u),
5565
+ bytes: z6.number().int().nonnegative().max(MAX_ARTIFACT_BYTES),
5566
+ createdAt: z6.string().datetime(),
5567
+ expiresAt: z6.string().datetime(),
5568
+ redacted: z6.boolean(),
5569
+ content: z6.string()
5570
+ }).strict();
5571
+ var ToolArtifactStore = class {
5572
+ workspace;
5573
+ directory;
5574
+ managedDirectory;
5575
+ now;
5576
+ maxArtifactBytes;
5577
+ maxTotalBytes;
5578
+ retentionMs;
5579
+ writes = Promise.resolve();
5580
+ constructor(workspace, options = {}) {
5581
+ this.workspace = resolve13(workspace);
5582
+ this.managedDirectory = options.directory === void 0;
5583
+ this.directory = options.directory ? resolve13(options.directory) : join11(resolveProjectNamespaceSync(this.workspace).active, "tool-artifacts");
5584
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
5585
+ this.maxArtifactBytes = options.maxArtifactBytes ?? MAX_ARTIFACT_BYTES;
5586
+ this.maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
5587
+ this.retentionMs = options.retentionMs ?? RETENTION_MS;
5588
+ if (!Number.isSafeInteger(this.maxArtifactBytes) || this.maxArtifactBytes < 1 || this.maxArtifactBytes > MAX_ARTIFACT_BYTES) {
5589
+ throw new Error(`maxArtifactBytes must be between 1 and ${MAX_ARTIFACT_BYTES}.`);
5590
+ }
5591
+ if (!Number.isSafeInteger(this.maxTotalBytes) || this.maxTotalBytes < this.maxArtifactBytes) {
5592
+ throw new Error("maxTotalBytes must be at least maxArtifactBytes.");
5593
+ }
5594
+ if (!Number.isSafeInteger(this.retentionMs) || this.retentionMs < 1) {
5595
+ throw new Error("retentionMs must be a positive integer.");
5596
+ }
5597
+ }
5598
+ async archive(sessionId, toolCallId, content, options) {
5599
+ sessionIdSchema2.parse(sessionId);
5600
+ toolCallIdSchema.parse(toolCallId);
5601
+ const bytes = Buffer.byteLength(content);
5602
+ if (bytes > this.maxArtifactBytes) return { stored: false, reason: "too_large" };
5603
+ return this.enqueue(() => this.withManagedLease(async () => {
5604
+ await this.ensureDirectory();
5605
+ const now = this.now();
5606
+ const artifacts = await this.loadArtifacts();
5607
+ await this.removeExpired(artifacts, now);
5608
+ const active = artifacts.filter((artifact2) => artifact2.expiresAt > now.toISOString());
5609
+ const sha256 = hash(content);
5610
+ const existing = active.find((artifact2) => artifact2.sessionId === sessionId && artifact2.toolCallId === toolCallId);
5611
+ if (existing) {
5612
+ if (existing.sha256 === sha256 && existing.bytes === bytes && existing.redacted === options.redacted) {
5613
+ return { stored: true, artifact: reference(existing) };
5614
+ }
5615
+ return { stored: false, reason: "conflict" };
5616
+ }
5617
+ const artifact = {
5618
+ version: 1,
5619
+ sessionId,
5620
+ toolCallId,
5621
+ sha256,
5622
+ bytes,
5623
+ createdAt: now.toISOString(),
5624
+ expiresAt: new Date(now.getTime() + this.retentionMs).toISOString(),
5625
+ redacted: options.redacted,
5626
+ content
5627
+ };
5628
+ const storageBytes = storedBytes(artifact);
5629
+ let retainedBytes = active.reduce((total, retained) => total + storedBytes(retained), 0);
5630
+ const evictable = active.slice().sort(
5631
+ (left, right) => left.createdAt.localeCompare(right.createdAt)
5632
+ );
5633
+ while (retainedBytes + storageBytes > this.maxTotalBytes && evictable.length) {
5634
+ const oldest = evictable.shift();
5635
+ await this.removeArtifact(oldest);
5636
+ retainedBytes -= storedBytes(oldest);
5637
+ }
5638
+ if (retainedBytes + storageBytes > this.maxTotalBytes) return { stored: false, reason: "total_limit" };
5639
+ const path = this.pathFor(sessionId, toolCallId);
5640
+ await ensureWorkspaceStorageDirectory(this.workspace, resolve13(path, ".."), {
5641
+ requireActiveNamespace: this.managedDirectory
5642
+ });
5643
+ await atomicWrite(path, `${JSON.stringify(artifact)}
5644
+ `, 384);
5645
+ return { stored: true, artifact: reference(artifact) };
5646
+ }));
5647
+ }
5648
+ async read(sessionId, toolCallId, options = {}) {
5649
+ sessionIdSchema2.parse(sessionId);
5650
+ toolCallIdSchema.parse(toolCallId);
5651
+ const startLine = options.startLine ?? 1;
5652
+ const startByte = options.startByte;
5653
+ const maxLines = options.maxLines ?? 200;
5654
+ const maxBytes = options.maxBytes ?? 3e3;
5655
+ if (startByte !== void 0 && options.startLine !== void 0) {
5656
+ throw new Error("Use either startLine or startByte, not both.");
5657
+ }
5658
+ if (!Number.isInteger(startLine) || startLine < 1) throw new Error("startLine must be a positive integer.");
5659
+ if (startByte !== void 0 && (!Number.isInteger(startByte) || startByte < 0)) {
5660
+ throw new Error("startByte must be a non-negative integer.");
5661
+ }
5662
+ if (!Number.isInteger(maxLines) || maxLines < 1 || maxLines > 1e3) {
5663
+ throw new Error("maxLines must be between 1 and 1000.");
5664
+ }
5665
+ if (!Number.isInteger(maxBytes) || maxBytes < 256 || maxBytes > 32e3) {
5666
+ throw new Error("maxBytes must be between 256 and 32000.");
5667
+ }
5668
+ await this.writes;
5669
+ return this.withManagedLease(async () => {
5670
+ const artifact = await this.readArtifact(sessionId, toolCallId);
5671
+ const now = this.now();
5672
+ if (artifact.expiresAt <= now.toISOString()) {
5673
+ await this.removeArtifact(artifact);
5674
+ throw new Error("The retained tool output has expired. Re-run the tool if it is still needed.");
5675
+ }
5676
+ const page = startByte === void 0 ? readLinePage(artifact.content, startLine, maxLines, maxBytes) : readBytePage(artifact.content, startByte, maxBytes);
5677
+ return {
5678
+ ...reference(artifact),
5679
+ ...page
5680
+ };
5681
+ });
5682
+ }
5683
+ /** Remove expired output and return the still-valid receipts for one session. */
5684
+ async prune(sessionId) {
5685
+ sessionIdSchema2.parse(sessionId);
5686
+ return this.enqueue(() => this.withManagedLease(async () => {
5687
+ const artifacts = await this.loadArtifacts();
5688
+ const now = this.now();
5689
+ await this.removeExpired(artifacts, now);
5690
+ return artifacts.filter((artifact) => artifact.sessionId === sessionId && artifact.expiresAt > now.toISOString()).sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map(reference);
5691
+ }));
5692
+ }
5693
+ /** Delete every retained result owned by a removed session. */
5694
+ async removeSession(sessionId) {
5695
+ sessionIdSchema2.parse(sessionId);
5696
+ await this.enqueue(() => this.withManagedLease(async () => {
5697
+ if (!await this.directoryAvailable()) return;
5698
+ const directory = this.sessionDirectoryFor(sessionId);
5699
+ try {
5700
+ const info = await lstat12(directory);
5701
+ if (info.isSymbolicLink() || !info.isDirectory()) {
5702
+ throw new Error(`Tool artifact session storage is not a regular directory: ${directory}`);
5703
+ }
5704
+ await assertNoSymlinkPath(this.workspace, directory);
5705
+ } catch (error) {
5706
+ if (error.code === "ENOENT") return;
5707
+ throw error;
5708
+ }
5709
+ await rm2(directory, { recursive: true, force: true });
5710
+ }));
5711
+ }
5712
+ async readArtifact(sessionId, toolCallId) {
5713
+ const path = this.pathFor(sessionId, toolCallId);
5714
+ await this.assertRegularFile(path);
5715
+ let artifact;
5716
+ try {
5717
+ artifact = artifactSchema.parse(JSON.parse(await readFile7(path, "utf8")));
5718
+ } catch {
5719
+ throw new Error("Retained tool output is unreadable or corrupt.");
5720
+ }
5721
+ if (artifact.sessionId !== sessionId || artifact.toolCallId !== toolCallId) {
5722
+ throw new Error("Retained tool output does not belong to this session.");
5723
+ }
5724
+ if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash(artifact.content)) {
5725
+ throw new Error("Retained tool output failed its integrity check.");
5726
+ }
5727
+ return artifact;
5728
+ }
5729
+ async loadArtifacts() {
5730
+ if (!await this.directoryAvailable()) return [];
5731
+ const artifacts = [];
5732
+ for (const entry of await readdir4(this.directory, { withFileTypes: true })) {
5733
+ const sessionDirectory = join11(this.directory, entry.name);
5734
+ if (entry.isSymbolicLink()) throw new Error(`Tool artifact storage contains a symbolic link: ${sessionDirectory}`);
5735
+ if (!entry.isDirectory() || !/^[a-f0-9]{64}$/u.test(entry.name)) continue;
5736
+ await assertNoSymlinkPath(this.workspace, sessionDirectory);
5737
+ for (const file of await readdir4(sessionDirectory, { withFileTypes: true })) {
5738
+ const path = join11(sessionDirectory, file.name);
5739
+ if (file.isSymbolicLink()) throw new Error(`Tool artifact storage contains a symbolic link: ${path}`);
5740
+ if (!file.isFile() || !/^[a-f0-9]{64}\.json$/u.test(file.name)) continue;
5741
+ await this.assertRegularFile(path);
5742
+ try {
5743
+ const artifact = artifactSchema.parse(JSON.parse(await readFile7(path, "utf8")));
5744
+ if (artifact.bytes !== Buffer.byteLength(artifact.content) || artifact.sha256 !== hash(artifact.content)) {
5745
+ throw new Error("integrity");
5746
+ }
5747
+ if (sessionDirectory !== this.sessionDirectoryFor(artifact.sessionId) || path !== this.pathFor(artifact.sessionId, artifact.toolCallId)) {
5748
+ throw new Error("identity");
5749
+ }
5750
+ artifacts.push(artifact);
5751
+ } catch {
5752
+ throw new Error(`Tool artifact is unreadable or corrupt: ${path}`);
5753
+ }
5754
+ }
5755
+ }
5756
+ return artifacts;
5757
+ }
5758
+ async removeExpired(artifacts, now) {
5759
+ for (const artifact of artifacts) {
5760
+ if (artifact.expiresAt <= now.toISOString()) await this.removeArtifact(artifact);
5761
+ }
5762
+ }
5763
+ async removeArtifact(artifact) {
5764
+ await rm2(this.pathFor(artifact.sessionId, artifact.toolCallId), { force: true });
5765
+ }
5766
+ pathFor(sessionId, toolCallId) {
5767
+ return join11(this.sessionDirectoryFor(sessionId), `${hash(`call\0${toolCallId}`)}.json`);
5768
+ }
5769
+ sessionDirectoryFor(sessionId) {
5770
+ return join11(this.directory, hash(`session\0${sessionId}`));
5771
+ }
5772
+ async ensureDirectory() {
5773
+ await ensureWorkspaceStorageDirectory(this.workspace, this.directory, {
5774
+ requireActiveNamespace: this.managedDirectory
5775
+ });
5776
+ }
5777
+ async directoryAvailable() {
5778
+ if (this.managedDirectory) await assertNoSymlinkPath(this.workspace, this.directory);
5779
+ try {
5780
+ const info = await lstat12(this.directory);
5781
+ if (info.isSymbolicLink() || !info.isDirectory()) {
5782
+ throw new Error(`Tool artifact storage is not a regular directory: ${this.directory}`);
5783
+ }
5784
+ return true;
5785
+ } catch (error) {
5786
+ if (error.code === "ENOENT") return false;
5787
+ throw error;
5788
+ }
5789
+ }
5790
+ async assertRegularFile(path) {
5791
+ await assertNoSymlinkPath(this.workspace, resolve13(path, ".."));
5792
+ try {
5793
+ const info = await lstat12(path);
5794
+ if (info.isSymbolicLink() || !info.isFile()) {
5795
+ throw new Error(`Tool artifact cannot be a symbolic link or non-regular file: ${path}`);
5796
+ }
5797
+ } catch (error) {
5798
+ if (error.code === "ENOENT") {
5799
+ throw new Error("No retained tool output matches this tool call in the current session.");
5800
+ }
5801
+ throw error;
5802
+ }
5803
+ }
5804
+ async withManagedLease(operation) {
5805
+ if (!this.managedDirectory) return operation();
5806
+ return withNamespaceLease(projectNamespacePaths(this.workspace).canonical, "shared", async () => {
5807
+ assertActiveProjectNamespacePath(this.workspace, this.directory);
5808
+ return operation();
5809
+ });
5810
+ }
5811
+ async enqueue(operation) {
5812
+ const next = this.writes.then(operation);
5813
+ this.writes = next.then(() => void 0, () => void 0);
5814
+ return next;
5815
+ }
5816
+ };
5817
+ function reference(artifact) {
5818
+ return {
5819
+ toolCallId: artifact.toolCallId,
5820
+ sha256: artifact.sha256,
5821
+ bytes: artifact.bytes,
5822
+ createdAt: artifact.createdAt,
5823
+ expiresAt: artifact.expiresAt,
5824
+ redacted: artifact.redacted
5825
+ };
5826
+ }
5827
+ function hash(value) {
5828
+ return createHash7("sha256").update(value).digest("hex");
5829
+ }
5830
+ function storedBytes(artifact) {
5831
+ return Buffer.byteLength(JSON.stringify(artifact)) + 1;
5832
+ }
5833
+ function readLinePage(content, startLine, maxLines, maxBytes) {
5834
+ const ranges = lineRanges(content);
5835
+ const startIndex = startLine - 1;
5836
+ if (startIndex >= ranges.length) {
5837
+ const totalBytes = Buffer.byteLength(content);
5838
+ return {
5839
+ content: "",
5840
+ startLine,
5841
+ endLine: startLine - 1,
5842
+ totalLines: ranges.length,
5843
+ startByte: totalBytes,
5844
+ endByte: totalBytes,
5845
+ hasMore: false
5846
+ };
5847
+ }
5848
+ const endIndex = Math.min(ranges.length - 1, startIndex + maxLines - 1);
5849
+ const startCharacter = ranges[startIndex]?.start ?? content.length;
5850
+ const endCharacter = ranges[endIndex]?.end ?? content.length;
5851
+ const startByte = Buffer.byteLength(content.slice(0, startCharacter));
5852
+ const selected = Buffer.from(content.slice(startCharacter, endCharacter));
5853
+ if (selected.length > maxBytes) {
5854
+ const page = sliceUtf8Buffer(selected, 0, maxBytes);
5855
+ const pageContent = page.content;
5856
+ return {
5857
+ content: pageContent,
5858
+ startLine,
5859
+ endLine: startLine + countNewlines(pageContent),
5860
+ totalLines: ranges.length,
5861
+ startByte,
5862
+ endByte: startByte + page.bytes,
5863
+ hasMore: true,
5864
+ nextStartByte: startByte + page.bytes
5865
+ };
5866
+ }
5867
+ const endLine = endIndex + 1;
5868
+ const hasMore = endLine < ranges.length;
5869
+ return {
5870
+ content: selected.toString("utf8"),
5871
+ startLine,
5872
+ endLine,
5873
+ totalLines: ranges.length,
5874
+ startByte,
5875
+ endByte: startByte + selected.length,
5876
+ hasMore,
5877
+ ...hasMore ? { nextStartLine: endLine + 1 } : {}
5878
+ };
5879
+ }
5880
+ function readBytePage(content, requestedStart, maxBytes) {
5881
+ const buffer = Buffer.from(content);
5882
+ const startByte = Math.min(requestedStart, buffer.length);
5883
+ if (startByte < buffer.length && isUtf8Continuation(buffer[startByte] ?? 0)) {
5884
+ throw new Error("startByte must be an exact UTF-8 boundary shown by a previous page.");
5885
+ }
5886
+ const page = sliceUtf8Buffer(buffer, startByte, maxBytes);
5887
+ const prefix = buffer.subarray(0, startByte).toString("utf8");
5888
+ const startLine = countNewlines(prefix) + 1;
5889
+ const endByte = startByte + page.bytes;
5890
+ const hasMore = endByte < buffer.length;
5891
+ return {
5892
+ content: page.content,
5893
+ startLine,
5894
+ endLine: startLine + countNewlines(page.content),
5895
+ totalLines: countNewlines(content) + 1,
5896
+ startByte,
5897
+ endByte,
5898
+ hasMore,
5899
+ ...hasMore ? { nextStartByte: endByte } : {}
5900
+ };
5901
+ }
5902
+ function sliceUtf8Buffer(buffer, start, maxBytes) {
5903
+ let end = Math.min(buffer.length, start + maxBytes);
5904
+ while (end > start && isUtf8Continuation(buffer[end] ?? 0)) end -= 1;
5905
+ return { content: buffer.subarray(start, end).toString("utf8"), bytes: end - start };
5906
+ }
5907
+ function lineRanges(content) {
5908
+ const ranges = [];
5909
+ const expression = /\r?\n/gu;
5910
+ let start = 0;
5911
+ for (const match of content.matchAll(expression)) {
5912
+ ranges.push({ start, end: match.index });
5913
+ start = match.index + match[0].length;
5914
+ }
5915
+ ranges.push({ start, end: content.length });
5916
+ return ranges;
5917
+ }
5918
+ function countNewlines(value) {
5919
+ return value.match(/\n/gu)?.length ?? 0;
5920
+ }
5921
+ function isUtf8Continuation(byte) {
5922
+ return byte >= 128 && byte < 192;
5923
+ }
5924
+
5301
5925
  // src/tools/apply-patch.ts
5302
- import { lstat as lstat12, readFile as readFile7, unlink as unlink4 } from "node:fs/promises";
5926
+ import { lstat as lstat13, readFile as readFile8, unlink as unlink4 } from "node:fs/promises";
5303
5927
  import { applyPatch as applyUnifiedPatch, parsePatch } from "diff";
5304
- import { z as z6 } from "zod";
5305
- var inputSchema2 = z6.object({
5306
- patch: z6.string().min(1).max(1e7),
5307
- dry_run: z6.boolean().optional()
5928
+ import { z as z7 } from "zod";
5929
+ var inputSchema2 = z7.object({
5930
+ patch: z7.string().min(1).max(1e7),
5931
+ dry_run: z7.boolean().optional()
5308
5932
  }).strict();
5309
5933
  var applyPatchTool = {
5310
5934
  definition: {
@@ -5560,10 +6184,10 @@ function findSequence(haystack, needle, hint, minimum) {
5560
6184
  }
5561
6185
  async function readSnapshot2(path) {
5562
6186
  try {
5563
- const info = await lstat12(path);
6187
+ const info = await lstat13(path);
5564
6188
  if (info.isSymbolicLink()) throw new Error(`Refusing to patch a symbolic link: ${path}`);
5565
6189
  if (!info.isFile()) throw new Error(`Patch target is not a regular file: ${path}`);
5566
- return { before: await readFile7(path), mode: info.mode };
6190
+ return { before: await readFile8(path), mode: info.mode };
5567
6191
  } catch (error) {
5568
6192
  if (error.code === "ENOENT") return { before: null };
5569
6193
  throw error;
@@ -5612,13 +6236,13 @@ function buffersEqual(left, right) {
5612
6236
  }
5613
6237
 
5614
6238
  // src/tools/git.ts
5615
- import { join as join11 } from "node:path";
5616
- import { z as z7 } from "zod";
5617
- var inputSchema3 = z7.object({
5618
- args: z7.array(z7.string().max(1e4)).min(1).max(200),
5619
- cwd: z7.string().min(1).optional(),
5620
- timeout_ms: z7.number().int().min(100).max(6e5).optional(),
5621
- stdin: z7.string().max(5e6).optional()
6239
+ import { join as join12 } from "node:path";
6240
+ import { z as z8 } from "zod";
6241
+ var inputSchema3 = z8.object({
6242
+ args: z8.array(z8.string().max(1e4)).min(1).max(200),
6243
+ cwd: z8.string().min(1).optional(),
6244
+ timeout_ms: z8.number().int().min(100).max(6e5).optional(),
6245
+ stdin: z8.string().max(5e6).optional()
5622
6246
  }).strict();
5623
6247
  var networkCommands = /* @__PURE__ */ new Set([
5624
6248
  "clone",
@@ -5831,7 +6455,7 @@ var gitTool = {
5831
6455
  for (const candidate of explicit) {
5832
6456
  if (!candidate || candidate.startsWith("-")) continue;
5833
6457
  try {
5834
- paths.add(await context.workspace.resolvePath(join11(cwd, candidate), { allowMissing: true }));
6458
+ paths.add(await context.workspace.resolvePath(join12(cwd, candidate), { allowMissing: true }));
5835
6459
  } catch {
5836
6460
  }
5837
6461
  }
@@ -5852,7 +6476,7 @@ var gitTool = {
5852
6476
  const candidates = candidate.includes(" -> ") ? candidate.split(" -> ") : [candidate];
5853
6477
  for (const value of candidates) {
5854
6478
  try {
5855
- paths.add(await context.workspace.resolvePath(join11(cwd, value), { allowMissing: true }));
6479
+ paths.add(await context.workspace.resolvePath(join12(cwd, value), { allowMissing: true }));
5856
6480
  } catch {
5857
6481
  }
5858
6482
  }
@@ -5933,7 +6557,7 @@ async function collectGitChanges(before, after, runtime, cwd, context) {
5933
6557
  const paths = [];
5934
6558
  for (const candidate of [...candidates].slice(0, 2e3)) {
5935
6559
  try {
5936
- paths.push(await context.workspace.resolvePath(join11(cwd, candidate), { allowMissing: true }));
6560
+ paths.push(await context.workspace.resolvePath(join12(cwd, candidate), { allowMissing: true }));
5937
6561
  } catch {
5938
6562
  }
5939
6563
  }
@@ -6049,7 +6673,7 @@ async function validateGitWorkspaceArguments(args, command2, cwd, context) {
6049
6673
  if (/^(?:[a-z]+:\/\/|[a-z]+@[^:]+:)/i.test(candidate)) continue;
6050
6674
  if (command2 === "clone" && index === 0 && !candidate) continue;
6051
6675
  await context.workspace.resolvePath(
6052
- candidate.startsWith("/") ? candidate : join11(cwd, candidate),
6676
+ candidate.startsWith("/") ? candidate : join12(cwd, candidate),
6053
6677
  { allowMissing: true }
6054
6678
  );
6055
6679
  }
@@ -6102,17 +6726,17 @@ function positionalArguments(args, command2) {
6102
6726
  }
6103
6727
 
6104
6728
  // src/tools/list.ts
6105
- import { lstat as lstat13 } from "node:fs/promises";
6106
- import { relative as relative6, resolve as resolve13 } from "node:path";
6729
+ import { lstat as lstat14 } from "node:fs/promises";
6730
+ import { relative as relative6, resolve as resolve14 } from "node:path";
6107
6731
  import fg3 from "fast-glob";
6108
- import { z as z8 } from "zod";
6109
- var inputSchema4 = z8.object({
6110
- path: z8.string().min(1).optional(),
6111
- pattern: z8.string().min(1).optional(),
6112
- depth: z8.number().int().min(1).max(20).optional(),
6113
- include_hidden: z8.boolean().optional(),
6114
- include_directories: z8.boolean().optional(),
6115
- limit: z8.number().int().min(1).max(5e3).optional()
6732
+ import { z as z9 } from "zod";
6733
+ var inputSchema4 = z9.object({
6734
+ path: z9.string().min(1).optional(),
6735
+ pattern: z9.string().min(1).optional(),
6736
+ depth: z9.number().int().min(1).max(20).optional(),
6737
+ include_hidden: z9.boolean().optional(),
6738
+ include_directories: z9.boolean().optional(),
6739
+ limit: z9.number().int().min(1).max(5e3).optional()
6116
6740
  }).strict();
6117
6741
  var ignored = [
6118
6742
  "**/.git/**",
@@ -6161,11 +6785,11 @@ var listFilesTool = {
6161
6785
  for (const path of selected) {
6162
6786
  let safePath;
6163
6787
  try {
6164
- safePath = await context.workspace.resolvePath(resolve13(directory, path), { expect: "any" });
6788
+ safePath = await context.workspace.resolvePath(resolve14(directory, path), { expect: "any" });
6165
6789
  } catch {
6166
6790
  continue;
6167
6791
  }
6168
- const info = await lstat13(safePath);
6792
+ const info = await lstat14(safePath);
6169
6793
  rendered.push(`${info.isDirectory() ? "d" : "f"} ${relative6(directory, safePath)}${info.isDirectory() ? "/" : ""}`);
6170
6794
  }
6171
6795
  const base = workspaceAliasPath(directory, context.workspace.roots);
@@ -6182,14 +6806,14 @@ var listFilesTool = {
6182
6806
  };
6183
6807
 
6184
6808
  // src/tools/read.ts
6185
- import { readFile as readFile8, stat as stat7 } from "node:fs/promises";
6186
- import { z as z9 } from "zod";
6187
- var inputSchema5 = z9.object({
6188
- path: z9.string().min(1),
6189
- start_line: z9.number().int().positive().optional(),
6190
- end_line: z9.number().int().positive().optional(),
6191
- line_numbers: z9.boolean().optional(),
6192
- max_bytes: z9.number().int().positive().max(1e6).optional()
6809
+ import { readFile as readFile9, stat as stat7 } from "node:fs/promises";
6810
+ import { z as z10 } from "zod";
6811
+ var inputSchema5 = z10.object({
6812
+ path: z10.string().min(1),
6813
+ start_line: z10.number().int().positive().optional(),
6814
+ end_line: z10.number().int().positive().optional(),
6815
+ line_numbers: z10.boolean().optional(),
6816
+ max_bytes: z10.number().int().positive().max(1e6).optional()
6193
6817
  }).strict();
6194
6818
  var readFileTool = {
6195
6819
  definition: {
@@ -6212,7 +6836,7 @@ var readFileTool = {
6212
6836
  if (info.size > 1e7) {
6213
6837
  throw new Error(`File is too large to read safely (${info.size} bytes).`);
6214
6838
  }
6215
- const buffer = await readFile8(path);
6839
+ const buffer = await readFile9(path);
6216
6840
  if (looksBinary(buffer)) throw new Error("Binary files cannot be read with read_file.");
6217
6841
  const raw = buffer.toString("utf8");
6218
6842
  const lines = raw.split("\n");
@@ -6256,6 +6880,73 @@ function truncateUtf8(input2, maxBytes) {
6256
6880
  return buffer.subarray(0, end).toString("utf8");
6257
6881
  }
6258
6882
 
6883
+ // src/tools/read-artifact.ts
6884
+ import { z as z11 } from "zod";
6885
+ var inputSchema6 = z11.object({
6886
+ sha256: z11.string().regex(/^[a-f0-9]{64}$/u),
6887
+ start_line: z11.number().int().positive().optional(),
6888
+ start_byte: z11.number().int().nonnegative().optional(),
6889
+ max_lines: z11.number().int().min(1).max(1e3).optional(),
6890
+ max_bytes: z11.number().int().min(256).max(768).optional()
6891
+ }).strict().refine((input2) => input2.start_line === void 0 || input2.start_byte === void 0, {
6892
+ message: "Use either start_line or start_byte, not both."
6893
+ });
6894
+ var readToolArtifactTool = {
6895
+ definition: {
6896
+ name: "read_tool_artifact",
6897
+ description: "Read a bounded page of oversized tool output retained for this session. Use only the SHA-256 shown in a tool-output receipt.",
6898
+ category: "read",
6899
+ inputSchema: jsonSchema({
6900
+ sha256: { type: "string", description: "The exact 64-character SHA-256 from a retained-output receipt." },
6901
+ start_line: { type: "integer", minimum: 1, default: 1 },
6902
+ start_byte: { type: "integer", minimum: 0, description: "Exact UTF-8 continuation byte shown by a previous page." },
6903
+ max_lines: { type: "integer", minimum: 1, maximum: 1e3, default: 200 },
6904
+ max_bytes: { type: "integer", minimum: 256, maximum: 768, default: 768 }
6905
+ }, ["sha256"])
6906
+ },
6907
+ async execute(arguments_, context) {
6908
+ const input2 = inputSchema6.parse(arguments_);
6909
+ const artifact = context.session.toolArtifacts?.find(
6910
+ (candidate) => candidate.sha256 === input2.sha256 && Date.parse(candidate.expiresAt) > Date.now()
6911
+ );
6912
+ if (!artifact) {
6913
+ throw new Error("No retained tool output matches this tool call in the current session.");
6914
+ }
6915
+ const store = context.toolArtifactStore ?? new ToolArtifactStore(context.workspace.primaryRoot);
6916
+ const page = await store.read(context.session.id, artifact.toolCallId, {
6917
+ ...input2.start_line !== void 0 ? { startLine: input2.start_line } : {},
6918
+ ...input2.start_byte !== void 0 ? { startByte: input2.start_byte } : {},
6919
+ ...input2.max_lines !== void 0 ? { maxLines: input2.max_lines } : {},
6920
+ maxBytes: input2.max_bytes ?? 768
6921
+ });
6922
+ if (page.sha256 !== artifact.sha256 || page.bytes !== artifact.bytes) {
6923
+ throw new Error("Retained tool output no longer matches the session receipt.");
6924
+ }
6925
+ const heading = `Retained output page: lines ${page.startLine}-${page.endLine} of ${page.totalLines}; bytes ${page.startByte}-${page.endByte} of ${page.bytes}`;
6926
+ const continuation = page.nextStartByte !== void 0 ? `continue with start_byte=${page.nextStartByte}` : page.nextStartLine !== void 0 ? `continue with start_line=${page.nextStartLine}` : "";
6927
+ return {
6928
+ content: `${heading}
6929
+ ${page.content}${page.hasMore ? `
6930
+ \u2026 more retained output; ${continuation}` : ""}`,
6931
+ metadata: {
6932
+ artifact: {
6933
+ toolCallId: page.toolCallId,
6934
+ sha256: page.sha256,
6935
+ bytes: page.bytes,
6936
+ expiresAt: page.expiresAt,
6937
+ redacted: page.redacted
6938
+ },
6939
+ startLine: page.startLine,
6940
+ endLine: page.endLine,
6941
+ totalLines: page.totalLines,
6942
+ startByte: page.startByte,
6943
+ endByte: page.endByte,
6944
+ truncated: page.hasMore
6945
+ }
6946
+ };
6947
+ }
6948
+ };
6949
+
6259
6950
  // src/tools/registry.ts
6260
6951
  var ToolRegistry = class {
6261
6952
  tools = /* @__PURE__ */ new Map();
@@ -6303,19 +6994,19 @@ function assertToolName(name) {
6303
6994
  }
6304
6995
 
6305
6996
  // src/tools/search.ts
6306
- import { readFile as readFile9, stat as stat8 } from "node:fs/promises";
6307
- import { resolve as resolve14 } from "node:path";
6997
+ import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
6998
+ import { resolve as resolve15 } from "node:path";
6308
6999
  import fg4 from "fast-glob";
6309
- import { z as z10 } from "zod";
6310
- var inputSchema6 = z10.object({
6311
- query: z10.string().min(1).max(1e4),
6312
- path: z10.string().min(1).optional(),
6313
- pattern: z10.string().min(1).optional(),
6314
- mode: z10.enum(["text", "ranked"]).optional(),
6315
- literal: z10.boolean().optional(),
6316
- case_sensitive: z10.boolean().optional(),
6317
- context_lines: z10.number().int().min(0).max(20).optional(),
6318
- max_results: z10.number().int().min(1).max(500).optional()
7000
+ import { z as z12 } from "zod";
7001
+ var inputSchema7 = z12.object({
7002
+ query: z12.string().min(1).max(1e4),
7003
+ path: z12.string().min(1).optional(),
7004
+ pattern: z12.string().min(1).optional(),
7005
+ mode: z12.enum(["text", "ranked"]).optional(),
7006
+ literal: z12.boolean().optional(),
7007
+ case_sensitive: z12.boolean().optional(),
7008
+ context_lines: z12.number().int().min(0).max(20).optional(),
7009
+ max_results: z12.number().int().min(1).max(500).optional()
6319
7010
  }).strict();
6320
7011
  var ignore = [
6321
7012
  "**/.git/**",
@@ -6350,7 +7041,7 @@ var searchCodeTool = {
6350
7041
  }, ["query"])
6351
7042
  },
6352
7043
  async execute(arguments_, context) {
6353
- const input2 = inputSchema6.parse(arguments_);
7044
+ const input2 = inputSchema7.parse(arguments_);
6354
7045
  const directory = await context.workspace.resolveDirectory(input2.path ?? ".");
6355
7046
  if ((input2.mode ?? "text") === "ranked") {
6356
7047
  if (!context.contextEngine) throw new Error("Local ranked retrieval is unavailable.");
@@ -6374,7 +7065,7 @@ var searchCodeTool = {
6374
7065
  if (results.length >= maxResults) break;
6375
7066
  let path;
6376
7067
  try {
6377
- path = await context.workspace.resolvePath(resolve14(directory, file), { expect: "file" });
7068
+ path = await context.workspace.resolvePath(resolve15(directory, file), { expect: "file" });
6378
7069
  } catch {
6379
7070
  skipped += 1;
6380
7071
  continue;
@@ -6384,7 +7075,7 @@ var searchCodeTool = {
6384
7075
  skipped += 1;
6385
7076
  continue;
6386
7077
  }
6387
- const buffer = await readFile9(path);
7078
+ const buffer = await readFile10(path);
6388
7079
  if (buffer.subarray(0, 8192).includes(0)) {
6389
7080
  skipped += 1;
6390
7081
  continue;
@@ -6456,17 +7147,17 @@ ${hit.content}`
6456
7147
  }
6457
7148
 
6458
7149
  // src/tools/shell.ts
6459
- import { createHash as createHash7 } from "node:crypto";
6460
- import { lstat as lstat14, readFile as readFile10, readdir as readdir4 } from "node:fs/promises";
6461
- import { join as join12 } from "node:path";
6462
- import { z as z11 } from "zod";
6463
- var inputSchema7 = z11.object({
6464
- command: z11.string().min(1).max(1e5),
6465
- cwd: z11.string().min(1).optional(),
6466
- timeout_ms: z11.number().int().min(100).max(6e5).optional(),
6467
- max_output_bytes: z11.number().int().min(1e3).max(5e6).optional(),
6468
- env: z11.record(z11.string(), z11.string()).optional(),
6469
- stdin: z11.string().max(5e6).optional()
7150
+ import { createHash as createHash8 } from "node:crypto";
7151
+ import { lstat as lstat15, readFile as readFile11, readdir as readdir5 } from "node:fs/promises";
7152
+ import { join as join13 } from "node:path";
7153
+ import { z as z13 } from "zod";
7154
+ var inputSchema8 = z13.object({
7155
+ command: z13.string().min(1).max(1e5),
7156
+ cwd: z13.string().min(1).optional(),
7157
+ timeout_ms: z13.number().int().min(100).max(6e5).optional(),
7158
+ max_output_bytes: z13.number().int().min(1e3).max(5e6).optional(),
7159
+ env: z13.record(z13.string(), z13.string()).optional(),
7160
+ stdin: z13.string().max(5e6).optional()
6470
7161
  }).strict();
6471
7162
  var shellTool = {
6472
7163
  definition: {
@@ -6483,7 +7174,7 @@ var shellTool = {
6483
7174
  }, ["command"])
6484
7175
  },
6485
7176
  permissionCategories(arguments_) {
6486
- const input2 = inputSchema7.parse(arguments_);
7177
+ const input2 = inputSchema8.parse(arguments_);
6487
7178
  validateEnvironment(input2.env);
6488
7179
  return [
6489
7180
  "shell",
@@ -6493,12 +7184,12 @@ var shellTool = {
6493
7184
  ];
6494
7185
  },
6495
7186
  async affectedPaths(arguments_, context) {
6496
- const input2 = inputSchema7.parse(arguments_);
7187
+ const input2 = inputSchema8.parse(arguments_);
6497
7188
  if (!appearsToModifyWorkspace(input2.command)) return [];
6498
7189
  return collectAffectedPaths(input2.command, input2.cwd ?? ".", context);
6499
7190
  },
6500
7191
  async execute(arguments_, context) {
6501
- const input2 = inputSchema7.parse(arguments_);
7192
+ const input2 = inputSchema8.parse(arguments_);
6502
7193
  validateEnvironment(input2.env);
6503
7194
  const cwd = await context.workspace.resolveDirectory(input2.cwd ?? ".");
6504
7195
  const candidates = appearsToModifyWorkspace(input2.command) ? await collectAffectedPaths(input2.command, input2.cwd ?? ".", context) : [];
@@ -6538,7 +7229,8 @@ Execution interrupted: ${error instanceof Error ? error.message : String(error)}
6538
7229
  result.stdout ? `stdout:
6539
7230
  ${result.stdout}` : "",
6540
7231
  result.stderr ? `stderr:
6541
- ${result.stderr}` : ""
7232
+ ${result.stderr}` : "",
7233
+ result.stdoutTruncated || result.stderrTruncated ? `Source output exceeded the command capture limit (${result.stdoutBytes} stdout bytes, ${result.stderrBytes} stderr bytes observed). Rerun with max_output_bytes up to 5000000 or narrow the command; omitted source bytes are not recoverable from this result.` : ""
6542
7234
  ].filter(Boolean);
6543
7235
  return {
6544
7236
  ok: result.exitCode === 0 && !result.timedOut,
@@ -6548,6 +7240,9 @@ ${result.stderr}` : ""
6548
7240
  exitCode: result.exitCode,
6549
7241
  timedOut: result.timedOut,
6550
7242
  durationMs: result.durationMs,
7243
+ stdoutBytes: result.stdoutBytes,
7244
+ stderrBytes: result.stderrBytes,
7245
+ sourceTruncated: result.stdoutTruncated || result.stderrTruncated,
6551
7246
  changeTracking: candidates.length ? "targeted" : beforeWorkspace && afterWorkspace && beforeWorkspace.complete && afterWorkspace.complete ? "workspace-snapshot" : beforeWorkspace ? "unresolved" : "read-only"
6552
7247
  },
6553
7248
  ...changedFiles.length ? { changedFiles } : {}
@@ -6611,7 +7306,7 @@ async function collectAffectedPaths(command2, cwdInput, context) {
6611
7306
  allowMissing: true
6612
7307
  });
6613
7308
  try {
6614
- if ((await lstat14(path)).isDirectory()) continue;
7309
+ if ((await lstat15(path)).isDirectory()) continue;
6615
7310
  } catch (error) {
6616
7311
  if (error.code !== "ENOENT") throw error;
6617
7312
  }
@@ -6639,7 +7334,7 @@ async function changedPaths(paths, before) {
6639
7334
  }
6640
7335
  async function snapshotPath(path) {
6641
7336
  try {
6642
- const info = await lstat14(path);
7337
+ const info = await lstat15(path);
6643
7338
  return { exists: true, size: info.size, mtimeMs: info.mtimeMs };
6644
7339
  } catch (error) {
6645
7340
  if (error.code === "ENOENT") return { exists: false };
@@ -6655,18 +7350,18 @@ async function captureWorkspaceSnapshot(roots) {
6655
7350
  if (!discovered.complete) complete = false;
6656
7351
  for (const path of discovered.files) {
6657
7352
  try {
6658
- const info = await lstat14(path);
7353
+ const info = await lstat15(path);
6659
7354
  if (!info.isFile() || info.isSymbolicLink()) continue;
6660
7355
  if (files.size >= MAX_SNAPSHOT_FILES || info.size > MAX_SNAPSHOT_FILE_BYTES || totalBytes + info.size > MAX_SNAPSHOT_TOTAL_BYTES) {
6661
7356
  complete = false;
6662
7357
  continue;
6663
7358
  }
6664
- const content = await readFile10(path);
7359
+ const content = await readFile11(path);
6665
7360
  totalBytes += content.length;
6666
7361
  files.set(path, {
6667
7362
  size: info.size,
6668
7363
  mtimeMs: info.mtimeMs,
6669
- hash: createHash7("sha256").update(content).digest("hex")
7364
+ hash: createHash8("sha256").update(content).digest("hex")
6670
7365
  });
6671
7366
  } catch (error) {
6672
7367
  if (error.code !== "ENOENT") complete = false;
@@ -6687,14 +7382,14 @@ async function discoverSnapshotFiles(root) {
6687
7382
  const directory = pending.pop();
6688
7383
  if (!directory) break;
6689
7384
  try {
6690
- const entries = await readdir4(directory, { withFileTypes: true, encoding: "utf8" });
7385
+ const entries = await readdir5(directory, { withFileTypes: true, encoding: "utf8" });
6691
7386
  for (const entry of entries) {
6692
7387
  if (ignored2.has(entry.name) || /^\.skein\.(?:migrating|rollback)-/u.test(entry.name)) continue;
6693
7388
  if (entry.isSymbolicLink()) {
6694
7389
  complete = false;
6695
7390
  continue;
6696
7391
  }
6697
- const path = join12(directory, entry.name);
7392
+ const path = join13(directory, entry.name);
6698
7393
  if (entry.isDirectory()) pending.push(path);
6699
7394
  else if (entry.isFile()) files.push(path);
6700
7395
  }
@@ -6708,18 +7403,18 @@ async function discoverSnapshotFiles(root) {
6708
7403
 
6709
7404
  // src/tools/task.ts
6710
7405
  import { randomUUID as randomUUID9 } from "node:crypto";
6711
- import { z as z12 } from "zod";
6712
- var taskSchema2 = z12.object({
6713
- id: z12.string().min(1).optional(),
6714
- title: z12.string().min(1).max(500),
6715
- status: z12.enum(["pending", "in_progress", "completed"])
7406
+ import { z as z14 } from "zod";
7407
+ var taskSchema2 = z14.object({
7408
+ id: z14.string().min(1).optional(),
7409
+ title: z14.string().min(1).max(500),
7410
+ status: z14.enum(["pending", "in_progress", "completed"])
6716
7411
  }).strict();
6717
- var inputSchema8 = z12.discriminatedUnion("action", [
6718
- z12.object({ action: z12.literal("list") }).strict(),
6719
- z12.object({ action: z12.literal("add"), title: z12.string().min(1).max(500), status: z12.enum(["pending", "in_progress", "completed"]).optional() }).strict(),
6720
- z12.object({ action: z12.literal("update"), id: z12.string().min(1), title: z12.string().min(1).max(500).optional(), status: z12.enum(["pending", "in_progress", "completed"]).optional() }).strict(),
6721
- z12.object({ action: z12.literal("remove"), id: z12.string().min(1) }).strict(),
6722
- z12.object({ action: z12.literal("replace"), tasks: z12.array(taskSchema2).max(100) }).strict()
7412
+ var inputSchema9 = z14.discriminatedUnion("action", [
7413
+ z14.object({ action: z14.literal("list") }).strict(),
7414
+ z14.object({ action: z14.literal("add"), title: z14.string().min(1).max(500), status: z14.enum(["pending", "in_progress", "completed"]).optional() }).strict(),
7415
+ z14.object({ action: z14.literal("update"), id: z14.string().min(1), title: z14.string().min(1).max(500).optional(), status: z14.enum(["pending", "in_progress", "completed"]).optional() }).strict(),
7416
+ z14.object({ action: z14.literal("remove"), id: z14.string().min(1) }).strict(),
7417
+ z14.object({ action: z14.literal("replace"), tasks: z14.array(taskSchema2).max(100) }).strict()
6723
7418
  ]);
6724
7419
  var taskTool = {
6725
7420
  definition: {
@@ -6750,7 +7445,7 @@ var taskTool = {
6750
7445
  }
6751
7446
  },
6752
7447
  async execute(arguments_, context) {
6753
- const input2 = inputSchema8.parse(arguments_);
7448
+ const input2 = inputSchema9.parse(arguments_);
6754
7449
  const tasks = context.session.tasks;
6755
7450
  switch (input2.action) {
6756
7451
  case "list":
@@ -6788,7 +7483,7 @@ var taskTool = {
6788
7483
 
6789
7484
  // src/tools/task-contract.ts
6790
7485
  import { randomUUID as randomUUID11 } from "node:crypto";
6791
- import { z as z13 } from "zod";
7486
+ import { z as z15 } from "zod";
6792
7487
 
6793
7488
  // src/agent/task-contract.ts
6794
7489
  import { randomUUID as randomUUID10 } from "node:crypto";
@@ -6869,14 +7564,14 @@ function compact(value, limit) {
6869
7564
  }
6870
7565
 
6871
7566
  // src/agent/completion-gate.ts
6872
- import { createHash as createHash9 } from "node:crypto";
7567
+ import { createHash as createHash10 } from "node:crypto";
6873
7568
 
6874
7569
  // src/tools/permissions.ts
6875
- import { createHash as createHash8, createHmac, randomBytes } from "node:crypto";
7570
+ import { createHash as createHash9, createHmac, randomBytes } from "node:crypto";
6876
7571
  var permissionScopeSecret = randomBytes(32);
6877
7572
  function permissionKey(call, category) {
6878
7573
  const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
6879
- const digest = createHash8("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
7574
+ const digest = createHash9("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
6880
7575
  return `${category}:${call.name}:${digest}`;
6881
7576
  }
6882
7577
  function permissionTarget(call) {
@@ -7070,7 +7765,7 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
7070
7765
  kind,
7071
7766
  ok: result.ok,
7072
7767
  changeSequence,
7073
- commandKey: createHash9("sha256").update(normalized).digest("hex")
7768
+ commandKey: createHash10("sha256").update(normalized).digest("hex")
7074
7769
  };
7075
7770
  }
7076
7771
  function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = []) {
@@ -7230,7 +7925,7 @@ function verificationRequirementMet(requirement, checks) {
7230
7925
  const normalized = normalizeCommand2(requirement);
7231
7926
  const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
7232
7927
  if (broad) return checks.length > 0;
7233
- const commandKey = createHash9("sha256").update(normalized).digest("hex");
7928
+ const commandKey = createHash10("sha256").update(normalized).digest("hex");
7234
7929
  return checks.some((item) => item.commandKey === commandKey);
7235
7930
  }
7236
7931
  function acceptanceUnresolved(acceptance) {
@@ -7282,29 +7977,29 @@ function fileCount(count) {
7282
7977
  }
7283
7978
 
7284
7979
  // src/tools/task-contract.ts
7285
- var criterionSchema = z13.object({
7286
- id: z13.string().min(1).max(128).optional(),
7287
- description: z13.string().min(1).max(2e3),
7288
- required: z13.boolean().optional()
7980
+ var criterionSchema = z15.object({
7981
+ id: z15.string().min(1).max(128).optional(),
7982
+ description: z15.string().min(1).max(2e3),
7983
+ required: z15.boolean().optional()
7289
7984
  }).strict();
7290
- var inputSchema9 = z13.discriminatedUnion("action", [
7291
- z13.object({ action: z13.literal("show") }).strict(),
7292
- z13.object({
7293
- action: z13.literal("replace"),
7294
- objective: z13.string().min(1).max(2e4),
7295
- scope: z13.array(z13.string().min(1).max(2e3)).max(64),
7296
- constraints: z13.array(z13.string().min(1).max(2e3)).max(64),
7297
- non_goals: z13.array(z13.string().min(1).max(2e3)).max(64),
7298
- acceptance_criteria: z13.array(criterionSchema).min(1).max(64),
7299
- verification_requirements: z13.array(z13.string().min(1).max(2e3)).min(1).max(64)
7985
+ var inputSchema10 = z15.discriminatedUnion("action", [
7986
+ z15.object({ action: z15.literal("show") }).strict(),
7987
+ z15.object({
7988
+ action: z15.literal("replace"),
7989
+ objective: z15.string().min(1).max(2e4),
7990
+ scope: z15.array(z15.string().min(1).max(2e3)).max(64),
7991
+ constraints: z15.array(z15.string().min(1).max(2e3)).max(64),
7992
+ non_goals: z15.array(z15.string().min(1).max(2e3)).max(64),
7993
+ acceptance_criteria: z15.array(criterionSchema).min(1).max(64),
7994
+ verification_requirements: z15.array(z15.string().min(1).max(2e3)).min(1).max(64)
7300
7995
  }).strict(),
7301
- z13.object({ action: z13.literal("activate") }).strict(),
7302
- z13.object({
7303
- action: z13.literal("update_criterion"),
7304
- id: z13.string().min(1).max(128),
7305
- status: z13.enum(["pending", "satisfied", "blocked"]),
7306
- evidence_refs: z13.array(z13.string().min(1).max(256)).max(64).optional(),
7307
- note: z13.string().max(2e3).optional()
7996
+ z15.object({ action: z15.literal("activate") }).strict(),
7997
+ z15.object({
7998
+ action: z15.literal("update_criterion"),
7999
+ id: z15.string().min(1).max(128),
8000
+ status: z15.enum(["pending", "satisfied", "blocked"]),
8001
+ evidence_refs: z15.array(z15.string().min(1).max(256)).max(64).optional(),
8002
+ note: z15.string().max(2e3).optional()
7308
8003
  }).strict()
7309
8004
  ]);
7310
8005
  var taskContractTool = {
@@ -7346,7 +8041,7 @@ var taskContractTool = {
7346
8041
  }
7347
8042
  },
7348
8043
  async execute(arguments_, context) {
7349
- const input2 = inputSchema9.parse(arguments_);
8044
+ const input2 = inputSchema10.parse(arguments_);
7350
8045
  let contract = context.session.taskContract;
7351
8046
  if (!contract) throw new Error("No Task Contract is active for this session.");
7352
8047
  if (input2.action === "replace") {
@@ -7443,17 +8138,17 @@ function validateVerificationRequirement(requirement) {
7443
8138
  }
7444
8139
 
7445
8140
  // src/tools/working-memory.ts
7446
- import { z as z14 } from "zod";
7447
- var inputSchema10 = z14.discriminatedUnion("action", [
7448
- z14.object({ action: z14.literal("show") }).strict(),
7449
- z14.object({ action: z14.literal("set_goal"), value: z14.string().min(1).max(1e3) }).strict(),
7450
- z14.object({ action: z14.literal("set_focus"), value: z14.string().min(1).max(1e3) }).strict(),
7451
- z14.object({ action: z14.literal("add_constraint"), value: z14.string().min(1).max(1e3) }).strict(),
7452
- z14.object({ action: z14.literal("add_decision"), value: z14.string().min(1).max(1e3) }).strict(),
7453
- z14.object({ action: z14.literal("add_question"), value: z14.string().min(1).max(1e3) }).strict(),
7454
- z14.object({ action: z14.literal("resolve_question"), value: z14.string().min(1).max(1e3) }).strict(),
7455
- z14.object({ action: z14.literal("add_file"), path: z14.string().min(1).max(4e3) }).strict(),
7456
- z14.object({ action: z14.literal("clear"), field: z14.enum(["constraints", "decisions", "openQuestions", "relevantFiles"]) }).strict()
8141
+ import { z as z16 } from "zod";
8142
+ var inputSchema11 = z16.discriminatedUnion("action", [
8143
+ z16.object({ action: z16.literal("show") }).strict(),
8144
+ z16.object({ action: z16.literal("set_goal"), value: z16.string().min(1).max(1e3) }).strict(),
8145
+ z16.object({ action: z16.literal("set_focus"), value: z16.string().min(1).max(1e3) }).strict(),
8146
+ z16.object({ action: z16.literal("add_constraint"), value: z16.string().min(1).max(1e3) }).strict(),
8147
+ z16.object({ action: z16.literal("add_decision"), value: z16.string().min(1).max(1e3) }).strict(),
8148
+ z16.object({ action: z16.literal("add_question"), value: z16.string().min(1).max(1e3) }).strict(),
8149
+ z16.object({ action: z16.literal("resolve_question"), value: z16.string().min(1).max(1e3) }).strict(),
8150
+ z16.object({ action: z16.literal("add_file"), path: z16.string().min(1).max(4e3) }).strict(),
8151
+ z16.object({ action: z16.literal("clear"), field: z16.enum(["constraints", "decisions", "openQuestions", "relevantFiles"]) }).strict()
7457
8152
  ]);
7458
8153
  var workingMemoryTool = {
7459
8154
  definition: {
@@ -7478,7 +8173,7 @@ var workingMemoryTool = {
7478
8173
  }, ["action"])
7479
8174
  },
7480
8175
  async execute(arguments_, context) {
7481
- const input2 = inputSchema10.parse(arguments_);
8176
+ const input2 = inputSchema11.parse(arguments_);
7482
8177
  const memory = context.session.workingMemory ?? emptyWorkingMemory2();
7483
8178
  switch (input2.action) {
7484
8179
  case "show":
@@ -7560,6 +8255,7 @@ function render(memory) {
7560
8255
  function createDefaultToolRegistry(_options = {}) {
7561
8256
  return new ToolRegistry([
7562
8257
  readFileTool,
8258
+ readToolArtifactTool,
7563
8259
  listFilesTool,
7564
8260
  searchCodeTool,
7565
8261
  writeFileTool,
@@ -7688,7 +8384,7 @@ function escapeAttribute3(value) {
7688
8384
  }
7689
8385
 
7690
8386
  // src/agent/tool-recovery.ts
7691
- import { createHash as createHash10 } from "node:crypto";
8387
+ import { createHash as createHash11 } from "node:crypto";
7692
8388
  var RETRY_BUDGET = {
7693
8389
  schema_input: 3,
7694
8390
  unknown_tool: 2,
@@ -7783,10 +8479,10 @@ function isRetryable(failureClass) {
7783
8479
  return RETRY_BUDGET[failureClass] > 0;
7784
8480
  }
7785
8481
  function callSignature(call) {
7786
- return createHash10("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
8482
+ return createHash11("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
7787
8483
  }
7788
8484
  function failureSignature(call, failureClass) {
7789
- return createHash10("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
8485
+ return createHash11("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
7790
8486
  }
7791
8487
  function redact(value, key = "") {
7792
8488
  if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
@@ -7803,10 +8499,170 @@ function isFailureClass(value) {
7803
8499
  return typeof value === "string" && Object.hasOwn(RETRY_BUDGET, value);
7804
8500
  }
7805
8501
 
8502
+ // src/agent/tool-output.ts
8503
+ import stripAnsi from "strip-ansi";
8504
+ var MIN_TOOL_OUTPUT_TOKENS = 1024;
8505
+ var MAX_TOOL_OUTPUT_TOKENS = 8192;
8506
+ async function protectToolOutput(options) {
8507
+ const safe = sanitizeToolOutput(options.content);
8508
+ const sanitized = redactToolOutput(safe.text);
8509
+ const estimatedTokens = estimateToolOutputTokens(sanitized.text);
8510
+ const budgetTokens = clamp2(options.budgetTokens, MIN_TOOL_OUTPUT_TOKENS, MAX_TOOL_OUTPUT_TOKENS);
8511
+ const base = {
8512
+ originalChars: options.content.length,
8513
+ originalBytes: Buffer.byteLength(options.content),
8514
+ estimatedTokens,
8515
+ budgetTokens,
8516
+ truncated: estimatedTokens > budgetTokens,
8517
+ redacted: sanitized.redacted,
8518
+ sanitized: safe.sanitized
8519
+ };
8520
+ if (estimatedTokens <= budgetTokens) return { content: sanitized.text, metadata: base };
8521
+ let archived;
8522
+ try {
8523
+ archived = await options.artifacts.archive(
8524
+ options.sessionId,
8525
+ options.toolCallId,
8526
+ sanitized.text,
8527
+ { redacted: sanitized.redacted }
8528
+ );
8529
+ } catch {
8530
+ archived = { stored: false, reason: "storage_error" };
8531
+ }
8532
+ const artifact = archived.stored ? archived.artifact : void 0;
8533
+ const artifactUnavailable = archived.stored ? void 0 : archived.reason;
8534
+ const metadata = {
8535
+ ...base,
8536
+ ...artifact ? {
8537
+ artifact: {
8538
+ toolCallId: artifact.toolCallId,
8539
+ sha256: artifact.sha256,
8540
+ bytes: artifact.bytes,
8541
+ createdAt: artifact.createdAt,
8542
+ expiresAt: artifact.expiresAt
8543
+ }
8544
+ } : artifactUnavailable ? { artifactUnavailable } : {}
8545
+ };
8546
+ const receipt = formatReceipt(options, metadata, "");
8547
+ const previewBudget = Math.max(0, budgetTokens - estimateToolOutputTokens(receipt));
8548
+ const preview = boundedPreview(sanitized.text, previewBudget);
8549
+ return {
8550
+ content: formatReceipt(options, metadata, preview),
8551
+ metadata
8552
+ };
8553
+ }
8554
+ function dynamicToolOutputBudget(contextWindowTokens, activeContextTokens, remainingSessionTokens) {
8555
+ const contextHeadroom = Math.max(0, contextWindowTokens - activeContextTokens);
8556
+ const sessionHeadroom = Math.max(0, remainingSessionTokens);
8557
+ const budget = Math.min(
8558
+ Math.floor(contextHeadroom * 0.35),
8559
+ Math.floor(sessionHeadroom * 0.12)
8560
+ );
8561
+ return clamp2(budget, MIN_TOOL_OUTPUT_TOKENS, MAX_TOOL_OUTPUT_TOKENS);
8562
+ }
8563
+ function estimateToolOutputTokens(value) {
8564
+ return Math.max(1, estimateTokens(value));
8565
+ }
8566
+ function formatReceipt(options, metadata, preview) {
8567
+ const lines = [
8568
+ "[Oversized tool output bounded for the model transcript]",
8569
+ `tool: ${boundedInlineByTokens(options.tool, 64)}`,
8570
+ `tool-call-id: ${boundedInlineByTokens(options.toolCallId, 96)}`,
8571
+ `status: ${options.ok ? "success" : "failure"}`,
8572
+ `original-output: ${metadata.originalChars} chars, ${metadata.originalBytes} bytes, ~${metadata.estimatedTokens} tokens`,
8573
+ `model-budget: ${metadata.budgetTokens} tokens (head + tail preview)`,
8574
+ ...metadata.redacted ? ["sensitive-values: redacted before display and retention"] : [],
8575
+ ...preservedSignals(options.metadata),
8576
+ ...metadata.artifact ? [
8577
+ `artifact: session-scoped; sha256 ${metadata.artifact.sha256}; ${metadata.artifact.bytes} bytes`,
8578
+ `read-back: read_tool_artifact({"sha256":"${metadata.artifact.sha256}","start_line":1,"max_lines":200}) before ${metadata.artifact.expiresAt}`
8579
+ ] : [`artifact: unavailable (${metadata.artifactUnavailable ?? "unknown"}); rerun the tool with a narrower result if more detail is needed`],
8580
+ "preview:",
8581
+ preview
8582
+ ];
8583
+ return lines.join("\n");
8584
+ }
8585
+ function preservedSignals(metadata) {
8586
+ const lines = [];
8587
+ if (typeof metadata.exitCode === "number") lines.push(`exit-code: ${metadata.exitCode}`);
8588
+ if (metadata.timedOut === true) lines.push("timed-out: true");
8589
+ if (metadata.sourceTruncated === true) {
8590
+ lines.push("source-truncated: true; the producing tool reached its capture limit before this firewall");
8591
+ }
8592
+ const changedFiles = metadata.changedFiles;
8593
+ if (Array.isArray(changedFiles)) {
8594
+ const allPaths = changedFiles.filter((path) => typeof path === "string");
8595
+ const paths = allPaths.slice(0, 3).map((path) => boundedInlineByTokens(path, 24));
8596
+ if (paths.length) {
8597
+ lines.push(`changed-files: ${paths.join(", ")}${allPaths.length > paths.length ? ` (+${allPaths.length - paths.length} more in metadata)` : ""}`);
8598
+ }
8599
+ }
8600
+ const failure = metadata.failure;
8601
+ if (isFailureReceipt(failure)) {
8602
+ lines.push(`failure-class: ${failure.class}; attempt ${failure.attempt}; ${failure.remaining} retries remain`);
8603
+ }
8604
+ return lines;
8605
+ }
8606
+ function isFailureReceipt(value) {
8607
+ return typeof value === "object" && value !== null && typeof value.class === "string" && typeof value.attempt === "number" && typeof value.remaining === "number";
8608
+ }
8609
+ function redactToolOutput(value) {
8610
+ let redacted = false;
8611
+ const replace = (expression, replacement) => {
8612
+ expression.lastIndex = 0;
8613
+ if (!expression.test(value)) return;
8614
+ redacted = true;
8615
+ expression.lastIndex = 0;
8616
+ value = value.replace(expression, replacement);
8617
+ };
8618
+ replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/gu, "[redacted-secret]");
8619
+ replace(/\b(authorization\s*:\s*(?:bearer\s+|basic\s+)?)\S+/giu, "$1[redacted]");
8620
+ replace(/\b((?:api[_-]?key|access[_-]?token|authorization|cookie|password|client[_-]?secret)\s*[:=]\s*)(?:"[A-Za-z0-9+/_=-]{8,}"|'[A-Za-z0-9+/_=-]{8,}'|[A-Za-z0-9+/_=-]{8,})/giu, "$1[redacted]");
8621
+ replace(/(--(?:api[_-]?key|access[_-]?token|password|client[_-]?secret)(?:=|\s+))[A-Za-z0-9+/_=-]{8,}/giu, "$1[redacted]");
8622
+ return { text: value, redacted };
8623
+ }
8624
+ function sanitizeToolOutput(value) {
8625
+ const text = stripAnsi(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, "");
8626
+ return { text, sanitized: text !== value };
8627
+ }
8628
+ function boundedPreview(value, budget) {
8629
+ if (budget <= 0) return "";
8630
+ if (estimateToolOutputTokens(value) <= budget) return value;
8631
+ let low = 0;
8632
+ let high = budget;
8633
+ let best = "";
8634
+ while (low <= high) {
8635
+ const contentBudget = Math.floor((low + high) / 2);
8636
+ const headBudget = Math.ceil(contentBudget * 0.6);
8637
+ const tailBudget = Math.max(0, contentBudget - headBudget);
8638
+ const head = sliceStartByTokens(value, headBudget);
8639
+ const tail = sliceEndByTokens(value, tailBudget);
8640
+ const preview = `${head}
8641
+ \u2026 ${Math.max(0, value.length - head.length - tail.length)} chars omitted \u2026
8642
+ ${tail}`;
8643
+ if (estimateToolOutputTokens(preview) <= budget) {
8644
+ best = preview;
8645
+ low = contentBudget + 1;
8646
+ } else {
8647
+ high = contentBudget - 1;
8648
+ }
8649
+ }
8650
+ return best || sliceStartByTokens(value, budget);
8651
+ }
8652
+ function clamp2(value, minimum, maximum) {
8653
+ return Math.max(minimum, Math.min(maximum, value));
8654
+ }
8655
+ function boundedInlineByTokens(value, maxTokens) {
8656
+ const normalized = value.replace(/[\r\n\t]+/gu, " ").trim();
8657
+ if (estimateToolOutputTokens(normalized) <= maxTokens) return normalized;
8658
+ const suffix = "\u2026";
8659
+ return `${sliceStartByTokens(normalized, Math.max(0, maxTokens - estimatedTokenCost(suffix)))}${suffix}`;
8660
+ }
8661
+
7806
8662
  // src/agent/rules.ts
7807
8663
  import { existsSync as existsSync2 } from "node:fs";
7808
- import { join as join13 } from "node:path";
7809
- import { lstat as lstat15, readFile as readFile11 } from "node:fs/promises";
8664
+ import { join as join14 } from "node:path";
8665
+ import { lstat as lstat16, readFile as readFile12 } from "node:fs/promises";
7810
8666
  var workspaceRulePaths = [
7811
8667
  "AGENTS.md",
7812
8668
  "CLAUDE.md",
@@ -7815,14 +8671,14 @@ var workspaceRulePaths = [
7815
8671
  ];
7816
8672
  async function discoverWorkspaceRules(workspace, maxChars = 12e4) {
7817
8673
  const workspaceAccess = new WorkspaceAccess([workspace]);
7818
- const activeProjectRules = join13(resolveProjectNamespaceSync(workspace).active, "rules.md");
8674
+ const activeProjectRules = join14(resolveProjectNamespaceSync(workspace).active, "rules.md");
7819
8675
  const projectCandidates = [
7820
- ...workspaceRulePaths.slice(0, 3).map((path) => join13(workspace, path)),
8676
+ ...workspaceRulePaths.slice(0, 3).map((path) => join14(workspace, path)),
7821
8677
  activeProjectRules,
7822
- ...workspaceRulePaths.slice(3).map((path) => join13(workspace, path))
8678
+ ...workspaceRulePaths.slice(3).map((path) => join14(workspace, path))
7823
8679
  ];
7824
8680
  const candidates = [
7825
- { path: join13(resolveHomeNamespace(), "rules.md"), scope: "user" },
8681
+ { path: join14(resolveHomeNamespace(), "rules.md"), scope: "user" },
7826
8682
  ...projectCandidates.map((path) => ({
7827
8683
  path,
7828
8684
  scope: "workspace"
@@ -7833,13 +8689,13 @@ async function discoverWorkspaceRules(workspace, maxChars = 12e4) {
7833
8689
  for (const candidate of candidates) {
7834
8690
  if (remaining <= 0 || !existsSync2(candidate.path)) continue;
7835
8691
  try {
7836
- const info = await lstat15(candidate.path);
8692
+ const info = await lstat16(candidate.path);
7837
8693
  if (!info.isFile() || info.size > 1e6) continue;
7838
8694
  if (candidate.scope === "workspace") {
7839
8695
  const safePath = await workspaceAccess.resolvePath(candidate.path, { expect: "file" });
7840
8696
  if (safePath !== candidate.path) continue;
7841
8697
  }
7842
- const raw = await readFile11(candidate.path, "utf8");
8698
+ const raw = await readFile12(candidate.path, "utf8");
7843
8699
  if (raw.includes("\0")) continue;
7844
8700
  const content = raw.slice(0, remaining);
7845
8701
  rules.push({
@@ -7871,13 +8727,10 @@ function escapeAttribute4(value) {
7871
8727
  }
7872
8728
 
7873
8729
  // src/context/context-sources.ts
7874
- import { readFile as readFile12, stat as stat9 } from "node:fs/promises";
8730
+ import { readFile as readFile13, stat as stat9 } from "node:fs/promises";
7875
8731
  var MAX_SOURCE_CHARS = 6e4;
7876
8732
  var MAX_PINNED_CHARS = 16e4;
7877
8733
  var MAX_CONTEXT_SOURCES = 32;
7878
- function estimateTokens3(value) {
7879
- return Math.ceil(value.length / 4);
7880
- }
7881
8734
  function sources(session) {
7882
8735
  return session.contextSources ?? (session.contextSources = []);
7883
8736
  }
@@ -7885,7 +8738,7 @@ async function pinContextSource(session, workspace, requested) {
7885
8738
  const resolved = await workspace.resolvePath(requested, { expect: "file" });
7886
8739
  const info = await stat9(resolved);
7887
8740
  const alias = workspace.display(resolved);
7888
- const tokens2 = estimateTokens3((await readFile12(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
8741
+ const tokens2 = estimateTokens((await readFile13(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
7889
8742
  const list2 = sources(session);
7890
8743
  const existing = list2.find((source2) => source2.path === alias);
7891
8744
  if (existing) {
@@ -7935,9 +8788,9 @@ async function resolvePinnedContent(session, workspace) {
7935
8788
  if (remaining <= 0) break;
7936
8789
  try {
7937
8790
  const safe = await workspace.resolvePath(source.path, { expect: "file" });
7938
- const raw = await readFile12(safe, "utf8");
8791
+ const raw = await readFile13(safe, "utf8");
7939
8792
  const capped = raw.slice(0, Math.min(MAX_SOURCE_CHARS, remaining));
7940
- source.tokens = estimateTokens3(capped);
8793
+ source.tokens = estimateTokens(capped);
7941
8794
  resolved.push({
7942
8795
  path: source.path,
7943
8796
  content: capped,
@@ -7983,6 +8836,7 @@ var AgentRunner = class {
7983
8836
  contextEngine;
7984
8837
  tools;
7985
8838
  sessionStore;
8839
+ toolArtifactStore;
7986
8840
  checkpointStore;
7987
8841
  workspace;
7988
8842
  hooks;
@@ -8004,6 +8858,7 @@ var AgentRunner = class {
8004
8858
  contextEngine: this.contextEngine
8005
8859
  });
8006
8860
  this.sessionStore = options.sessionStore ?? new SessionStore(this.workspace.primaryRoot);
8861
+ this.toolArtifactStore = options.toolArtifactStore ?? new ToolArtifactStore(this.workspace.primaryRoot);
8007
8862
  this.checkpointStore = options.checkpointStore ?? new CheckpointStore(this.workspace);
8008
8863
  this.hooks = new HookRunner(options.config.hooks, this.workspace);
8009
8864
  this.contextManager = options.contextManager ?? new ContextManager(options.config);
@@ -8096,17 +8951,25 @@ var AgentRunner = class {
8096
8951
  };
8097
8952
  try {
8098
8953
  throwIfAborted(options.signal);
8954
+ await this.reconcileToolArtifacts();
8099
8955
  if (this.session.messages.length === 0 && this.session.title === "New session") {
8100
8956
  this.session.title = titleFromInput(request);
8101
8957
  }
8102
8958
  this.contextManager.startTurn(this.session, request);
8103
- this.session.messages.push(message("user", request));
8959
+ const userMessage = message("user", request);
8960
+ this.session.messages.push(userMessage);
8104
8961
  await this.persist();
8105
8962
  const trivialTurn = isTrivialTurn(request);
8106
- 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 });
8107
8970
  if (!trivialTurn) await emit({ type: "context", packed });
8108
8971
  const mentions = await this.packMentions(request);
8109
- const retrievedContext = buildRetrievedContext(
8972
+ const retrievedContext = trivialTurn && !mentions.length ? "" : buildRetrievedContext(
8110
8973
  packed,
8111
8974
  mentions,
8112
8975
  this.workspace.primaryRoot,
@@ -8132,9 +8995,6 @@ var AgentRunner = class {
8132
8995
  scope: augmentation.memoryScope ?? "session"
8133
8996
  });
8134
8997
  }
8135
- const turnDirective = buildTurnDirective(request, {
8136
- agents: Boolean(this.config.agents?.enabled)
8137
- });
8138
8998
  const contractEnabled = shouldUseTaskContract(
8139
8999
  request,
8140
9000
  turnDirective.intent,
@@ -8158,21 +9018,6 @@ var AgentRunner = class {
8158
9018
  ...augmentation.skills?.length ? [`skills:${augmentation.skills.length}`] : [],
8159
9019
  ...augmentation.memoryCount ? [`memory:${augmentation.memoryCount}`] : []
8160
9020
  ];
8161
- if (!trivialTurn) await emit({
8162
- type: "prompt",
8163
- intent: turnDirective.intent,
8164
- sections: promptSections,
8165
- estimatedTokens: Math.ceil([
8166
- turnDirective.text,
8167
- buildSessionStatePrompt(this.session),
8168
- this.contextManager.buildShortTermPrompt(this.session),
8169
- options.turnInstructions ?? "",
8170
- augmentation.text,
8171
- retrievedContext,
8172
- pinnedContext,
8173
- workspaceRules
8174
- ].join("\n").length / 4)
8175
- });
8176
9021
  let verificationAttempted = false;
8177
9022
  const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
8178
9023
  const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
@@ -8187,24 +9032,29 @@ var AgentRunner = class {
8187
9032
  }
8188
9033
  this.applySteering();
8189
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");
8190
9043
  const messages = packConversation(
8191
9044
  stableSystemPrompt,
8192
- [
8193
- buildSessionStatePrompt(this.session),
8194
- turnDirective.text,
8195
- this.contextManager.buildShortTermPrompt(this.session),
8196
- pinnedContext,
8197
- options.turnInstructions ?? "",
8198
- augmentation.text
8199
- ].filter(Boolean).join("\n\n"),
9045
+ dynamicPrompt,
8200
9046
  retrievedContext,
8201
9047
  activeMessages(this.session),
8202
9048
  contextBudget
8203
9049
  );
8204
9050
  const availableTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
8205
- const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(
8206
- visibleToolDefinitions(this.tools, options.askMode === true, contractEnabled)
9051
+ const visibleTools = visibleToolDefinitions(
9052
+ this.tools,
9053
+ options.askMode === true,
9054
+ contractEnabled,
9055
+ this.hasReadableToolArtifact()
8207
9056
  );
9057
+ const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
8208
9058
  if (availableTokens <= 0 || estimatedInputTokens >= availableTokens) {
8209
9059
  return finishRun("token_budget");
8210
9060
  }
@@ -8212,7 +9062,23 @@ var AgentRunner = class {
8212
9062
  this.config.model.maxTokens ?? 8192,
8213
9063
  availableTokens - estimatedInputTokens
8214
9064
  ));
8215
- const visibleTools = visibleToolDefinitions(this.tools, options.askMode === true, contractEnabled);
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
+ }
8216
9082
  const assistantId = randomUUID12();
8217
9083
  const response = await this.completeModel(
8218
9084
  messages,
@@ -8228,17 +9094,49 @@ var AgentRunner = class {
8228
9094
  assistantMessage.id = assistantId;
8229
9095
  this.session.messages.push(assistantMessage);
8230
9096
  if (response.content) await emit({ type: "assistant", id: assistantId, content: response.content });
8231
- const inputTokens = response.usage?.inputTokens ?? estimatedInputTokens;
8232
- const outputTokens = response.usage?.outputTokens ?? estimateResponseTokens(response);
8233
- this.session.usage.inputTokens += inputTokens;
8234
- this.session.usage.outputTokens += outputTokens;
8235
- if (inputTokens || outputTokens) {
8236
- await emit({
8237
- type: "usage",
8238
- inputTokens: this.session.usage.inputTokens,
8239
- outputTokens: this.session.usage.outputTokens
8240
- });
8241
- }
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
+ });
8242
9140
  await this.persist();
8243
9141
  if (this.session.usage.inputTokens + this.session.usage.outputTokens >= this.config.agent.maxSessionTokens) {
8244
9142
  for (const call of response.toolCalls) {
@@ -8370,17 +9268,17 @@ ${input2}`
8370
9268
  async executeTool(call, options, emit, recovery = new ToolRecoveryController(), visibleToolNames) {
8371
9269
  const preflight = recovery.preflight(call);
8372
9270
  if (preflight) {
8373
- const result = failedResult(call, "Tool call rejected by the recovery circuit.", preflight);
9271
+ const result = await this.protectToolResult(
9272
+ failedResult(call, "Tool call rejected by the recovery circuit.", preflight)
9273
+ );
8374
9274
  this.recordToolResult(result);
8375
9275
  await emit({ type: "tool_result", result });
8376
9276
  return result;
8377
9277
  }
8378
9278
  if (visibleToolNames && !visibleToolNames.has(call.name)) {
8379
9279
  const receipt = recovery.recordFailure(call, "unknown_tool");
8380
- const result = failedResult(
8381
- call,
8382
- `Tool is not exposed for this turn: ${call.name}`,
8383
- receipt
9280
+ const result = await this.protectToolResult(
9281
+ failedResult(call, `Tool is not exposed for this turn: ${call.name}`, receipt)
8384
9282
  );
8385
9283
  this.recordToolResult(result);
8386
9284
  await emit({ type: "tool_result", result });
@@ -8389,7 +9287,7 @@ ${input2}`
8389
9287
  const tool = this.tools.get(call.name);
8390
9288
  if (!tool) {
8391
9289
  const receipt = recovery.recordFailure(call, "unknown_tool");
8392
- const result = failedResult(call, `Unknown tool: ${call.name}`, receipt);
9290
+ const result = await this.protectToolResult(failedResult(call, `Unknown tool: ${call.name}`, receipt));
8393
9291
  this.recordToolResult(result);
8394
9292
  await emit({ type: "tool_result", result });
8395
9293
  return result;
@@ -8402,17 +9300,15 @@ ${input2}`
8402
9300
  } catch (error) {
8403
9301
  const failureClass = classifyThrownToolFailure(error, options.signal);
8404
9302
  const receipt = recovery.recordFailure(call, failureClass);
8405
- const result = failedResult(call, formatToolError(error), receipt);
9303
+ const result = await this.protectToolResult(failedResult(call, formatToolError(error), receipt));
8406
9304
  this.recordToolResult(result, tool.definition.category);
8407
9305
  await emit({ type: "tool_result", result });
8408
9306
  return result;
8409
9307
  }
8410
9308
  if (categories.some((category) => category !== "read") && this.session.taskContract?.state === "draft") {
8411
9309
  const receipt = recovery.recordFailure(call, "contract_required");
8412
- const result = failedResult(
8413
- call,
8414
- "Potentially mutating work is paused until the draft Task Contract is activated.",
8415
- receipt
9310
+ const result = await this.protectToolResult(
9311
+ failedResult(call, "Potentially mutating work is paused until the draft Task Contract is activated.", receipt)
8416
9312
  );
8417
9313
  this.recordToolResult(result, tool.definition.category);
8418
9314
  await emit({ type: "tool_result", result });
@@ -8422,7 +9318,9 @@ ${input2}`
8422
9318
  const allowed = await this.authorize(call, category, options, emit);
8423
9319
  if (!allowed) {
8424
9320
  const receipt = recovery.recordFailure(call, "permission_denied");
8425
- const result = failedResult(call, `Permission denied for ${category} operation.`, receipt);
9321
+ const result = await this.protectToolResult(
9322
+ failedResult(call, `Permission denied for ${category} operation.`, receipt)
9323
+ );
8426
9324
  this.recordToolResult(result, category);
8427
9325
  await emit({ type: "tool_result", result });
8428
9326
  return result;
@@ -8436,6 +9334,7 @@ ${input2}`
8436
9334
  workspace: this.workspace,
8437
9335
  session: this.session,
8438
9336
  contextEngine: this.contextEngine,
9337
+ toolArtifactStore: this.toolArtifactStore,
8439
9338
  emit,
8440
9339
  ...options.signal ? { signal: options.signal } : {}
8441
9340
  };
@@ -8469,30 +9368,33 @@ ${input2}`
8469
9368
  } catch (error) {
8470
9369
  afterHookError = toError(error);
8471
9370
  }
8472
- const result = {
8473
- toolCallId: call.id,
8474
- name: call.name,
8475
- ok: execution.ok !== false && !afterHookError,
8476
- content: truncateToolOutput(afterHookError ? `${execution.content}
9371
+ let completeContent = afterHookError ? `${execution.content}
8477
9372
 
8478
- Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : execution.content),
8479
- metadata: {
8480
- ...execution.metadata ?? {},
8481
- ...changedFiles.length ? { changedFiles } : {},
8482
- ...checkpointId ? { checkpointId } : {},
8483
- ...beforeHooks.length || afterHooks.length ? { hooks: { before: beforeHooks.length, after: afterHooks.length } } : {},
8484
- ...afterHookError ? { toolSucceeded: true, hookError: afterHookError.message } : {}
8485
- }
9373
+ Tool succeeded, but afterTool hook failed: ${afterHookError.message}` : execution.content;
9374
+ const metadata = {
9375
+ ...execution.metadata ?? {},
9376
+ ...changedFiles.length ? { changedFiles } : {},
9377
+ ...checkpointId ? { checkpointId } : {},
9378
+ ...beforeHooks.length || afterHooks.length ? { hooks: { before: beforeHooks.length, after: afterHooks.length } } : {},
9379
+ ...afterHookError ? { toolSucceeded: true, hookError: afterHookError.message } : {}
8486
9380
  };
8487
- if (!result.ok) {
8488
- const failureClass = options.signal?.aborted ? "cancelled" : classifyToolFailure(result);
9381
+ const ok = execution.ok !== false && !afterHookError;
9382
+ if (!ok) {
9383
+ const failureClass = options.signal?.aborted ? "cancelled" : classifyToolFailure({ toolCallId: call.id, name: call.name, ok, content: completeContent, metadata });
8489
9384
  const receipt = recovery.recordFailure(call, failureClass);
8490
- result.content = truncateToolOutput(`${formatFailureReceipt(receipt)}
8491
- ${result.content}`);
8492
- result.metadata = { ...result.metadata, failure: receipt };
9385
+ completeContent = `${formatFailureReceipt(receipt)}
9386
+ ${completeContent}`;
9387
+ metadata.failure = receipt;
8493
9388
  } else {
8494
9389
  recovery.recordSuccess(call);
8495
9390
  }
9391
+ const result = await this.protectToolResult({
9392
+ toolCallId: call.id,
9393
+ name: call.name,
9394
+ ok,
9395
+ content: completeContent,
9396
+ metadata
9397
+ });
8496
9398
  this.contextManager.recordTool(this.session, call, result);
8497
9399
  if (JSON.stringify(this.session.tasks) !== tasksBefore || call.name === "task") {
8498
9400
  await emit({ type: "tasks", tasks: this.session.tasks.map((task) => ({ ...task })) });
@@ -8507,7 +9409,7 @@ ${result.content}`);
8507
9409
  const normalized = toError(error);
8508
9410
  const failureClass = classifyThrownToolFailure(normalized, options.signal);
8509
9411
  const receipt = recovery.recordFailure(call, failureClass);
8510
- const result = failedResult(call, formatToolError(error), receipt);
9412
+ const result = await this.protectToolResult(failedResult(call, formatToolError(error), receipt));
8511
9413
  this.recordToolResult(result, tool.definition.category);
8512
9414
  await emit({ type: "tool_result", result });
8513
9415
  return result;
@@ -8613,8 +9515,8 @@ ${result.content}`);
8613
9515
  toolCalls: toolCalls.map((call) => ({ id: call.id, name: call.name }))
8614
9516
  }, signal);
8615
9517
  }
8616
- async packContext(input2) {
8617
- return this.contextEngine.pack(input2);
9518
+ async packContext(input2, options) {
9519
+ return this.contextEngine.pack(input2, options);
8618
9520
  }
8619
9521
  async packMentions(input2) {
8620
9522
  try {
@@ -8661,6 +9563,63 @@ ${result.content}`);
8661
9563
  persist() {
8662
9564
  return this.persistSession ? this.sessionStore.save(this.session) : Promise.resolve();
8663
9565
  }
9566
+ toolOutputBudget() {
9567
+ const contextWindowTokens = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
9568
+ const activeContextTokens = this.contextManager.status(this.session, contextWindowTokens).activeTokens;
9569
+ const remainingSessionTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
9570
+ return dynamicToolOutputBudget(contextWindowTokens, activeContextTokens, remainingSessionTokens);
9571
+ }
9572
+ async protectToolResult(result) {
9573
+ const metadata = { ...result.metadata ?? {} };
9574
+ const output2 = await protectToolOutput({
9575
+ content: result.content,
9576
+ sessionId: this.session.id,
9577
+ toolCallId: result.toolCallId,
9578
+ tool: result.name,
9579
+ ok: result.ok,
9580
+ budgetTokens: this.toolOutputBudget(),
9581
+ metadata,
9582
+ artifacts: this.toolArtifactStore
9583
+ });
9584
+ if (output2.metadata.artifact) this.registerToolArtifact({
9585
+ ...output2.metadata.artifact,
9586
+ redacted: output2.metadata.redacted
9587
+ });
9588
+ return {
9589
+ ...result,
9590
+ content: output2.content,
9591
+ ...output2.metadata.truncated || output2.metadata.redacted || output2.metadata.sanitized || output2.metadata.artifact || output2.metadata.artifactUnavailable ? { metadata: { ...metadata, toolOutput: output2.metadata } } : Object.keys(metadata).length ? { metadata } : {}
9592
+ };
9593
+ }
9594
+ hasReadableToolArtifact() {
9595
+ const now = Date.now();
9596
+ return (this.session.toolArtifacts ?? []).some(
9597
+ (artifact) => Date.parse(artifact.expiresAt) > now
9598
+ );
9599
+ }
9600
+ registerToolArtifact(artifact) {
9601
+ const now = Date.now();
9602
+ const artifacts = (this.session.toolArtifacts ?? []).filter((candidate) => Date.parse(candidate.expiresAt) > now && candidate.toolCallId !== artifact.toolCallId);
9603
+ artifacts.push(artifact);
9604
+ artifacts.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
9605
+ if (artifacts.length > 200) artifacts.splice(0, artifacts.length - 200);
9606
+ this.session.toolArtifacts = artifacts;
9607
+ }
9608
+ async reconcileToolArtifacts() {
9609
+ let stored;
9610
+ try {
9611
+ stored = await this.toolArtifactStore.prune(this.session.id);
9612
+ } catch {
9613
+ this.session.toolArtifacts = [];
9614
+ return;
9615
+ }
9616
+ const valid = new Map(stored.map((artifact) => [artifact.toolCallId, artifact]));
9617
+ const reconciled = (this.session.toolArtifacts ?? []).filter((receipt) => {
9618
+ const artifact = valid.get(receipt.toolCallId);
9619
+ return artifact !== void 0 && artifact.sha256 === receipt.sha256 && artifact.bytes === receipt.bytes && artifact.createdAt === receipt.createdAt && artifact.expiresAt === receipt.expiresAt && artifact.redacted === receipt.redacted;
9620
+ });
9621
+ this.session.toolArtifacts = reconciled;
9622
+ }
8664
9623
  };
8665
9624
  function message(role, content, extra = {}) {
8666
9625
  return {
@@ -8675,14 +9634,14 @@ function packConversation(systemPrompt, dynamicPrompt, retrievedContext, history
8675
9634
  const system = message("system", systemPrompt);
8676
9635
  const dynamic = dynamicPrompt ? message("system", dynamicPrompt) : void 0;
8677
9636
  const context = retrievedContext ? message("system", retrievedContext) : void 0;
8678
- const reserved = estimateTokens4(system.content) + estimateTokens4(dynamic?.content ?? "") + estimateTokens4(context?.content ?? "");
9637
+ const reserved = estimateTokens(system.content) + estimateTokens(dynamic?.content ?? "") + estimateTokens(context?.content ?? "");
8679
9638
  const budget = Math.max(4e3, tokenBudget - reserved);
8680
9639
  const groups = groupMessages(clearOldToolResults(history));
8681
9640
  const selected = [];
8682
9641
  let used = 0;
8683
9642
  for (let index = groups.length - 1; index >= 0; index -= 1) {
8684
9643
  const group = groups[index] ?? [];
8685
- 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);
8686
9645
  if (selected.length && used + cost > budget) break;
8687
9646
  selected.unshift(group);
8688
9647
  used += cost;
@@ -8720,17 +9679,114 @@ function groupMessages(messages) {
8720
9679
  }
8721
9680
  return groups;
8722
9681
  }
8723
- function estimateTokens4(input2) {
8724
- return Math.ceil(input2.length / 4);
8725
- }
8726
9682
  function estimateMessages2(messages) {
8727
- 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);
8728
9684
  }
8729
9685
  function estimateToolDefinitions(tools) {
8730
- return estimateTokens4(JSON.stringify(tools));
9686
+ return estimateTokens(JSON.stringify(tools));
8731
9687
  }
8732
9688
  function estimateResponseTokens(response) {
8733
- 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";
8734
9790
  }
8735
9791
  function uniqueCategories(categories) {
8736
9792
  return [...new Set(categories)];
@@ -8740,21 +9796,16 @@ function failedResult(call, content, failure) {
8740
9796
  toolCallId: call.id,
8741
9797
  name: call.name,
8742
9798
  ok: false,
8743
- content: truncateToolOutput(failure ? `${formatFailureReceipt(failure)}
8744
- ${content}` : content),
9799
+ content: failure ? `${formatFailureReceipt(failure)}
9800
+ ${content}` : content,
8745
9801
  ...failure ? { metadata: { failure } } : {}
8746
9802
  };
8747
9803
  }
8748
- function visibleToolDefinitions(tools, askMode, contractEnabled) {
9804
+ function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable) {
8749
9805
  return tools.definitions().filter(
8750
- (tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract")
9806
+ (tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact")
8751
9807
  );
8752
9808
  }
8753
- function truncateToolOutput(content) {
8754
- const max = 8e4;
8755
- return content.length <= max ? content : `${content.slice(0, max)}
8756
- \u2026 tool output truncated`;
8757
- }
8758
9809
  function formatToolError(error) {
8759
9810
  const normalized = toError(error);
8760
9811
  if (normalized.name === "ZodError") return `Invalid tool arguments: ${normalized.message}`;
@@ -8787,9 +9838,9 @@ async function safeEmit(emit, event) {
8787
9838
  }
8788
9839
 
8789
9840
  // src/agent/profiles.ts
8790
- import { lstat as lstat16, readFile as readFile13, readdir as readdir5 } from "node:fs/promises";
9841
+ import { lstat as lstat17, readFile as readFile14, readdir as readdir6 } from "node:fs/promises";
8791
9842
  import { homedir as homedir3 } from "node:os";
8792
- import { basename as basename8, join as join14 } from "node:path";
9843
+ import { basename as basename8, join as join15 } from "node:path";
8793
9844
  import { parse as parseYaml2 } from "yaml";
8794
9845
  var builtInProfiles = [
8795
9846
  {
@@ -8881,14 +9932,14 @@ var AgentProfileCatalog = class {
8881
9932
  profiles = new Map(builtInProfiles.map((profile) => [profile.name, profile]));
8882
9933
  async discover() {
8883
9934
  const locations = [
8884
- { path: join14(resolveHomeNamespace(), "agents"), source: "user" },
8885
- { path: join14(homedir3(), ".claude", "agents"), source: "user" },
8886
- { path: join14(resolveProjectNamespaceSync(this.workspace).active, "agents"), source: "workspace" },
8887
- { path: join14(this.workspace, ".claude", "agents"), source: "workspace" }
9935
+ { path: join15(resolveHomeNamespace(), "agents"), source: "user" },
9936
+ { path: join15(homedir3(), ".claude", "agents"), source: "user" },
9937
+ { path: join15(resolveProjectNamespaceSync(this.workspace).active, "agents"), source: "workspace" },
9938
+ { path: join15(this.workspace, ".claude", "agents"), source: "workspace" }
8888
9939
  ];
8889
9940
  for (const location of locations) {
8890
9941
  for (const file of await markdownFiles(location.path)) {
8891
- const profile = await readProfile(join14(location.path, file), location.source);
9942
+ const profile = await readProfile(join15(location.path, file), location.source);
8892
9943
  if (profile) this.profiles.set(profile.name, profile);
8893
9944
  }
8894
9945
  }
@@ -8904,18 +9955,18 @@ var AgentProfileCatalog = class {
8904
9955
  };
8905
9956
  async function markdownFiles(directory) {
8906
9957
  try {
8907
- const info = await lstat16(directory);
9958
+ const info = await lstat17(directory);
8908
9959
  if (!info.isDirectory() || info.isSymbolicLink()) return [];
8909
- return (await readdir5(directory, { withFileTypes: true })).filter((entry) => entry.isFile() && !entry.isSymbolicLink() && entry.name.endsWith(".md")).map((entry) => entry.name).slice(0, 200);
9960
+ return (await readdir6(directory, { withFileTypes: true })).filter((entry) => entry.isFile() && !entry.isSymbolicLink() && entry.name.endsWith(".md")).map((entry) => entry.name).slice(0, 200);
8910
9961
  } catch {
8911
9962
  return [];
8912
9963
  }
8913
9964
  }
8914
9965
  async function readProfile(path, source) {
8915
9966
  try {
8916
- const info = await lstat16(path);
9967
+ const info = await lstat17(path);
8917
9968
  if (!info.isFile() || info.isSymbolicLink() || info.size > 1e5) return void 0;
8918
- const raw = await readFile13(path, "utf8");
9969
+ const raw = await readFile14(path, "utf8");
8919
9970
  const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
8920
9971
  const frontmatter = match ? parseYaml2(match[1] ?? "") : {};
8921
9972
  const prompt = (match ? raw.slice(match[0].length) : raw).trim();
@@ -8943,8 +9994,8 @@ function integer(value, fallback, min, max) {
8943
9994
 
8944
9995
  // src/agent/delegation.ts
8945
9996
  import { randomUUID as randomUUID14 } from "node:crypto";
8946
- import { join as join17 } from "node:path";
8947
- import { z as z16 } from "zod";
9997
+ import { join as join18 } from "node:path";
9998
+ import { z as z18 } from "zod";
8948
9999
 
8949
10000
  // src/agent/external-runtime.ts
8950
10001
  async function runExternalAgent(request) {
@@ -9071,95 +10122,95 @@ function numeric(...values) {
9071
10122
  }
9072
10123
 
9073
10124
  // src/agent/team-store.ts
9074
- import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
9075
- import { lstat as lstat17, readFile as readFile14, readdir as readdir6, rm as rm2 } from "node:fs/promises";
9076
- import { join as join15, resolve as resolve15 } from "node:path";
9077
- import { z as z15 } from "zod";
9078
- var runIdSchema = z15.string().uuid();
9079
- var hashSchema = z15.string().regex(/^[a-f0-9]{64}$/u);
9080
- var artifactSchema = z15.object({
10125
+ import { createHash as createHash12, randomUUID as randomUUID13 } from "node:crypto";
10126
+ import { lstat as lstat18, readFile as readFile15, readdir as readdir7, rm as rm3 } from "node:fs/promises";
10127
+ import { join as join16, resolve as resolve16 } from "node:path";
10128
+ import { z as z17 } from "zod";
10129
+ var runIdSchema = z17.string().uuid();
10130
+ var hashSchema = z17.string().regex(/^[a-f0-9]{64}$/u);
10131
+ var artifactSchema2 = z17.object({
9081
10132
  sha256: hashSchema,
9082
- bytes: z15.number().int().nonnegative().max(5e5)
10133
+ bytes: z17.number().int().nonnegative().max(5e5)
9083
10134
  }).strict();
9084
- var phaseSchema = z15.enum(["work", "review", "revision", "write"]);
9085
- var agentRecordSchema = z15.object({
9086
- id: z15.string().uuid(),
9087
- profile: z15.string(),
9088
- provider: z15.string(),
9089
- model: z15.string(),
10135
+ var phaseSchema = z17.enum(["work", "review", "revision", "write"]);
10136
+ var agentRecordSchema = z17.object({
10137
+ id: z17.string().uuid(),
10138
+ profile: z17.string(),
10139
+ provider: z17.string(),
10140
+ model: z17.string(),
9090
10141
  phase: phaseSchema,
9091
- ok: z15.boolean(),
9092
- createdAt: z15.string(),
9093
- startedAt: z15.string().optional(),
9094
- endedAt: z15.string().optional(),
9095
- durationMs: z15.number().int().nonnegative().optional(),
9096
- toolCalls: z15.number().int().nonnegative().optional(),
9097
- usage: z15.object({
9098
- inputTokens: z15.number().int().nonnegative(),
9099
- outputTokens: z15.number().int().nonnegative()
10142
+ ok: z17.boolean(),
10143
+ createdAt: z17.string(),
10144
+ startedAt: z17.string().optional(),
10145
+ endedAt: z17.string().optional(),
10146
+ durationMs: z17.number().int().nonnegative().optional(),
10147
+ toolCalls: z17.number().int().nonnegative().optional(),
10148
+ usage: z17.object({
10149
+ inputTokens: z17.number().int().nonnegative(),
10150
+ outputTokens: z17.number().int().nonnegative()
9100
10151
  }).strict().optional(),
9101
- report: artifactSchema
10152
+ report: artifactSchema2
9102
10153
  }).strict();
9103
- var messageRecordSchema = z15.object({
9104
- id: z15.string().uuid(),
9105
- from: z15.string(),
9106
- to: z15.string(),
9107
- createdAt: z15.string(),
9108
- content: artifactSchema
10154
+ var messageRecordSchema = z17.object({
10155
+ id: z17.string().uuid(),
10156
+ from: z17.string(),
10157
+ to: z17.string(),
10158
+ createdAt: z17.string(),
10159
+ content: artifactSchema2
9109
10160
  }).strict();
9110
- var writerIntegrationSchema = z15.object({
9111
- status: z15.enum(["ready", "conflict", "integrated"]),
9112
- checkedAt: z15.string(),
9113
- detail: z15.string().max(2e4),
9114
- checkpoint: z15.object({
9115
- sessionId: z15.string(),
9116
- checkpointId: z15.string()
10161
+ var writerIntegrationSchema = z17.object({
10162
+ status: z17.enum(["ready", "conflict", "integrated"]),
10163
+ checkedAt: z17.string(),
10164
+ detail: z17.string().max(2e4),
10165
+ checkpoint: z17.object({
10166
+ sessionId: z17.string(),
10167
+ checkpointId: z17.string()
9117
10168
  }).strict().optional(),
9118
- integratedAt: z15.string().optional()
10169
+ integratedAt: z17.string().optional()
9119
10170
  }).strict();
9120
- var writerLaneSchema = z15.object({
9121
- profile: z15.string(),
9122
- reviewer: z15.string(),
9123
- baseCommit: z15.string().regex(/^[a-f0-9]{40,64}$/u),
9124
- outcome: z15.enum(["accepted", "rejected", "failed", "cancelled"]),
9125
- patch: artifactSchema,
9126
- files: z15.array(z15.string().min(1).max(4e3)).max(2e3),
9127
- worktreeCleaned: z15.boolean(),
9128
- review: artifactSchema.optional(),
10171
+ var writerLaneSchema = z17.object({
10172
+ profile: z17.string(),
10173
+ reviewer: z17.string(),
10174
+ baseCommit: z17.string().regex(/^[a-f0-9]{40,64}$/u),
10175
+ outcome: z17.enum(["accepted", "rejected", "failed", "cancelled"]),
10176
+ patch: artifactSchema2,
10177
+ files: z17.array(z17.string().min(1).max(4e3)).max(2e3),
10178
+ worktreeCleaned: z17.boolean(),
10179
+ review: artifactSchema2.optional(),
9129
10180
  integration: writerIntegrationSchema.optional()
9130
10181
  }).strict();
9131
10182
  var manifestFields = {
9132
10183
  id: runIdSchema,
9133
- workspace: z15.string(),
9134
- objective: z15.string().max(3e4),
9135
- reviewer: z15.string(),
9136
- createdAt: z15.string(),
9137
- updatedAt: z15.string(),
9138
- status: z15.enum(["running", "accepted", "rejected", "failed"]),
9139
- maxReviewRounds: z15.number().int().min(0).max(3),
9140
- reviewRounds: z15.number().int().min(0).max(3),
9141
- agents: z15.array(agentRecordSchema).max(256),
9142
- messages: z15.array(messageRecordSchema).max(512)
10184
+ workspace: z17.string(),
10185
+ objective: z17.string().max(3e4),
10186
+ reviewer: z17.string(),
10187
+ createdAt: z17.string(),
10188
+ updatedAt: z17.string(),
10189
+ status: z17.enum(["running", "accepted", "rejected", "failed"]),
10190
+ maxReviewRounds: z17.number().int().min(0).max(3),
10191
+ reviewRounds: z17.number().int().min(0).max(3),
10192
+ agents: z17.array(agentRecordSchema).max(256),
10193
+ messages: z17.array(messageRecordSchema).max(512)
9143
10194
  };
9144
- var manifestV1Schema = z15.object({
9145
- version: z15.literal(1),
10195
+ var manifestV1Schema = z17.object({
10196
+ version: z17.literal(1),
9146
10197
  ...manifestFields
9147
10198
  }).strict();
9148
- var manifestV2Schema = z15.object({
9149
- version: z15.literal(2),
10199
+ var manifestV2Schema = z17.object({
10200
+ version: z17.literal(2),
9150
10201
  ...manifestFields,
9151
10202
  writer: writerLaneSchema.optional()
9152
10203
  }).strict();
9153
- var manifestSchema2 = z15.discriminatedUnion("version", [manifestV1Schema, manifestV2Schema]);
10204
+ var manifestSchema2 = z17.discriminatedUnion("version", [manifestV1Schema, manifestV2Schema]);
9154
10205
  var TeamRunStore = class {
9155
10206
  workspace;
9156
10207
  directory;
9157
10208
  managedDirectory;
9158
10209
  writes = Promise.resolve();
9159
10210
  constructor(workspace, directory) {
9160
- this.workspace = resolve15(workspace);
10211
+ this.workspace = resolve16(workspace);
9161
10212
  this.managedDirectory = directory === void 0;
9162
- this.directory = directory ? resolve15(directory) : join15(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
10213
+ this.directory = directory ? resolve16(directory) : join16(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
9163
10214
  }
9164
10215
  async create(input2) {
9165
10216
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -9243,10 +10294,10 @@ var TeamRunStore = class {
9243
10294
  runIdSchema.parse(runId);
9244
10295
  await this.writes;
9245
10296
  await this.assertRunDirectory(runId);
9246
- const path = join15(this.runDirectory(runId), "manifest.json");
10297
+ const path = join16(this.runDirectory(runId), "manifest.json");
9247
10298
  await this.assertRegularFile(path);
9248
- const manifest = manifestSchema2.parse(JSON.parse(await readFile14(path, "utf8")));
9249
- if (manifest.id !== runId || resolve15(manifest.workspace) !== this.workspace) {
10299
+ const manifest = manifestSchema2.parse(JSON.parse(await readFile15(path, "utf8")));
10300
+ if (manifest.id !== runId || resolve16(manifest.workspace) !== this.workspace) {
9250
10301
  throw new Error("Team run manifest identity does not match its location.");
9251
10302
  }
9252
10303
  if (verify) {
@@ -9260,13 +10311,13 @@ var TeamRunStore = class {
9260
10311
  }
9261
10312
  async readArtifact(runId, artifact) {
9262
10313
  await this.verifyArtifact(runId, artifact);
9263
- return readFile14(this.artifactPath(runId, artifact.sha256), "utf8");
10314
+ return readFile15(this.artifactPath(runId, artifact.sha256), "utf8");
9264
10315
  }
9265
10316
  async list() {
9266
10317
  await this.writes;
9267
10318
  try {
9268
10319
  await assertNoSymlinkPath(this.workspace, this.directory);
9269
- const entries = await readdir6(this.directory, { withFileTypes: true });
10320
+ const entries = await readdir7(this.directory, { withFileTypes: true });
9270
10321
  const summaries = [];
9271
10322
  for (const entry of entries) {
9272
10323
  if (!entry.isDirectory() || entry.isSymbolicLink() || !runIdSchema.safeParse(entry.name).success) continue;
@@ -9289,9 +10340,9 @@ var TeamRunStore = class {
9289
10340
  const directory = this.runDirectory(runId);
9290
10341
  await assertNoSymlinkPath(this.workspace, directory);
9291
10342
  try {
9292
- const info = await lstat17(directory);
10343
+ const info = await lstat18(directory);
9293
10344
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
9294
- await rm2(directory, { recursive: true });
10345
+ await rm3(directory, { recursive: true });
9295
10346
  return true;
9296
10347
  } catch (error) {
9297
10348
  if (error.code === "ENOENT") return false;
@@ -9314,31 +10365,31 @@ var TeamRunStore = class {
9314
10365
  }
9315
10366
  async loadUnlocked(runId) {
9316
10367
  await this.assertRunDirectory(runId);
9317
- const path = join15(this.runDirectory(runId), "manifest.json");
10368
+ const path = join16(this.runDirectory(runId), "manifest.json");
9318
10369
  await this.assertRegularFile(path);
9319
- return manifestSchema2.parse(JSON.parse(await readFile14(path, "utf8")));
10370
+ return manifestSchema2.parse(JSON.parse(await readFile15(path, "utf8")));
9320
10371
  }
9321
10372
  async writeManifest(manifest) {
9322
10373
  const directory = this.runDirectory(manifest.id);
9323
10374
  await ensureWorkspaceStorageDirectory(this.workspace, directory, {
9324
10375
  requireActiveNamespace: this.managedDirectory
9325
10376
  });
9326
- await atomicWrite(join15(directory, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
10377
+ await atomicWrite(join16(directory, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
9327
10378
  `, 384);
9328
10379
  }
9329
10380
  async writeArtifact(runId, content, truncate = true) {
9330
10381
  const data = boundedArtifactText(content, 5e5, truncate);
9331
10382
  const bytes = Buffer.byteLength(data);
9332
- const sha256 = createHash11("sha256").update(data).digest("hex");
9333
- const directory = join15(this.runDirectory(runId), "blobs");
10383
+ const sha256 = createHash12("sha256").update(data).digest("hex");
10384
+ const directory = join16(this.runDirectory(runId), "blobs");
9334
10385
  await ensureWorkspaceStorageDirectory(this.workspace, directory, {
9335
10386
  requireActiveNamespace: this.managedDirectory
9336
10387
  });
9337
- const path = join15(directory, `${sha256}.txt`);
10388
+ const path = join16(directory, `${sha256}.txt`);
9338
10389
  try {
9339
10390
  await this.assertRegularFile(path);
9340
- const existing = await readFile14(path);
9341
- if (createHash11("sha256").update(existing).digest("hex") !== sha256) {
10391
+ const existing = await readFile15(path);
10392
+ if (createHash12("sha256").update(existing).digest("hex") !== sha256) {
9342
10393
  throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
9343
10394
  }
9344
10395
  } catch (error) {
@@ -9358,27 +10409,27 @@ var TeamRunStore = class {
9358
10409
  hashSchema.parse(artifact.sha256);
9359
10410
  const path = this.artifactPath(runId, artifact.sha256);
9360
10411
  await this.assertRegularFile(path);
9361
- const data = await readFile14(path);
9362
- const hash = createHash11("sha256").update(data).digest("hex");
9363
- if (hash !== artifact.sha256 || data.byteLength !== artifact.bytes) {
10412
+ const data = await readFile15(path);
10413
+ const hash2 = createHash12("sha256").update(data).digest("hex");
10414
+ if (hash2 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
9364
10415
  throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
9365
10416
  }
9366
10417
  }
9367
10418
  artifactPath(runId, sha256) {
9368
- return join15(this.runDirectory(runId), "blobs", `${sha256}.txt`);
10419
+ return join16(this.runDirectory(runId), "blobs", `${sha256}.txt`);
9369
10420
  }
9370
10421
  runDirectory(runId) {
9371
- return join15(this.directory, runId);
10422
+ return join16(this.directory, runId);
9372
10423
  }
9373
10424
  async assertRunDirectory(runId) {
9374
10425
  const directory = this.runDirectory(runId);
9375
10426
  await assertNoSymlinkPath(this.workspace, directory);
9376
- const info = await lstat17(directory);
10427
+ const info = await lstat18(directory);
9377
10428
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
9378
10429
  }
9379
10430
  async assertRegularFile(path) {
9380
- await assertNoSymlinkPath(this.workspace, resolve15(path, ".."));
9381
- const info = await lstat17(path);
10431
+ await assertNoSymlinkPath(this.workspace, resolve16(path, ".."));
10432
+ const info = await lstat18(path);
9382
10433
  if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Team run file is not a regular file: ${path}`);
9383
10434
  }
9384
10435
  };
@@ -9422,10 +10473,10 @@ function resolveAgentModelRoute(team, parent, profile) {
9422
10473
  }
9423
10474
 
9424
10475
  // src/agent/writer-lane.ts
9425
- import { createHash as createHash12 } from "node:crypto";
9426
- import { lstat as lstat18, mkdtemp, realpath as realpath7, rm as rm3 } from "node:fs/promises";
10476
+ import { createHash as createHash13 } from "node:crypto";
10477
+ import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
9427
10478
  import { tmpdir as tmpdir2 } from "node:os";
9428
- import { isAbsolute as isAbsolute5, join as join16, resolve as resolve16 } from "node:path";
10479
+ import { isAbsolute as isAbsolute5, join as join17, resolve as resolve17 } from "node:path";
9429
10480
  var WriterLaneApplyError = class extends Error {
9430
10481
  constructor(message2, attempted, cause) {
9431
10482
  super(message2, cause === void 0 ? void 0 : { cause });
@@ -9438,18 +10489,18 @@ var WriterLane = class {
9438
10489
  workspace;
9439
10490
  constructor(workspace, workspaceRoots) {
9440
10491
  this.workspace = new WorkspaceAccess([
9441
- resolve16(workspace),
9442
- ...workspaceRoots.map((root) => resolve16(root))
10492
+ resolve17(workspace),
10493
+ ...workspaceRoots.map((root) => resolve17(root))
9443
10494
  ]);
9444
10495
  }
9445
10496
  async createDraft(maxPatchBytes, operation, signal) {
9446
10497
  const repository = await this.repository();
9447
10498
  const baseCommit = await this.head(repository);
9448
10499
  const lease = await acquireNamespaceLease(
9449
- join16(repository.commonDirectory, "skein-writer-lane"),
10500
+ join17(repository.commonDirectory, "skein-writer-lane"),
9450
10501
  "exclusive"
9451
10502
  );
9452
- const worktree = await mkdtemp(join16(tmpdir2(), "skein-writer-"));
10503
+ const worktree = await mkdtemp(join17(tmpdir2(), "skein-writer-"));
9453
10504
  let added = false;
9454
10505
  let value;
9455
10506
  let patch = "";
@@ -9520,7 +10571,7 @@ var WriterLane = class {
9520
10571
  return {
9521
10572
  baseCommit,
9522
10573
  patch,
9523
- patchSha256: createHash12("sha256").update(patch).digest("hex"),
10574
+ patchSha256: createHash13("sha256").update(patch).digest("hex"),
9524
10575
  files,
9525
10576
  worktreeCleaned,
9526
10577
  value
@@ -9539,7 +10590,7 @@ var WriterLane = class {
9539
10590
  try {
9540
10591
  const repository = await this.repository();
9541
10592
  const lease = await acquireNamespaceLease(
9542
- join16(repository.commonDirectory, "skein-writer-lane"),
10593
+ join17(repository.commonDirectory, "skein-writer-lane"),
9543
10594
  "exclusive"
9544
10595
  );
9545
10596
  try {
@@ -9647,7 +10698,7 @@ var WriterLane = class {
9647
10698
  if (isAbsolute5(file) || file === ".git" || file.startsWith(".git/") || file.includes("\0")) {
9648
10699
  throw new Error(`Writer patch contains an unsafe path: ${file}`);
9649
10700
  }
9650
- await this.workspace.resolvePath(join16(repository.root, file), { allowMissing: true });
10701
+ await this.workspace.resolvePath(join17(repository.root, file), { allowMissing: true });
9651
10702
  }
9652
10703
  if (expectedFiles && !sameSet(files, expectedFiles)) {
9653
10704
  throw new Error("Writer patch paths do not match the persisted file manifest.");
@@ -9662,7 +10713,7 @@ var WriterLane = class {
9662
10713
  if (topLevel.exitCode !== 0 || !topLevel.stdout.trim()) {
9663
10714
  throw new Error("The primary workspace must be a Git repository root for writer lanes.");
9664
10715
  }
9665
- const discoveredRoot = resolve16(topLevel.stdout.trim());
10716
+ const discoveredRoot = resolve17(topLevel.stdout.trim());
9666
10717
  if (await realpath7(discoveredRoot) !== await realpath7(root)) {
9667
10718
  throw new Error("The primary workspace must be the Git repository root for writer lanes.");
9668
10719
  }
@@ -9671,7 +10722,7 @@ var WriterLane = class {
9671
10722
  if (common.exitCode !== 0 || !common.stdout.trim()) {
9672
10723
  throw new Error(`Unable to resolve the Git common directory: ${processDetail(common)}`);
9673
10724
  }
9674
- const commonDirectory = await realpath7(isAbsolute5(common.stdout.trim()) ? common.stdout.trim() : resolve16(repositoryRoot, common.stdout.trim()));
10725
+ const commonDirectory = await realpath7(isAbsolute5(common.stdout.trim()) ? common.stdout.trim() : resolve17(repositoryRoot, common.stdout.trim()));
9675
10726
  return { root: repositoryRoot, commonDirectory, runtime };
9676
10727
  }
9677
10728
  async head(repository) {
@@ -9683,7 +10734,7 @@ var WriterLane = class {
9683
10734
  return value;
9684
10735
  }
9685
10736
  async cleanupWorktree(repository, worktree, added) {
9686
- const physicalWorktree = await realpath7(worktree).catch(() => resolve16(worktree));
10737
+ const physicalWorktree = await realpath7(worktree).catch(() => resolve17(worktree));
9687
10738
  if (added) {
9688
10739
  await runIsolatedGit(repository.runtime, [
9689
10740
  "worktree",
@@ -9692,7 +10743,7 @@ var WriterLane = class {
9692
10743
  worktree
9693
10744
  ], repository.root, { timeoutMs: 6e4 }).catch(() => void 0);
9694
10745
  }
9695
- await rm3(worktree, { recursive: true, force: true }).catch(() => void 0);
10746
+ await rm4(worktree, { recursive: true, force: true }).catch(() => void 0);
9696
10747
  await runIsolatedGit(repository.runtime, [
9697
10748
  "worktree",
9698
10749
  "prune",
@@ -9741,7 +10792,7 @@ function processDetail(result) {
9741
10792
  }
9742
10793
  async function pathExists2(path) {
9743
10794
  try {
9744
- await lstat18(path);
10795
+ await lstat19(path);
9745
10796
  return true;
9746
10797
  } catch (error) {
9747
10798
  if (error.code === "ENOENT") return false;
@@ -9750,14 +10801,14 @@ async function pathExists2(path) {
9750
10801
  }
9751
10802
 
9752
10803
  // src/agent/delegation.ts
9753
- var writerRunInputSchema = z16.object({
9754
- task: z16.string().min(1).max(2e4),
9755
- profile: z16.string().max(64).optional(),
9756
- reviewer: z16.string().max(64).optional()
10804
+ var writerRunInputSchema = z18.object({
10805
+ task: z18.string().min(1).max(2e4),
10806
+ profile: z18.string().max(64).optional(),
10807
+ reviewer: z18.string().max(64).optional()
9757
10808
  }).strict();
9758
- var writerIntegrateInputSchema = z16.object({
9759
- run_id: z16.string().uuid(),
9760
- patch_sha256: z16.string().regex(/^[a-f0-9]{64}$/u)
10809
+ var writerIntegrateInputSchema = z18.object({
10810
+ run_id: z18.string().uuid(),
10811
+ patch_sha256: z18.string().regex(/^[a-f0-9]{64}$/u)
9761
10812
  }).strict();
9762
10813
  var DelegationManager = class {
9763
10814
  constructor(options) {
@@ -9818,10 +10869,10 @@ var DelegationManager = class {
9818
10869
  },
9819
10870
  async execute(arguments_, context) {
9820
10871
  if (!manager.team.enabled) return { ok: false, content: "Multi-agent delegation is disabled." };
9821
- const input2 = z16.object({
9822
- tasks: z16.array(z16.object({
9823
- profile: z16.string().max(64).optional(),
9824
- task: z16.string().min(1).max(2e4)
10872
+ const input2 = z18.object({
10873
+ tasks: z18.array(z18.object({
10874
+ profile: z18.string().max(64).optional(),
10875
+ task: z18.string().min(1).max(2e4)
9825
10876
  })).min(1).max(manager.team.maxDelegations)
9826
10877
  }).parse(arguments_);
9827
10878
  const tasks = input2.tasks.map((task) => ({
@@ -9867,13 +10918,13 @@ var DelegationManager = class {
9867
10918
  },
9868
10919
  async execute(arguments_, context) {
9869
10920
  if (!manager.team.enabled) return { ok: false, content: "Multi-agent teams are disabled." };
9870
- const input2 = z16.object({
9871
- objective: z16.string().min(1).max(3e4),
9872
- tasks: z16.array(z16.object({
9873
- profile: z16.string().max(64).optional(),
9874
- task: z16.string().min(1).max(2e4)
10921
+ const input2 = z18.object({
10922
+ objective: z18.string().min(1).max(3e4),
10923
+ tasks: z18.array(z18.object({
10924
+ profile: z18.string().max(64).optional(),
10925
+ task: z18.string().min(1).max(2e4)
9875
10926
  })).min(1).max(manager.team.maxDelegations),
9876
- reviewer: z16.string().max(64).optional()
10927
+ reviewer: z18.string().max(64).optional()
9877
10928
  }).parse(arguments_);
9878
10929
  const tasks = input2.tasks.map((task) => ({
9879
10930
  profile: task.profile ?? manager.team.defaultProfile,
@@ -9936,7 +10987,7 @@ var DelegationManager = class {
9936
10987
  const plan = await manager.loadWriterPlan(input2.run_id, input2.patch_sha256);
9937
10988
  const files = await manager.writerLane.inspectPatch(plan.patch, plan.writer.files);
9938
10989
  return Promise.all(files.map(
9939
- (file) => context.workspace.resolvePath(join17(context.workspace.primaryRoot, file), { allowMissing: true })
10990
+ (file) => context.workspace.resolvePath(join18(context.workspace.primaryRoot, file), { allowMissing: true })
9940
10991
  ));
9941
10992
  },
9942
10993
  async execute(arguments_, context) {
@@ -10123,7 +11174,7 @@ ${review.summary}`,
10123
11174
  const plan = await this.loadWriterPlan(runId, patchSha256);
10124
11175
  const files = await this.writerLane.inspectPatch(plan.patch, plan.writer.files);
10125
11176
  const paths = await Promise.all(files.map(
10126
- (file) => context.workspace.resolvePath(join17(context.workspace.primaryRoot, file), { allowMissing: true })
11177
+ (file) => context.workspace.resolvePath(join18(context.workspace.primaryRoot, file), { allowMissing: true })
10127
11178
  ));
10128
11179
  const checkpointStore = new CheckpointStore(context.workspace);
10129
11180
  let checkpointId = context.checkpointId;
@@ -11406,9 +12457,10 @@ var HeadlessReporter = class {
11406
12457
  }
11407
12458
  if (!this.options.quiet && !this.options.compact) {
11408
12459
  const usage = session.usage.inputTokens + session.usage.outputTokens;
12460
+ const usageLabel = tokenUsageLabel(session.usage);
11409
12461
  process.stderr.write(this.paint.dim(
11410
12462
  `
11411
- ${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)}
11412
12464
  `
11413
12465
  ));
11414
12466
  }
@@ -11432,16 +12484,19 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
11432
12484
  if (!compact2) process.stderr.write(this.paint.dim(`${this.glyphs.meta} reasoning ${this.glyphs.separator} turn ${event.turn}
11433
12485
  `));
11434
12486
  break;
11435
- case "context":
12487
+ case "context": {
12488
+ const budget = event.packed.budgetTokens === void 0 ? "" : ` ${this.glyphs.separator} ${event.packed.budgetTier ?? "adaptive"} ${event.packed.budgetTokens} budget`;
11436
12489
  process.stderr.write(this.paint.cyan(
11437
- `${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}` : ""}
11438
12491
  `
11439
12492
  ));
11440
12493
  break;
12494
+ }
11441
12495
  case "prompt":
11442
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}` : "";
11443
12498
  process.stderr.write(this.paint.dim(
11444
- `${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}
11445
12500
  `
11446
12501
  ));
11447
12502
  }
@@ -11573,9 +12628,14 @@ function sessionSummary(session) {
11573
12628
  ...session.taskContract ? { taskContract: session.taskContract } : {},
11574
12629
  changedFiles: session.changedFiles,
11575
12630
  ...session.lastRun ? { lastRun: session.lastRun } : {},
12631
+ ...session.tokenLedger?.length ? { tokenLedger: session.tokenLedger } : {},
11576
12632
  usage: session.usage
11577
12633
  };
11578
12634
  }
12635
+ function tokenUsageLabel(usage) {
12636
+ if (usage.source) return usage.source;
12637
+ return usage.inputTokens + usage.outputTokens > 0 ? "unknown source" : "no usage";
12638
+ }
11579
12639
  function formatToolDetail(call, paint = chalk2, glyphs = resolveCliGlyphs()) {
11580
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() : [];
11581
12641
  const environmentDetail = environment.length ? ` ${glyphs.separator} env ${environment.join(", ")}` : "";
@@ -11598,7 +12658,7 @@ function formatResultDetail(result, paint = chalk2, glyphs = resolveCliGlyphs())
11598
12658
  }
11599
12659
 
11600
12660
  // src/cli/namespace-leases.ts
11601
- import { resolve as resolve17 } from "node:path";
12661
+ import { resolve as resolve18 } from "node:path";
11602
12662
  function cliNamespaceLeaseScopes(actionCommand) {
11603
12663
  const names = commandNames(actionCommand);
11604
12664
  const topLevel = names[1];
@@ -11620,7 +12680,7 @@ async function acquireCliNamespaceLeases(actionCommand) {
11620
12680
  const scopes = cliNamespaceLeaseScopes(actionCommand);
11621
12681
  const localOptions = actionCommand.opts();
11622
12682
  const globalOptions = actionCommand.optsWithGlobals();
11623
- const workspace = resolve17(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
12683
+ const workspace = resolve18(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
11624
12684
  const targets = scopes.map((scope) => scope === "project" ? projectNamespacePaths(workspace).canonical : homeNamespacePaths().canonical);
11625
12685
  const leases = [];
11626
12686
  try {
@@ -11774,11 +12834,11 @@ function command(name, description, usage, aliases) {
11774
12834
 
11775
12835
  // src/ui/text.ts
11776
12836
  import stringWidth from "string-width";
11777
- import stripAnsi from "strip-ansi";
12837
+ import stripAnsi2 from "strip-ansi";
11778
12838
  var controlCharacters = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
11779
12839
  var escapeSequences = /[[\]][0-9;?=>!]*[ -/]*[@-~]|[[\]][?=>][0-9;?=>!]*[a-zA-Z~]/gu;
11780
12840
  function sanitizeTerminalText(value) {
11781
- return stripAnsi(value).replace(escapeSequences, "").replace(/\r\n?/g, "\n").replace(controlCharacters, "");
12841
+ return stripAnsi2(value).replace(escapeSequences, "").replace(/\r\n?/g, "\n").replace(controlCharacters, "");
11782
12842
  }
11783
12843
  function terminalEllipsis() {
11784
12844
  return process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii" ? "..." : "\u2026";
@@ -11871,8 +12931,8 @@ function graphemes(value) {
11871
12931
  }
11872
12932
 
11873
12933
  // src/ui/theme.ts
11874
- import { lstat as lstat19, readdir as readdir7, readFile as readFile15 } from "node:fs/promises";
11875
- import { basename as basename9, join as join18, resolve as resolve18 } from "node:path";
12934
+ import { lstat as lstat20, readdir as readdir8, readFile as readFile16 } from "node:fs/promises";
12935
+ import { basename as basename9, join as join19, resolve as resolve19 } from "node:path";
11876
12936
  import React, { createContext, useContext } from "react";
11877
12937
  function defineTheme(seed) {
11878
12938
  return {
@@ -11994,10 +13054,10 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
11994
13054
  userThemeNames.clear();
11995
13055
  const loaded = [];
11996
13056
  const errors = [];
11997
- const resolvedDirectory = resolve18(directory);
13057
+ const resolvedDirectory = resolve19(directory);
11998
13058
  let entries;
11999
13059
  try {
12000
- entries = await readdir7(resolvedDirectory, { encoding: "utf8" });
13060
+ entries = await readdir8(resolvedDirectory, { encoding: "utf8" });
12001
13061
  } catch (error) {
12002
13062
  if (error.code === "ENOENT") {
12003
13063
  return { directory: resolvedDirectory, loaded, errors };
@@ -12006,13 +13066,13 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
12006
13066
  }
12007
13067
  for (const entry of entries) {
12008
13068
  if (!entry.endsWith(".json")) continue;
12009
- const path = join18(resolvedDirectory, entry);
13069
+ const path = join19(resolvedDirectory, entry);
12010
13070
  try {
12011
- const info = await lstat19(path);
13071
+ const info = await lstat20(path);
12012
13072
  if (!info.isFile() || info.isSymbolicLink() || info.size > 64e3) {
12013
13073
  throw new Error("must be a regular JSON file smaller than 64 KB");
12014
13074
  }
12015
- const parsed = JSON.parse(await readFile15(path, "utf8"));
13075
+ const parsed = JSON.parse(await readFile16(path, "utf8"));
12016
13076
  const name = themeName(parsed, basename9(entry, ".json"));
12017
13077
  if (builtInThemeNames.has(name)) throw new Error(`cannot replace built-in theme \`${name}\``);
12018
13078
  themes[name] = defineTheme(themeSeed(parsed, name));
@@ -12025,7 +13085,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
12025
13085
  return { directory: resolvedDirectory, loaded, errors };
12026
13086
  }
12027
13087
  function userThemeDirectory(environment = process.env) {
12028
- return environment.SKEIN_THEME_DIR ?? environment.MOSAIC_THEME_DIR ?? join18(resolveHomeNamespace(environment), "themes");
13088
+ return environment.SKEIN_THEME_DIR ?? environment.MOSAIC_THEME_DIR ?? join19(resolveHomeNamespace(environment), "themes");
12029
13089
  }
12030
13090
  var defaultTheme = themes.graphite;
12031
13091
  var palette = {
@@ -12300,10 +13360,11 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
12300
13360
  width,
12301
13361
  glyph: glyphs.context,
12302
13362
  label: "context",
12303
- 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` : ""}`,
12304
13364
  labelColor: theme.accent
12305
13365
  }
12306
13366
  ),
13367
+ !compact2 && item.budgetReason ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`${glyphs.branchLast} ${sanitizeInlineTerminalText(item.budgetReason)}`, innerWidth) }) : null,
12307
13368
  spans.slice(0, spanLimit).map((span, spanIndex) => {
12308
13369
  const lines = span.startLine === span.endLine ? `${span.startLine}` : `${span.startLine}-${span.endLine}`;
12309
13370
  const location = `${compactDisplayPath(sanitizeInlineTerminalText(span.path), 44)}:${lines}`;
@@ -12325,13 +13386,14 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
12325
13386
  ] }, item.id);
12326
13387
  }
12327
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`;
12328
13390
  return /* @__PURE__ */ jsx(
12329
13391
  MetaRow,
12330
13392
  {
12331
13393
  width,
12332
13394
  glyph: glyphs.pending,
12333
13395
  label: `prompt/${sanitizeInlineTerminalText(item.intent)}`,
12334
- detail: `${item.sections.map(sanitizeInlineTerminalText).join(` ${glyphs.separator} `)} ${glyphs.separator} ~${formatTokens(item.tokens)}`
13396
+ detail
12335
13397
  },
12336
13398
  item.id
12337
13399
  );
@@ -13252,9 +14314,9 @@ function safeWidth(width) {
13252
14314
  }
13253
14315
 
13254
14316
  // src/utils/update-check.ts
13255
- import { mkdir as mkdir9, readFile as readFile16, writeFile as writeFile2 } from "node:fs/promises";
13256
- import { join as join19 } from "node:path";
13257
- import stripAnsi2 from "strip-ansi";
14317
+ import { mkdir as mkdir9, readFile as readFile17, writeFile as writeFile2 } from "node:fs/promises";
14318
+ import { join as join20 } from "node:path";
14319
+ import stripAnsi3 from "strip-ansi";
13258
14320
  var PACKAGE_NAME = "@skein-code/cli";
13259
14321
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
13260
14322
  var CACHE_FILE = "update-check.json";
@@ -13269,7 +14331,7 @@ function sanitizeHighlights(value) {
13269
14331
  const cleaned = [];
13270
14332
  for (const entry of value) {
13271
14333
  if (typeof entry !== "string") continue;
13272
- const flattened = stripAnsi2(entry).replace(BIDI_CONTROLS, "").replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ").replace(/\s+/gu, " ").trim();
14334
+ const flattened = stripAnsi3(entry).replace(BIDI_CONTROLS, "").replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ").replace(/\s+/gu, " ").trim();
13273
14335
  if (!flattened || flattened.length > MAX_HIGHLIGHT_LENGTH) continue;
13274
14336
  cleaned.push(flattened);
13275
14337
  if (cleaned.length >= MAX_HIGHLIGHTS) break;
@@ -13333,7 +14395,7 @@ function compareVersions(a, b) {
13333
14395
  return 0;
13334
14396
  }
13335
14397
  function updateCachePath(env = process.env) {
13336
- return join19(resolveHomeNamespace(env), CACHE_FILE);
14398
+ return join20(resolveHomeNamespace(env), CACHE_FILE);
13337
14399
  }
13338
14400
  function upgradeCommand() {
13339
14401
  return `npm i -g ${PACKAGE_NAME}`;
@@ -13350,7 +14412,7 @@ function noticeIfNewer(latest, current, highlights) {
13350
14412
  }
13351
14413
  async function readUpdateCache(env = process.env) {
13352
14414
  try {
13353
- const raw = await readFile16(updateCachePath(env), "utf8");
14415
+ const raw = await readFile17(updateCachePath(env), "utf8");
13354
14416
  const parsed = JSON.parse(raw);
13355
14417
  if (typeof parsed?.checkedAt === "number" && Number.isFinite(parsed.checkedAt) && parsed.checkedAt >= 0 && (parsed.latest === null || typeof parsed.latest === "string" && parseVersion(parsed.latest))) {
13356
14418
  const latest = typeof parsed.latest === "string" ? parsed.latest : null;
@@ -14431,7 +15493,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
14431
15493
  return () => clearInterval(timer);
14432
15494
  }, [busy]);
14433
15495
  const requestPermission = useCallback((call, category) => {
14434
- return new Promise((resolve23) => setPermission({ call, category, resolve: resolve23 }));
15496
+ return new Promise((resolve24) => setPermission({ call, category, resolve: resolve24 }));
14435
15497
  }, []);
14436
15498
  const onEvent = useCallback((event) => {
14437
15499
  switch (event.type) {
@@ -14446,6 +15508,9 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
14446
15508
  engine: event.packed.engine,
14447
15509
  hits: event.packed.hits.length,
14448
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 } : {},
14449
15514
  truncated: event.packed.truncated,
14450
15515
  spans: event.packed.hits.slice(0, 5).map((hit) => ({
14451
15516
  path: relative8(runner.workspace.primaryRoot, hit.path) || hit.path,
@@ -14459,7 +15524,14 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
14459
15524
  setActivity({ label: "Assembling relevant context", startedAt: Date.now() });
14460
15525
  break;
14461
15526
  case "prompt":
14462
- 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
+ });
14463
15535
  setActivity({ label: "Preparing the model prompt", startedAt: Date.now() });
14464
15536
  break;
14465
15537
  case "assistant_delta":
@@ -15006,7 +16078,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
15006
16078
  { label: config.agents?.enabled ? `${config.agents.maxConcurrent} concurrent` : "disabled", detail: "expert delegation" },
15007
16079
  {
15008
16080
  label: `${usage.inputTokens.toLocaleString()} in ${separator} ${usage.outputTokens.toLocaleString()} out`,
15009
- 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"}`
15010
16082
  },
15011
16083
  {
15012
16084
  label: `${Math.round(status.pressure * 100)}% context pressure`,
@@ -15255,8 +16327,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
15255
16327
  }, [append]);
15256
16328
  function settlePermission(grant, stop = false) {
15257
16329
  if (!permission) return;
15258
- const { call, category, resolve: resolve23 } = permission;
15259
- resolve23(grant);
16330
+ const { call, category, resolve: resolve24 } = permission;
16331
+ resolve24(grant);
15260
16332
  setPermission(void 0);
15261
16333
  if (grant === "session") {
15262
16334
  append({
@@ -16591,24 +17663,24 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
16591
17663
  }
16592
17664
 
16593
17665
  // src/runtime/extensions.ts
16594
- import { resolve as resolve21 } from "node:path";
17666
+ import { resolve as resolve22 } from "node:path";
16595
17667
 
16596
17668
  // src/mcp/manager.ts
16597
17669
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
16598
17670
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
16599
17671
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
16600
- import stripAnsi4 from "strip-ansi";
17672
+ import stripAnsi5 from "strip-ansi";
16601
17673
 
16602
17674
  // src/mcp/tool.ts
16603
- import { createHash as createHash13 } from "node:crypto";
16604
- import stripAnsi3 from "strip-ansi";
17675
+ import { createHash as createHash14 } from "node:crypto";
17676
+ import stripAnsi4 from "strip-ansi";
16605
17677
  var MAX_ARGUMENT_BYTES = 256e3;
16606
17678
  var MAX_DESCRIPTION_LENGTH = 4e3;
16607
- var MAX_RESULT_LENGTH = 8e4;
17679
+ var MAX_RESULT_BYTES = 5 * 1024 * 1024;
16608
17680
  var MAX_SCHEMA_BYTES = 1e5;
16609
17681
  function createMcpToolAdapter(options) {
16610
17682
  const { remoteTool } = options;
16611
- const inputSchema11 = copyInputSchema(remoteTool.inputSchema);
17683
+ const inputSchema12 = copyInputSchema(remoteTool.inputSchema);
16612
17684
  return {
16613
17685
  definition: {
16614
17686
  name: options.exposedName,
@@ -16616,7 +17688,7 @@ function createMcpToolAdapter(options) {
16616
17688
  // MCP servers are an external trust boundary. Read-only annotations are
16617
17689
  // hints from that server and must not lower the local permission level.
16618
17690
  category: "network",
16619
- inputSchema: inputSchema11
17691
+ inputSchema: inputSchema12
16620
17692
  },
16621
17693
  permissionCategories: () => ["network"],
16622
17694
  async execute(arguments_, context) {
@@ -16636,6 +17708,8 @@ function createMcpToolAdapter(options) {
16636
17708
  metadata: {
16637
17709
  mcpServer: options.serverName,
16638
17710
  mcpTool: remoteTool.name,
17711
+ sourceBytes: normalized.sourceBytes,
17712
+ sourceTruncated: normalized.sourceTruncated,
16639
17713
  ...normalized.isError ? { mcpError: true } : {}
16640
17714
  }
16641
17715
  };
@@ -16665,8 +17739,8 @@ function isUsableRemoteTool(tool) {
16665
17739
  }
16666
17740
  }
16667
17741
  function describeTool(serverName, tool) {
16668
- const label = stripAnsi3(tool.title?.trim() || tool.name).replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 200);
16669
- const description = tool.description ? stripAnsi3(tool.description).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim().slice(0, MAX_DESCRIPTION_LENGTH) : void 0;
17742
+ const label = stripAnsi4(tool.title?.trim() || tool.name).replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 200);
17743
+ const description = tool.description ? stripAnsi4(tool.description).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim().slice(0, MAX_DESCRIPTION_LENGTH) : void 0;
16670
17744
  return description ? `[MCP ${serverName}/${label}] ${description}` : `Call the ${label} tool provided by the ${serverName} MCP server.`;
16671
17745
  }
16672
17746
  function copyInputSchema(schema) {
@@ -16689,7 +17763,7 @@ function assertArguments(arguments_) {
16689
17763
  }
16690
17764
  function normalizeCallResult(result) {
16691
17765
  if (!isRecord2(result)) {
16692
- return { content: truncateResult(safeJson(result)), isError: false };
17766
+ return boundResult(safeJson(result), false);
16693
17767
  }
16694
17768
  const isError = result.isError === true;
16695
17769
  const sections = [];
@@ -16705,7 +17779,22 @@ ${safeJson(result.structuredContent)}`);
16705
17779
  ${safeJson(result.toolResult)}`);
16706
17780
  }
16707
17781
  const content = sections.filter(Boolean).join("\n\n") || (isError ? "The MCP tool reported an error." : "The MCP tool completed without output.");
16708
- return { content: truncateResult(content), isError };
17782
+ return boundResult(content, isError);
17783
+ }
17784
+ function boundResult(content, isError) {
17785
+ const sourceBytes = Buffer.byteLength(content);
17786
+ if (sourceBytes <= MAX_RESULT_BYTES) {
17787
+ return { content, isError, sourceBytes, sourceTruncated: false };
17788
+ }
17789
+ const marker = `
17790
+ ... ${sourceBytes - MAX_RESULT_BYTES} or more source bytes omitted by the MCP safety limit ...
17791
+ `;
17792
+ const markerBytes = Buffer.byteLength(marker);
17793
+ const available = MAX_RESULT_BYTES - markerBytes;
17794
+ const headBytes = Math.floor(available * 0.6);
17795
+ const tailBytes = available - headBytes;
17796
+ const bounded = `${sliceUtf8Start(content, headBytes)}${marker}${sliceUtf8End(content, tailBytes)}`;
17797
+ return { content: bounded, isError, sourceBytes, sourceTruncated: true };
16709
17798
  }
16710
17799
  function formatContentBlock(block) {
16711
17800
  if (!isRecord2(block) || typeof block.type !== "string") return safeJson(block);
@@ -16753,15 +17842,10 @@ function fitToolName(name, identity) {
16753
17842
  return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
16754
17843
  }
16755
17844
  function shortHash(value) {
16756
- return createHash13("sha256").update(value).digest("hex").slice(0, 8);
16757
- }
16758
- function truncateResult(content) {
16759
- if (content.length <= MAX_RESULT_LENGTH) return content;
16760
- return `${content.slice(0, MAX_RESULT_LENGTH)}
16761
- ... MCP result truncated`;
17845
+ return createHash14("sha256").update(value).digest("hex").slice(0, 8);
16762
17846
  }
16763
17847
  function sanitizeOutputText(value) {
16764
- return stripAnsi3(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
17848
+ return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
16765
17849
  }
16766
17850
  function sanitizeInlineText(value) {
16767
17851
  return sanitizeOutputText(value).replace(/[\r\n\t]+/g, " ").trim().slice(0, 2e3);
@@ -16773,13 +17857,25 @@ function safeJson(value) {
16773
17857
  return String(value);
16774
17858
  }
16775
17859
  }
17860
+ function sliceUtf8Start(value, maxBytes) {
17861
+ const buffer = Buffer.from(value);
17862
+ let end = Math.min(buffer.length, maxBytes);
17863
+ while (end > 0 && (buffer[end] ?? 0) >= 128 && (buffer[end] ?? 0) < 192) end -= 1;
17864
+ return buffer.subarray(0, end).toString("utf8");
17865
+ }
17866
+ function sliceUtf8End(value, maxBytes) {
17867
+ const buffer = Buffer.from(value);
17868
+ let start = Math.max(0, buffer.length - maxBytes);
17869
+ while (start < buffer.length && (buffer[start] ?? 0) >= 128 && (buffer[start] ?? 0) < 192) start += 1;
17870
+ return buffer.subarray(start).toString("utf8");
17871
+ }
16776
17872
  function isRecord2(value) {
16777
17873
  return typeof value === "object" && value !== null && !Array.isArray(value);
16778
17874
  }
16779
17875
 
16780
17876
  // src/mcp/validation.ts
16781
17877
  import { stat as stat10, realpath as realpath8 } from "node:fs/promises";
16782
- import { isAbsolute as isAbsolute6, resolve as resolve19 } from "node:path";
17878
+ import { isAbsolute as isAbsolute6, resolve as resolve20 } from "node:path";
16783
17879
  var SERVER_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
16784
17880
  var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
16785
17881
  var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
@@ -16851,9 +17947,9 @@ async function validateCwd(configured, options) {
16851
17947
  if (configured !== void 0 && (configured.length > 4e3 || CONTROL_CHARACTERS.test(configured))) {
16852
17948
  throw new Error("MCP working directory is invalid or too long");
16853
17949
  }
16854
- const defaultCwd = resolve19(options.cwd ?? process.cwd());
16855
- const candidate = configured ? isAbsolute6(configured) ? resolve19(configured) : resolve19(defaultCwd, configured) : defaultCwd;
16856
- const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) => resolve19(root)) : [defaultCwd];
17950
+ const defaultCwd = resolve20(options.cwd ?? process.cwd());
17951
+ const candidate = configured ? isAbsolute6(configured) ? resolve20(configured) : resolve20(defaultCwd, configured) : defaultCwd;
17952
+ const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) => resolve20(root)) : [defaultCwd];
16857
17953
  const resolvedCandidate = await realpath8(candidate).catch(() => {
16858
17954
  throw new Error(`MCP working directory does not exist: ${candidate}`);
16859
17955
  });
@@ -17191,7 +18287,7 @@ var McpManager = class {
17191
18287
  stderr: "pipe"
17192
18288
  });
17193
18289
  transport.stderr?.on("data", (chunk) => {
17194
- const text = stripAnsi4(chunk.toString()).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim();
18290
+ const text = stripAnsi5(chunk.toString()).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim();
17195
18291
  if (text) this.options.logger?.(`MCP ${name} stderr`, { text: text.slice(0, 2e3) });
17196
18292
  });
17197
18293
  return transport;
@@ -17350,7 +18446,7 @@ function errorMessage2(error) {
17350
18446
  return sanitizeStatusText(message2) || "Unknown MCP error";
17351
18447
  }
17352
18448
  function sanitizeStatusText(value) {
17353
- return stripAnsi4(value).replace(/[\u0000-\u001f\u007f]/g, " ").trim().slice(0, 1e3);
18449
+ return stripAnsi5(value).replace(/[\u0000-\u001f\u007f]/g, " ").trim().slice(0, 1e3);
17354
18450
  }
17355
18451
  function timeoutError(timeoutMs) {
17356
18452
  const error = new Error(`MCP request timed out after ${timeoutMs} ms`);
@@ -17376,9 +18472,9 @@ async function closeTransportQuietly(transport) {
17376
18472
  }
17377
18473
 
17378
18474
  // src/memory/tools.ts
17379
- import { z as z17 } from "zod";
17380
- var scopeSchema = z17.enum(["user", "workspace", "session", "agent"]);
17381
- var kindSchema = z17.enum(["semantic", "episodic", "procedural"]);
18475
+ import { z as z19 } from "zod";
18476
+ var scopeSchema = z19.enum(["user", "workspace", "session", "agent"]);
18477
+ var kindSchema = z19.enum(["semantic", "episodic", "procedural"]);
17382
18478
  function createMemoryTools(store) {
17383
18479
  return [
17384
18480
  {
@@ -17393,10 +18489,10 @@ function createMemoryTools(store) {
17393
18489
  }, ["query"])
17394
18490
  },
17395
18491
  async execute(arguments_, context) {
17396
- const input2 = z17.object({
17397
- query: z17.string().max(4e3),
18492
+ const input2 = z19.object({
18493
+ query: z19.string().max(4e3),
17398
18494
  scope: scopeSchema.optional(),
17399
- limit: z17.number().int().min(1).max(20).optional()
18495
+ limit: z19.number().int().min(1).max(20).optional()
17400
18496
  }).parse(arguments_);
17401
18497
  const scopes = input2.scope ? [{ scope: input2.scope, scopeKey: scopeKey(input2.scope, context) }] : availableScopes(context);
17402
18498
  const records = store.search(input2.query, { scopes, limit: input2.limit ?? 8 });
@@ -17428,17 +18524,17 @@ ${record.content}`
17428
18524
  }, ["content", "rationale"])
17429
18525
  },
17430
18526
  async execute(arguments_, context) {
17431
- const input2 = z17.object({
17432
- content: z17.string().min(1).max(12e3),
17433
- rationale: z17.string().min(1).max(1e3),
18527
+ const input2 = z19.object({
18528
+ content: z19.string().min(1).max(12e3),
18529
+ rationale: z19.string().min(1).max(1e3),
17434
18530
  scope: scopeSchema.optional(),
17435
18531
  kind: kindSchema.optional(),
17436
- tags: z17.array(z17.string()).max(24).optional(),
17437
- importance: z17.number().min(0).max(1).optional(),
17438
- confidence: z17.number().min(0).max(1).optional(),
17439
- agent: z17.string().max(64).optional(),
17440
- revision: z17.string().max(240).optional(),
17441
- conflictKey: z17.string().max(240).optional()
18532
+ tags: z19.array(z19.string()).max(24).optional(),
18533
+ importance: z19.number().min(0).max(1).optional(),
18534
+ confidence: z19.number().min(0).max(1).optional(),
18535
+ agent: z19.string().max(64).optional(),
18536
+ revision: z19.string().max(240).optional(),
18537
+ conflictKey: z19.string().max(240).optional()
17442
18538
  }).strict().parse(arguments_);
17443
18539
  const scope = input2.scope ?? "workspace";
17444
18540
  const candidate = store.propose({
@@ -17476,9 +18572,9 @@ ${record.content}`
17476
18572
  }, ["id"])
17477
18573
  },
17478
18574
  async execute(arguments_) {
17479
- const input2 = z17.object({
17480
- id: z17.string().uuid(),
17481
- permanent: z17.boolean().optional()
18575
+ const input2 = z19.object({
18576
+ id: z19.string().uuid(),
18577
+ permanent: z19.boolean().optional()
17482
18578
  }).parse(arguments_);
17483
18579
  const changed = input2.permanent ? store.remove(input2.id) : store.archive(input2.id);
17484
18580
  return {
@@ -17505,9 +18601,9 @@ function scopeKey(scope, context) {
17505
18601
  }
17506
18602
 
17507
18603
  // src/skills/catalog.ts
17508
- import { lstat as lstat20, readFile as readFile17, readdir as readdir8, realpath as realpath9 } from "node:fs/promises";
18604
+ import { lstat as lstat21, readFile as readFile18, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
17509
18605
  import { homedir as homedir4 } from "node:os";
17510
- import { basename as basename11, join as join20, resolve as resolve20 } from "node:path";
18606
+ import { basename as basename11, join as join21, resolve as resolve21 } from "node:path";
17511
18607
  import { parse as parseYaml3 } from "yaml";
17512
18608
  var SkillCatalog = class {
17513
18609
  constructor(workspace, config) {
@@ -17527,7 +18623,7 @@ var SkillCatalog = class {
17527
18623
  for (const location of locations) {
17528
18624
  const entries = await safeDirectories(location.path);
17529
18625
  for (const entry of entries) {
17530
- const skillPath = join20(location.path, entry, "SKILL.md");
18626
+ const skillPath = join21(location.path, entry, "SKILL.md");
17531
18627
  const metadata = await readMetadata(skillPath);
17532
18628
  if (!metadata) continue;
17533
18629
  const descriptor = {
@@ -17576,31 +18672,31 @@ ${skill.content}
17576
18672
  }
17577
18673
  function discoveryLocations(workspace, configured) {
17578
18674
  const home = homedir4();
17579
- const workspaceRoot = resolve20(workspace);
18675
+ const workspaceRoot = resolve21(workspace);
17580
18676
  return [
17581
- { path: join20(home, ".agents", "skills"), scope: "user", trusted: true },
17582
- { path: join20(home, ".claude", "skills"), scope: "user", trusted: true },
17583
- { path: join20(home, ".augment", "skills"), scope: "user", trusted: true },
17584
- { path: join20(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
18677
+ { path: join21(home, ".agents", "skills"), scope: "user", trusted: true },
18678
+ { path: join21(home, ".claude", "skills"), scope: "user", trusted: true },
18679
+ { path: join21(home, ".augment", "skills"), scope: "user", trusted: true },
18680
+ { path: join21(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
17585
18681
  ...configured.map((path) => {
17586
- const resolved = resolve20(workspaceRoot, path);
18682
+ const resolved = resolve21(workspaceRoot, path);
17587
18683
  return {
17588
18684
  path: resolved,
17589
18685
  scope: "configured",
17590
18686
  trusted: !isInside(workspaceRoot, resolved)
17591
18687
  };
17592
18688
  }),
17593
- { path: join20(workspace, ".agents", "skills"), scope: "workspace", trusted: false },
17594
- { path: join20(workspace, ".claude", "skills"), scope: "workspace", trusted: false },
17595
- { path: join20(workspace, ".augment", "skills"), scope: "workspace", trusted: false },
17596
- { path: join20(resolveProjectNamespaceSync(workspaceRoot).active, "skills"), scope: "workspace", trusted: false }
18689
+ { path: join21(workspace, ".agents", "skills"), scope: "workspace", trusted: false },
18690
+ { path: join21(workspace, ".claude", "skills"), scope: "workspace", trusted: false },
18691
+ { path: join21(workspace, ".augment", "skills"), scope: "workspace", trusted: false },
18692
+ { path: join21(resolveProjectNamespaceSync(workspaceRoot).active, "skills"), scope: "workspace", trusted: false }
17597
18693
  ];
17598
18694
  }
17599
18695
  async function safeDirectories(path) {
17600
18696
  try {
17601
- const info = await lstat20(path);
18697
+ const info = await lstat21(path);
17602
18698
  if (!info.isDirectory() || info.isSymbolicLink()) return [];
17603
- const entries = await readdir8(path, { withFileTypes: true });
18699
+ const entries = await readdir9(path, { withFileTypes: true });
17604
18700
  return entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).map((entry) => entry.name).slice(0, 500);
17605
18701
  } catch {
17606
18702
  return [];
@@ -17612,7 +18708,7 @@ async function readMetadata(path) {
17612
18708
  const { frontmatter } = splitFrontmatter(raw);
17613
18709
  if (!frontmatter) return void 0;
17614
18710
  const parsed = parseYaml3(frontmatter);
17615
- const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename11(resolve20(path, ".."));
18711
+ const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename11(resolve21(path, ".."));
17616
18712
  const description = typeof parsed?.description === "string" ? parsed.description.trim() : "";
17617
18713
  if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || !description || description.length > 1e3) {
17618
18714
  return void 0;
@@ -17631,13 +18727,13 @@ async function readSkill(path, maxChars) {
17631
18727
  }
17632
18728
  async function safeRead(path, maxBytes) {
17633
18729
  try {
17634
- const info = await lstat20(path);
18730
+ const info = await lstat21(path);
17635
18731
  if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes) return void 0;
17636
- const parent = resolve20(path, "..");
18732
+ const parent = resolve21(path, "..");
17637
18733
  const resolvedParent = await realpath9(parent);
17638
18734
  const resolvedPath = await realpath9(path);
17639
18735
  if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
17640
- return await readFile17(path, "utf8");
18736
+ return await readFile18(path, "utf8");
17641
18737
  } catch {
17642
18738
  return void 0;
17643
18739
  }
@@ -17682,7 +18778,7 @@ function escapeAttribute6(value) {
17682
18778
  }
17683
18779
 
17684
18780
  // src/workflows/catalog.ts
17685
- import { z as z18 } from "zod";
18781
+ import { z as z20 } from "zod";
17686
18782
  var builtInWorkflows = [
17687
18783
  {
17688
18784
  name: "implement",
@@ -17773,7 +18869,7 @@ function createWorkflowTool(catalog) {
17773
18869
  }, ["name", "task"])
17774
18870
  },
17775
18871
  async execute(arguments_) {
17776
- const input2 = z18.object({ name: z18.string(), task: z18.string().min(1).max(2e4) }).parse(arguments_);
18872
+ const input2 = z20.object({ name: z20.string(), task: z20.string().min(1).max(2e4) }).parse(arguments_);
17777
18873
  const workflow = catalog.get(input2.name);
17778
18874
  if (!workflow) return { ok: false, content: `Unknown workflow: ${input2.name}` };
17779
18875
  return {
@@ -17818,7 +18914,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
17818
18914
  delegation;
17819
18915
  initialized = false;
17820
18916
  static async create(config, registry, options = {}) {
17821
- const runtime = new _ExtensionRuntime(config, resolve21(config.workspaceRoots[0] ?? process.cwd()), options);
18917
+ const runtime = new _ExtensionRuntime(config, resolve22(config.workspaceRoots[0] ?? process.cwd()), options);
17822
18918
  try {
17823
18919
  await runtime.initialize(registry, options.signal);
17824
18920
  return runtime;
@@ -18023,15 +19119,15 @@ function upgradeCommandOverride(env = process.env) {
18023
19119
  return trimmed ? trimmed : void 0;
18024
19120
  }
18025
19121
  function runUpgrade(plan) {
18026
- return new Promise((resolve23) => {
19122
+ return new Promise((resolve24) => {
18027
19123
  const child = spawn2(plan.command, plan.args, {
18028
19124
  stdio: "inherit",
18029
19125
  shell: plan.shell ?? false
18030
19126
  });
18031
- child.on("error", () => resolve23({ ok: false, exitCode: 127, display: plan.display }));
19127
+ child.on("error", () => resolve24({ ok: false, exitCode: 127, display: plan.display }));
18032
19128
  child.on("close", (code) => {
18033
19129
  const exitCode = code ?? 1;
18034
- resolve23({ ok: exitCode === 0, exitCode, display: plan.display });
19130
+ resolve24({ ok: exitCode === 0, exitCode, display: plan.display });
18035
19131
  });
18036
19132
  });
18037
19133
  }
@@ -18117,7 +19213,7 @@ program.command("context").description("Pack task-oriented context under a token
18117
19213
 
18118
19214
  `);
18119
19215
  process.stderr.write(chalk4.dim(
18120
- `${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}` : ""}
18121
19217
  `
18122
19218
  ));
18123
19219
  });
@@ -18280,9 +19376,11 @@ sessionCommand.command("show <id>").description("Show a saved session transcript
18280
19376
  else process.stdout.write(sessionMarkdown(session));
18281
19377
  });
18282
19378
  sessionCommand.command("delete <id>").description("Delete a saved session").option("-w, --workspace <path>", "workspace root").option("--yes", "skip confirmation").action(async (id, options) => {
18283
- const store = new SessionStore(workspaceOption(options.workspace));
19379
+ const workspace = workspaceOption(options.workspace);
19380
+ const store = new SessionStore(workspace);
18284
19381
  const session = await requireSessionSelector(store, id);
18285
19382
  if (!options.yes && !await confirm(`Delete session ${session.id.slice(0, 8)}?`)) return;
19383
+ await new ToolArtifactStore(workspace).removeSession(session.id);
18286
19384
  await store.remove(session.id);
18287
19385
  process.stdout.write(`Deleted ${session.id}
18288
19386
  `);
@@ -18291,7 +19389,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
18291
19389
  const store = new SessionStore(workspaceOption(options.workspace));
18292
19390
  const session = await requireSessionSelector(store, id);
18293
19391
  const markdown = sessionMarkdown(session);
18294
- if (options.output) await writeFile3(resolve22(options.output), markdown, "utf8");
19392
+ if (options.output) await writeFile3(resolve23(options.output), markdown, "utf8");
18295
19393
  else process.stdout.write(markdown);
18296
19394
  });
18297
19395
  var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
@@ -18668,7 +19766,7 @@ async function runChat(prompts, options) {
18668
19766
  const stdinPrompt = !process.stdin.isTTY ? await readStdin() : "";
18669
19767
  const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
18670
19768
  if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
18671
- const workspace = resolve22(options.workspace);
19769
+ const workspace = resolve23(options.workspace);
18672
19770
  let config = await runtimeConfig(workspace, options);
18673
19771
  let completedOnboarding = false;
18674
19772
  if (!shouldPrint && needsFirstRunOnboarding(config)) {
@@ -18893,14 +19991,14 @@ async function runAgentSetup(options) {
18893
19991
  `);
18894
19992
  }
18895
19993
  async function runtimeConfig(workspaceInput, options) {
18896
- const workspace = resolve22(workspaceInput);
19994
+ const workspace = resolve23(workspaceInput);
18897
19995
  const loaded = await loadConfig(workspace, options.config, {
18898
19996
  trustProjectConfig: options.trustProjectConfig === true
18899
19997
  });
18900
19998
  const roots = [
18901
19999
  workspace,
18902
20000
  ...loaded.workspaceRoots,
18903
- ...(options.addWorkspace ?? []).map((root) => resolve22(workspace, root))
20001
+ ...(options.addWorkspace ?? []).map((root) => resolve23(workspace, root))
18904
20002
  ];
18905
20003
  const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
18906
20004
  return {
@@ -19056,7 +20154,7 @@ function collect(value, previous) {
19056
20154
  }
19057
20155
  function workspaceOption(value) {
19058
20156
  const rootOptions = program.opts();
19059
- return resolve22(value ?? rootOptions.workspace ?? process.cwd());
20157
+ return resolve23(value ?? rootOptions.workspace ?? process.cwd());
19060
20158
  }
19061
20159
  function runtimeOptions(options) {
19062
20160
  const root = program.opts();