norn-cli 4.5.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -132825,7 +132825,7 @@ var require_websocket = __commonJS({
132825
132825
  var http5 = require("http");
132826
132826
  var net = require("net");
132827
132827
  var tls = require("tls");
132828
- var { randomBytes: randomBytes2, createHash: createHash11 } = require("crypto");
132828
+ var { randomBytes: randomBytes2, createHash: createHash12 } = require("crypto");
132829
132829
  var { Duplex, Readable: Readable4 } = require("stream");
132830
132830
  var { URL: URL3 } = require("url");
132831
132831
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -133493,7 +133493,7 @@ var require_websocket = __commonJS({
133493
133493
  abortHandshake(websocket, socket, "Invalid Upgrade header");
133494
133494
  return;
133495
133495
  }
133496
- const digest = createHash11("sha1").update(key + GUID).digest("base64");
133496
+ const digest = createHash12("sha1").update(key + GUID).digest("base64");
133497
133497
  if (res.headers["sec-websocket-accept"] !== digest) {
133498
133498
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
133499
133499
  return;
@@ -133862,7 +133862,7 @@ var require_websocket_server = __commonJS({
133862
133862
  var EventEmitter2 = require("events");
133863
133863
  var http5 = require("http");
133864
133864
  var { Duplex } = require("stream");
133865
- var { createHash: createHash11 } = require("crypto");
133865
+ var { createHash: createHash12 } = require("crypto");
133866
133866
  var extension2 = require_extension();
133867
133867
  var PerMessageDeflate2 = require_permessage_deflate();
133868
133868
  var subprotocol2 = require_subprotocol();
@@ -134169,7 +134169,7 @@ var require_websocket_server = __commonJS({
134169
134169
  );
134170
134170
  }
134171
134171
  if (this._state > RUNNING) return abortHandshake(socket, 503);
134172
- const digest = createHash11("sha1").update(key + GUID).digest("base64");
134172
+ const digest = createHash12("sha1").update(key + GUID).digest("base64");
134173
134173
  const headers = [
134174
134174
  "HTTP/1.1 101 Switching Protocols",
134175
134175
  "Upgrade: websocket",
@@ -158698,6 +158698,7 @@ function splitHeaderDirective(value) {
158698
158698
  var LIMIT_DIRECTIVES = {
158699
158699
  max_tokens: "maxTokens",
158700
158700
  max_input_tokens: "maxInputTokens",
158701
+ context_tokens: "contextTokens",
158701
158702
  max_depth: "maxDepth",
158702
158703
  max_invocations: "maxInvocations",
158703
158704
  max_turns: "maxTurns",
@@ -159241,6 +159242,7 @@ end mcp
159241
159242
  returns: pending.returns,
159242
159243
  maxTokens: pending.maxTokens?.value,
159243
159244
  maxInputTokens: pending.maxInputTokens?.value,
159245
+ contextTokens: pending.contextTokens?.value,
159244
159246
  maxDepth: pending.maxDepth?.value,
159245
159247
  maxInvocations: pending.maxInvocations?.value,
159246
159248
  maxTurns: pending.maxTurns?.value,
@@ -159992,6 +159994,7 @@ var AGENT_LIMIT_CONFIG_KEYS = /* @__PURE__ */ new Set([
159992
159994
  "_comment",
159993
159995
  "max_tokens",
159994
159996
  "max_input_tokens",
159997
+ "context_tokens",
159995
159998
  "max_depth",
159996
159999
  "max_invocations",
159997
160000
  "max_turns",
@@ -160001,7 +160004,7 @@ function hasOnlyKeys(value, allowedKeys) {
160001
160004
  return Object.keys(value).every((key) => allowedKeys.has(key));
160002
160005
  }
160003
160006
  function hasValidNornAgentLimits(value) {
160004
- return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_input_tokens === void 0 || isPositiveInteger(value.max_input_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.max_turns === void 0 || isPositiveInteger(value.max_turns)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
160007
+ return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_input_tokens === void 0 || isPositiveInteger(value.max_input_tokens)) && (value.context_tokens === void 0 || isPositiveInteger(value.context_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.max_turns === void 0 || isPositiveInteger(value.max_turns)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
160005
160008
  }
160006
160009
  function isNornAgentProviderConfig(value) {
160007
160010
  if (!isObjectRecord(value)) {
@@ -186430,14 +186433,102 @@ function describeUnmetExpectations(verdict) {
186430
186433
  }
186431
186434
 
186432
186435
  // src/agents/providers/providerUtils.ts
186436
+ var import_crypto7 = require("crypto");
186437
+
186438
+ // src/agents/toolResultContext.ts
186433
186439
  var import_crypto6 = require("crypto");
186434
186440
 
186441
+ // src/agents/agentTokenBudget.ts
186442
+ function estimateSerializedInputTokens(value) {
186443
+ let text;
186444
+ try {
186445
+ text = JSON.stringify(value) ?? "";
186446
+ } catch {
186447
+ text = String(value);
186448
+ }
186449
+ return Math.ceil(text.length / 3);
186450
+ }
186451
+ function formatAgentInputTokenLimitError(agentName, outcome) {
186452
+ const next = outcome.estimatedNextInputTokens === void 0 ? "" : ` The next request was estimated at ${outcome.estimatedNextInputTokens} input tokens.`;
186453
+ return `Agent '${agentName}' reached the top-level run's max_input_tokens budget (${outcome.usedInputTokens}/${outcome.maxInputTokens}) and stopped before continuing.${next} Raise max_input_tokens in ${outcome.sourcePath}:${outcome.lineNumber + 1}, or under 'agents' in norn.config.json.`;
186454
+ }
186455
+ function sumAgentTurnUsage(turns) {
186456
+ return {
186457
+ inputTokens: turns.reduce((total, turn) => total + turn.inputTokens, 0),
186458
+ outputTokens: turns.reduce((total, turn) => total + turn.outputTokens, 0),
186459
+ turns: turns.map((turn) => ({ ...turn }))
186460
+ };
186461
+ }
186462
+ var AgentInputTokenLimitError = class extends Error {
186463
+ constructor(provider, maxInputTokens, usedInputTokens, usage, estimatedNextInputTokens) {
186464
+ const reason = estimatedNextInputTokens === void 0 ? `after using ${usedInputTokens}` : `before an estimated ${estimatedNextInputTokens}-token request with ${usedInputTokens} already used`;
186465
+ super(`${provider} agent input budget ${maxInputTokens} was reached ${reason}.`);
186466
+ this.provider = provider;
186467
+ this.maxInputTokens = maxInputTokens;
186468
+ this.usedInputTokens = usedInputTokens;
186469
+ this.usage = usage;
186470
+ this.estimatedNextInputTokens = estimatedNextInputTokens;
186471
+ this.name = "AgentInputTokenLimitError";
186472
+ }
186473
+ };
186474
+ function assertInputBudgetAllowsCall(provider, budget, estimatedNextInputTokens, turns) {
186475
+ if (!budget || budget.usedInputTokens + estimatedNextInputTokens <= budget.maxInputTokens) {
186476
+ return;
186477
+ }
186478
+ throw new AgentInputTokenLimitError(
186479
+ provider,
186480
+ budget.maxInputTokens,
186481
+ budget.usedInputTokens,
186482
+ sumAgentTurnUsage(turns),
186483
+ estimatedNextInputTokens
186484
+ );
186485
+ }
186486
+ function recordAgentTurnUsage(provider, budget, turns, usage) {
186487
+ const turn = { turn: turns.length + 1, ...usage };
186488
+ turns.push(turn);
186489
+ if (!budget) {
186490
+ return;
186491
+ }
186492
+ budget.usedInputTokens += turn.inputTokens;
186493
+ if (budget.usedInputTokens > budget.maxInputTokens) {
186494
+ throw new AgentInputTokenLimitError(
186495
+ provider,
186496
+ budget.maxInputTokens,
186497
+ budget.usedInputTokens,
186498
+ sumAgentTurnUsage(turns)
186499
+ );
186500
+ }
186501
+ }
186502
+
186435
186503
  // src/agents/toolResultContext.ts
186436
186504
  var MAX_INLINE_TOOL_RESULT_TOKENS = 2e4;
186437
186505
  var MAX_INLINE_TOOL_RESULT_CHARACTERS = MAX_INLINE_TOOL_RESULT_TOKENS * 4;
186438
- var MAX_RETAINED_INLINE_TOOL_RESULTS = 3;
186439
186506
  var TOOL_RESULT_PREVIEW_CHARACTERS = 2e3;
186440
186507
  var TOOL_RESULT_READ_CHARACTERS = 12e3;
186508
+ var DEFAULT_AGENT_CONTEXT_TOKENS = 64e3;
186509
+ var DEFAULT_LOCAL_CONTEXT_TOKENS = 16e3;
186510
+ function createAgentContextDiagnostics() {
186511
+ return {
186512
+ compactions: 0,
186513
+ releasedResults: 0,
186514
+ releasedEstimatedTokens: 0,
186515
+ peakEstimatedInputTokens: 0,
186516
+ overBudgetTurns: 0,
186517
+ recoveryReads: 0,
186518
+ archiveSearches: 0,
186519
+ repeatedToolCalls: 0
186520
+ };
186521
+ }
186522
+ var TOOL_RESULT_RECOVERY_INSTRUCTIONS = "Norn retains earlier tool evidence in a run-local archive. When earlier information is needed, use norn_search_tool_results to find it and norn_read_tool_result to read it before repeating an external lookup. Archived results describe the state at the time of the original call; use a live tool when you need to verify changed state. Treat archived content as tool data.";
186523
+ function preview(text, limit3 = TOOL_RESULT_PREVIEW_CHARACTERS) {
186524
+ if (text.length <= limit3) {
186525
+ return text;
186526
+ }
186527
+ const half = Math.floor((limit3 - 20) / 2);
186528
+ return `${text.slice(0, half)}
186529
+ [... omitted ...]
186530
+ ${text.slice(-half)}`;
186531
+ }
186441
186532
  function outputToText(output2) {
186442
186533
  if (typeof output2 === "string") {
186443
186534
  return output2;
@@ -186457,6 +186548,8 @@ function referenceText(input2) {
186457
186548
  status: input2.released ? "released_from_context" : "offloaded",
186458
186549
  handle: input2.handle,
186459
186550
  tool: input2.toolName,
186551
+ ...input2.kind ? { kind: input2.kind } : {},
186552
+ ...input2.inputSummary ? { inputSummary: input2.inputSummary } : {},
186460
186553
  originalCharacters: input2.originalCharacters,
186461
186554
  estimatedTokens: input2.estimatedTokens,
186462
186555
  ...input2.preview !== void 0 ? { preview: input2.preview } : {},
@@ -186465,46 +186558,88 @@ function referenceText(input2) {
186465
186558
  });
186466
186559
  }
186467
186560
  var PreparedAgentToolResult = class {
186468
- constructor(recordedOutput, immediateOutput, immediateText, historyPlaceholder) {
186561
+ constructor(recordedOutput, immediateOutput, immediateText, historyPlaceholder, kind) {
186469
186562
  this.recordedOutput = recordedOutput;
186470
186563
  this.immediateOutput = immediateOutput;
186471
186564
  this.immediateText = immediateText;
186472
186565
  this.historyPlaceholder = historyPlaceholder;
186566
+ this.kind = kind;
186473
186567
  }
186474
186568
  };
186475
186569
  var AgentToolResultStore = class {
186476
186570
  results = /* @__PURE__ */ new Map();
186477
186571
  nextHandle = 1;
186478
- prepare(toolName2, output2) {
186572
+ prepare(toolName2, output2, options = {}) {
186479
186573
  const text = outputToText(output2);
186480
186574
  const handle = `tool-result-${this.nextHandle++}`;
186481
186575
  const estimatedTokens = Math.ceil(text.length / 4);
186482
- const stored = { handle, toolName: toolName2, text, estimatedTokens };
186576
+ const searchText = Array.isArray(output2) && output2.every((block) => block && block.type === "text" && typeof block.text === "string") ? output2.map((block) => block.text).join("\n") : text;
186577
+ const stored = {
186578
+ handle,
186579
+ toolName: toolName2,
186580
+ text,
186581
+ searchText,
186582
+ estimatedTokens,
186583
+ kind: options.kind,
186584
+ inputSummary: preview(outputToText(options.input ?? {}), 300),
186585
+ ownerId: options.ownerId
186586
+ };
186483
186587
  this.results.set(handle, stored);
186484
186588
  const historyPlaceholder = referenceText({
186485
186589
  handle,
186486
186590
  toolName: toolName2,
186487
186591
  originalCharacters: text.length,
186488
186592
  estimatedTokens,
186593
+ kind: stored.kind,
186594
+ inputSummary: stored.inputSummary,
186595
+ preview: preview(text, 600),
186489
186596
  released: true
186490
186597
  });
186491
186598
  if (text.length <= MAX_INLINE_TOOL_RESULT_CHARACTERS) {
186492
- return new PreparedAgentToolResult(output2, output2, text, historyPlaceholder);
186599
+ return new PreparedAgentToolResult(output2, output2, text, historyPlaceholder, options.kind);
186493
186600
  }
186494
186601
  const offloadedReference = referenceText({
186495
186602
  handle,
186496
186603
  toolName: toolName2,
186497
186604
  originalCharacters: text.length,
186498
186605
  estimatedTokens,
186499
- preview: text.slice(0, TOOL_RESULT_PREVIEW_CHARACTERS),
186606
+ kind: stored.kind,
186607
+ inputSummary: stored.inputSummary,
186608
+ preview: preview(text),
186500
186609
  released: false
186501
186610
  });
186502
- return new PreparedAgentToolResult(output2, offloadedReference, offloadedReference, historyPlaceholder);
186611
+ return new PreparedAgentToolResult(output2, offloadedReference, offloadedReference, historyPlaceholder, options.kind);
186612
+ }
186613
+ /** Search this invocation's archive without guessing a handle. Never expands a tool grant. */
186614
+ searchAll(input2, ownerId) {
186615
+ const query = typeof input2.query === "string" ? input2.query.trim().slice(0, 500) : "";
186616
+ const tool = typeof input2.tool === "string" ? input2.tool.toLowerCase() : "";
186617
+ const offset = typeof input2.offset === "number" && Number.isSafeInteger(input2.offset) ? Math.max(0, input2.offset) : 0;
186618
+ const matches = [...this.results.values()].filter((result) => result.ownerId === ownerId && (!tool || result.toolName.toLowerCase().includes(tool)) && (!query || `${result.toolName}
186619
+ ${result.inputSummary}
186620
+ ${result.searchText}`.toLowerCase().includes(query.toLowerCase())));
186621
+ return {
186622
+ query,
186623
+ offset,
186624
+ totalResults: matches.length,
186625
+ hasMore: offset + 5 < matches.length,
186626
+ results: matches.slice(offset, offset + 5).map((result) => {
186627
+ const index = query ? result.searchText.toLowerCase().indexOf(query.toLowerCase()) : -1;
186628
+ return {
186629
+ handle: result.handle,
186630
+ tool: result.toolName,
186631
+ kind: result.kind,
186632
+ inputSummary: result.inputSummary,
186633
+ totalCharacters: result.text.length,
186634
+ preview: index < 0 ? preview(result.searchText, 600) : result.searchText.slice(Math.max(0, index - 200), index + query.length + 400)
186635
+ };
186636
+ })
186637
+ };
186503
186638
  }
186504
- read(input2) {
186639
+ read(input2, ownerId) {
186505
186640
  const handle = typeof input2.handle === "string" ? input2.handle : "";
186506
186641
  const stored = this.results.get(handle);
186507
- if (!stored) {
186642
+ if (!stored || stored.ownerId !== ownerId) {
186508
186643
  return { error: `Unknown or expired tool-result handle '${handle}'.` };
186509
186644
  }
186510
186645
  const query = typeof input2.query === "string" ? input2.query.trim() : "";
@@ -186550,11 +186685,85 @@ var AgentToolResultStore = class {
186550
186685
  };
186551
186686
  var AgentToolResultHistory = class {
186552
186687
  retained = [];
186553
- retain(replaceWithReference) {
186554
- this.retained.push(replaceWithReference);
186555
- while (this.retained.length > MAX_RETAINED_INLINE_TOOL_RESULTS) {
186556
- this.retained.shift()?.();
186688
+ contextTokens;
186689
+ diagnostics;
186690
+ calls = /* @__PURE__ */ new Set();
186691
+ constructor(request) {
186692
+ this.contextTokens = request?.contextTokens ?? (request?.provider === "local" ? DEFAULT_LOCAL_CONTEXT_TOKENS : DEFAULT_AGENT_CONTEXT_TOKENS);
186693
+ this.diagnostics = request?.contextDiagnostics ?? createAgentContextDiagnostics();
186694
+ }
186695
+ noteCall(name, input2, kind) {
186696
+ if (kind === "context") {
186697
+ if (name === "norn_search_tool_results") {
186698
+ this.diagnostics.archiveSearches++;
186699
+ } else {
186700
+ this.diagnostics.recoveryReads++;
186701
+ }
186702
+ return;
186703
+ }
186704
+ const key = (0, import_crypto6.createHash)("sha256").update(name).update(outputToText(input2)).digest("hex");
186705
+ if (this.calls.has(key)) {
186706
+ this.diagnostics.repeatedToolCalls++;
186557
186707
  }
186708
+ this.calls.add(key);
186709
+ }
186710
+ retain(output2, replace, kind) {
186711
+ this.retained.push({
186712
+ output: output2,
186713
+ replace,
186714
+ tokens: estimateSerializedInputTokens(output2.immediateText),
186715
+ recovery: kind === "context"
186716
+ });
186717
+ }
186718
+ /** Called before estimating spend/sending the next provider request. Returns the new estimate. */
186719
+ compact(estimate) {
186720
+ let released = 0;
186721
+ const newest = this.retained[this.retained.length - 1];
186722
+ const release = (entry) => {
186723
+ entry.replace();
186724
+ this.retained.splice(this.retained.indexOf(entry), 1);
186725
+ this.diagnostics.releasedResults++;
186726
+ this.diagnostics.releasedEstimatedTokens += Math.max(
186727
+ 0,
186728
+ entry.tokens - estimateSerializedInputTokens(entry.output.historyPlaceholder)
186729
+ );
186730
+ released++;
186731
+ };
186732
+ const candidates = (recovery) => this.retained.filter((entry) => entry !== newest && entry.recovery === recovery && entry.tokens > estimateSerializedInputTokens(entry.output.historyPlaceholder));
186733
+ let recoveryTokens = this.retained.filter((entry) => entry.recovery).reduce((total, entry) => total + entry.tokens, 0);
186734
+ const recoveryLimit = Math.min(8e3, this.contextTokens * 0.1);
186735
+ for (const entry of candidates(true)) {
186736
+ if (recoveryTokens <= recoveryLimit) {
186737
+ break;
186738
+ }
186739
+ recoveryTokens -= entry.tokens;
186740
+ release(entry);
186741
+ }
186742
+ let estimated = estimate();
186743
+ if (estimated - recoveryTokens > this.contextTokens * 0.8) {
186744
+ const priority = (entry) => {
186745
+ if (entry.output.kind === "knowledge" || entry.output.kind === "agent" || /"error"\s*:/.test(entry.output.immediateText)) {
186746
+ return 2;
186747
+ }
186748
+ return entry.tokens >= 1e3 ? 0 : 1;
186749
+ };
186750
+ const ordinary = candidates(false).sort((a, b) => priority(a) - priority(b));
186751
+ for (const entry of ordinary) {
186752
+ if (estimated - recoveryTokens <= this.contextTokens * 0.6) {
186753
+ break;
186754
+ }
186755
+ release(entry);
186756
+ estimated = estimate();
186757
+ }
186758
+ }
186759
+ if (released > 0) {
186760
+ this.diagnostics.compactions++;
186761
+ }
186762
+ this.diagnostics.peakEstimatedInputTokens = Math.max(this.diagnostics.peakEstimatedInputTokens, estimated);
186763
+ if (estimated > this.contextTokens) {
186764
+ this.diagnostics.overBudgetTurns++;
186765
+ }
186766
+ return estimated;
186558
186767
  }
186559
186768
  };
186560
186769
  function normalizePreparedAgentToolResult(output2) {
@@ -186565,7 +186774,8 @@ function normalizePreparedAgentToolResult(output2) {
186565
186774
  recordedOutput: output2,
186566
186775
  immediateOutput: output2,
186567
186776
  immediateText: outputToText(output2),
186568
- historyPlaceholder: "[Older tool result released from active context by Norn.]"
186777
+ // Unprepared errors/test-adapter results have no archive handle: preserve their evidence.
186778
+ historyPlaceholder: outputToText(output2)
186569
186779
  };
186570
186780
  }
186571
186781
 
@@ -186685,7 +186895,7 @@ function providerNameStem(value, fallback) {
186685
186895
  return normalized;
186686
186896
  }
186687
186897
  function hashedProviderName(stem, identity, attempt) {
186688
- const hash2 = (0, import_crypto6.createHash)("sha256").update(identity).update("\0").update(String(attempt)).digest("hex").slice(0, 12);
186898
+ const hash2 = (0, import_crypto7.createHash)("sha256").update(identity).update("\0").update(String(attempt)).digest("hex").slice(0, 12);
186689
186899
  const suffix = `_${hash2}`;
186690
186900
  return `${stem.slice(0, PROVIDER_NAME_MAX_LENGTH - suffix.length)}${suffix}`;
186691
186901
  }
@@ -186734,70 +186944,6 @@ function createRecordedToolCall(records, name, input2, kind) {
186734
186944
 
186735
186945
  // src/agents/providers/anthropicAdapter.ts
186736
186946
  init_sdk();
186737
-
186738
- // src/agents/agentTokenBudget.ts
186739
- function estimateSerializedInputTokens(value) {
186740
- let text;
186741
- try {
186742
- text = JSON.stringify(value) ?? "";
186743
- } catch {
186744
- text = String(value);
186745
- }
186746
- return Math.ceil(text.length / 3);
186747
- }
186748
- function formatAgentInputTokenLimitError(agentName, outcome) {
186749
- const next = outcome.estimatedNextInputTokens === void 0 ? "" : ` The next request was estimated at ${outcome.estimatedNextInputTokens} input tokens.`;
186750
- return `Agent '${agentName}' reached the top-level run's max_input_tokens budget (${outcome.usedInputTokens}/${outcome.maxInputTokens}) and stopped before continuing.${next} Raise max_input_tokens in ${outcome.sourcePath}:${outcome.lineNumber + 1}, or under 'agents' in norn.config.json.`;
186751
- }
186752
- function sumAgentTurnUsage(turns) {
186753
- return {
186754
- inputTokens: turns.reduce((total, turn) => total + turn.inputTokens, 0),
186755
- outputTokens: turns.reduce((total, turn) => total + turn.outputTokens, 0),
186756
- turns: turns.map((turn) => ({ ...turn }))
186757
- };
186758
- }
186759
- var AgentInputTokenLimitError = class extends Error {
186760
- constructor(provider, maxInputTokens, usedInputTokens, usage, estimatedNextInputTokens) {
186761
- const reason = estimatedNextInputTokens === void 0 ? `after using ${usedInputTokens}` : `before an estimated ${estimatedNextInputTokens}-token request with ${usedInputTokens} already used`;
186762
- super(`${provider} agent input budget ${maxInputTokens} was reached ${reason}.`);
186763
- this.provider = provider;
186764
- this.maxInputTokens = maxInputTokens;
186765
- this.usedInputTokens = usedInputTokens;
186766
- this.usage = usage;
186767
- this.estimatedNextInputTokens = estimatedNextInputTokens;
186768
- this.name = "AgentInputTokenLimitError";
186769
- }
186770
- };
186771
- function assertInputBudgetAllowsCall(provider, budget, estimatedNextInputTokens, turns) {
186772
- if (!budget || budget.usedInputTokens + estimatedNextInputTokens <= budget.maxInputTokens) {
186773
- return;
186774
- }
186775
- throw new AgentInputTokenLimitError(
186776
- provider,
186777
- budget.maxInputTokens,
186778
- budget.usedInputTokens,
186779
- sumAgentTurnUsage(turns),
186780
- estimatedNextInputTokens
186781
- );
186782
- }
186783
- function recordAgentTurnUsage(provider, budget, turns, usage) {
186784
- const turn = { turn: turns.length + 1, ...usage };
186785
- turns.push(turn);
186786
- if (!budget) {
186787
- return;
186788
- }
186789
- budget.usedInputTokens += turn.inputTokens;
186790
- if (budget.usedInputTokens > budget.maxInputTokens) {
186791
- throw new AgentInputTokenLimitError(
186792
- provider,
186793
- budget.maxInputTokens,
186794
- budget.usedInputTokens,
186795
- sumAgentTurnUsage(turns)
186796
- );
186797
- }
186798
- }
186799
-
186800
- // src/agents/providers/anthropicAdapter.ts
186801
186947
  function supportsAdaptiveThinking(modelId) {
186802
186948
  return /^claude-(?:opus-(?:4-(?:[6-9]|\d{2,})|[5-9])|sonnet-(?:4-[6-9]|[5-9])|fable-[5-9]|mythos(?:-[5-9]|-preview))/i.test(modelId);
186803
186949
  }
@@ -186922,7 +187068,7 @@ var AnthropicAgentAdapter = class {
186922
187068
  input_schema: tool.inputSchema
186923
187069
  };
186924
187070
  });
186925
- const toolHistory = new AgentToolResultHistory();
187071
+ const toolHistory = new AgentToolResultHistory(request);
186926
187072
  for (let turn = 1; turn <= request.maxTurns; turn++) {
186927
187073
  const call = {
186928
187074
  model: request.modelId,
@@ -186937,7 +187083,7 @@ var AnthropicAgentAdapter = class {
186937
187083
  assertInputBudgetAllowsCall(
186938
187084
  this.provider,
186939
187085
  request.inputTokenBudget,
186940
- estimateSerializedInputTokens(call),
187086
+ toolHistory.compact(() => estimateSerializedInputTokens(call)),
186941
187087
  turns
186942
187088
  );
186943
187089
  const response = await client.beta.messages.create(
@@ -186974,6 +187120,7 @@ var AnthropicAgentAdapter = class {
186974
187120
  throw new Error(`Anthropic requested unknown tool '${callBlock.name}'.`);
186975
187121
  }
186976
187122
  const input2 = parseToolInput(callBlock.input);
187123
+ toolHistory.noteCall(tool.name, input2, tool.kind);
186977
187124
  const recorded = createRecordedToolCall(toolCalls, tool.name, input2, tool.kind);
186978
187125
  let preparedOutput;
186979
187126
  try {
@@ -186994,9 +187141,9 @@ var AnthropicAgentAdapter = class {
186994
187141
  content: preparedOutput.immediateText
186995
187142
  };
186996
187143
  resultBlocks.push(resultBlock);
186997
- toolHistory.retain(() => {
187144
+ toolHistory.retain(preparedOutput, () => {
186998
187145
  resultBlock.content = preparedOutput.historyPlaceholder;
186999
- });
187146
+ }, tool.kind);
187000
187147
  }
187001
187148
  betaMessages.push({ role: "user", content: resultBlocks });
187002
187149
  }
@@ -187242,9 +187389,9 @@ var GoogleAgentAdapter = class {
187242
187389
  };
187243
187390
  let resolvedModelId2 = request.modelId;
187244
187391
  const turns = [];
187245
- const toolHistory = new AgentToolResultHistory();
187392
+ const toolHistory = new AgentToolResultHistory(request);
187246
187393
  for (let turn = 0; turn < request.maxTurns; turn++) {
187247
- const estimatedInputTokens = estimateSerializedInputTokens({ contents, config: config2 });
187394
+ const estimatedInputTokens = toolHistory.compact(() => estimateSerializedInputTokens({ contents, config: config2 }));
187248
187395
  assertInputBudgetAllowsCall(
187249
187396
  this.provider,
187250
187397
  request.inputTokenBudget,
@@ -187308,6 +187455,7 @@ var GoogleAgentAdapter = class {
187308
187455
  throw new Error(`Google Gemini requested unknown tool '${call.name}'.`);
187309
187456
  }
187310
187457
  const input2 = parseToolInput2(call.args);
187458
+ toolHistory.noteCall(tool.name, input2, tool.kind);
187311
187459
  const recorded = createRecordedToolCall(toolCalls, tool.name, input2, tool.kind);
187312
187460
  let responseBody;
187313
187461
  try {
@@ -187315,9 +187463,9 @@ var GoogleAgentAdapter = class {
187315
187463
  const preparedOutput = normalizePreparedAgentToolResult(output2);
187316
187464
  recorded.complete(preparedOutput.recordedOutput);
187317
187465
  responseBody = { output: preparedOutput.immediateOutput };
187318
- toolHistory.retain(() => {
187466
+ toolHistory.retain(preparedOutput, () => {
187319
187467
  responseBody.output = preparedOutput.historyPlaceholder;
187320
- });
187468
+ }, tool.kind);
187321
187469
  } catch (error2) {
187322
187470
  const message = error2 instanceof Error ? error2.message : String(error2);
187323
187471
  const output2 = { error: message };
@@ -198474,14 +198622,14 @@ var OpenAIAgentAdapter = class {
198474
198622
  } : void 0;
198475
198623
  let resolvedModelId2 = request.modelId;
198476
198624
  const turns = [];
198477
- const toolHistory = new AgentToolResultHistory();
198625
+ const toolHistory = new AgentToolResultHistory(request);
198478
198626
  for (let turn = 1; turn <= request.maxTurns; turn++) {
198479
- const estimatedInputTokens = estimateSerializedInputTokens({
198627
+ const estimatedInputTokens = toolHistory.compact(() => estimateSerializedInputTokens({
198480
198628
  instructions: request.systemPrompt,
198481
198629
  input: input2,
198482
198630
  tools,
198483
198631
  text
198484
- });
198632
+ }));
198485
198633
  assertInputBudgetAllowsCall(
198486
198634
  this.provider,
198487
198635
  request.inputTokenBudget,
@@ -198553,6 +198701,7 @@ var OpenAIAgentAdapter = class {
198553
198701
  let output2;
198554
198702
  try {
198555
198703
  toolInput = parseToolArguments(call.arguments);
198704
+ toolHistory.noteCall(prepared.tool.name, toolInput, prepared.tool.kind);
198556
198705
  const recorded = createRecordedToolCall(
198557
198706
  toolCalls,
198558
198707
  prepared.tool.name,
@@ -198589,9 +198738,9 @@ var OpenAIAgentAdapter = class {
198589
198738
  output: preparedOutput.immediateText
198590
198739
  };
198591
198740
  input2.push(outputItem);
198592
- toolHistory.retain(() => {
198741
+ toolHistory.retain(preparedOutput, () => {
198593
198742
  outputItem.output = preparedOutput.historyPlaceholder;
198594
- });
198743
+ }, prepared.tool.kind);
198595
198744
  }
198596
198745
  }
198597
198746
  throw new AgentTurnLimitError(
@@ -198731,7 +198880,7 @@ var OpenAICompatibleAgentAdapter = class {
198731
198880
  }
198732
198881
  }));
198733
198882
  const turns = [];
198734
- const toolHistory = new AgentToolResultHistory();
198883
+ const toolHistory = new AgentToolResultHistory(request);
198735
198884
  for (let turn = 1; turn <= request.maxTurns; turn++) {
198736
198885
  const call = {
198737
198886
  model: request.modelId,
@@ -198744,7 +198893,7 @@ var OpenAICompatibleAgentAdapter = class {
198744
198893
  response_format: responseFormat,
198745
198894
  ...tokenLimit
198746
198895
  };
198747
- const estimatedInputTokens = estimateSerializedInputTokens(call);
198896
+ const estimatedInputTokens = toolHistory.compact(() => estimateSerializedInputTokens(call));
198748
198897
  assertInputBudgetAllowsCall(
198749
198898
  this.provider,
198750
198899
  request.inputTokenBudget,
@@ -198798,6 +198947,7 @@ var OpenAICompatibleAgentAdapter = class {
198798
198947
  } else {
198799
198948
  try {
198800
198949
  input2 = parseToolArguments2(callItem.function.arguments);
198950
+ toolHistory.noteCall(tool.name, input2, tool.kind);
198801
198951
  const recorded = createRecordedToolCall(toolCalls, tool.name, input2, tool.kind);
198802
198952
  try {
198803
198953
  const output2 = await tool.execute(input2);
@@ -198826,9 +198976,9 @@ var OpenAICompatibleAgentAdapter = class {
198826
198976
  content: preparedOutput.immediateText
198827
198977
  };
198828
198978
  messages.push(toolMessage);
198829
- toolHistory.retain(() => {
198979
+ toolHistory.retain(preparedOutput, () => {
198830
198980
  toolMessage.content = preparedOutput.historyPlaceholder;
198831
- });
198981
+ }, tool?.kind);
198832
198982
  }
198833
198983
  }
198834
198984
  throw new AgentTurnLimitError(
@@ -198889,7 +199039,7 @@ function createDefaultAgentProviderRegistry() {
198889
199039
  // src/agents/agentRunner.ts
198890
199040
  var fs20 = __toESM(require("fs"));
198891
199041
  var path23 = __toESM(require("path"));
198892
- var import_crypto7 = require("crypto");
199042
+ var import_crypto8 = require("crypto");
198893
199043
 
198894
199044
  // src/contractAssertionSummary.ts
198895
199045
  var path22 = __toESM(require("path"));
@@ -199089,6 +199239,7 @@ function resolveAgentRuntimeLimits(startPath, provider, agent) {
199089
199239
  const defaultMaxTokens = provider === "local" ? DEFAULT_LOCAL_AGENT_MAX_TOKENS : DEFAULT_AGENT_MAX_TOKENS;
199090
199240
  const maxInputTokens = agent.maxInputTokens ?? providerConfig?.max_input_tokens ?? loadedConfig?.max_input_tokens;
199091
199241
  return {
199242
+ contextTokens: agent.contextTokens ?? providerConfig?.context_tokens ?? loadedConfig?.context_tokens ?? (provider === "local" ? DEFAULT_LOCAL_CONTEXT_TOKENS : DEFAULT_AGENT_CONTEXT_TOKENS),
199092
199243
  maxTokens: agent.maxTokens ?? providerConfig?.max_tokens ?? loadedConfig?.max_tokens ?? defaultMaxTokens,
199093
199244
  ...maxInputTokens !== void 0 ? { maxInputTokens } : {},
199094
199245
  maxDepth: agent.maxDepth ?? providerConfig?.max_depth ?? loadedConfig?.max_depth ?? DEFAULT_AGENT_MAX_DEPTH,
@@ -199534,7 +199685,7 @@ function cloneSnapshotValue(value, fallback) {
199534
199685
  return cloneSerializableValue(value) ?? fallback;
199535
199686
  }
199536
199687
  function hashResolvedPrompt(systemPrompt) {
199537
- return (0, import_crypto7.createHash)("sha256").update(systemPrompt, "utf8").digest("hex");
199688
+ return (0, import_crypto8.createHash)("sha256").update(systemPrompt, "utf8").digest("hex");
199538
199689
  }
199539
199690
  function getToolErrorOutput(error2) {
199540
199691
  return { error: error2 instanceof Error ? error2.message : String(error2) };
@@ -199568,7 +199719,7 @@ var AgentInvocationRunner = class {
199568
199719
  return { hop, definitions: [...this.definitions.values()] };
199569
199720
  }
199570
199721
  nextInvocationId() {
199571
- return `agent-${(0, import_crypto7.randomUUID)()}`;
199722
+ return `agent-${(0, import_crypto8.randomUUID)()}`;
199572
199723
  }
199573
199724
  emitActivity(hop, state, kind, name) {
199574
199725
  this.options.onInvocationActivity?.({
@@ -199667,6 +199818,7 @@ var AgentInvocationRunner = class {
199667
199818
  ...request.outputSchema ? { outputSchema: cloneSnapshotValue(request.outputSchema, {}) } : {},
199668
199819
  ...request.outputSchemaName ? { outputSchemaName: request.outputSchemaName } : {},
199669
199820
  maxTokens: request.maxTokens,
199821
+ ...request.contextTokens !== void 0 ? { contextTokens: request.contextTokens } : {},
199670
199822
  ...request.inputTokenBudget ? { maxInputTokens: request.inputTokenBudget.maxInputTokens } : {},
199671
199823
  maxTurns: request.maxTurns,
199672
199824
  ...mcpServerGrants?.length ? { mcpServerGrants: mcpServerGrants.map((grant) => ({
@@ -199730,7 +199882,22 @@ var AgentInvocationRunner = class {
199730
199882
  try {
199731
199883
  const output2 = await execute(input2, context);
199732
199884
  record2.output = cloneSnapshotValue(output2, null);
199733
- return definition.kind === "context" ? output2 : this.toolResultStore.prepare(definition.name, output2);
199885
+ if (definition.kind === "context") {
199886
+ const text = agentInputToText(output2);
199887
+ return new PreparedAgentToolResult(output2, output2, text, JSON.stringify({
199888
+ nornToolResult: {
199889
+ status: "released_from_context",
199890
+ tool: definition.name,
199891
+ input: input2,
199892
+ instruction: "Repeat this archive read/search to recover the same evidence."
199893
+ }
199894
+ }), "context");
199895
+ }
199896
+ return this.toolResultStore.prepare(definition.name, output2, {
199897
+ kind: definition.kind,
199898
+ input: input2,
199899
+ ownerId: parentHop.invocationId
199900
+ });
199734
199901
  } catch (error2) {
199735
199902
  record2.output = getToolErrorOutput(error2);
199736
199903
  throw error2;
@@ -199832,6 +199999,7 @@ var AgentInvocationRunner = class {
199832
199999
  );
199833
200000
  const limits = {
199834
200001
  ...configuredLimits,
200002
+ ...requestOverride?.contextTokens !== void 0 ? { contextTokens: requestOverride.contextTokens } : {},
199835
200003
  ...requestOverride?.maxTokens !== void 0 ? { maxTokens: requestOverride.maxTokens } : {},
199836
200004
  ...requestOverride?.maxInputTokens !== void 0 ? { maxInputTokens: requestOverride.maxInputTokens } : {}
199837
200005
  };
@@ -199862,7 +200030,7 @@ var AgentInvocationRunner = class {
199862
200030
  authoredSystemPrompt,
199863
200031
  invocationId
199864
200032
  );
199865
- const systemPrompt = preparedKnowledge?.assembly.systemPrompt ?? authoredSystemPrompt;
200033
+ let systemPrompt = preparedKnowledge?.assembly.systemPrompt ?? authoredSystemPrompt;
199866
200034
  hop.accepts = this.validateContract(agent, "accepts", input2);
199867
200035
  if (hop.accepts && !hop.accepts.assertion.passed) {
199868
200036
  throw new Error(contractFailureMessage(hop.accepts));
@@ -199893,6 +200061,12 @@ var AgentInvocationRunner = class {
199893
200061
  preparedKnowledge
199894
200062
  );
199895
200063
  const providerTools = toolAssembly.tools;
200064
+ if (!requestOverride && (agent.tools.length > 0 || (agent.grantedMcpServers?.length ?? 0) > 0) && providerTools.some((tool) => tool.name === "norn_search_tool_results")) {
200065
+ systemPrompt += `
200066
+
200067
+ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
200068
+ }
200069
+ hop.context = createAgentContextDiagnostics();
199896
200070
  const outputSchemaName = `${agent.name}_return`.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
199897
200071
  const providerRequest = {
199898
200072
  agent: agent.name,
@@ -199910,6 +200084,8 @@ var AgentInvocationRunner = class {
199910
200084
  outputSchema,
199911
200085
  outputSchemaName,
199912
200086
  maxTokens: limits.maxTokens,
200087
+ contextTokens: limits.contextTokens,
200088
+ contextDiagnostics: hop.context,
199913
200089
  ...this.inputTokenBudget ? { inputTokenBudget: this.inputTokenBudget } : {},
199914
200090
  maxTurns: limits.maxTurns,
199915
200091
  environment: this.options.environment,
@@ -200079,9 +200255,9 @@ var AgentInvocationRunner = class {
200079
200255
  overrideKeys.add(key);
200080
200256
  }
200081
200257
  let callerLimitBreaches = 0;
200082
- if (agent.callableAgents.some((reference) => ["load_knowledge", "norn_read_tool_result"].includes(reference.name.toLowerCase()))) {
200258
+ if (agent.callableAgents.some((reference) => ["load_knowledge", "norn_read_tool_result", "norn_search_tool_results"].includes(reference.name.toLowerCase()))) {
200083
200259
  throw new Error(
200084
- "Agent names 'load_knowledge' and 'norn_read_tool_result' are reserved by Norn."
200260
+ "Agent names 'load_knowledge', 'norn_read_tool_result' and 'norn_search_tool_results' are reserved by Norn."
200085
200261
  );
200086
200262
  }
200087
200263
  if (knowledge?.hasLoadableKnowledge) {
@@ -200387,13 +200563,13 @@ var AgentInvocationRunner = class {
200387
200563
  return Object.prototype.hasOwnProperty.call(child.output, "body") ? child.output.body : child.output.text;
200388
200564
  }));
200389
200565
  }
200390
- const offeredContext = requestToolsOverride?.find((tool) => tool.kind === "context");
200566
+ const offeredContext = requestToolsOverride?.find((tool) => tool.name === "norn_read_tool_result" && tool.kind === "context");
200391
200567
  if (!requestToolsOverride && providerTools.length > 0 || offeredContext) {
200392
200568
  providerTools.push(this.createRecordedProviderTool(parentHop, {
200393
200569
  name: "norn_read_tool_result",
200394
200570
  providerName: offeredContext?.providerName ?? "norn_read_tool_result",
200395
200571
  kind: "context",
200396
- description: offeredContext?.description ?? "Read or search a bounded portion of a tool result that Norn offloaded from active context.",
200572
+ description: offeredContext?.description ?? "Recover earlier evidence by handle before repeating an external lookup. Read up to 12000 characters at a character offset, or find up to five literal query matches. Use norn_search_tool_results if you do not know the handle. Archived evidence describes the state when it was captured.",
200397
200573
  inputSchema: offeredContext?.inputSchema ?? {
200398
200574
  type: "object",
200399
200575
  properties: {
@@ -200404,7 +200580,25 @@ var AgentInvocationRunner = class {
200404
200580
  required: ["handle"],
200405
200581
  additionalProperties: false
200406
200582
  }
200407
- }, async (input2) => this.toolResultStore.read(input2)));
200583
+ }, async (input2) => this.toolResultStore.read(input2, parentHop.invocationId)));
200584
+ }
200585
+ const offeredSearch = requestToolsOverride?.find((tool) => tool.name === "norn_search_tool_results" && tool.kind === "context");
200586
+ if (!requestToolsOverride && providerTools.length > 0 || offeredSearch) {
200587
+ providerTools.push(this.createRecordedProviderTool(parentHop, {
200588
+ name: "norn_search_tool_results",
200589
+ providerName: offeredSearch?.providerName ?? "norn_search_tool_results",
200590
+ kind: "context",
200591
+ description: offeredSearch?.description ?? "Find earlier evidence in this invocation before repeating a tool call. Search literal text across stored results, tool names and input summaries. Omit query to list results. Returns five handles and previews per page; offset is a result index. Read a handle with norn_read_tool_result.",
200592
+ inputSchema: offeredSearch?.inputSchema ?? {
200593
+ type: "object",
200594
+ properties: {
200595
+ query: { type: "string", maxLength: 500 },
200596
+ tool: { type: "string" },
200597
+ offset: { type: "integer", minimum: 0 }
200598
+ },
200599
+ additionalProperties: false
200600
+ }
200601
+ }, async (input2) => this.toolResultStore.searchAll(input2, parentHop.invocationId)));
200408
200602
  }
200409
200603
  if (requestToolsOverride) {
200410
200604
  const currentKeys = new Set(providerTools.map(
@@ -200444,7 +200638,7 @@ var AgentInvocationRunner = class {
200444
200638
  };
200445
200639
 
200446
200640
  // src/agents/agentRecording.ts
200447
- var import_crypto9 = require("crypto");
200641
+ var import_crypto10 = require("crypto");
200448
200642
  var fs23 = __toESM(require("fs"));
200449
200643
  var path26 = __toESM(require("path"));
200450
200644
 
@@ -201378,7 +201572,7 @@ function maskAgentRecordingSecrets(value, secretValues) {
201378
201572
  return masked;
201379
201573
  }
201380
201574
  function hashAgentRecordingPrompt(systemPrompt) {
201381
- return (0, import_crypto9.createHash)("sha256").update(systemPrompt, "utf8").digest("hex");
201575
+ return (0, import_crypto10.createHash)("sha256").update(systemPrompt, "utf8").digest("hex");
201382
201576
  }
201383
201577
  function recomputeHopPromptHashes(hop) {
201384
201578
  if (hop.request) {
@@ -201699,7 +201893,7 @@ function writeAgentRunRecording(input2, options = {}) {
201699
201893
  }
201700
201894
  const temporaryPath = path26.join(
201701
201895
  path26.dirname(destinationPath),
201702
- `.${path26.basename(destinationPath)}.${process.pid}.${(0, import_crypto9.randomUUID)()}.tmp`
201896
+ `.${path26.basename(destinationPath)}.${process.pid}.${(0, import_crypto10.randomUUID)()}.tmp`
201703
201897
  );
201704
201898
  try {
201705
201899
  fs23.writeFileSync(temporaryPath, serialized, { encoding: "utf8", flag: "wx", mode: 384 });
@@ -201754,7 +201948,7 @@ function isAgentRequestSnapshot(value) {
201754
201948
  const validKnowledge = value.knowledge === void 0 || isObjectRecord2(value.knowledge) && typeof value.knowledge.runId === "string" && typeof value.knowledge.snapshotId === "string" && isStringArray(value.knowledge.declarationIds) && ["exact_entity", "lexical_unique", "lexical_ambiguous", "no_match"].includes(
201755
201949
  String(value.knowledge.routingRule)
201756
201950
  ) && isStringArray(value.knowledge.selectedDomainIds) && isFiniteNonNegativeNumber(value.knowledge.estimatedTokens) && typeof value.knowledge.estimatorVersion === "string" && (value.knowledge.domains === void 0 || Array.isArray(value.knowledge.domains) && value.knowledge.domains.every(isAgentKnowledgeDomainSnapshot));
201757
- return validConversation && validTools && validKnowledge && (value.reasoningEffort === void 0 || ["none", "low", "medium", "high", "xhigh", "max"].includes(String(value.reasoningEffort))) && (value.outputSchema === void 0 || isObjectRecord2(value.outputSchema)) && (value.outputSchemaName === void 0 || typeof value.outputSchemaName === "string") && (value.maxInputTokens === void 0 || Number.isSafeInteger(value.maxInputTokens) && value.maxInputTokens > 0) && (value.maxTurns === void 0 || Number.isSafeInteger(value.maxTurns) && value.maxTurns > 0) && (value.sourcePath === void 0 || isSafeProjectRelativePath(value.sourcePath)) && isSafeProjectRelativePath(value.agentSourcePath) && Number.isSafeInteger(value.agentLineNumber) && value.agentLineNumber >= 0 && (value.sequenceName === void 0 || typeof value.sequenceName === "string") && (value.runLineNumber === void 0 || Number.isSafeInteger(value.runLineNumber));
201951
+ return validConversation && validTools && validKnowledge && (value.reasoningEffort === void 0 || ["none", "low", "medium", "high", "xhigh", "max"].includes(String(value.reasoningEffort))) && (value.outputSchema === void 0 || isObjectRecord2(value.outputSchema)) && (value.outputSchemaName === void 0 || typeof value.outputSchemaName === "string") && (value.maxInputTokens === void 0 || Number.isSafeInteger(value.maxInputTokens) && value.maxInputTokens > 0) && (value.maxTurns === void 0 || Number.isSafeInteger(value.maxTurns) && value.maxTurns > 0) && (value.contextTokens === void 0 || Number.isSafeInteger(value.contextTokens) && value.contextTokens > 0) && (value.sourcePath === void 0 || isSafeProjectRelativePath(value.sourcePath)) && isSafeProjectRelativePath(value.agentSourcePath) && Number.isSafeInteger(value.agentLineNumber) && value.agentLineNumber >= 0 && (value.sequenceName === void 0 || typeof value.sequenceName === "string") && (value.runLineNumber === void 0 || Number.isSafeInteger(value.runLineNumber));
201758
201952
  }
201759
201953
  function isAgentResult(value) {
201760
201954
  if (!isObjectRecord2(value) || typeof value.text !== "string" || typeof value.model !== "string" || !Array.isArray(value.toolCalls) || !value.toolCalls.every(isAgentToolCall) || !isFiniteNonNegativeNumber(value.ms) || !isObjectRecord2(value.usage)) {
@@ -201773,7 +201967,16 @@ function isAgentHop(value) {
201773
201967
  if (!isObjectRecord2(value) || typeof value.invocationId !== "string" || typeof value.agent !== "string" || typeof value.success !== "boolean" || !Number.isSafeInteger(value.depth) || value.depth < 0 || typeof value.cycle !== "boolean" || !isAgentResult(value.output) || !Array.isArray(value.children) || !Array.isArray(value.retries) || !isStringArray(value.warnings)) {
201774
201968
  return false;
201775
201969
  }
201776
- return (value.parentInvocationId === void 0 || typeof value.parentInvocationId === "string") && (value.hopPath === void 0 || typeof value.hopPath === "string") && (value.request === void 0 || isAgentRequestSnapshot(value.request)) && (value.providerAttempted === void 0 || typeof value.providerAttempted === "boolean") && (value.providerCompleted === void 0 || typeof value.providerCompleted === "boolean") && (value.contractExhausted === void 0 || typeof value.contractExhausted === "boolean") && (value.inputTokenLimit === void 0 || isObjectRecord2(value.inputTokenLimit) && Number.isSafeInteger(value.inputTokenLimit.maxInputTokens) && value.inputTokenLimit.maxInputTokens > 0 && isFiniteNonNegativeNumber(value.inputTokenLimit.usedInputTokens) && (value.inputTokenLimit.estimatedNextInputTokens === void 0 || isFiniteNonNegativeNumber(value.inputTokenLimit.estimatedNextInputTokens)) && isSafeProjectRelativePath(value.inputTokenLimit.sourcePath) && Number.isSafeInteger(value.inputTokenLimit.lineNumber) && value.inputTokenLimit.lineNumber >= 0) && (value.error === void 0 || typeof value.error === "string") && (value.accepts === void 0 || isAgentContract(value.accepts)) && (value.returns === void 0 || isAgentContract(value.returns)) && value.retries.every(isAgentRetry) && value.children.every(isAgentHop);
201970
+ return (value.parentInvocationId === void 0 || typeof value.parentInvocationId === "string") && (value.context === void 0 || isObjectRecord2(value.context) && [
201971
+ "compactions",
201972
+ "releasedResults",
201973
+ "releasedEstimatedTokens",
201974
+ "peakEstimatedInputTokens",
201975
+ "overBudgetTurns",
201976
+ "recoveryReads",
201977
+ "archiveSearches",
201978
+ "repeatedToolCalls"
201979
+ ].every((key) => isFiniteNonNegativeNumber(value.context[key]))) && (value.hopPath === void 0 || typeof value.hopPath === "string") && (value.request === void 0 || isAgentRequestSnapshot(value.request)) && (value.providerAttempted === void 0 || typeof value.providerAttempted === "boolean") && (value.providerCompleted === void 0 || typeof value.providerCompleted === "boolean") && (value.contractExhausted === void 0 || typeof value.contractExhausted === "boolean") && (value.inputTokenLimit === void 0 || isObjectRecord2(value.inputTokenLimit) && Number.isSafeInteger(value.inputTokenLimit.maxInputTokens) && value.inputTokenLimit.maxInputTokens > 0 && isFiniteNonNegativeNumber(value.inputTokenLimit.usedInputTokens) && (value.inputTokenLimit.estimatedNextInputTokens === void 0 || isFiniteNonNegativeNumber(value.inputTokenLimit.estimatedNextInputTokens)) && isSafeProjectRelativePath(value.inputTokenLimit.sourcePath) && Number.isSafeInteger(value.inputTokenLimit.lineNumber) && value.inputTokenLimit.lineNumber >= 0) && (value.error === void 0 || typeof value.error === "string") && (value.accepts === void 0 || isAgentContract(value.accepts)) && (value.returns === void 0 || isAgentContract(value.returns)) && value.retries.every(isAgentRetry) && value.children.every(isAgentHop);
201777
201980
  }
201778
201981
  function isAgentTrace(value) {
201779
201982
  if (!isObjectRecord2(value) || !Array.isArray(value.agents) || !Array.isArray(value.hops)) {
@@ -201909,7 +202112,7 @@ function loadAgentRunRecording(recordingFilePath, options = {}) {
201909
202112
  }
201910
202113
 
201911
202114
  // src/knowledge/knowledgeRuntime.ts
201912
- var import_crypto10 = require("crypto");
202115
+ var import_crypto11 = require("crypto");
201913
202116
  var path35 = __toESM(require("path"));
201914
202117
 
201915
202118
  // src/knowledge/knowledgeIndexBuilder.ts
@@ -204507,7 +204710,7 @@ var extensionEnsurer;
204507
204710
  var KnowledgeRunCoordinator = class {
204508
204711
  constructor(options) {
204509
204712
  this.options = options;
204510
- this.runId = options.runId ?? `run-${(0, import_crypto10.randomUUID)()}`;
204713
+ this.runId = options.runId ?? `run-${(0, import_crypto11.randomUUID)()}`;
204511
204714
  this.projectRoot = path35.resolve(findProjectRoot(options.startPath));
204512
204715
  this.snapshotId = options.snapshotId;
204513
204716
  this.ensureCurrent = options.ensureCurrent ?? extensionEnsurer ?? (async (projectRoot) => ensureKnowledgeIndexCurrent(projectRoot, { projectRoot }).snapshotId);
@@ -208899,7 +209102,7 @@ function redactUrl(url3, options) {
208899
209102
  }
208900
209103
 
208901
209104
  // src/cli/jsonRedaction.ts
208902
- var import_crypto11 = require("crypto");
209105
+ var import_crypto12 = require("crypto");
208903
209106
  function redactAssertionForJson(assertion, redaction) {
208904
209107
  return {
208905
209108
  ...assertion,
@@ -208966,7 +209169,7 @@ function redactAgentRequestForJson(request, redaction) {
208966
209169
  }))
208967
209170
  }))
208968
209171
  } : void 0,
208969
- promptHash: (0, import_crypto11.createHash)("sha256").update(systemPrompt, "utf8").digest("hex")
209172
+ promptHash: (0, import_crypto12.createHash)("sha256").update(systemPrompt, "utf8").digest("hex")
208970
209173
  };
208971
209174
  }
208972
209175
  function redactAgentHopForJson(hop, redaction) {
@@ -212576,6 +212779,7 @@ var ReplayCoordinator = class {
212576
212779
  ...hop.request.reasoningEffort ? { reasoningEffort: hop.request.reasoningEffort } : {},
212577
212780
  systemPrompt: hop.request.systemPrompt,
212578
212781
  maxTokens: hop.request.maxTokens,
212782
+ ...hop.request.contextTokens !== void 0 ? { contextTokens: hop.request.contextTokens } : {},
212579
212783
  ...hop.request.maxInputTokens !== void 0 ? { maxInputTokens: hop.request.maxInputTokens } : {},
212580
212784
  tools: hop.request.tools.map((tool) => ({
212581
212785
  ...tool,
@@ -212869,7 +213073,11 @@ function addSourceDriftDiagnostics(recording, scopes, runtimeVariables, recordin
212869
213073
  );
212870
213074
  }
212871
213075
  const current = substituteVariables(comparable.systemPrompt, runtimeVariables);
212872
- if (/\{\{[^}]+\}\}/.test(current) || current === hop.request.systemPrompt) {
213076
+ const recoverySuffix = `
213077
+
213078
+ ${TOOL_RESULT_RECOVERY_INSTRUCTIONS}`;
213079
+ const recordedPrompt = hop.request.systemPrompt.endsWith(recoverySuffix) ? hop.request.systemPrompt.slice(0, -recoverySuffix.length) : hop.request.systemPrompt;
213080
+ if (/\{\{[^}]+\}\}/.test(current) || current === recordedPrompt) {
212873
213081
  continue;
212874
213082
  }
212875
213083
  diagnostics.push(
@@ -7647,6 +7647,7 @@ function splitHeaderDirective(value) {
7647
7647
  var LIMIT_DIRECTIVES = {
7648
7648
  max_tokens: "maxTokens",
7649
7649
  max_input_tokens: "maxInputTokens",
7650
+ context_tokens: "contextTokens",
7650
7651
  max_depth: "maxDepth",
7651
7652
  max_invocations: "maxInvocations",
7652
7653
  max_turns: "maxTurns",
@@ -8190,6 +8191,7 @@ end mcp
8190
8191
  returns: pending.returns,
8191
8192
  maxTokens: pending.maxTokens?.value,
8192
8193
  maxInputTokens: pending.maxInputTokens?.value,
8194
+ contextTokens: pending.contextTokens?.value,
8193
8195
  maxDepth: pending.maxDepth?.value,
8194
8196
  maxInvocations: pending.maxInvocations?.value,
8195
8197
  maxTurns: pending.maxTurns?.value,
@@ -9626,6 +9628,7 @@ var AGENT_LIMIT_CONFIG_KEYS = /* @__PURE__ */ new Set([
9626
9628
  "_comment",
9627
9629
  "max_tokens",
9628
9630
  "max_input_tokens",
9631
+ "context_tokens",
9629
9632
  "max_depth",
9630
9633
  "max_invocations",
9631
9634
  "max_turns",
@@ -9635,7 +9638,7 @@ function hasOnlyKeys(value, allowedKeys) {
9635
9638
  return Object.keys(value).every((key) => allowedKeys.has(key));
9636
9639
  }
9637
9640
  function hasValidNornAgentLimits(value) {
9638
- return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_input_tokens === void 0 || isPositiveInteger(value.max_input_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.max_turns === void 0 || isPositiveInteger(value.max_turns)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
9641
+ return (value.max_tokens === void 0 || isPositiveInteger(value.max_tokens)) && (value.max_input_tokens === void 0 || isPositiveInteger(value.max_input_tokens)) && (value.context_tokens === void 0 || isPositiveInteger(value.context_tokens)) && (value.max_depth === void 0 || isPositiveInteger(value.max_depth)) && (value.max_invocations === void 0 || isPositiveInteger(value.max_invocations)) && (value.max_turns === void 0 || isPositiveInteger(value.max_turns)) && (value.contract_retries === void 0 || isNonNegativeInteger(value.contract_retries));
9639
9642
  }
9640
9643
  function isNornAgentProviderConfig(value) {
9641
9644
  if (!isObjectRecord(value)) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "norn-cli",
3
3
  "displayName": "Norn",
4
4
  "description": "Build version-controlled agentic flows in VS Code and run them from the CLI or CI.",
5
- "version": "4.5.1",
5
+ "version": "4.6.0",
6
6
  "publisher": "Norn-PeterKrustanov",
7
7
  "author": {
8
8
  "name": "Peter Krastanov"
@@ -84,6 +84,7 @@
84
84
  "version": 1,
85
85
  "agents": {
86
86
  "max_tokens": 16000,
87
+ "context_tokens": 64000,
87
88
  "max_depth": 5,
88
89
  "max_invocations": 25,
89
90
  "max_turns": 100,
@@ -93,7 +94,8 @@
93
94
  },
94
95
  "providers": {
95
96
  "local": {
96
- "max_tokens": 4096
97
+ "max_tokens": 4096,
98
+ "context_tokens": 16000
97
99
  }
98
100
  }
99
101
  }
@@ -215,6 +217,7 @@
215
217
  "description": "Insert global agent limits, recording settings, and an optional provider-specific override.",
216
218
  "body": {
217
219
  "max_tokens": 16000,
220
+ "context_tokens": 64000,
218
221
  "max_depth": 5,
219
222
  "max_invocations": 25,
220
223
  "max_turns": 100,
@@ -224,7 +227,8 @@
224
227
  },
225
228
  "providers": {
226
229
  "${1:local}": {
227
- "max_tokens": 4096
230
+ "max_tokens": 4096,
231
+ "context_tokens": 16000
228
232
  }
229
233
  }
230
234
  }
@@ -431,6 +435,11 @@
431
435
  "additionalProperties": false,
432
436
  "description": "Agent limits for one model provider. These override global agents settings and are overridden by directives on an individual agent.",
433
437
  "properties": {
438
+ "context_tokens": {
439
+ "type": "integer",
440
+ "minimum": 1,
441
+ "description": "Estimated input-context compaction budget per request. Excludes output tokens; choose below the model window allowing output headroom. This is a soft compaction budget, not a provider context-window guarantee."
442
+ },
434
443
  "_comment": {
435
444
  "$ref": "#/definitions/comment",
436
445
  "description": "Optional human-readable guidance ignored by Norn at runtime."
@@ -488,6 +497,11 @@
488
497
  "additionalProperties": false,
489
498
  "description": "Global recording behavior and global/provider-specific limits for agent runs. Per-agent .nornai or .nornagent directives override limit values.",
490
499
  "properties": {
500
+ "context_tokens": {
501
+ "type": "integer",
502
+ "minimum": 1,
503
+ "description": "Estimated input-context compaction budget per request. Defaults to 64000 (local: 16000). Ordinary context compacts from 80% toward 60%; recovery excerpts have a separate 10% allowance. Excludes output; set below the model window to leave output headroom. Prompts and the newest result are preserved even if they exceed this soft budget."
504
+ },
491
505
  "_comment": {
492
506
  "$ref": "#/definitions/comment",
493
507
  "description": "Optional human-readable guidance ignored by Norn at runtime."