@kmugalkhod/lightcode 0.18.4 → 0.19.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/server.js CHANGED
@@ -635,7 +635,7 @@ var init_web_auth = __esm(() => {
635
635
  });
636
636
 
637
637
  // packages/ai/src/constants.ts
638
- var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24, TODO_CONTENT_MAX_CHARS = 200, TODO_ACTIVE_FORM_MAX_CHARS = 80, PROGRESS_NOTE_PRUNE_MIN_CHARS = 200;
638
+ var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, READ_FILE_OUTPUT_CHARS = 24000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24, TODO_CONTENT_MAX_CHARS = 200, TODO_ACTIVE_FORM_MAX_CHARS = 80, PROGRESS_NOTE_PRUNE_MIN_CHARS = 200;
639
639
 
640
640
  // packages/ai/src/coding-agent-modes.ts
641
641
  import { z as z2 } from "zod";
@@ -2002,14 +2002,15 @@ var init_schema8 = __esm(() => {
2002
2002
 
2003
2003
  // packages/ai/src/read-file/schema.ts
2004
2004
  import { z as z19 } from "zod";
2005
- var readFileDescription = "Read file content from the working directory. Prefer a startLine/endLine range over whole files \u2014 large files are truncated head+tail.", readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
2005
+ var readFileDescription, readFileInputSchema, readFileProviderInputSchema, readFileOutputSchema;
2006
2006
  var init_schema9 = __esm(() => {
2007
2007
  init_base_schemas();
2008
+ readFileDescription = "Read file content from the working directory. Returns whole lines, up to about 24,000 characters (roughly 600 lines) per call, so read a file in one call instead of in slices. " + "If the file is longer, the result stops at endLine and nextStartLine says where to continue. " + "To inspect one spot in a large file, grep for it first, then read a generous startLine/endLine range around it.";
2008
2009
  readFileInputSchema = z19.object({
2009
2010
  path: boundedPathSchema,
2010
2011
  startLine: positiveLineSchema.optional(),
2011
2012
  endLine: positiveLineSchema.optional(),
2012
- maxChars: integerRangeSchema(1, MAX_TOOL_TEXT_OUTPUT_CHARS, "maxChars").optional()
2013
+ maxChars: integerRangeSchema(1, READ_FILE_OUTPUT_CHARS, "maxChars").optional()
2013
2014
  }).superRefine((value, ctx) => {
2014
2015
  if (value.startLine !== undefined && value.endLine !== undefined && value.endLine < value.startLine) {
2015
2016
  ctx.addIssue({
@@ -2030,7 +2031,8 @@ var init_schema9 = __esm(() => {
2030
2031
  endLine: z19.number().int().min(1),
2031
2032
  content: z19.string(),
2032
2033
  totalLines: z19.number().int().nonnegative(),
2033
- truncated: z19.boolean()
2034
+ truncated: z19.boolean(),
2035
+ nextStartLine: z19.number().int().min(1).optional()
2034
2036
  });
2035
2037
  });
2036
2038
 
@@ -2350,7 +2352,7 @@ var init_tool_registry = __esm(() => {
2350
2352
  permissionMode: "read-only",
2351
2353
  execution: "server",
2352
2354
  availability: "git-workspace",
2353
- activation: "specialized",
2355
+ activation: "core",
2354
2356
  outputPolicy: "artifact-if-large"
2355
2357
  },
2356
2358
  git_diff: {
@@ -2363,7 +2365,7 @@ var init_tool_registry = __esm(() => {
2363
2365
  permissionMode: "read-only",
2364
2366
  execution: "server",
2365
2367
  availability: "git-workspace",
2366
- activation: "specialized",
2368
+ activation: "core",
2367
2369
  outputPolicy: "artifact-if-large"
2368
2370
  },
2369
2371
  git_log: {
@@ -2376,7 +2378,7 @@ var init_tool_registry = __esm(() => {
2376
2378
  permissionMode: "read-only",
2377
2379
  execution: "server",
2378
2380
  availability: "git-workspace",
2379
- activation: "specialized",
2381
+ activation: "core",
2380
2382
  outputPolicy: "artifact-if-large"
2381
2383
  },
2382
2384
  git_show: {
@@ -2389,7 +2391,7 @@ var init_tool_registry = __esm(() => {
2389
2391
  permissionMode: "read-only",
2390
2392
  execution: "server",
2391
2393
  availability: "git-workspace",
2392
- activation: "specialized",
2394
+ activation: "core",
2393
2395
  outputPolicy: "artifact-if-large"
2394
2396
  },
2395
2397
  tool_search: {
@@ -2790,6 +2792,7 @@ var init_agent_tools = __esm(() => {
2790
2792
  contextWindow: z27.number().int().positive().optional(),
2791
2793
  preserveRecentTokens: z27.number().int().nonnegative().optional(),
2792
2794
  originalInputTokens: z27.number().int().nonnegative().optional(),
2795
+ promptCacheActive: z27.boolean().optional(),
2793
2796
  assemblyAbortSignal: z27.custom().optional(),
2794
2797
  turnKey: z27.string().min(1).max(200).optional()
2795
2798
  });
@@ -3068,10 +3071,10 @@ ${environmentContext.trim()}` : "");
3068
3071
  var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
3069
3072
  var init_coding_agent_prompt = __esm(() => {
3070
3073
  init_coding_agent_modes();
3071
- defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + "Check the available skill descriptions for every task. When a skill is explicitly named or clearly matches the task, load it with the skill tool before acting and follow its instructions within the user's scope and tool permissions. Skill files may be installed outside the workspace; use the skill tool to read their relative supporting resources. Never claim to have used a skill you have not loaded. " + `Inspect context before risky operations. Progress notes: before a major tool action you may emit ONE short note (one sentence, ~15 words) saying what you are about to do. After a tool returns, mention a finding only if it changes your plan, and state it as fact grounded in what the tool actually returned \u2014 cite the file/line/symbol. NEVER claim you found, changed, or fixed something before a tool result confirms it. Skip notes for routine reads, and do not restate the todo list in prose.
3074
+ defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + "Gather context efficiently \u2014 every tool round trip costs the user seconds. The environment section maps the repository's files: use it to go straight to the right paths instead of listing directories. Locate before you read: use grep or glob_search to find the relevant files and line numbers, then read whole files (read_file returns up to about 600 lines per call) or, in larger files, a range of a few hundred lines around the spot \u2014 never many small slices, and never re-read content you already have. When several reads or searches do not depend on each other, request them together in one step. Skip generated output (dist/, build/, minified bundles, node_modules) unless the task is about it. Stop exploring once you have enough evidence to act or answer. " + "Check the available skill descriptions for every task. When a skill is explicitly named or clearly matches the task, load it with the skill tool before acting and follow its instructions within the user's scope and tool permissions. Skill files may be installed outside the workspace; use the skill tool to read their relative supporting resources. Never claim to have used a skill you have not loaded. " + `Inspect context before risky operations. Progress notes: before a major tool action you may emit ONE short note (one sentence, ~15 words) saying what you are about to do. After a tool returns, mention a finding only if it changes your plan, and state it as fact grounded in what the tool actually returned \u2014 cite the file/line/symbol. NEVER claim you found, changed, or fixed something before a tool result confirms it. Skip notes for routine reads, and do not restate the todo list in prose.
3072
3075
 
3073
3076
  ` + `Task list discipline \u2014 use the todo_write tool to keep a visible, evolving checklist:
3074
- ` + `- Create the list FIRST. For any task with roughly three or more distinct steps, or any multi-file or multi-stage work, call todo_write as your very first action \u2014 before reading or editing \u2014 with every step listed and all of them set to pending.
3077
+ ` + `- Create the list in your first step. For any task with roughly three or more distinct steps, or any multi-file or multi-stage work, call todo_write in your first response with every step listed and all of them set to pending \u2014 request it together with your first reads or searches, not as a separate step, and always before any edit.
3075
3078
  ` + `- Keep exactly ONE item in_progress at a time. Mark an item in_progress immediately before you start it; never have two items in_progress at once.
3076
3079
  ` + `- Mark an item completed only after a tool result confirms it is actually done (test passed, file written, build clean) \u2014 not on intent. Do this the MOMENT it is confirmed; do not batch completions or wait until the end of the turn.
3077
3080
  ` + `- Completions are monotonic: once a task is completed, do not silently move it back to pending or in_progress. Reopen it only if it genuinely regressed.
@@ -3083,7 +3086,7 @@ var init_coding_agent_prompt = __esm(() => {
3083
3086
  ` + `When the task is complete, end with a short summary (a few lines) grounded in the todos you actually completed and the real file diffs \u2014 name the files you edited, do not list steps you did not verify, and do not repeat the todo card verbatim. If anything is incomplete or you are blocked, say so plainly.
3084
3087
 
3085
3088
  ` + "Long-running processes (dev servers, watchers): never run them as plain foreground bash \u2014 the bash tool enforces a timeout and will kill them. Start them detached with output redirected to a log " + '(Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; Unix: nohup npm run dev > dev.log 2>&1 &), then read the log to confirm startup and the actual port (do not assume the default). ' + "Never kill processes or free ports you did not open yourself.";
3086
- toolCallingDisciplineAddendum = "Invoke tools only through the function-calling interface. " + "Never write tool calls as XML or plain text such as <tool_call>, <function_call>, <invoke>, or bare JSON in your message. " + "Tool arguments must be valid JSON matching the tool's schema. " + "Call one tool at a time and wait for its result before deciding the next step.";
3089
+ toolCallingDisciplineAddendum = "Invoke tools only through the function-calling interface. " + "Never write tool calls as XML or plain text such as <tool_call>, <function_call>, <invoke>, or bare JSON in your message. " + "Tool arguments must be valid JSON matching the tool's schema. " + "You may request several independent tool calls in one response; wait for their results before making calls that depend on them.";
3087
3090
  });
3088
3091
 
3089
3092
  // packages/ai/src/common/coerce-tool-input.ts
@@ -3481,20 +3484,26 @@ function collectTextFromUnknown(value) {
3481
3484
  }
3482
3485
  return "";
3483
3486
  }
3484
- function getLastUserText(messages, prompt) {
3487
+ function getUserTexts(messages, prompt) {
3488
+ const texts = [];
3485
3489
  if (Array.isArray(messages)) {
3486
- for (let index = messages.length - 1;index >= 0; index -= 1) {
3487
- const message = messages[index];
3490
+ for (const message of messages) {
3488
3491
  if (!isRecord4(message) || message.role !== "user") {
3489
3492
  continue;
3490
3493
  }
3491
3494
  const text = collectTextFromUnknown(message).trim();
3492
3495
  if (text) {
3493
- return text;
3496
+ texts.push(text);
3494
3497
  }
3495
3498
  }
3496
3499
  }
3497
- return collectTextFromUnknown(prompt).trim();
3500
+ if (texts.length === 0) {
3501
+ const promptText = collectTextFromUnknown(prompt).trim();
3502
+ if (promptText) {
3503
+ texts.push(promptText);
3504
+ }
3505
+ }
3506
+ return texts;
3498
3507
  }
3499
3508
  function escapeRegExp(value) {
3500
3509
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -3520,23 +3529,20 @@ function selectCodingAgentIntentTools({
3520
3529
  messages,
3521
3530
  availableSkillNames = []
3522
3531
  }) {
3523
- const userText = getLastUserText(messages, prompt);
3524
- const normalizedText = userText.toLowerCase();
3525
- if (!normalizedText || casualPromptPattern.test(normalizedText)) {
3532
+ const userTexts = getUserTexts(messages, prompt);
3533
+ if (userTexts.every((text) => casualPromptPattern.test(text))) {
3526
3534
  return [];
3527
3535
  }
3536
+ const latestText = userTexts.at(-1) ?? "";
3528
3537
  const modeTools = getRegistryToolsForMode(mode);
3529
3538
  const selected = new Set(modeTools.filter((toolName) => codingToolRegistry[toolName].activation === "core"));
3530
- if (gitIntentPattern.test(userText)) {
3531
- addTools(selected, gitToolNames);
3532
- }
3533
- if (webIntentPattern.test(userText)) {
3539
+ if (webIntentPattern.test(latestText)) {
3534
3540
  addTools(selected, webToolNames);
3535
3541
  }
3536
- if (mcpIntentPattern.test(userText)) {
3542
+ if (userTexts.some((text) => mcpIntentPattern.test(text))) {
3537
3543
  addTools(selected, mcpToolNames);
3538
3544
  }
3539
- if (skillIntentPattern.test(userText) || requestsAvailableSkill(userText, availableSkillNames)) {
3545
+ if (userTexts.some((text) => skillIntentPattern.test(text) || requestsAvailableSkill(text, availableSkillNames))) {
3540
3546
  selected.add("skill");
3541
3547
  }
3542
3548
  return modeTools.filter((toolName) => selected.has(toolName));
@@ -3616,21 +3622,14 @@ function limitProviderActiveTools(tools, maxTools = maxProviderActiveTools) {
3616
3622
  const toolSet = new Set(tools);
3617
3623
  return providerToolPriority.filter((toolName) => toolSet.has(toolName)).slice(0, maxTools);
3618
3624
  }
3619
- var casualPromptPattern, gitIntentPattern, webIntentPattern, mcpIntentPattern, skillIntentPattern, gitToolNames, webToolNames, mcpToolNames, maxProviderActiveTools = 24, providerToolPriority;
3625
+ var casualPromptPattern, webIntentPattern, mcpIntentPattern, skillIntentPattern, webToolNames, mcpToolNames, maxProviderActiveTools = 24, providerToolPriority;
3620
3626
  var init_intent_tool_selection = __esm(() => {
3621
3627
  init_coding_agent_modes();
3622
3628
  init_tool_registry();
3623
3629
  casualPromptPattern = /^(hi|hello|hey|how are you|how r you|what'?s up|whats up|sup|thanks|thank you|ok|okay|yes|no|nice|cool|great)[\s?!.,]*$/i;
3624
- gitIntentPattern = /(?:\bgit\b|\bcommits?\b|\bstaged?\b|\bunstaged\b|\buntracked\b|\bworking tree\b|\brepositor(?:y|ies)\s+(?:status|diff|history|changes))/i;
3625
3630
  webIntentPattern = /(?:\bhttps?:\/\/|\bwww\.|\bweb(?:site|page)?\b|\binternet\b|\bonline\b|\burl\b|\bbrowser\b|\bsearch\s+(?:the\s+)?web\b|\bbrowse\s+(?:the\s+web|online|for)\b|\blook\s+up\b|\b(?:latest|current|recent|today'?s)\s+(?:news|price|weather|docs?|documentation|release|version)\b)/i;
3626
3631
  mcpIntentPattern = /(?:\bmcp\b|\bmodel context protocol\b)/i;
3627
3632
  skillIntentPattern = /\bskills?\b/i;
3628
- gitToolNames = [
3629
- "git_status",
3630
- "git_diff",
3631
- "git_log",
3632
- "git_show"
3633
- ];
3634
3633
  webToolNames = [
3635
3634
  "web_fetch",
3636
3635
  "web_search"
@@ -5160,19 +5159,44 @@ async function executeReadFile(input, workspaceContext = getDefaultWorkspaceCont
5160
5159
  const fileContent = await fs4.readFile(resolvedPath, "utf8");
5161
5160
  const lines = fileContent.split(/\r?\n/);
5162
5161
  const { start, end } = clampLineRange(lines.length, parsedInput.startLine, parsedInput.endLine);
5163
- const selectedContent = lines.slice(start - 1, end).join(`
5164
- `);
5165
- const maxChars = parsedInput.maxChars ?? DEFAULT_TOOL_TEXT_OUTPUT_CHARS;
5166
- const truncated = truncateText(selectedContent, maxChars);
5162
+ const maxChars = parsedInput.maxChars ?? READ_FILE_OUTPUT_CHARS;
5163
+ const taken = takeWholeLines(lines.slice(start - 1, end), maxChars);
5164
+ const lastLine = start + taken.lineCount - 1;
5167
5165
  return readFileOutputSchema.parse({
5168
5166
  path: relativePath,
5169
5167
  startLine: start,
5170
- endLine: end,
5171
- content: truncated.text,
5168
+ endLine: lastLine,
5169
+ content: taken.text,
5172
5170
  totalLines: lines.length,
5173
- truncated: truncated.truncated
5171
+ truncated: taken.truncated,
5172
+ ...lastLine < end ? { nextStartLine: lastLine + 1 } : {}
5174
5173
  });
5175
5174
  }
5175
+ function takeWholeLines(lines, maxChars) {
5176
+ let used = 0;
5177
+ let lineCount = 0;
5178
+ for (const line of lines) {
5179
+ const cost = line.length + (lineCount > 0 ? 1 : 0);
5180
+ if (used + cost > maxChars) {
5181
+ break;
5182
+ }
5183
+ used += cost;
5184
+ lineCount += 1;
5185
+ }
5186
+ if (lineCount === 0 && lines.length > 0) {
5187
+ return {
5188
+ text: truncateText(lines[0] ?? "", maxChars).text,
5189
+ lineCount: 1,
5190
+ truncated: true
5191
+ };
5192
+ }
5193
+ return {
5194
+ text: lines.slice(0, lineCount).join(`
5195
+ `),
5196
+ lineCount,
5197
+ truncated: lineCount < lines.length
5198
+ };
5199
+ }
5176
5200
  var init_runtime8 = __esm(() => {
5177
5201
  init_resolve_within_workspace();
5178
5202
  init_output_utils();
@@ -6444,10 +6468,12 @@ function buildProviderView(options) {
6444
6468
  }
6445
6469
  const tokenBoundedCutoffIndex = computeTokenBoundedCompactionCutoffIndex(messages, preserveRecentTokens, afterAnchorIndex);
6446
6470
  const userIndicesAfterAnchor = messages.map((message, index) => ({ message, index })).filter(({ message, index }) => index >= afterAnchorIndex && message.role === "user").map(({ index }) => index);
6447
- const rollingTurnCutoffIndex = userIndicesAfterAnchor.length > config.uncachedRollingCompactionUserTurns ? userIndicesAfterAnchor[userIndicesAfterAnchor.length - config.uncachedRollingCompactionUserTurns] ?? afterAnchorIndex : afterAnchorIndex;
6471
+ const rollingKeepUserTurns = Math.max(1, Math.ceil(config.uncachedRollingCompactionUserTurns / 2));
6472
+ const rollingTurnCutoffIndex = userIndicesAfterAnchor.length > rollingKeepUserTurns ? userIndicesAfterAnchor[userIndicesAfterAnchor.length - rollingKeepUserTurns] ?? afterAnchorIndex : afterAnchorIndex;
6448
6473
  const overCompactThreshold = triggerTokens(estimate.tokens) > config.compactAtFraction * inputBudgetTokens;
6474
+ const rollingActive = (options.rollingCompaction ?? options.cacheActive === false) && config.aggressivePruneWhenUncached;
6449
6475
  const userTurnsAfterAnchor = messages.slice(afterAnchorIndex).filter((message) => message.role === "user").length;
6450
- const rollingThresholdReached = uncachedActive && userTurnsAfterAnchor > config.uncachedRollingCompactionUserTurns;
6476
+ const rollingThresholdReached = rollingActive && userTurnsAfterAnchor > (options.continueRolling ? rollingKeepUserTurns : config.uncachedRollingCompactionUserTurns);
6451
6477
  const compactionCutoffIndex = rollingThresholdReached ? Math.max(tokenBoundedCutoffIndex, rollingTurnCutoffIndex) : tokenBoundedCutoffIndex;
6452
6478
  const coveredMessages = capCoverageToTokenBudget(messages.slice(afterAnchorIndex, compactionCutoffIndex), config.maxCoverageTokensPerCompaction);
6453
6479
  let compactionBlockedReason = null;
@@ -6901,6 +6927,14 @@ import path12 from "path";
6901
6927
  import {
6902
6928
  toolModelMessageSchema
6903
6929
  } from "ai";
6930
+ function resolveToolOutputArtifactThresholds({
6931
+ promptCacheActive
6932
+ }) {
6933
+ return {
6934
+ thresholdCharacters: promptCacheActive ? DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS : DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS,
6935
+ currentTurnThresholdCharacters: DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS
6936
+ };
6937
+ }
6904
6938
  function isRecord10(value) {
6905
6939
  return typeof value === "object" && value !== null;
6906
6940
  }
@@ -7080,12 +7114,19 @@ async function artifactizeLargeToolOutputs(messages, options = {}) {
7080
7114
  if (previewCharacters < MIN_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS) {
7081
7115
  throw new RangeError(`Tool-output artifact previews must allow at least ${MIN_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS} characters.`);
7082
7116
  }
7117
+ const currentTurnThreshold = Math.max(0, Math.floor(options.currentTurnThresholdCharacters ?? thresholdCharacters));
7118
+ let currentTurnStart = 0;
7119
+ messages.forEach((message, index) => {
7120
+ if (message.role === "user")
7121
+ currentTurnStart = index + 1;
7122
+ });
7083
7123
  const uniqueArtifacts = new Map;
7084
7124
  let artifactizedOutputs = 0;
7085
7125
  let savedCharacters = 0;
7086
7126
  const providerMessages = [];
7087
- for (const message of messages) {
7127
+ for (const [index, message] of messages.entries()) {
7088
7128
  options.signal?.throwIfAborted();
7129
+ const limit = index >= currentTurnStart ? currentTurnThreshold : thresholdCharacters;
7089
7130
  if (!Array.isArray(message.content)) {
7090
7131
  providerMessages.push(message);
7091
7132
  continue;
@@ -7099,7 +7140,7 @@ async function artifactizeLargeToolOutputs(messages, options = {}) {
7099
7140
  }
7100
7141
  const output = part.output;
7101
7142
  const serialized = serializeToolResultOutput(output);
7102
- if (serialized.length <= thresholdCharacters) {
7143
+ if (serialized.length <= limit) {
7103
7144
  providerContent.push(part);
7104
7145
  continue;
7105
7146
  }
@@ -7131,7 +7172,7 @@ async function artifactizeLargeToolOutputs(messages, options = {}) {
7131
7172
  savedCharacters
7132
7173
  };
7133
7174
  }
7134
- var TOOL_OUTPUT_ARTIFACT_HANDLE_PREFIX = "lightcode-artifact://tool-output/sha256/", DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS, DEFAULT_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 2400, MIN_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 512, SHA256_PATTERN, ARTIFACT_PREVIEW_MARKER = "[Lightcode tool-output artifact]";
7175
+ var TOOL_OUTPUT_ARTIFACT_HANDLE_PREFIX = "lightcode-artifact://tool-output/sha256/", DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS, DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS = 32000, DEFAULT_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 2400, MIN_TOOL_OUTPUT_ARTIFACT_PREVIEW_CHARACTERS = 512, SHA256_PATTERN, ARTIFACT_PREVIEW_MARKER = "[Lightcode tool-output artifact]";
7135
7176
  var init_tool_output_artifacts = __esm(() => {
7136
7177
  init_src();
7137
7178
  DEFAULT_TOOL_OUTPUT_ARTIFACT_THRESHOLD_CHARACTERS = DEFAULT_TOOL_TEXT_OUTPUT_CHARS;
@@ -8617,7 +8658,10 @@ function createCodingAgent({
8617
8658
  originalInputTokens: context.originalInputTokens
8618
8659
  });
8619
8660
  const artifactized = await artifactizeLargeToolOutputs(messages, {
8620
- signal: context.assemblyAbortSignal
8661
+ signal: context.assemblyAbortSignal,
8662
+ ...resolveToolOutputArtifactThresholds({
8663
+ promptCacheActive: context.promptCacheActive ?? false
8664
+ })
8621
8665
  });
8622
8666
  const assembled = assembler.assembleModelMessages(artifactized.messages, {
8623
8667
  preserveRecentTokens: context.preserveRecentTokens
@@ -8683,6 +8727,7 @@ function createCodingAgent({
8683
8727
  reservedOutputTokens: options.maxOutputTokens,
8684
8728
  preserveRecentTokens: options.preserveRecentTokens,
8685
8729
  originalInputTokens: options.originalInputTokens,
8730
+ promptCacheActive: options.promptCacheActive,
8686
8731
  assemblyAbortSignal: options.assemblyAbortSignal
8687
8732
  },
8688
8733
  instructions
@@ -13342,7 +13387,8 @@ function summarizeRawModel(raw3) {
13342
13387
  const pricingRaw = Reflect.get(raw3, "pricing");
13343
13388
  const pricing = typeof pricingRaw === "object" && pricingRaw !== null ? {
13344
13389
  prompt: typeof Reflect.get(pricingRaw, "prompt") === "string" ? Reflect.get(pricingRaw, "prompt") : null,
13345
- completion: typeof Reflect.get(pricingRaw, "completion") === "string" ? Reflect.get(pricingRaw, "completion") : null
13390
+ completion: typeof Reflect.get(pricingRaw, "completion") === "string" ? Reflect.get(pricingRaw, "completion") : null,
13391
+ inputCacheRead: typeof Reflect.get(pricingRaw, "input_cache_read") === "string" ? Reflect.get(pricingRaw, "input_cache_read") : null
13346
13392
  } : null;
13347
13393
  return {
13348
13394
  id,
@@ -13372,12 +13418,12 @@ async function listOpenRouterModels({
13372
13418
  forceRefresh = false
13373
13419
  } = {}) {
13374
13420
  const cache = readCache();
13375
- if (!forceRefresh && cache && Date.now() - cache.fetchedAt < cacheTtlMs) {
13421
+ if (!forceRefresh && cache && cache.version === modelsCacheVersion && Date.now() - cache.fetchedAt < cacheTtlMs) {
13376
13422
  return { models: cache.models, fromCache: true };
13377
13423
  }
13378
13424
  try {
13379
13425
  const models = await fetchModelsFromApi();
13380
- writeCache({ fetchedAt: Date.now(), models });
13426
+ writeCache({ version: modelsCacheVersion, fetchedAt: Date.now(), models });
13381
13427
  return { models, fromCache: false };
13382
13428
  } catch (error) {
13383
13429
  logger5.warn("openrouter_models_fetch_failed", {
@@ -13415,12 +13461,14 @@ function toCapabilityPricing(pricing) {
13415
13461
  if (!Number.isFinite(promptPerToken) || !Number.isFinite(completionPerToken)) {
13416
13462
  return null;
13417
13463
  }
13464
+ const cacheReadPerToken = pricing.inputCacheRead ? Number.parseFloat(pricing.inputCacheRead) : Number.NaN;
13418
13465
  return {
13419
13466
  inputPerMTok: promptPerToken * 1e6,
13420
- outputPerMTok: completionPerToken * 1e6
13467
+ outputPerMTok: completionPerToken * 1e6,
13468
+ cachedInputPerMTok: Number.isFinite(cacheReadPerToken) && cacheReadPerToken > 0 ? cacheReadPerToken * 1e6 : null
13421
13469
  };
13422
13470
  }
13423
- var logger5, openRouterModelsUrl = "https://openrouter.ai/api/v1/models", cacheTtlMs, fetchTimeoutMs = 15000, openRouterModelSummarySchema, modelsCacheSchema;
13471
+ var logger5, openRouterModelsUrl = "https://openrouter.ai/api/v1/models", cacheTtlMs, fetchTimeoutMs = 15000, modelsCacheVersion = 2, openRouterModelSummarySchema, modelsCacheSchema;
13424
13472
  var init_openrouter_models = __esm(() => {
13425
13473
  init_src();
13426
13474
  init_network_fetch();
@@ -13435,10 +13483,12 @@ var init_openrouter_models = __esm(() => {
13435
13483
  supportsReasoning: z40.boolean(),
13436
13484
  pricing: z40.object({
13437
13485
  prompt: z40.string().nullable(),
13438
- completion: z40.string().nullable()
13486
+ completion: z40.string().nullable(),
13487
+ inputCacheRead: z40.string().nullable().default(null)
13439
13488
  }).nullable()
13440
13489
  });
13441
13490
  modelsCacheSchema = z40.object({
13491
+ version: z40.number().int().optional(),
13442
13492
  fetchedAt: z40.number(),
13443
13493
  models: z40.array(openRouterModelSummarySchema)
13444
13494
  });
@@ -13722,6 +13772,7 @@ function resolveAnthropicModel({
13722
13772
  contextWindow: getModelContextWindow("anthropic", modelId),
13723
13773
  missingCredentialHints,
13724
13774
  supportsPromptCaching: true,
13775
+ rollingCompaction: false,
13725
13776
  pricing: getAnthropicModelPricing(modelId),
13726
13777
  webSearchCapability,
13727
13778
  providerTools: webSearchCapability.execution === "provider" ? {
@@ -13737,7 +13788,7 @@ function resolveCapabilityPricing(capabilities, cachedInputFactor) {
13737
13788
  return {
13738
13789
  inputPerMTok: pricing.inputPerMTok,
13739
13790
  outputPerMTok: pricing.outputPerMTok,
13740
- cachedInputPerMTok: cachedInputFactor === null ? null : pricing.inputPerMTok * cachedInputFactor
13791
+ cachedInputPerMTok: pricing.cachedInputPerMTok ?? (cachedInputFactor === null ? null : pricing.inputPerMTok * cachedInputFactor)
13741
13792
  };
13742
13793
  }
13743
13794
  function resolveOpenAITransportModel({
@@ -13786,6 +13837,8 @@ function resolveOpenAITransportModel({
13786
13837
  env,
13787
13838
  storedCredentials
13788
13839
  });
13840
+ const openRouterAnthropicFamily = isOpenRouter && isOpenRouterAnthropicFamilyModel(configuredModel);
13841
+ const openRouterCatalogCaching = isOpenRouter && (capabilities?.pricing?.cachedInputPerMTok ?? 0) > 0;
13789
13842
  return {
13790
13843
  provider: config6.provider,
13791
13844
  configuredModel,
@@ -13799,8 +13852,9 @@ function resolveOpenAITransportModel({
13799
13852
  contextWindow: capabilities?.contextLength ?? getModelContextWindow(config6.provider, configuredModel),
13800
13853
  missingCredentialHints: apiKey ? [] : isOpenCodeZen ? ["Set OPENCODE_API_KEY to call OpenCode Zen models."] : isOpenRouter ? ["Set OPENROUTER_API_KEY to call OpenRouter models."] : [],
13801
13854
  maxCompletionTokens: capabilities?.maxCompletionTokens ?? null,
13802
- supportsPromptCaching: isOpenRouter && isOpenRouterAnthropicFamilyModel(configuredModel),
13803
- pricing: resolveCapabilityPricing(capabilities, isOpenRouter && isOpenRouterAnthropicFamilyModel(configuredModel) ? 0.1 : null),
13855
+ supportsPromptCaching: openRouterAnthropicFamily || openRouterCatalogCaching,
13856
+ rollingCompaction: !openRouterAnthropicFamily,
13857
+ pricing: resolveCapabilityPricing(capabilities, openRouterAnthropicFamily ? 0.1 : null),
13804
13858
  webSearchCapability,
13805
13859
  providerTools: openRouterProvider && webSearchCapability.execution === "provider" ? {
13806
13860
  web_search: createOpenRouterWebSearchTool(openRouterProvider, webSearchCapability)
@@ -14058,6 +14112,7 @@ var init_subagent_task_store = __esm(() => {
14058
14112
  });
14059
14113
 
14060
14114
  // apps/server/src/lib/workspace-context.ts
14115
+ import { execFile as execFile3 } from "child_process";
14061
14116
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
14062
14117
  import {
14063
14118
  chmod as chmod2,
@@ -14072,6 +14127,7 @@ import {
14072
14127
  } from "fs/promises";
14073
14128
  import { platform as platform3 } from "os";
14074
14129
  import path21 from "path";
14130
+ import { promisify as promisify4 } from "util";
14075
14131
  import { z as z41 } from "zod";
14076
14132
  function isContainedPath(root, candidate) {
14077
14133
  const relative = path21.relative(root, candidate);
@@ -14080,12 +14136,16 @@ function isContainedPath(root, candidate) {
14080
14136
  function escapeAttribute(value) {
14081
14137
  return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
14082
14138
  }
14083
- function instructionEpochStatePath({
14139
+ function sessionStatePath({
14140
+ directory,
14084
14141
  sessionId,
14085
14142
  dataDir
14086
14143
  }) {
14087
14144
  const sessionKey = createHash5("sha256").update(sessionId).digest("hex");
14088
- return path21.join(dataDir ?? getLightcodeDataDir(), "instruction-epochs", `${sessionKey}.json`);
14145
+ return path21.join(dataDir ?? getLightcodeDataDir(), directory, `${sessionKey}.json`);
14146
+ }
14147
+ function instructionEpochStatePath(options) {
14148
+ return sessionStatePath({ directory: "instruction-epochs", ...options });
14089
14149
  }
14090
14150
  async function loadInstructionEpochs(statePath) {
14091
14151
  try {
@@ -14106,6 +14166,9 @@ async function loadInstructionEpochs(statePath) {
14106
14166
  return [];
14107
14167
  }
14108
14168
  async function persistInstructionEpochs(statePath, epochs) {
14169
+ await writePrivateJsonFile(statePath, { epochs });
14170
+ }
14171
+ async function writePrivateJsonFile(statePath, value) {
14109
14172
  const directory = path21.dirname(statePath);
14110
14173
  await mkdir3(directory, { recursive: true, mode: 448 });
14111
14174
  await chmod2(directory, 448).catch(() => {
@@ -14113,7 +14176,7 @@ async function persistInstructionEpochs(statePath, epochs) {
14113
14176
  });
14114
14177
  const temporaryPath = `${statePath}.${process.pid}.${randomUUID3()}.tmp`;
14115
14178
  try {
14116
- await writeFile2(temporaryPath, `${JSON.stringify({ epochs }, null, 2)}
14179
+ await writeFile2(temporaryPath, `${JSON.stringify(value, null, 2)}
14117
14180
  `, { encoding: "utf8", flag: "wx", mode: 384 });
14118
14181
  await rename2(temporaryPath, statePath);
14119
14182
  await chmod2(statePath, 384).catch(() => {
@@ -14362,6 +14425,138 @@ async function buildDirectoryListing(cwd) {
14362
14425
  return "(unable to list directory)";
14363
14426
  }
14364
14427
  }
14428
+ async function listRepositoryFiles(cwd) {
14429
+ try {
14430
+ const { stdout } = await execFileAsync3("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
14431
+ cwd,
14432
+ encoding: "utf8",
14433
+ maxBuffer: 64 * 1024 * 1024,
14434
+ timeout: repositoryFileListTimeoutMs,
14435
+ windowsHide: true
14436
+ });
14437
+ return [...new Set(stdout.split("\x00").filter(Boolean))];
14438
+ } catch {
14439
+ return null;
14440
+ }
14441
+ }
14442
+ function createRepositoryTreeNode() {
14443
+ return { directories: new Map, files: [], fileCount: 0 };
14444
+ }
14445
+ function compareNames(left, right) {
14446
+ return left < right ? -1 : left > right ? 1 : 0;
14447
+ }
14448
+ function visibleSubdirectories(node) {
14449
+ const all = [...node.directories.entries()].sort(([left], [right]) => compareNames(left, right)).map(([name, child]) => ({ name, node: child }));
14450
+ return {
14451
+ shown: all.slice(0, maxRepositoryMapChildren),
14452
+ hiddenCount: Math.max(0, all.length - maxRepositoryMapChildren)
14453
+ };
14454
+ }
14455
+ function subdirectoryLines(node, depth) {
14456
+ const indent = " ".repeat(depth);
14457
+ const { shown, hiddenCount } = visibleSubdirectories(node);
14458
+ const lines = shown.map(({ name, node: child }) => `${indent}${name}/ (${child.fileCount})`);
14459
+ if (hiddenCount > 0) {
14460
+ lines.push(`${indent}\u2026 ${hiddenCount} more folders`);
14461
+ }
14462
+ return lines;
14463
+ }
14464
+ function fileNameLines(node, depth) {
14465
+ const indent = " ".repeat(depth);
14466
+ const files = [...node.files].sort(compareNames);
14467
+ const lines = files.slice(0, maxRepositoryMapChildren).map((name) => `${indent}${name}`);
14468
+ if (files.length > lines.length) {
14469
+ lines.push(`${indent}\u2026 ${files.length - lines.length} more files`);
14470
+ }
14471
+ return lines;
14472
+ }
14473
+ function filePlaceholderLines(node, depth) {
14474
+ return node.files.length > 0 ? [`${" ".repeat(depth)}\u2026 ${node.files.length} files`] : [];
14475
+ }
14476
+ function linesCost(lines) {
14477
+ return lines.reduce((sum, line) => sum + line.length + 1, 0);
14478
+ }
14479
+ function formatRepositoryMap(paths, maxChars = maxRepositoryMapChars) {
14480
+ const root = createRepositoryTreeNode();
14481
+ for (const filePath of paths) {
14482
+ const segments = filePath.split("/").filter(Boolean);
14483
+ const fileName = segments.pop();
14484
+ if (!fileName) {
14485
+ continue;
14486
+ }
14487
+ let current = root;
14488
+ current.fileCount += 1;
14489
+ for (const segment of segments) {
14490
+ let child = current.directories.get(segment);
14491
+ if (!child) {
14492
+ child = createRepositoryTreeNode();
14493
+ current.directories.set(segment, child);
14494
+ }
14495
+ child.fileCount += 1;
14496
+ current = child;
14497
+ }
14498
+ current.files.push(fileName);
14499
+ }
14500
+ if (root.fileCount === 0) {
14501
+ return "(no files)";
14502
+ }
14503
+ const order = [
14504
+ { node: root, depth: 0 }
14505
+ ];
14506
+ const expanded = new Set;
14507
+ let used = 0;
14508
+ for (let index = 0;index < order.length; index += 1) {
14509
+ const { node, depth } = order[index];
14510
+ const cost = linesCost(subdirectoryLines(node, depth)) + linesCost(filePlaceholderLines(node, depth));
14511
+ if (node !== root && used + cost > maxChars) {
14512
+ continue;
14513
+ }
14514
+ used += cost;
14515
+ expanded.add(node);
14516
+ for (const { name, node: child } of visibleSubdirectories(node).shown) {
14517
+ if (!name.startsWith(".")) {
14518
+ order.push({ node: child, depth: depth + 1 });
14519
+ }
14520
+ }
14521
+ }
14522
+ const fileCandidates = order.filter(({ node }) => expanded.has(node) && node.files.length > 0).sort((left, right) => left.node === root ? -1 : right.node === root ? 1 : left.node.files.length - right.node.files.length);
14523
+ const filesShown = new Set;
14524
+ for (const { node, depth } of fileCandidates) {
14525
+ const delta = linesCost(fileNameLines(node, depth)) - linesCost(filePlaceholderLines(node, depth));
14526
+ if (used + delta > maxChars) {
14527
+ continue;
14528
+ }
14529
+ used += delta;
14530
+ filesShown.add(node);
14531
+ }
14532
+ const lines = [];
14533
+ const render = (node, depth) => {
14534
+ const { shown } = visibleSubdirectories(node);
14535
+ subdirectoryLines(node, depth).forEach((line, index) => {
14536
+ lines.push(line);
14537
+ const child = shown[index]?.node;
14538
+ if (child && expanded.has(child)) {
14539
+ render(child, depth + 1);
14540
+ }
14541
+ });
14542
+ lines.push(...filesShown.has(node) ? fileNameLines(node, depth) : filePlaceholderLines(node, depth));
14543
+ };
14544
+ render(root, 0);
14545
+ return lines.join(`
14546
+ `);
14547
+ }
14548
+ async function buildRepositoryOverview(cwd) {
14549
+ const files = await listRepositoryFiles(cwd);
14550
+ if (!files) {
14551
+ return ["Top-level entries:", await buildDirectoryListing(cwd)].join(`
14552
+ `);
14553
+ }
14554
+ return [
14555
+ `Repository files (${files.length}; dir/ (N) = N files inside, "\u2026" = more not shown; use glob_search or list_files for the rest):`,
14556
+ formatRepositoryMap(files)
14557
+ ].join(`
14558
+ `);
14559
+ }
14365
14560
  function buildGitSummary(cwd) {
14366
14561
  const cached = gitSummaryCache.get(cwd);
14367
14562
  if (cached && cached.expiresAt > Date.now()) {
@@ -14441,13 +14636,98 @@ async function buildWorkspaceInstructionBaseline({
14441
14636
  `)
14442
14637
  };
14443
14638
  }
14639
+ function memoizeEnvironmentSnapshot(statePath, snapshot) {
14640
+ environmentSnapshotMemo.delete(statePath);
14641
+ environmentSnapshotMemo.set(statePath, snapshot);
14642
+ if (environmentSnapshotMemo.size > maxMemoizedEnvironmentSnapshots) {
14643
+ const oldest = environmentSnapshotMemo.keys().next().value;
14644
+ if (oldest !== undefined) {
14645
+ environmentSnapshotMemo.delete(oldest);
14646
+ }
14647
+ }
14648
+ return snapshot;
14649
+ }
14650
+ async function captureEnvironmentSnapshot(cwd) {
14651
+ const [overview, git] = await Promise.all([
14652
+ buildRepositoryOverview(cwd),
14653
+ buildGitSummary(cwd)
14654
+ ]);
14655
+ const capturedAt = new Date().toISOString();
14656
+ return {
14657
+ cwd,
14658
+ capturedAt,
14659
+ body: [
14660
+ `Working directory: ${cwd}`,
14661
+ `Platform: ${describePlatform()}`,
14662
+ `Date: ${capturedAt.slice(0, 10)} (session start)`,
14663
+ "",
14664
+ "Git (snapshot from session start; call git_status for the current state):",
14665
+ git,
14666
+ "",
14667
+ overview
14668
+ ].join(`
14669
+ `)
14670
+ };
14671
+ }
14672
+ async function loadEnvironmentSnapshot(statePath, cwd) {
14673
+ const memoized = environmentSnapshotMemo.get(statePath);
14674
+ if (memoized?.cwd === cwd) {
14675
+ return memoized;
14676
+ }
14677
+ try {
14678
+ const parsed = environmentSnapshotSchema.safeParse(JSON.parse(await readFile4(statePath, "utf8")));
14679
+ if (parsed.success && parsed.data.cwd === cwd) {
14680
+ return memoizeEnvironmentSnapshot(statePath, parsed.data);
14681
+ }
14682
+ if (!parsed.success) {
14683
+ logger7.warn("workspace_environment_snapshot_invalid", { statePath });
14684
+ }
14685
+ } catch (error) {
14686
+ const code = isRecord3(error) ? Reflect.get(error, "code") : undefined;
14687
+ if (code !== "ENOENT") {
14688
+ logger7.warn("workspace_environment_snapshot_load_failed", {
14689
+ statePath,
14690
+ error: getErrorMessage(error)
14691
+ });
14692
+ }
14693
+ }
14694
+ return null;
14695
+ }
14696
+ async function resolveEnvironmentSnapshot({
14697
+ cwd,
14698
+ sessionId,
14699
+ dataDir
14700
+ }) {
14701
+ if (!sessionId) {
14702
+ return captureEnvironmentSnapshot(cwd);
14703
+ }
14704
+ const statePath = sessionStatePath({
14705
+ directory: "session-environment",
14706
+ sessionId,
14707
+ dataDir
14708
+ });
14709
+ const existing = await loadEnvironmentSnapshot(statePath, cwd);
14710
+ if (existing) {
14711
+ return existing;
14712
+ }
14713
+ const snapshot = memoizeEnvironmentSnapshot(statePath, await captureEnvironmentSnapshot(cwd));
14714
+ try {
14715
+ await writePrivateJsonFile(statePath, snapshot);
14716
+ } catch (error) {
14717
+ logger7.warn("workspace_environment_snapshot_persist_failed", {
14718
+ statePath,
14719
+ error: getErrorMessage(error)
14720
+ });
14721
+ }
14722
+ return snapshot;
14723
+ }
14444
14724
  async function buildWorkspaceContext({
14445
14725
  cwd,
14446
14726
  sessionId,
14447
14727
  relatedPaths,
14448
14728
  dataDir
14449
14729
  }) {
14450
- const [baseline, nestedInstructions, listing, git] = await Promise.all([
14730
+ const [baseline, nestedInstructions, snapshot] = await Promise.all([
14451
14731
  buildWorkspaceInstructionBaseline({ cwd }),
14452
14732
  buildNestedWorkspaceInstructionBlock({
14453
14733
  cwd,
@@ -14455,26 +14735,16 @@ async function buildWorkspaceContext({
14455
14735
  relatedPaths,
14456
14736
  dataDir
14457
14737
  }),
14458
- buildDirectoryListing(cwd),
14459
- buildGitSummary(cwd)
14738
+ resolveEnvironmentSnapshot({ cwd, sessionId, dataDir })
14460
14739
  ]);
14461
14740
  const skills = buildAvailableSkills(cwd);
14462
- const today = new Date().toISOString().slice(0, 10);
14463
14741
  const parts = [
14464
14742
  baseline.block,
14465
14743
  nestedInstructions ? `
14466
14744
  ${nestedInstructions}` : "",
14467
14745
  "",
14468
14746
  "<environment>",
14469
- `Working directory: ${cwd}`,
14470
- `Platform: ${describePlatform()}`,
14471
- `Date: ${today}`,
14472
- "",
14473
- "Git:",
14474
- git,
14475
- "",
14476
- "Top-level entries:",
14477
- listing,
14747
+ snapshot.body,
14478
14748
  skills ? `
14479
14749
  ${skills}` : "",
14480
14750
  "</environment>"
@@ -14485,48 +14755,12 @@ ${skills}` : "",
14485
14755
  \u2026 (environment truncated)
14486
14756
  </environment>` : block;
14487
14757
  }
14488
- async function buildWorkspaceContextDelta({
14489
- cwd,
14490
- sessionId,
14491
- relatedPaths,
14492
- dataDir
14493
- }) {
14494
- const [baseline, nestedInstructions, git] = await Promise.all([
14495
- buildWorkspaceInstructionBaseline({ cwd }),
14496
- buildNestedWorkspaceInstructionBlock({
14497
- cwd,
14498
- sessionId,
14499
- relatedPaths,
14500
- dataDir
14501
- }),
14502
- buildGitSummary(cwd)
14503
- ]);
14504
- const skills = buildAvailableSkills(cwd);
14505
- const today = new Date().toISOString().slice(0, 10);
14506
- return [
14507
- baseline.block,
14508
- nestedInstructions ? `
14509
- ${nestedInstructions}` : "",
14510
- "",
14511
- "<environment>",
14512
- `Working directory: ${cwd}`,
14513
- `Platform: ${describePlatform()}`,
14514
- `Date: ${today}`,
14515
- "(Top-level listing is available through list_files; current git state follows.)",
14516
- "",
14517
- "Git:",
14518
- git,
14519
- skills ? `
14520
- ${skills}` : "",
14521
- "</environment>"
14522
- ].join(`
14523
- `);
14524
- }
14525
- var logger7, projectInstructionNames, maxListedEntries = 40, maxInstructionChars = 6000, maxNestedInstructionChars = 6000, maxNestedInstructionFileChars = 1600, maxBlockChars = 18000, instructionEpochSchema, instructionEpochStateSchema, gitSummaryCacheTtlMs = 5000, gitSummaryCache;
14758
+ var execFileAsync3, logger7, projectInstructionNames, maxListedEntries = 40, maxInstructionChars = 6000, maxNestedInstructionChars = 6000, maxNestedInstructionFileChars = 1600, maxRepositoryMapChars = 6000, maxRepositoryMapChildren = 32, maxBlockChars = 24000, repositoryFileListTimeoutMs = 5000, instructionEpochSchema, instructionEpochStateSchema, gitSummaryCacheTtlMs = 5000, gitSummaryCache, environmentSnapshotSchema, maxMemoizedEnvironmentSnapshots = 256, environmentSnapshotMemo;
14526
14759
  var init_workspace_context = __esm(() => {
14527
14760
  init_runtime17();
14528
14761
  init_src2();
14529
14762
  init_src();
14763
+ execFileAsync3 = promisify4(execFile3);
14530
14764
  logger7 = createLogger("workspace-context");
14531
14765
  projectInstructionNames = ["AGENTS.md", "CLAUDE.md"];
14532
14766
  instructionEpochSchema = z41.object({
@@ -14540,6 +14774,12 @@ var init_workspace_context = __esm(() => {
14540
14774
  epochs: z41.array(instructionEpochSchema)
14541
14775
  });
14542
14776
  gitSummaryCache = new Map;
14777
+ environmentSnapshotSchema = z41.object({
14778
+ cwd: z41.string().min(1),
14779
+ capturedAt: z41.string().min(1),
14780
+ body: z41.string()
14781
+ });
14782
+ environmentSnapshotMemo = new Map;
14543
14783
  });
14544
14784
 
14545
14785
  // apps/server/src/lib/subagent-runner.ts
@@ -14621,7 +14861,8 @@ async function runSubagentToolTask(input, context2, deps) {
14621
14861
  abortSignal: context2.abortSignal,
14622
14862
  prepareStep: async ({ messages }) => {
14623
14863
  const artifactized = await artifactizeLargeToolOutputs(messages, {
14624
- signal: context2.abortSignal
14864
+ signal: context2.abortSignal,
14865
+ currentTurnThresholdCharacters: DEFAULT_CURRENT_TURN_TOOL_OUTPUT_THRESHOLD_CHARACTERS
14625
14866
  });
14626
14867
  const turn = assembler.assembleModelMessages(artifactized.messages, {
14627
14868
  preserveRecentTokens
@@ -15129,6 +15370,7 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
15129
15370
  });
15130
15371
  const contextConfig = lightcodeConfigResult.config.context;
15131
15372
  const cacheActive = resolvedProviderModel.supportsPromptCaching;
15373
+ const rollingCompaction = resolvedProviderModel.rollingCompaction ?? !cacheActive;
15132
15374
  const endpointLimitTokens = getLearnedContextLimit(chatModelId);
15133
15375
  const effectiveContextWindow = resolveContextWindowTokens({
15134
15376
  config: contextConfig,
@@ -15136,9 +15378,12 @@ async function streamSessionChat(c, sessionIdentifier, messagesPayload, cwd, mod
15136
15378
  endpointLimitTokens
15137
15379
  });
15138
15380
  const reservedOutputTokens = Math.min(resolveMaxOutputTokens(lightcodeConfigResult.config.maxOutputTokens, lightcodeConfigDefaults.maxOutputTokens, resolvedProviderModel.maxCompletionTokens), Math.max(1024, Math.floor(effectiveContextWindow / 2)));
15139
- const isFirstAssistantTurn = !validatedMessages.some((message) => message.role === "assistant");
15140
15381
  const relatedPaths = collectRelatedWorkspacePaths(validatedMessages);
15141
- const environmentContext = isFirstAssistantTurn ? await buildWorkspaceContext({ cwd, sessionId, relatedPaths }) : await buildWorkspaceContextDelta({ cwd, sessionId, relatedPaths });
15382
+ const environmentContext = await buildWorkspaceContext({
15383
+ cwd,
15384
+ sessionId,
15385
+ relatedPaths
15386
+ });
15142
15387
  const availableSkillNames = listSkills({ cwd }).map((skill) => skill.name);
15143
15388
  const useFastChatPath = shouldUseFastChatPath({
15144
15389
  messages: providerInputMessages,
@@ -15208,6 +15453,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15208
15453
  endpointLimitTokens,
15209
15454
  pendingInteractionCount,
15210
15455
  cacheActive,
15456
+ rollingCompaction,
15211
15457
  fixedInputTokens: turnAssembler.fixedInputTokens
15212
15458
  });
15213
15459
  if (contextConfig.autoCompact) {
@@ -15238,6 +15484,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15238
15484
  endpointLimitTokens,
15239
15485
  pendingInteractionCount,
15240
15486
  cacheActive,
15487
+ rollingCompaction,
15241
15488
  fixedInputTokens: turnAssembler.fixedInputTokens
15242
15489
  });
15243
15490
  logger11.info("context_auto_compacted", {
@@ -15280,6 +15527,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15280
15527
  endpointLimitTokens,
15281
15528
  pendingInteractionCount: 0,
15282
15529
  cacheActive,
15530
+ rollingCompaction,
15283
15531
  fixedInputTokens: turnAssembler.fixedInputTokens
15284
15532
  });
15285
15533
  if (overflowView.coveredMessages.length > 0) {
@@ -15306,6 +15554,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15306
15554
  endpointLimitTokens,
15307
15555
  pendingInteractionCount: 0,
15308
15556
  cacheActive,
15557
+ rollingCompaction,
15309
15558
  fixedInputTokens: turnAssembler.fixedInputTokens
15310
15559
  });
15311
15560
  logger11.info("context_overflow_recovery", {
@@ -15359,7 +15608,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15359
15608
  noteContextOverflow(sessionId);
15360
15609
  };
15361
15610
  const maybeRunRollingCompaction = async (finishedMessages) => {
15362
- if (cacheActive || !contextConfig.autoCompact) {
15611
+ if (!rollingCompaction || !contextConfig.autoCompact) {
15363
15612
  return;
15364
15613
  }
15365
15614
  if (rollingCompactionInFlight.has(sessionId)) {
@@ -15367,38 +15616,45 @@ ${environmentContext}` : agentCallSettings.instructions;
15367
15616
  }
15368
15617
  rollingCompactionInFlight.add(sessionId);
15369
15618
  try {
15370
- const latestState = await loadSessionContextStateSafe(sessionId);
15371
- const rollingView = buildProviderView({
15372
- messages: finishedMessages,
15373
- contextState: latestState,
15374
- config: contextConfig,
15375
- modelContextWindow: resolvedProviderModel.contextWindow,
15376
- reservedOutputTokens: budgetReservedOutputTokens,
15377
- endpointLimitTokens,
15378
- pendingInteractionCount: await countPendingChatInteractions(sessionId),
15379
- cacheActive,
15380
- fixedInputTokens: turnAssembler.fixedInputTokens
15381
- });
15382
- if (!rollingView.needsRollingCompaction) {
15383
- return;
15619
+ let latestState = await loadSessionContextStateSafe(sessionId);
15620
+ const pendingInteractionCount2 = await countPendingChatInteractions(sessionId);
15621
+ for (let round = 1;round <= MAX_ROLLING_COMPACTION_ROUNDS; round += 1) {
15622
+ const rollingView = buildProviderView({
15623
+ messages: finishedMessages,
15624
+ contextState: latestState,
15625
+ config: contextConfig,
15626
+ modelContextWindow: resolvedProviderModel.contextWindow,
15627
+ reservedOutputTokens: budgetReservedOutputTokens,
15628
+ endpointLimitTokens,
15629
+ pendingInteractionCount: pendingInteractionCount2,
15630
+ cacheActive,
15631
+ rollingCompaction,
15632
+ continueRolling: round > 1,
15633
+ fixedInputTokens: turnAssembler.fixedInputTokens
15634
+ });
15635
+ if (!rollingView.needsRollingCompaction) {
15636
+ return;
15637
+ }
15638
+ const compaction = await compactSessionContext({
15639
+ sessionId,
15640
+ coveredMessages: rollingView.coveredMessages,
15641
+ previousState: latestState,
15642
+ model: resolvedProviderModel.model,
15643
+ modelId: chatModelId,
15644
+ cwd,
15645
+ config: contextConfig,
15646
+ estimatedTokens: rollingView.estimate.tokens,
15647
+ contextWindow: rollingView.contextWindow
15648
+ });
15649
+ latestState = compaction.state;
15650
+ logger11.info("context_rolling_compaction", {
15651
+ sessionId,
15652
+ round,
15653
+ tier: compaction.state.tier,
15654
+ usedFallback: compaction.usedFallback,
15655
+ coveredMessageCount: compaction.state.coveredMessageCount
15656
+ });
15384
15657
  }
15385
- const compaction = await compactSessionContext({
15386
- sessionId,
15387
- coveredMessages: rollingView.coveredMessages,
15388
- previousState: latestState,
15389
- model: resolvedProviderModel.model,
15390
- modelId: chatModelId,
15391
- cwd,
15392
- config: contextConfig,
15393
- estimatedTokens: rollingView.estimate.tokens,
15394
- contextWindow: rollingView.contextWindow
15395
- });
15396
- logger11.info("context_rolling_compaction", {
15397
- sessionId,
15398
- tier: compaction.state.tier,
15399
- usedFallback: compaction.usedFallback,
15400
- coveredMessageCount: compaction.state.coveredMessageCount
15401
- });
15402
15658
  } catch (error) {
15403
15659
  logger11.error("context_rolling_compaction_failed", {
15404
15660
  sessionId,
@@ -15615,6 +15871,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15615
15871
  contextWindow: view.contextWindow,
15616
15872
  preserveRecentTokens: view.preserveRecentTokens,
15617
15873
  originalInputTokens: estimateStructuralTokens(providerInputMessages) + turnAssembler.fixedInputTokens,
15874
+ promptCacheActive: cacheActive,
15618
15875
  assemblyAbortSignal: requestAbortSignal,
15619
15876
  turnKey: [...validatedMessages].reverse().find((message) => message.role === "user")?.id
15620
15877
  },
@@ -15680,7 +15937,7 @@ ${environmentContext}` : agentCallSettings.instructions;
15680
15937
  }, 500);
15681
15938
  }
15682
15939
  }
15683
- var logger11, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, MAX_COMPACTION_ROUNDS = 1, rollingCompactionInFlight;
15940
+ var logger11, recoverableDisconnectMessage = "Connection interrupted. Please retry or regenerate your last message.", providerBillingOrQuotaMessage, fastChatSystemPrompt, fastChatMaxOutputTokens = 512, MAX_COMPACTION_ROUNDS = 1, MAX_ROLLING_COMPACTION_ROUNDS = 4, rollingCompactionInFlight;
15684
15941
  var init_chat_stream = __esm(() => {
15685
15942
  init_src2();
15686
15943
  init_src();
@@ -15742,11 +15999,7 @@ async function assembleStoredSessionProviderTurn({
15742
15999
  messages,
15743
16000
  cwd: session.cwd
15744
16001
  });
15745
- const environmentContext = messages.some((message) => message.role === "assistant") ? await buildWorkspaceContextDelta({
15746
- cwd: session.cwd,
15747
- sessionId: session.id,
15748
- relatedPaths: collectRelatedWorkspacePaths(messages)
15749
- }) : await buildWorkspaceContext({
16002
+ const environmentContext = await buildWorkspaceContext({
15750
16003
  cwd: session.cwd,
15751
16004
  sessionId: session.id,
15752
16005
  relatedPaths: collectRelatedWorkspacePaths(messages)
@@ -15799,6 +16052,7 @@ async function assembleStoredSessionProviderTurn({
15799
16052
  endpointLimitTokens,
15800
16053
  reservedOutputTokens,
15801
16054
  cacheActive: resolvedProviderModel.supportsPromptCaching,
16055
+ rollingCompaction: resolvedProviderModel.rollingCompaction ?? !resolvedProviderModel.supportsPromptCaching,
15802
16056
  fixedInputTokens: baseAssembler.fixedInputTokens
15803
16057
  });
15804
16058
  const assembler = new ProviderTurnAssembler({
@@ -15811,7 +16065,10 @@ async function assembleStoredSessionProviderTurn({
15811
16065
  });
15812
16066
  const modelMessages = await convertToModelMessages(normalizeProviderMessages(view.providerMessages), { tools });
15813
16067
  const artifactized = await artifactizeLargeToolOutputs(modelMessages, {
15814
- signal: abortSignal
16068
+ signal: abortSignal,
16069
+ ...resolveToolOutputArtifactThresholds({
16070
+ promptCacheActive: resolvedProviderModel.supportsPromptCaching
16071
+ })
15815
16072
  });
15816
16073
  const assembled = assembler.assembleModelMessages(artifactized.messages, { preserveRecentTokens: view.preserveRecentTokens });
15817
16074
  return { view, assembled };