@corbat-tech/coco 2.15.0 → 2.16.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/index.js CHANGED
@@ -4705,12 +4705,12 @@ var init_copilot2 = __esm({
4705
4705
  init_openai();
4706
4706
  init_copilot();
4707
4707
  CONTEXT_WINDOWS4 = {
4708
- // Claude models
4709
- "claude-sonnet-4.6": 2e5,
4710
- "claude-opus-4.6": 2e5,
4711
- "claude-sonnet-4.5": 2e5,
4712
- "claude-opus-4.5": 2e5,
4713
- "claude-haiku-4.5": 2e5,
4708
+ // Claude models — Copilot API caps these at 168 000 (not 200 000 like Anthropic direct)
4709
+ "claude-sonnet-4.6": 168e3,
4710
+ "claude-opus-4.6": 168e3,
4711
+ "claude-sonnet-4.5": 168e3,
4712
+ "claude-opus-4.5": 168e3,
4713
+ "claude-haiku-4.5": 168e3,
4714
4714
  // OpenAI models — chat/completions
4715
4715
  "gpt-4.1": 1048576,
4716
4716
  // OpenAI models — /responses API (Codex/GPT-5+)
@@ -7716,7 +7716,11 @@ var init_manager = __esm({
7716
7716
  "src/cli/repl/context/manager.ts"() {
7717
7717
  DEFAULT_CONTEXT_CONFIG = {
7718
7718
  maxTokens: 2e5,
7719
- compactionThreshold: 0.8,
7719
+ // 75% leaves a comfortable headroom for the compaction summary call itself
7720
+ // and the active tool-call accumulation within the current turn.
7721
+ // Industry reference: OpenCode uses 75%, Claude Code effective ~83.5% but
7722
+ // with an additional 33K output reserve. 75% is the recommended safe value.
7723
+ compactionThreshold: 0.75,
7720
7724
  reservedTokens: 4096
7721
7725
  };
7722
7726
  ContextManager = class {
@@ -7832,35 +7836,59 @@ var init_manager = __esm({
7832
7836
 
7833
7837
  // src/cli/repl/context/compactor.ts
7834
7838
  function buildCompactionPrompt(focusTopic) {
7835
- let prompt = `Summarize the following conversation history concisely, preserving:
7836
- 1. Key decisions made
7837
- 2. Important code/file changes discussed (always include file paths)
7838
- 3. Current task context and goals
7839
- 4. Any errors or issues encountered
7840
- 5. Original user requests (verbatim if short)`;
7839
+ let prompt = `This is a coding agent session that needs to be compacted due to context length.
7840
+ Create a structured summary that preserves everything the agent needs to continue working.
7841
+
7842
+ ## Required sections (use these exact headings):
7843
+
7844
+ ### Original Request
7845
+ State the user's original task or question verbatim (or paraphrase if very long).
7846
+
7847
+ ### Work Completed
7848
+ List every concrete action taken: files created/modified (with paths), commands run,
7849
+ bugs fixed, features implemented. Be specific \u2014 include file paths and function names.
7850
+
7851
+ ### Key Decisions
7852
+ Document architectural decisions, approaches chosen, and the reasoning behind them.
7853
+
7854
+ ### Current State
7855
+ Describe exactly where the work stands: what is done, what is in progress, what remains.
7856
+
7857
+ ### Files Touched
7858
+ List all file paths that were read, modified, or created during this session.
7859
+
7860
+ ### Errors & Resolutions
7861
+ Document any errors encountered and how they were resolved (or if still unresolved).
7862
+
7863
+ ### Next Steps
7864
+ If the task is incomplete, list the immediate next actions the agent should take.`;
7841
7865
  if (focusTopic) {
7842
7866
  prompt += `
7843
7867
 
7844
- **IMPORTANT**: Preserve ALL details related to "${focusTopic}" \u2014 include specific code snippets, file paths, decisions, and context about this topic. You may be more concise about unrelated topics.`;
7868
+ **PRIORITY**: Preserve ALL details related to "${focusTopic}" \u2014 include specific code snippets, exact file paths, and full context. Be concise about unrelated topics.`;
7845
7869
  }
7846
7870
  prompt += `
7847
7871
 
7848
- Keep the summary under 500 words. Format as bullet points.
7872
+ Keep the total summary under 600 words. Use bullet points within each section.
7849
7873
 
7850
- CONVERSATION:
7874
+ SESSION HISTORY TO SUMMARIZE:
7851
7875
  `;
7852
7876
  return prompt;
7853
7877
  }
7854
7878
  function createContextCompactor(config) {
7855
7879
  return new ContextCompactor(config);
7856
7880
  }
7857
- var DEFAULT_COMPACTOR_CONFIG, ContextCompactor;
7881
+ var DEFAULT_COMPACTOR_CONFIG, PRESERVED_RESULT_SOFT_CAP, PRESERVED_RESULT_SOFT_HEAD, PRESERVED_RESULT_SOFT_TAIL, HOT_TAIL_TOOL_PAIRS, ContextCompactor;
7858
7882
  var init_compactor = __esm({
7859
7883
  "src/cli/repl/context/compactor.ts"() {
7860
7884
  DEFAULT_COMPACTOR_CONFIG = {
7861
- preserveLastN: 4,
7885
+ preserveLastN: 8,
7862
7886
  summaryMaxTokens: 1e3
7863
7887
  };
7888
+ PRESERVED_RESULT_SOFT_CAP = 16e3;
7889
+ PRESERVED_RESULT_SOFT_HEAD = 13e3;
7890
+ PRESERVED_RESULT_SOFT_TAIL = 1500;
7891
+ HOT_TAIL_TOOL_PAIRS = 4;
7864
7892
  ContextCompactor = class {
7865
7893
  config;
7866
7894
  constructor(config) {
@@ -7899,7 +7927,9 @@ var init_compactor = __esm({
7899
7927
  }
7900
7928
  }
7901
7929
  const messagesToSummarize = conversationMessages.slice(0, preserveStart);
7902
- const messagesToPreserve = conversationMessages.slice(preserveStart);
7930
+ const messagesToPreserve = this.trimPreservedToolResults(
7931
+ conversationMessages.slice(preserveStart)
7932
+ );
7903
7933
  if (messagesToSummarize.length === 0) {
7904
7934
  return {
7905
7935
  messages,
@@ -7997,6 +8027,60 @@ ${summary}
7997
8027
  return `[Summary generation failed: ${errorMessage}. Previous conversation had ${conversationText.length} characters.]`;
7998
8028
  }
7999
8029
  }
8030
+ /**
8031
+ * Hot-tail policy: apply a soft cap to tool results in the preserved window.
8032
+ *
8033
+ * The last HOT_TAIL_TOOL_PAIRS tool-result pairs are kept verbatim (hot tail).
8034
+ * Older pairs in the preserved window that contain results larger than
8035
+ * PRESERVED_RESULT_SOFT_CAP are trimmed to head+tail with a marker.
8036
+ *
8037
+ * This handles legacy results that were stored before the inline cap was in
8038
+ * place, ensuring that a single large stale tree/grep/web result cannot fill
8039
+ * the context even after compaction.
8040
+ */
8041
+ trimPreservedToolResults(messages) {
8042
+ let hotTailStart = messages.length;
8043
+ let pairsFound = 0;
8044
+ for (let i = messages.length - 1; i >= 0; i--) {
8045
+ const msg = messages[i];
8046
+ if (!msg) continue;
8047
+ const isToolResultMsg = Array.isArray(msg.content) && msg.content.length > 0 && msg.content[0]?.type === "tool_result";
8048
+ if (isToolResultMsg) {
8049
+ pairsFound++;
8050
+ if (pairsFound >= HOT_TAIL_TOOL_PAIRS) {
8051
+ hotTailStart = i > 0 ? i - 1 : i;
8052
+ break;
8053
+ }
8054
+ }
8055
+ }
8056
+ return messages.map((msg, idx) => {
8057
+ if (idx >= hotTailStart) return msg;
8058
+ if (!Array.isArray(msg.content)) return msg;
8059
+ const hasOversized = msg.content.some((block) => {
8060
+ const b = block;
8061
+ return b.type === "tool_result" && typeof b.content === "string" && b.content.length > PRESERVED_RESULT_SOFT_CAP;
8062
+ });
8063
+ if (!hasOversized) return msg;
8064
+ const blocks = msg.content;
8065
+ const trimmedContent = blocks.map((block) => {
8066
+ if (block.type === "tool_result" && block.content.length > PRESERVED_RESULT_SOFT_CAP) {
8067
+ const full = block.content;
8068
+ const head = full.slice(0, PRESERVED_RESULT_SOFT_HEAD);
8069
+ const tail = full.slice(-PRESERVED_RESULT_SOFT_TAIL);
8070
+ const omitted = full.length - PRESERVED_RESULT_SOFT_HEAD - PRESERVED_RESULT_SOFT_TAIL;
8071
+ const trimmedResult = {
8072
+ ...block,
8073
+ content: `${head}
8074
+ [... ${omitted.toLocaleString()} chars trimmed (compaction soft-cap) ...]
8075
+ ${tail}`
8076
+ };
8077
+ return trimmedResult;
8078
+ }
8079
+ return block;
8080
+ });
8081
+ return { ...msg, content: trimmedContent };
8082
+ });
8083
+ }
8000
8084
  /**
8001
8085
  * Estimate token count for messages
8002
8086
  */
@@ -8526,7 +8610,7 @@ async function checkAndCompactContext(session, provider, signal, toolRegistry) {
8526
8610
  return null;
8527
8611
  }
8528
8612
  const compactor = createContextCompactor({
8529
- preserveLastN: 4,
8613
+ preserveLastN: 8,
8530
8614
  summaryMaxTokens: 1e3
8531
8615
  });
8532
8616
  const result = await compactor.compact(session.messages, provider, signal);
@@ -9401,7 +9485,7 @@ function humanizeError(message, toolName) {
9401
9485
  )) {
9402
9486
  return msg;
9403
9487
  }
9404
- if (/run git_init\b/.test(msg)) {
9488
+ if (/run git_init\b/i.test(msg)) {
9405
9489
  return msg;
9406
9490
  }
9407
9491
  if (/ECONNREFUSED/i.test(msg)) {
@@ -39183,10 +39267,37 @@ Examples:
39183
39267
  }
39184
39268
  }
39185
39269
  });
39270
+ var TREE_IGNORED_DIRS = /* @__PURE__ */ new Set([
39271
+ "node_modules",
39272
+ "dist",
39273
+ "build",
39274
+ "out",
39275
+ ".next",
39276
+ ".nuxt",
39277
+ ".cache",
39278
+ ".turbo",
39279
+ ".parcel-cache",
39280
+ "coverage",
39281
+ ".nyc_output",
39282
+ "vendor",
39283
+ "__pycache__",
39284
+ ".venv",
39285
+ "venv",
39286
+ "env",
39287
+ "target",
39288
+ ".gradle",
39289
+ ".mvn",
39290
+ "bin",
39291
+ "obj"
39292
+ ]);
39293
+ var MAX_TREE_LINES = 500;
39186
39294
  var treeTool = defineTool({
39187
39295
  name: "tree",
39188
39296
  description: `Display directory structure as a tree.
39189
39297
 
39298
+ Large dependency directories (node_modules, dist, .next, etc.) are excluded
39299
+ automatically. Output is capped at ${MAX_TREE_LINES} lines to keep context lean.
39300
+
39190
39301
  Examples:
39191
39302
  - Current dir: { }
39192
39303
  - Specific dir: { "path": "src" }
@@ -39206,9 +39317,12 @@ Examples:
39206
39317
  let totalFiles = 0;
39207
39318
  let totalDirs = 0;
39208
39319
  const lines = [path36__default.basename(absolutePath) + "/"];
39320
+ let truncated = false;
39209
39321
  async function buildTree(dir, prefix, currentDepth) {
39210
39322
  if (currentDepth > (depth ?? 4)) return;
39323
+ if (lines.length >= MAX_TREE_LINES) return;
39211
39324
  let items = await fs34__default.readdir(dir, { withFileTypes: true });
39325
+ items = items.filter((item) => !TREE_IGNORED_DIRS.has(item.name));
39212
39326
  if (!showHidden) {
39213
39327
  items = items.filter((item) => !item.name.startsWith("."));
39214
39328
  }
@@ -39221,6 +39335,10 @@ Examples:
39221
39335
  return a.name.localeCompare(b.name);
39222
39336
  });
39223
39337
  for (let i = 0; i < items.length; i++) {
39338
+ if (lines.length >= MAX_TREE_LINES) {
39339
+ truncated = true;
39340
+ return;
39341
+ }
39224
39342
  const item = items[i];
39225
39343
  const isLast = i === items.length - 1;
39226
39344
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
@@ -39236,10 +39354,17 @@ Examples:
39236
39354
  }
39237
39355
  }
39238
39356
  await buildTree(absolutePath, "", 1);
39357
+ if (truncated) {
39358
+ lines.push(
39359
+ `
39360
+ [... output truncated at ${MAX_TREE_LINES} lines. Use a deeper path or lower depth to see more.]`
39361
+ );
39362
+ }
39239
39363
  return {
39240
39364
  tree: lines.join("\n"),
39241
39365
  totalFiles,
39242
- totalDirs
39366
+ totalDirs,
39367
+ truncated
39243
39368
  };
39244
39369
  } catch (error) {
39245
39370
  if (isENOENT(error)) {
@@ -41790,7 +41915,7 @@ init_registry4();
41790
41915
  init_errors();
41791
41916
  init_version();
41792
41917
  var DEFAULT_TIMEOUT_MS5 = 3e4;
41793
- var DEFAULT_MAX_LENGTH = 5e4;
41918
+ var DEFAULT_MAX_LENGTH = 8e3;
41794
41919
  var MAX_DOWNLOAD_SIZE = 10 * 1024 * 1024;
41795
41920
  var BLOCKED_SCHEMES = ["file:", "ftp:", "data:", "javascript:"];
41796
41921
  var PRIVATE_IP_PATTERNS = [
@@ -48443,6 +48568,18 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
48443
48568
  const maxIterations = session.config.agent.maxToolIterations;
48444
48569
  const toolErrorCounts = /* @__PURE__ */ new Map();
48445
48570
  const MAX_CONSECUTIVE_TOOL_ERRORS = 3;
48571
+ const INLINE_RESULT_MAX_CHARS = 8e3;
48572
+ const INLINE_RESULT_HEAD_CHARS = 6500;
48573
+ const INLINE_RESULT_TAIL_CHARS = 1e3;
48574
+ function truncateInlineResult(content, toolName) {
48575
+ if (content.length <= INLINE_RESULT_MAX_CHARS) return content;
48576
+ const head = content.slice(0, INLINE_RESULT_HEAD_CHARS);
48577
+ const tail = content.slice(-INLINE_RESULT_TAIL_CHARS);
48578
+ const omitted = content.length - INLINE_RESULT_HEAD_CHARS - INLINE_RESULT_TAIL_CHARS;
48579
+ return `${head}
48580
+ [... ${omitted.toLocaleString()} characters omitted \u2014 use read_file with offset/limit to retrieve more of '${toolName}' output ...]
48581
+ ${tail}`;
48582
+ }
48446
48583
  while (iteration < maxIterations) {
48447
48584
  iteration++;
48448
48585
  if (options.signal?.aborted) {
@@ -48695,7 +48832,7 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
48695
48832
  toolResults.push({
48696
48833
  type: "tool_result",
48697
48834
  tool_use_id: toolCall.id,
48698
- content: executedCall.result.output,
48835
+ content: truncateInlineResult(executedCall.result.output, toolCall.name),
48699
48836
  is_error: !executedCall.result.success
48700
48837
  });
48701
48838
  } else {
@@ -50073,6 +50210,25 @@ async function startRepl(options = {}) {
50073
50210
  continue;
50074
50211
  }
50075
50212
  const errorMsg = error instanceof Error ? error.message : String(error);
50213
+ if (errorMsg.includes("prompt token count") && errorMsg.includes("exceeds the limit")) {
50214
+ renderError("Context window full \u2014 compacting conversation history...");
50215
+ try {
50216
+ const compactionResult = await checkAndCompactContext(
50217
+ session,
50218
+ provider,
50219
+ void 0,
50220
+ toolRegistry
50221
+ );
50222
+ if (compactionResult?.wasCompacted) {
50223
+ console.log(chalk2.green(" \u2713 Context compacted. Please retry your message."));
50224
+ } else {
50225
+ console.log(chalk2.yellow(" \u26A0 Could not compact context. Use /clear to start fresh."));
50226
+ }
50227
+ } catch {
50228
+ console.log(chalk2.yellow(" \u26A0 Context compaction failed. Use /clear to start fresh."));
50229
+ }
50230
+ continue;
50231
+ }
50076
50232
  if (errorMsg.includes("context length") || errorMsg.includes("tokens to keep")) {
50077
50233
  renderError(errorMsg);
50078
50234
  console.log();