@corbat-tech/coco 2.15.1 → 2.17.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
@@ -19,7 +19,7 @@ import { randomUUID } from 'crypto';
19
19
  import * as http from 'http';
20
20
  import * as p26 from '@clack/prompts';
21
21
  import chalk2 from 'chalk';
22
- import { execFile, execSync, execFileSync, spawn, exec } from 'child_process';
22
+ import { execFile, execSync, spawn, execFileSync, exec } from 'child_process';
23
23
  import { promisify } from 'util';
24
24
  import { GoogleGenerativeAI, FunctionCallingMode } from '@google/generative-ai';
25
25
  import matter from 'gray-matter';
@@ -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 blocks2 = msg.content;
8065
+ const trimmedContent = blocks2.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);
@@ -8589,6 +8673,7 @@ Rules:
8589
8673
  - NEVER ask "should I?" or "do you want me to?" \u2014 the user already told you. JUST DO IT.
8590
8674
  - If you need real-time data, CALL web_search. NEVER say "I don't have access to real-time data."
8591
8675
  - Before answering "I can't do that", check your full tool catalog below \u2014 you likely have a tool for it.
8676
+ - NEVER claim you cannot run a command because you lack credentials, access, or connectivity. bash_exec runs in the user's own shell environment and inherits their full PATH, kubeconfig, gcloud auth, AWS profiles, SSH keys, and every other tool installed on their machine. kubectl, gcloud, aws, docker, and any other CLI available to the user are available to you. ALWAYS attempt the command with bash_exec; report failure only if it actually returns a non-zero exit code.
8592
8677
 
8593
8678
  ## Available Tools
8594
8679
  {TOOL_CATALOG}
@@ -16390,13 +16475,25 @@ var init_bash = __esm({
16390
16475
  ];
16391
16476
  bashExecTool = defineTool({
16392
16477
  name: "bash_exec",
16393
- description: `Execute a bash/shell command with safety controls.
16478
+ description: `Execute a bash/shell command in the user's shell environment.
16479
+
16480
+ Runs with the user's full PATH and inherited environment \u2014 any tool installed
16481
+ on the user's machine is available: kubectl, gcloud, aws, docker, git, node,
16482
+ pnpm, and others. Credentials configured locally are inherited automatically:
16483
+ kubeconfig contexts, gcloud auth, AWS profiles, SSH keys, etc.
16484
+
16485
+ IMPORTANT: never claim you cannot run a command because you lack credentials
16486
+ or access \u2014 the environment is the user's own shell. Always attempt; report
16487
+ failure only if the command actually returns a non-zero exit code.
16394
16488
 
16395
16489
  Examples:
16396
- - List files: { "command": "ls -la" }
16397
- - Run npm script: { "command": "npm run build" }
16398
- - Check disk space: { "command": "df -h" }
16399
- - Find process: { "command": "ps aux | grep node" }`,
16490
+ - List files: { "command": "ls -la" }
16491
+ - Run npm script: { "command": "npm run build" }
16492
+ - Check disk space: { "command": "df -h" }
16493
+ - Find process: { "command": "ps aux | grep node" }
16494
+ - Kubernetes logs: { "command": "kubectl logs -n my-ns deploy/my-app --since=30m" }
16495
+ - Cloud CLI: { "command": "gcloud container clusters list" }
16496
+ - AWS CLI: { "command": "aws s3 ls s3://my-bucket" }`,
16400
16497
  category: "bash",
16401
16498
  parameters: z.object({
16402
16499
  command: z.string().describe("Command to execute"),
@@ -16490,12 +16587,15 @@ ${message}
16490
16587
  });
16491
16588
  bashBackgroundTool = defineTool({
16492
16589
  name: "bash_background",
16493
- description: `Execute a command in the background (returns immediately with PID).
16590
+ description: `Execute a command in the background in the user's shell environment (returns immediately with PID).
16591
+
16592
+ Like bash_exec, runs with the user's full PATH and inherited environment \u2014 any
16593
+ tool available in the user's shell (docker, kubectl, gcloud, etc.) can be used.
16494
16594
 
16495
16595
  Examples:
16496
- - Start dev server: { "command": "npm run dev" }
16497
- - Run watcher: { "command": "npx nodemon src/index.ts" }
16498
- - Start database: { "command": "docker-compose up" }`,
16596
+ - Start dev server: { "command": "npm run dev" }
16597
+ - Run watcher: { "command": "npx nodemon src/index.ts" }
16598
+ - Start database: { "command": "docker-compose up" }`,
16499
16599
  category: "bash",
16500
16600
  parameters: z.object({
16501
16601
  command: z.string().describe("Command to execute"),
@@ -34864,1213 +34964,415 @@ To update manually, run: ${updateInfo.updateCommand}
34864
34964
  }
34865
34965
  };
34866
34966
 
34867
- // src/cli/repl/output/renderer.ts
34868
- init_syntax();
34869
- var lineBuffer = "";
34870
- var rawMarkdownBuffer = "";
34871
- var inCodeBlock = false;
34872
- var codeBlockLang = "";
34873
- var codeBlockLines = [];
34874
- var inNestedCodeBlock = false;
34875
- var codeBlockFenceChar = "";
34876
- var streamingIndicatorActive = false;
34877
- var streamingIndicatorInterval = null;
34878
- var streamingIndicatorFrame = 0;
34879
- var STREAMING_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
34880
- var getTerminalWidth2 = () => process.stdout.columns || 80;
34881
- function startStreamingIndicator() {
34882
- if (streamingIndicatorActive) return;
34883
- streamingIndicatorActive = true;
34884
- streamingIndicatorFrame = 0;
34885
- const frame = STREAMING_FRAMES[0];
34886
- process.stdout.write(`\r${chalk2.magenta(frame)} ${chalk2.dim("Receiving markdown...")}`);
34887
- streamingIndicatorInterval = setInterval(() => {
34888
- streamingIndicatorFrame = (streamingIndicatorFrame + 1) % STREAMING_FRAMES.length;
34889
- const frame2 = STREAMING_FRAMES[streamingIndicatorFrame];
34890
- const lines = codeBlockLines.length;
34891
- const linesText = lines > 0 ? ` (${lines} lines)` : "";
34892
- process.stdout.write(
34893
- `\r${chalk2.magenta(frame2)} ${chalk2.dim(`Receiving markdown...${linesText}`)}`
34894
- );
34895
- }, 80);
34896
- }
34897
- function stopStreamingIndicator() {
34898
- if (!streamingIndicatorActive) return;
34899
- streamingIndicatorActive = false;
34900
- if (streamingIndicatorInterval) {
34901
- clearInterval(streamingIndicatorInterval);
34902
- streamingIndicatorInterval = null;
34967
+ // src/cli/repl/output/block-store.ts
34968
+ var MAX_BLOCKS = 100;
34969
+ var blocks = [];
34970
+ var blockCounter = 0;
34971
+ function storeBlock(lang, lines) {
34972
+ blockCounter++;
34973
+ const block = { id: blockCounter, lang, content: lines.join("\n") };
34974
+ blocks.push(block);
34975
+ if (blocks.length > MAX_BLOCKS) {
34976
+ blocks.shift();
34903
34977
  }
34904
- process.stdout.write("\r\x1B[K");
34978
+ return block.id;
34905
34979
  }
34906
- function flushLineBuffer() {
34907
- if (lineBuffer) {
34908
- processAndOutputLine(lineBuffer);
34909
- lineBuffer = "";
34910
- }
34911
- if (inCodeBlock && codeBlockLines.length > 0) {
34912
- stopStreamingIndicator();
34913
- try {
34914
- renderCodeBlock(codeBlockLang, codeBlockLines);
34915
- } finally {
34916
- stopStreamingIndicator();
34917
- }
34918
- inCodeBlock = false;
34919
- codeBlockFenceChar = "";
34920
- codeBlockLang = "";
34921
- codeBlockLines = [];
34922
- }
34980
+ function getBlock(id) {
34981
+ if (id <= 0) return void 0;
34982
+ return blocks.find((b) => b.id === id);
34923
34983
  }
34924
- function resetLineBuffer() {
34925
- lineBuffer = "";
34926
- inCodeBlock = false;
34927
- inNestedCodeBlock = false;
34928
- codeBlockFenceChar = "";
34929
- codeBlockLang = "";
34930
- codeBlockLines = [];
34931
- stopStreamingIndicator();
34984
+ function getLastBlock() {
34985
+ return blocks[blocks.length - 1];
34932
34986
  }
34933
- function getRawMarkdown() {
34934
- return rawMarkdownBuffer;
34987
+ function getBlockCount() {
34988
+ return blocks.length;
34935
34989
  }
34936
- function renderStreamChunk(chunk) {
34937
- if (chunk.type === "text" && chunk.text) {
34938
- lineBuffer += chunk.text;
34939
- rawMarkdownBuffer += chunk.text;
34940
- let newlineIndex;
34941
- while ((newlineIndex = lineBuffer.indexOf("\n")) !== -1) {
34942
- const line = lineBuffer.slice(0, newlineIndex);
34943
- lineBuffer = lineBuffer.slice(newlineIndex + 1);
34944
- processAndOutputLine(line);
34945
- }
34946
- } else if (chunk.type === "done") {
34947
- flushLineBuffer();
34990
+ function resetBlockStore() {
34991
+ blocks = [];
34992
+ blockCounter = 0;
34993
+ }
34994
+ function getClipboardCommand() {
34995
+ const platform = process.platform;
34996
+ if (platform === "darwin") {
34997
+ return { command: "pbcopy", args: [] };
34948
34998
  }
34999
+ if (platform === "linux") {
35000
+ return { command: "xclip", args: ["-selection", "clipboard"] };
35001
+ }
35002
+ if (platform === "win32") {
35003
+ return { command: "clip", args: [] };
35004
+ }
35005
+ return null;
34949
35006
  }
34950
- function processAndOutputLine(line) {
34951
- line = line.replace(/^(?:\u{200B}|\u{FEFF}|\u{200C}|\u{200D}|\u{2060}|\u{00AD})+/u, "");
34952
- const tildeFenceMatch = line.match(/^~~~(\w*)$/);
34953
- if (tildeFenceMatch) {
34954
- const lang = tildeFenceMatch[1] || "";
34955
- if (!inCodeBlock) {
34956
- if (lang) {
34957
- inCodeBlock = true;
34958
- inNestedCodeBlock = false;
34959
- codeBlockFenceChar = "~~~";
34960
- codeBlockLang = lang;
34961
- codeBlockLines = [];
34962
- if (codeBlockLang === "markdown" || codeBlockLang === "md") {
34963
- startStreamingIndicator();
34964
- }
34965
- } else {
34966
- const formatted = formatMarkdownLine(line);
34967
- const termWidth = getTerminalWidth2();
34968
- const wrapped = wrapText(formatted, termWidth);
34969
- for (const wl of wrapped) {
34970
- console.log(wl);
35007
+ async function copyToClipboard(text13) {
35008
+ const clipboardCmd = getClipboardCommand();
35009
+ if (!clipboardCmd) {
35010
+ return false;
35011
+ }
35012
+ return new Promise((resolve4) => {
35013
+ try {
35014
+ const proc = spawn(clipboardCmd.command, clipboardCmd.args, {
35015
+ stdio: ["pipe", "ignore", "ignore"]
35016
+ });
35017
+ let resolved = false;
35018
+ proc.on("error", () => {
35019
+ if (resolved) return;
35020
+ resolved = true;
35021
+ if (process.platform === "linux") {
35022
+ try {
35023
+ const xselProc = spawn("xsel", ["--clipboard", "--input"], {
35024
+ stdio: ["pipe", "ignore", "ignore"]
35025
+ });
35026
+ xselProc.on("error", () => resolve4(false));
35027
+ xselProc.on("close", (code) => resolve4(code === 0));
35028
+ xselProc.stdin.write(text13);
35029
+ xselProc.stdin.end();
35030
+ } catch {
35031
+ resolve4(false);
35032
+ }
35033
+ } else {
35034
+ resolve4(false);
34971
35035
  }
34972
- }
34973
- } else if (codeBlockFenceChar === "~~~") {
34974
- if (lang && !inNestedCodeBlock) {
34975
- inNestedCodeBlock = true;
34976
- codeBlockLines.push(line);
34977
- } else if (!lang && inNestedCodeBlock) {
34978
- inNestedCodeBlock = false;
34979
- codeBlockLines.push(line);
34980
- } else if (!lang && !inNestedCodeBlock) {
34981
- stopStreamingIndicator();
34982
- renderCodeBlock(codeBlockLang, codeBlockLines);
34983
- inCodeBlock = false;
34984
- inNestedCodeBlock = false;
34985
- codeBlockFenceChar = "";
34986
- codeBlockLang = "";
34987
- codeBlockLines = [];
34988
- } else {
34989
- codeBlockLines.push(line);
34990
- }
34991
- } else {
34992
- if (lang && !inNestedCodeBlock) {
34993
- inNestedCodeBlock = true;
34994
- codeBlockLines.push(line);
34995
- } else if (!lang && inNestedCodeBlock) {
34996
- inNestedCodeBlock = false;
34997
- codeBlockLines.push(line);
35036
+ });
35037
+ proc.on("close", (code) => {
35038
+ if (resolved) return;
35039
+ resolved = true;
35040
+ resolve4(code === 0);
35041
+ });
35042
+ proc.stdin.on("error", () => {
35043
+ });
35044
+ proc.stdin.write(text13);
35045
+ proc.stdin.end();
35046
+ } catch {
35047
+ resolve4(false);
35048
+ }
35049
+ });
35050
+ }
35051
+ async function isClipboardAvailable() {
35052
+ const clipboardCmd = getClipboardCommand();
35053
+ if (!clipboardCmd) return false;
35054
+ return new Promise((resolve4) => {
35055
+ const testCmd = process.platform === "win32" ? "where" : "which";
35056
+ const proc = spawn(testCmd, [clipboardCmd.command], {
35057
+ stdio: ["ignore", "ignore", "ignore"]
35058
+ });
35059
+ proc.on("error", () => {
35060
+ if (process.platform === "linux") {
35061
+ const xselProc = spawn("which", ["xsel"], {
35062
+ stdio: ["ignore", "ignore", "ignore"]
35063
+ });
35064
+ xselProc.on("error", () => resolve4(false));
35065
+ xselProc.on("close", (code) => resolve4(code === 0));
34998
35066
  } else {
34999
- codeBlockLines.push(line);
35067
+ resolve4(false);
35000
35068
  }
35001
- }
35002
- return;
35069
+ });
35070
+ proc.on("close", (code) => resolve4(code === 0));
35071
+ });
35072
+ }
35073
+ async function readClipboardImage() {
35074
+ const platform = process.platform;
35075
+ if (platform === "darwin") {
35076
+ return readClipboardImageMacOS();
35003
35077
  }
35004
- const codeBlockMatch = line.match(/^(`{3,4})(\w*)$/);
35005
- if (codeBlockMatch) {
35006
- const fenceChars = codeBlockMatch[1];
35007
- const lang = codeBlockMatch[2] || "";
35008
- if (!inCodeBlock) {
35009
- inCodeBlock = true;
35010
- inNestedCodeBlock = false;
35011
- codeBlockFenceChar = fenceChars;
35012
- codeBlockLang = lang;
35013
- codeBlockLines = [];
35014
- if (codeBlockLang === "markdown" || codeBlockLang === "md") {
35015
- startStreamingIndicator();
35016
- }
35017
- } else if (!lang && inNestedCodeBlock && fenceChars === "```") {
35018
- inNestedCodeBlock = false;
35019
- codeBlockLines.push(line);
35020
- } else if (!inNestedCodeBlock && lang && fenceChars === "```") {
35021
- inNestedCodeBlock = true;
35022
- codeBlockLines.push(line);
35023
- } else if (!lang && !inNestedCodeBlock && codeBlockFenceChar === fenceChars) {
35024
- stopStreamingIndicator();
35025
- renderCodeBlock(codeBlockLang, codeBlockLines);
35026
- inCodeBlock = false;
35027
- inNestedCodeBlock = false;
35028
- codeBlockFenceChar = "";
35029
- codeBlockLang = "";
35030
- codeBlockLines = [];
35031
- } else {
35032
- codeBlockLines.push(line);
35033
- }
35034
- return;
35078
+ if (platform === "linux") {
35079
+ return readClipboardImageLinux();
35035
35080
  }
35036
- if (inCodeBlock) {
35037
- codeBlockLines.push(line);
35038
- } else {
35039
- const formatted = formatMarkdownLine(line);
35040
- const termWidth = getTerminalWidth2();
35041
- const wrapped = wrapText(formatted, termWidth);
35042
- for (const wl of wrapped) {
35043
- console.log(wl);
35044
- }
35081
+ if (platform === "win32") {
35082
+ return readClipboardImageWindows();
35045
35083
  }
35084
+ return null;
35046
35085
  }
35047
- function renderCodeBlock(lang, lines) {
35048
- if (lang === "markdown" || lang === "md") {
35049
- renderMarkdownBlock(lines);
35050
- return;
35086
+ async function readClipboardImageMacOS() {
35087
+ const tmpFile = path36.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
35088
+ try {
35089
+ const script = `
35090
+ set theFile to POSIX file "${tmpFile}"
35091
+ try
35092
+ set theImage to the clipboard as \xABclass PNGf\xBB
35093
+ set fileRef to open for access theFile with write permission
35094
+ write theImage to fileRef
35095
+ close access fileRef
35096
+ return "ok"
35097
+ on error errMsg
35098
+ return "error: " & errMsg
35099
+ end try
35100
+ `;
35101
+ const result = execFileSync("osascript", ["-e", script], {
35102
+ encoding: "utf-8",
35103
+ timeout: 1e4,
35104
+ stdio: ["pipe", "pipe", "pipe"]
35105
+ // suppress stderr (JP2 color space warnings from macOS ImageIO)
35106
+ }).trim();
35107
+ if (!result.startsWith("ok")) {
35108
+ return null;
35109
+ }
35110
+ const buffer = await fs34.readFile(tmpFile);
35111
+ return {
35112
+ data: buffer.toString("base64"),
35113
+ media_type: "image/png"
35114
+ };
35115
+ } catch {
35116
+ return null;
35117
+ } finally {
35118
+ try {
35119
+ await fs34.unlink(tmpFile);
35120
+ } catch {
35121
+ }
35051
35122
  }
35052
- renderSimpleCodeBlock(lang, lines);
35053
35123
  }
35054
- function renderMarkdownBlock(lines) {
35055
- const width = Math.min(getTerminalWidth2() - 4, 100);
35056
- const contentWidth = width - 4;
35057
- const title = "Markdown";
35058
- console.log(chalk2.magenta("\u256D\u2500\u2500 " + title + " \u2500\u2500"));
35059
- let i = 0;
35060
- while (i < lines.length) {
35061
- const line = lines[i];
35062
- const nestedMatch = line.match(/^(~~~|```)(\w*)$/);
35063
- if (nestedMatch) {
35064
- const delimiter = nestedMatch[1];
35065
- const nestedLang = nestedMatch[2] || "";
35066
- const nestedLines = [];
35067
- i++;
35068
- const closePattern = new RegExp(`^${delimiter}$`);
35069
- while (i < lines.length && !closePattern.test(lines[i])) {
35070
- nestedLines.push(lines[i]);
35071
- i++;
35072
- }
35073
- i++;
35074
- renderNestedCodeBlock(nestedLang, nestedLines, contentWidth);
35075
- } else if (isTableLine(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
35076
- const tableLines = [];
35077
- while (i < lines.length && (isTableLine(lines[i]) || isTableSeparator(lines[i]))) {
35078
- tableLines.push(lines[i]);
35079
- i++;
35080
- }
35081
- renderNestedTable(tableLines, contentWidth);
35082
- } else {
35083
- const formatted = formatMarkdownLine(line);
35084
- const wrappedLines = wrapText(formatted, contentWidth);
35085
- for (const wrappedLine of wrappedLines) {
35086
- console.log(chalk2.magenta("\u2502") + " " + wrappedLine);
35087
- }
35088
- i++;
35124
+ async function readClipboardImageLinux() {
35125
+ try {
35126
+ const targets = execFileSync("xclip", ["-selection", "clipboard", "-t", "TARGETS", "-o"], {
35127
+ encoding: "utf-8",
35128
+ timeout: 5e3,
35129
+ stdio: ["pipe", "pipe", "pipe"]
35130
+ // suppress stderr
35131
+ });
35132
+ if (!targets.includes("image/png")) {
35133
+ return null;
35089
35134
  }
35135
+ const buffer = execFileSync("xclip", ["-selection", "clipboard", "-t", "image/png", "-o"], {
35136
+ timeout: 5e3,
35137
+ stdio: ["pipe", "pipe", "pipe"]
35138
+ // suppress stderr
35139
+ });
35140
+ return {
35141
+ data: Buffer.from(buffer).toString("base64"),
35142
+ media_type: "image/png"
35143
+ };
35144
+ } catch {
35145
+ return null;
35090
35146
  }
35091
- console.log(chalk2.magenta("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
35092
35147
  }
35093
- function isTableLine(line) {
35094
- const trimmed = line.trim();
35095
- if (!/^\|.*\|$/.test(trimmed)) return false;
35096
- if (isTableSeparator(line)) return false;
35097
- const inner = trimmed.slice(1, -1);
35098
- return inner.length > 0 && !/^[\s|:-]+$/.test(inner);
35099
- }
35100
- function isTableSeparator(line) {
35101
- const trimmed = line.trim();
35102
- if (!/^\|.*\|$/.test(trimmed)) return false;
35103
- const inner = trimmed.slice(1, -1);
35104
- if (!/^[\s|:-]+$/.test(inner)) return false;
35105
- return /-{3,}/.test(inner);
35148
+ async function readClipboardImageWindows() {
35149
+ const tmpFile = path36.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
35150
+ try {
35151
+ const escapedPath = tmpFile.replace(/'/g, "''");
35152
+ const script = `
35153
+ Add-Type -AssemblyName System.Windows.Forms;
35154
+ $img = [System.Windows.Forms.Clipboard]::GetImage();
35155
+ if ($img -ne $null) {
35156
+ $img.Save('${escapedPath}', [System.Drawing.Imaging.ImageFormat]::Png);
35157
+ Write-Output 'ok';
35158
+ } else {
35159
+ Write-Output 'no-image';
35106
35160
  }
35107
- function renderNestedTable(lines, parentWidth) {
35108
- const rows = [];
35109
- let columnWidths = [];
35110
- for (const line of lines) {
35111
- if (isTableSeparator(line)) continue;
35112
- const cells = line.split("|").slice(1, -1).map((c) => c.trim());
35113
- rows.push(cells);
35114
- cells.forEach((cell, idx) => {
35115
- const cellWidth = cell.length;
35116
- if (!columnWidths[idx] || cellWidth > columnWidths[idx]) {
35117
- columnWidths[idx] = cellWidth;
35118
- }
35119
- });
35120
- }
35121
- if (rows.length === 0) return;
35122
- const minCellPadding = 2;
35123
- let totalWidth = columnWidths.reduce((sum, w) => sum + w + minCellPadding, 0) + columnWidths.length + 1;
35124
- const maxTableWidth = parentWidth - 4;
35125
- if (totalWidth > maxTableWidth) {
35126
- const scale = maxTableWidth / totalWidth;
35127
- columnWidths = columnWidths.map((w) => Math.max(3, Math.floor(w * scale)));
35128
- }
35129
- const tableTop = "\u256D" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u256E";
35130
- const tableMid = "\u251C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524";
35131
- const tableBot = "\u2570" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u256F";
35132
- const renderRow = (cells, isHeader) => {
35133
- const formatted = cells.map((cell, idx) => {
35134
- const width = columnWidths[idx] || 10;
35135
- const truncated = cell.length > width ? cell.slice(0, width - 1) + "\u2026" : cell;
35136
- const padded = truncated.padEnd(width);
35137
- return isHeader ? chalk2.bold(padded) : padded;
35138
- });
35139
- return "\u2502 " + formatted.join(" \u2502 ") + " \u2502";
35140
- };
35141
- const outputTableLine = (tableLine) => {
35142
- console.log(chalk2.magenta("\u2502") + " " + chalk2.cyan(tableLine));
35143
- };
35144
- outputTableLine(tableTop);
35145
- rows.forEach((row, idx) => {
35146
- outputTableLine(renderRow(row, idx === 0));
35147
- if (idx === 0 && rows.length > 1) {
35148
- outputTableLine(tableMid);
35161
+ `;
35162
+ const result = execFileSync("powershell", ["-Command", script], {
35163
+ encoding: "utf-8",
35164
+ timeout: 1e4
35165
+ }).trim();
35166
+ if (result !== "ok") return null;
35167
+ const buffer = await fs34.readFile(tmpFile);
35168
+ return {
35169
+ data: buffer.toString("base64"),
35170
+ media_type: "image/png"
35171
+ };
35172
+ } catch {
35173
+ return null;
35174
+ } finally {
35175
+ try {
35176
+ await fs34.unlink(tmpFile);
35177
+ } catch {
35149
35178
  }
35150
- });
35151
- outputTableLine(tableBot);
35179
+ }
35152
35180
  }
35153
- function renderNestedCodeBlock(lang, lines, parentWidth) {
35154
- const innerWidth = parentWidth - 4;
35155
- const title = lang || "code";
35156
- const isDiff = lang === "diff" || !lang && looksLikeDiff(lines);
35157
- const innerTopPadding = Math.floor((innerWidth - title.length - 4) / 2);
35158
- const innerTopRemainder = innerWidth - title.length - 4 - innerTopPadding;
35159
- console.log(
35160
- chalk2.magenta("\u2502") + " " + chalk2.cyan(
35161
- "\u256D" + "\u2500".repeat(Math.max(0, innerTopPadding)) + " " + title + " " + "\u2500".repeat(Math.max(0, innerTopRemainder)) + "\u256E"
35162
- )
35163
- );
35164
- const bgDel = chalk2.bgRgb(80, 20, 20);
35165
- const bgAdd = chalk2.bgRgb(20, 60, 20);
35166
- for (const line of lines) {
35167
- const formatted = formatCodeLine(line, lang);
35168
- const codeWidth = innerWidth - 4;
35169
- const wrappedLines = wrapText(formatted, codeWidth);
35170
- for (const wrappedLine of wrappedLines) {
35171
- const padding = Math.max(0, codeWidth - stripAnsi2(wrappedLine).length);
35172
- if (isDiff && isDiffDeletion(line)) {
35173
- console.log(
35174
- chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2502") + bgDel(" " + wrappedLine + " ".repeat(padding) + " ") + chalk2.cyan("\u2502")
35175
- );
35176
- } else if (isDiff && isDiffAddition(line)) {
35181
+ function isClipboardImageAvailable() {
35182
+ const platform = process.platform;
35183
+ return platform === "darwin" || platform === "win32" || platform === "linux";
35184
+ }
35185
+
35186
+ // src/cli/repl/commands/copy.ts
35187
+ var copyCommand = {
35188
+ name: "copy",
35189
+ aliases: ["cp"],
35190
+ description: "Copy code block to clipboard (last or #N)",
35191
+ usage: "/copy [N]",
35192
+ async execute(args) {
35193
+ const clipboardAvailable = await isClipboardAvailable();
35194
+ if (!clipboardAvailable) {
35195
+ console.log(chalk2.red(" \u2717 Clipboard not available on this system"));
35196
+ console.log(chalk2.dim(" macOS: pbcopy, Linux: xclip or xsel, Windows: clip"));
35197
+ return false;
35198
+ }
35199
+ const rawArg = args[0];
35200
+ const hasArg = rawArg !== void 0 && rawArg !== "";
35201
+ const blockNum = hasArg ? Number(rawArg) : NaN;
35202
+ const isValidId = hasArg && Number.isInteger(blockNum) && blockNum > 0;
35203
+ if (hasArg && !isValidId) {
35204
+ console.log(chalk2.yellow(` \u26A0 Invalid block number "${rawArg}"`));
35205
+ console.log(
35206
+ chalk2.dim(" Use /copy N where N is a positive integer, or /copy for the last block")
35207
+ );
35208
+ return false;
35209
+ }
35210
+ const block = isValidId ? getBlock(blockNum) : getLastBlock();
35211
+ if (!block) {
35212
+ if (isValidId) {
35213
+ const count = getBlockCount();
35177
35214
  console.log(
35178
- chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2502") + bgAdd(" " + wrappedLine + " ".repeat(padding) + " ") + chalk2.cyan("\u2502")
35215
+ chalk2.yellow(` \u26A0 Block #${blockNum} not found`) + chalk2.dim(` (${count} block${count === 1 ? "" : "s"} available)`)
35179
35216
  );
35180
35217
  } else {
35181
- console.log(
35182
- chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2502") + " " + wrappedLine + " ".repeat(padding) + " " + chalk2.cyan("\u2502")
35183
- );
35218
+ console.log(chalk2.yellow(" \u26A0 No code blocks to copy"));
35219
+ console.log(chalk2.dim(" Code blocks appear as you chat \u2014 then use /copy or Option+C"));
35184
35220
  }
35221
+ return false;
35185
35222
  }
35223
+ const success = await copyToClipboard(block.content);
35224
+ if (success) {
35225
+ const lang = block.lang || "code";
35226
+ console.log(chalk2.green(` \u2713 ${lang} #${block.id} copied`));
35227
+ } else {
35228
+ console.log(chalk2.red(" \u2717 Failed to copy to clipboard"));
35229
+ }
35230
+ return false;
35186
35231
  }
35187
- console.log(chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2570" + "\u2500".repeat(innerWidth - 2) + "\u256F"));
35188
- }
35189
- function looksLikeDiff(lines) {
35190
- const head = lines.slice(0, 5);
35191
- return head.some((l) => l.startsWith("--- ") || l.startsWith("+++ ") || l.startsWith("@@ "));
35192
- }
35193
- function isDiffDeletion(line) {
35194
- return line.startsWith("-") && !line.startsWith("---");
35195
- }
35196
- function isDiffAddition(line) {
35197
- return line.startsWith("+") && !line.startsWith("+++");
35198
- }
35199
- function renderSimpleCodeBlock(lang, lines) {
35200
- const width = Math.min(getTerminalWidth2() - 4, 100);
35201
- const contentWidth = width - 4;
35202
- const isDiff = lang === "diff" || !lang && looksLikeDiff(lines);
35203
- const title = lang || "Code";
35204
- const titleDisplay = ` ${title} `;
35205
- const topPadding = Math.floor((width - titleDisplay.length - 2) / 2);
35206
- const topRemainder = width - titleDisplay.length - 2 - topPadding;
35207
- console.log(
35208
- chalk2.magenta("\u256D" + "\u2500".repeat(topPadding) + titleDisplay + "\u2500".repeat(topRemainder) + "\u256E")
35209
- );
35210
- const bgDel = chalk2.bgRgb(80, 20, 20);
35211
- const bgAdd = chalk2.bgRgb(20, 60, 20);
35212
- let oldLineNo = 0;
35213
- let newLineNo = 0;
35214
- for (const line of lines) {
35215
- if (isDiff) {
35216
- const hunkMatch = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
35217
- if (hunkMatch) {
35218
- oldLineNo = parseInt(hunkMatch[1], 10);
35219
- newLineNo = parseInt(hunkMatch[2], 10);
35220
- }
35232
+ };
35233
+
35234
+ // src/cli/repl/commands/allow-path.ts
35235
+ init_allowed_paths();
35236
+ var BLOCKED_SYSTEM_PATHS = [
35237
+ "/etc",
35238
+ "/var",
35239
+ "/usr",
35240
+ "/root",
35241
+ "/sys",
35242
+ "/proc",
35243
+ "/boot",
35244
+ "/bin",
35245
+ "/sbin"
35246
+ ];
35247
+ var allowPathCommand = {
35248
+ name: "allow-path",
35249
+ aliases: ["ap"],
35250
+ description: "Allow file operations in an additional directory",
35251
+ usage: "/allow-path <directory> | /allow-path list | /allow-path revoke <directory>",
35252
+ execute: async (args, session) => {
35253
+ const subcommand = args[0] ?? "";
35254
+ if (subcommand === "list" || subcommand === "ls") {
35255
+ showAllowedPaths(session);
35256
+ return false;
35221
35257
  }
35222
- const formatted = formatCodeLine(line, lang);
35223
- const lineNoStr = isDiff ? formatDiffLineNo(line, oldLineNo, newLineNo) : "";
35224
- const adjustedWidth = isDiff ? contentWidth - 7 : contentWidth;
35225
- const wrappedLines = wrapText(formatted, adjustedWidth);
35226
- for (const wrappedLine of wrappedLines) {
35227
- const fullLine = lineNoStr + wrappedLine;
35228
- const padding = Math.max(0, contentWidth - stripAnsi2(fullLine).length);
35229
- if (isDiff && isDiffDeletion(line)) {
35230
- console.log(
35231
- chalk2.magenta("\u2502") + bgDel(" " + fullLine + " ".repeat(padding) + " ") + chalk2.magenta("\u2502")
35232
- );
35233
- } else if (isDiff && isDiffAddition(line)) {
35234
- console.log(
35235
- chalk2.magenta("\u2502") + bgAdd(" " + fullLine + " ".repeat(padding) + " ") + chalk2.magenta("\u2502")
35236
- );
35237
- } else {
35238
- console.log(
35239
- chalk2.magenta("\u2502") + " " + fullLine + " ".repeat(padding) + " " + chalk2.magenta("\u2502")
35240
- );
35241
- }
35258
+ if (subcommand === "revoke" || subcommand === "rm") {
35259
+ await revokePath(args.slice(1).join(" "));
35260
+ return false;
35242
35261
  }
35243
- if (isDiff) {
35244
- if (isDiffDeletion(line)) {
35245
- oldLineNo++;
35246
- } else if (isDiffAddition(line)) {
35247
- newLineNo++;
35248
- } else if (!line.startsWith("@@") && !line.startsWith("diff ") && !line.startsWith("index ") && !line.startsWith("---") && !line.startsWith("+++")) {
35249
- oldLineNo++;
35250
- newLineNo++;
35251
- }
35262
+ if (!subcommand) {
35263
+ p26.log.info("Usage: /allow-path <directory>");
35264
+ p26.log.info(" /allow-path list");
35265
+ p26.log.info(" /allow-path revoke <directory>");
35266
+ return false;
35252
35267
  }
35268
+ const dirPath = args.join(" ");
35269
+ await addPath(dirPath, session);
35270
+ return false;
35253
35271
  }
35254
- console.log(chalk2.magenta("\u2570" + "\u2500".repeat(width - 2) + "\u256F"));
35255
- }
35256
- function formatDiffLineNo(line, oldLineNo, newLineNo) {
35257
- if (isDiffDeletion(line)) {
35258
- return chalk2.dim(String(oldLineNo).padStart(5) + " ");
35259
- } else if (isDiffAddition(line)) {
35260
- return chalk2.dim(String(newLineNo).padStart(5) + " ");
35261
- } else if (line.startsWith("@@") || line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++")) {
35262
- return " ";
35263
- }
35264
- return chalk2.dim(String(newLineNo).padStart(5) + " ");
35265
- }
35266
- function formatCodeLine(line, lang) {
35267
- if (lang === "markdown" || lang === "md") {
35268
- return formatMarkdownLine(line);
35269
- }
35270
- return highlightLine(line, lang);
35271
- }
35272
- function formatMarkdownLine(line) {
35273
- if (line.startsWith("# ")) {
35274
- return chalk2.green.bold(line.slice(2));
35275
- }
35276
- if (line.startsWith("## ")) {
35277
- return chalk2.green.bold(line.slice(3));
35278
- }
35279
- if (line.startsWith("### ")) {
35280
- return chalk2.green.bold(line.slice(4));
35281
- }
35282
- if (line.match(/^>\s?/)) {
35283
- const content = line.replace(/^>\s?/, "");
35284
- const formatted = formatInlineMarkdown(content);
35285
- return chalk2.dim("\u258C ") + chalk2.italic(formatted);
35286
- }
35287
- if (/^-{3,}$/.test(line) || /^\*{3,}$/.test(line)) {
35288
- return chalk2.dim("\u2500".repeat(40));
35289
- }
35290
- const htmlResult = formatHtmlLine(line);
35291
- if (htmlResult !== null) {
35292
- return htmlResult;
35293
- }
35294
- if (line.match(/^(\s*)[-*]\s\[x\]\s/i)) {
35295
- line = line.replace(/^(\s*)[-*]\s\[x\]\s/i, "$1" + chalk2.green("\u2714 "));
35296
- } else if (line.match(/^(\s*)[-*]\s\[\s?\]\s/)) {
35297
- line = line.replace(/^(\s*)[-*]\s\[\s?\]\s/, "$1" + chalk2.dim("\u2610 "));
35298
- }
35299
- if (line.match(/^(\s*)[-*]\s/)) {
35300
- line = line.replace(/^(\s*)([-*])\s/, "$1\u2022 ");
35301
- }
35302
- if (line.match(/^(\s*)\d+\.\s/)) ;
35303
- line = formatInlineMarkdown(line);
35304
- return line;
35305
- }
35306
- function formatHtmlLine(line) {
35307
- const trimmed = line.trim();
35308
- if (/^<details\s*\/?>$/i.test(trimmed) || /^<details\s+[^>]*>$/i.test(trimmed)) {
35309
- return chalk2.dim("\u25B6 ") + chalk2.dim.italic("details");
35310
- }
35311
- if (/^<\/details>$/i.test(trimmed)) {
35312
- return chalk2.dim(" \u25C0 end details");
35313
- }
35314
- const summaryInlineMatch = trimmed.match(/^<summary>(.*?)<\/summary>$/i);
35315
- if (summaryInlineMatch) {
35316
- const content = summaryInlineMatch[1] || "";
35317
- return chalk2.dim("\u25B6 ") + chalk2.bold(formatInlineMarkdown(content));
35318
- }
35319
- if (/^<summary\s*\/?>$/i.test(trimmed) || /^<summary\s+[^>]*>$/i.test(trimmed)) {
35320
- return chalk2.dim("\u25B6 ") + chalk2.dim.italic("summary:");
35321
- }
35322
- if (/^<\/summary>$/i.test(trimmed)) {
35323
- return "";
35324
- }
35325
- if (/^<br\s*\/?>$/i.test(trimmed)) {
35326
- return "";
35327
- }
35328
- if (/^<hr\s*\/?>$/i.test(trimmed)) {
35329
- return chalk2.dim("\u2500".repeat(40));
35330
- }
35331
- const headingMatch = trimmed.match(/^<h([1-6])>(.*?)<\/h\1>$/i);
35332
- if (headingMatch) {
35333
- const content = headingMatch[2] || "";
35334
- return chalk2.green.bold(formatInlineMarkdown(content));
35335
- }
35336
- const pMatch = trimmed.match(/^<p>(.*?)<\/p>$/i);
35337
- if (pMatch) {
35338
- return formatInlineMarkdown(pMatch[1] || "");
35339
- }
35340
- if (/^<\/?p>$/i.test(trimmed)) {
35341
- return "";
35342
- }
35343
- const boldMatch = trimmed.match(/^<(?:strong|b)>(.*?)<\/(?:strong|b)>$/i);
35344
- if (boldMatch) {
35345
- return chalk2.bold(formatInlineMarkdown(boldMatch[1] || ""));
35346
- }
35347
- const italicMatch = trimmed.match(/^<(?:em|i)>(.*?)<\/(?:em|i)>$/i);
35348
- if (italicMatch) {
35349
- return chalk2.italic(formatInlineMarkdown(italicMatch[1] || ""));
35350
- }
35351
- const codeMatch = trimmed.match(/^<code>(.*?)<\/code>$/i);
35352
- if (codeMatch) {
35353
- return chalk2.cyan(codeMatch[1] || "");
35354
- }
35355
- if (/^<blockquote\s*>$/i.test(trimmed)) {
35356
- return chalk2.dim("\u258C ");
35357
- }
35358
- if (/^<\/blockquote>$/i.test(trimmed)) {
35359
- return "";
35360
- }
35361
- if (/^<\/?[uo]l\s*>$/i.test(trimmed)) {
35362
- return "";
35363
- }
35364
- const liMatch = trimmed.match(/^<li>(.*?)<\/li>$/i);
35365
- if (liMatch) {
35366
- return "\u2022 " + formatInlineMarkdown(liMatch[1] || "");
35367
- }
35368
- if (/^<li\s*>$/i.test(trimmed)) {
35369
- return "\u2022 ";
35370
- }
35371
- if (/^<\/li>$/i.test(trimmed)) {
35372
- return "";
35373
- }
35374
- if (/^<\/?div\s*[^>]*>$/i.test(trimmed)) {
35375
- return "";
35376
- }
35377
- const spanMatch = trimmed.match(/^<span[^>]*>(.*?)<\/span>$/i);
35378
- if (spanMatch) {
35379
- return formatInlineMarkdown(spanMatch[1] || "");
35380
- }
35381
- const imgMatch = trimmed.match(/^<img\s[^>]*alt=["']([^"']*)["'][^>]*\/?>$/i);
35382
- if (imgMatch) {
35383
- return chalk2.dim("[image: ") + chalk2.italic(imgMatch[1] || "") + chalk2.dim("]");
35384
- }
35385
- const aMatch = trimmed.match(/^<a\s[^>]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>$/i);
35386
- if (aMatch) {
35387
- return chalk2.blue.underline(aMatch[2] || aMatch[1] || "");
35388
- }
35389
- if (/^<\/?[a-z][a-z0-9]*(\s[^>]*)?\s*\/?>$/i.test(trimmed)) {
35390
- return chalk2.dim(trimmed);
35391
- }
35392
- if (/<[a-z][a-z0-9]*(\s[^>]*)?\s*\/?>/i.test(trimmed) && /<\/[a-z][a-z0-9]*>/i.test(trimmed)) {
35393
- let stripped = trimmed;
35394
- stripped = stripped.replace(/<br\s*\/?>/gi, " ").replace(/<\/?(?:strong|b)>/gi, "**").replace(/<\/?(?:em|i)>/gi, "*").replace(/<\/?code>/gi, "`").replace(/<a\s[^>]*href=["']([^"']*)["'][^>]*>/gi, "").replace(/<\/a>/gi, "");
35395
- let prevStripped = "";
35396
- while (prevStripped !== stripped) {
35397
- prevStripped = stripped;
35398
- stripped = stripped.replace(/<[^>]*>/g, "");
35272
+ };
35273
+ async function addPath(dirPath, session) {
35274
+ const absolute = path36__default.resolve(dirPath);
35275
+ try {
35276
+ const stat2 = await fs34__default.stat(absolute);
35277
+ if (!stat2.isDirectory()) {
35278
+ p26.log.error(`Not a directory: ${absolute}`);
35279
+ return;
35399
35280
  }
35400
- stripped = stripped.replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#39;/g, "'").replace(/&amp;/g, "&");
35401
- return formatInlineMarkdown(stripped);
35402
- }
35403
- return null;
35404
- }
35405
- function formatInlineMarkdown(text13) {
35406
- text13 = text13.replace(/\*\*\*(.+?)\*\*\*/g, (_, content) => chalk2.bold.italic(content));
35407
- text13 = text13.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk2.bold(content));
35408
- text13 = text13.replace(/\*([^*]+)\*/g, (_, content) => chalk2.italic(content));
35409
- text13 = text13.replace(/_([^_]+)_/g, (_, content) => chalk2.italic(content));
35410
- text13 = text13.replace(/`([^`]+)`/g, (_, content) => chalk2.cyan(content));
35411
- text13 = text13.replace(/~~(.+?)~~/g, (_, content) => chalk2.strikethrough(content));
35412
- text13 = text13.replace(/\[([^\]]+)\]\([^)]+\)/g, (_, linkText) => chalk2.blue.underline(linkText));
35413
- return text13;
35414
- }
35415
- function wrapText(text13, maxWidth) {
35416
- if (maxWidth <= 0) return [text13];
35417
- const plainText = stripAnsi2(text13);
35418
- if (plainText.length <= maxWidth) {
35419
- return [text13];
35281
+ } catch {
35282
+ p26.log.error(`Directory not found: ${absolute}`);
35283
+ return;
35420
35284
  }
35421
- const lines = [];
35422
- let remaining = text13;
35423
- while (true) {
35424
- const plain = stripAnsi2(remaining);
35425
- if (plain.length <= maxWidth) break;
35426
- let breakPoint = maxWidth;
35427
- const lastSpace = plain.lastIndexOf(" ", maxWidth);
35428
- if (lastSpace > maxWidth * 0.5) {
35429
- breakPoint = lastSpace;
35430
- }
35431
- const ansiRegex = /\x1b\[[0-9;]*m/g;
35432
- let match;
35433
- const ansiPositions = [];
35434
- ansiRegex.lastIndex = 0;
35435
- while ((match = ansiRegex.exec(remaining)) !== null) {
35436
- ansiPositions.push({ start: match.index, end: match.index + match[0].length });
35437
- }
35438
- let rawPos = 0;
35439
- let visualPos = 0;
35440
- let ansiIdx = 0;
35441
- while (visualPos < breakPoint && rawPos < remaining.length) {
35442
- while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
35443
- rawPos = ansiPositions[ansiIdx].end;
35444
- ansiIdx++;
35445
- }
35446
- if (rawPos >= remaining.length) break;
35447
- rawPos++;
35448
- visualPos++;
35449
- }
35450
- while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
35451
- rawPos = ansiPositions[ansiIdx].end;
35452
- ansiIdx++;
35285
+ for (const blocked of BLOCKED_SYSTEM_PATHS) {
35286
+ const normalizedBlocked = path36__default.normalize(blocked);
35287
+ if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path36__default.sep)) {
35288
+ p26.log.error(`System path '${blocked}' cannot be allowed`);
35289
+ return;
35453
35290
  }
35454
- lines.push(remaining.slice(0, rawPos) + "\x1B[0m");
35455
- remaining = "\x1B[0m" + remaining.slice(rawPos).trimStart();
35456
- }
35457
- if (remaining) {
35458
- lines.push(remaining);
35459
- }
35460
- return lines.length > 0 ? lines : [text13];
35461
- }
35462
- function stripAnsi2(str) {
35463
- return str.replace(/\x1b\[[0-9;]*m/g, "");
35464
- }
35465
- var TOOL_ICONS = {
35466
- read_file: "\u{1F4C4}",
35467
- write_file_create: "\u{1F4DD}+",
35468
- write_file_modify: "\u270F\uFE0F",
35469
- edit_file: "\u270F\uFE0F",
35470
- delete_file: "\u{1F5D1}\uFE0F",
35471
- list_directory: "\u{1F4C1}",
35472
- list_dir: "\u{1F4C1}",
35473
- search_files: "\u{1F50D}",
35474
- grep: "\u{1F50D}",
35475
- bash_exec: "\u26A1",
35476
- web_search: "\u{1F310}",
35477
- git_status: "\u{1F4CA}",
35478
- git_commit: "\u{1F4BE}",
35479
- git_push: "\u2B06\uFE0F",
35480
- git_pull: "\u2B07\uFE0F",
35481
- run_tests: "\u{1F9EA}",
35482
- run_linter: "\u{1F50E}",
35483
- default: "\u{1F527}"
35484
- };
35485
- function getToolIcon(toolName, input) {
35486
- if (toolName === "write_file" && input) {
35487
- const wouldCreate = input.wouldCreate === true;
35488
- return wouldCreate ? TOOL_ICONS.write_file_create : TOOL_ICONS.write_file_modify;
35489
35291
  }
35490
- return TOOL_ICONS[toolName] ?? "\u{1F527}";
35491
- }
35492
- function renderToolStart(toolName, input, metadata) {
35493
- const icon = getToolIcon(toolName, { ...input, wouldCreate: metadata?.isCreate });
35494
- const summary = formatToolSummary(toolName, input);
35495
- if (toolName === "write_file") {
35496
- const label = chalk2.yellow.bold("MODIFY") + " " + chalk2.cyan(String(input.path || ""));
35497
- console.log(`
35498
- ${icon} ${label}`);
35499
- const preview = renderContentPreview(String(input.content || ""), 3);
35500
- if (preview) console.log(preview);
35292
+ const normalizedCwd = path36__default.normalize(session.projectPath);
35293
+ if (absolute === normalizedCwd || absolute.startsWith(normalizedCwd + path36__default.sep)) {
35294
+ p26.log.info("That path is already within the project directory");
35501
35295
  return;
35502
35296
  }
35503
- if (toolName === "edit_file") {
35504
- console.log(`
35505
- ${icon} ${chalk2.yellow.bold("EDIT")} ${chalk2.cyan(String(input.path || ""))}`);
35506
- const editPreview = renderEditPreview(
35507
- String(input.old_string || ""),
35508
- String(input.new_string || "")
35509
- );
35510
- if (editPreview) console.log(editPreview);
35297
+ const existing = getAllowedPaths();
35298
+ if (existing.some((e) => path36__default.normalize(e.path) === path36__default.normalize(absolute))) {
35299
+ p26.log.info(`Already allowed: ${absolute}`);
35511
35300
  return;
35512
35301
  }
35513
- console.log(`
35514
- ${icon} ${chalk2.cyan.bold(toolName)} ${chalk2.dim(summary)}`);
35515
- }
35516
- function renderContentPreview(content, maxLines) {
35517
- const maxWidth = Math.max(getTerminalWidth2() - 6, 40);
35518
- const lines = content.split("\n");
35519
- const preview = [];
35520
- for (const line of lines) {
35521
- if (preview.length >= maxLines) break;
35522
- const trimmed = line.trimEnd();
35523
- if (trimmed.length === 0 && preview.length === 0) continue;
35524
- const truncated = trimmed.length > maxWidth ? trimmed.slice(0, maxWidth - 1) + "\u2026" : trimmed;
35525
- preview.push(` ${truncated}`);
35526
- }
35527
- if (preview.length === 0) return "";
35528
- const totalNonEmpty = lines.filter((l) => l.trim().length > 0).length;
35529
- const more = totalNonEmpty > maxLines ? chalk2.dim(` \u2026 +${totalNonEmpty - maxLines} lines`) : "";
35530
- return chalk2.dim(preview.join("\n")) + more;
35531
- }
35532
- function renderEditPreview(oldStr, newStr) {
35533
- const maxWidth = Math.max(getTerminalWidth2() - 8, 30);
35534
- const MAX_PREVIEW_LINES = 8;
35535
- const bgDel = chalk2.bgRgb(80, 20, 20);
35536
- const bgAdd = chalk2.bgRgb(20, 60, 20);
35537
- const oldLines = oldStr.split("\n").filter((l) => l.trim().length > 0);
35538
- const newLines = newStr.split("\n").filter((l) => l.trim().length > 0);
35539
- if (oldLines.length === 0 && newLines.length === 0) return "";
35540
- const truncate2 = (s) => s.length > maxWidth ? s.slice(0, maxWidth - 1) + "\u2026" : s;
35541
- const result = [];
35542
- let shown = 0;
35543
- for (const line of oldLines) {
35544
- if (shown >= MAX_PREVIEW_LINES) break;
35545
- const text13 = `- ${truncate2(line.trim())}`;
35546
- const pad = Math.max(0, maxWidth - text13.length);
35547
- result.push(" " + bgDel(text13 + " ".repeat(pad)));
35548
- shown++;
35549
- }
35550
- for (const line of newLines) {
35551
- if (shown >= MAX_PREVIEW_LINES) break;
35552
- const text13 = `+ ${truncate2(line.trim())}`;
35553
- const pad = Math.max(0, maxWidth - text13.length);
35554
- result.push(" " + bgAdd(text13 + " ".repeat(pad)));
35555
- shown++;
35302
+ console.log();
35303
+ console.log(chalk2.yellow(" \u26A0 Grant access to external directory"));
35304
+ console.log(chalk2.dim(` \u{1F4C1} ${absolute}`));
35305
+ console.log();
35306
+ const action = await p26.select({
35307
+ message: "Grant access?",
35308
+ options: [
35309
+ { value: "session-write", label: "\u2713 Write access (this session only)" },
35310
+ { value: "session-read", label: "\u25D0 Read-only (this session only)" },
35311
+ { value: "persist-write", label: "\u26A1 Write access (remember for this project)" },
35312
+ { value: "persist-read", label: "\u{1F4BE} Read-only (remember for this project)" },
35313
+ { value: "no", label: "\u2717 Cancel" }
35314
+ ]
35315
+ });
35316
+ if (p26.isCancel(action) || action === "no") {
35317
+ p26.log.info("Cancelled");
35318
+ return;
35556
35319
  }
35557
- const total = oldLines.length + newLines.length;
35558
- if (total > MAX_PREVIEW_LINES) {
35559
- result.push(chalk2.dim(` \u2026 +${total - MAX_PREVIEW_LINES} more lines`));
35320
+ const level = action.includes("read") ? "read" : "write";
35321
+ const persist = action.startsWith("persist");
35322
+ addAllowedPathToSession(absolute, level);
35323
+ if (persist) {
35324
+ await persistAllowedPath(absolute, level);
35560
35325
  }
35561
- return result.join("\n");
35326
+ const levelLabel = level === "write" ? "write" : "read-only";
35327
+ const persistLabel = persist ? " (persisted)" : " (session only)";
35328
+ p26.log.success(`Access granted: ${levelLabel}${persistLabel}`);
35329
+ console.log(chalk2.dim(` \u{1F4C1} ${absolute}`));
35562
35330
  }
35563
- function renderToolEnd(result) {
35564
- const status = result.result.success ? chalk2.green("\u2713") : chalk2.red("\u2717");
35565
- const duration = chalk2.dim(`${result.duration.toFixed(0)}ms`);
35566
- const preview = formatResultPreview(result);
35567
- console.log(` ${status} ${duration}${preview ? ` ${preview}` : ""}`);
35568
- if (!result.result.success && result.result.error) {
35569
- console.log(chalk2.red(` \u2514\u2500 ${result.result.error}`));
35331
+ function showAllowedPaths(session) {
35332
+ const paths = getAllowedPaths();
35333
+ console.log();
35334
+ console.log(chalk2.bold(" Allowed Paths"));
35335
+ console.log();
35336
+ console.log(chalk2.dim(` \u{1F4C1} ${session.projectPath}`) + chalk2.green(" (project root)"));
35337
+ if (paths.length === 0) {
35338
+ console.log(chalk2.dim(" No additional paths allowed"));
35339
+ } else {
35340
+ for (const entry of paths) {
35341
+ const level = entry.level === "write" ? chalk2.yellow("write") : chalk2.cyan("read");
35342
+ console.log(chalk2.dim(` \u{1F4C1} ${entry.path}`) + ` [${level}]`);
35343
+ }
35570
35344
  }
35571
- const details = formatResultDetails(result);
35572
- if (details) console.log(details);
35345
+ console.log();
35573
35346
  }
35574
- function formatToolSummary(toolName, input) {
35575
- switch (toolName) {
35576
- case "read_file":
35577
- case "write_file":
35578
- case "edit_file":
35579
- case "delete_file":
35580
- return String(input.path || "");
35581
- case "list_directory":
35582
- return String(input.path || ".");
35583
- case "grep":
35584
- case "search_files": {
35585
- const pattern = String(input.pattern || "");
35586
- const path57 = input.path ? ` in ${input.path}` : "";
35587
- return `"${pattern}"${path57}`;
35347
+ async function revokePath(dirPath, _session) {
35348
+ if (!dirPath) {
35349
+ const paths = getAllowedPaths();
35350
+ if (paths.length === 0) {
35351
+ p26.log.info("No additional paths to revoke");
35352
+ return;
35588
35353
  }
35589
- case "bash_exec": {
35590
- const cmd = String(input.command || "");
35591
- const max = Math.max(getTerminalWidth2() - 20, 50);
35592
- return cmd.length > max ? cmd.slice(0, max - 1) + "\u2026" : cmd;
35354
+ const selected = await p26.select({
35355
+ message: "Revoke access to:",
35356
+ options: [
35357
+ ...paths.map((entry) => ({
35358
+ value: entry.path,
35359
+ label: `${entry.path} [${entry.level}]`
35360
+ })),
35361
+ { value: "__cancel__", label: "Cancel" }
35362
+ ]
35363
+ });
35364
+ if (p26.isCancel(selected) || selected === "__cancel__") {
35365
+ return;
35593
35366
  }
35594
- default:
35595
- return formatToolInput(input);
35596
- }
35597
- }
35598
- function formatResultPreview(result) {
35599
- if (!result.result.success) return "";
35600
- const { name, result: toolResult } = result;
35601
- try {
35602
- const data = JSON.parse(toolResult.output);
35603
- switch (name) {
35604
- case "read_file":
35605
- if (data.lines !== void 0) {
35606
- return chalk2.dim(`(${data.lines} lines)`);
35607
- }
35608
- break;
35609
- case "list_directory":
35610
- if (Array.isArray(data.entries)) {
35611
- const dirs = data.entries.filter((e) => e.type === "directory").length;
35612
- const files = data.entries.length - dirs;
35613
- return chalk2.dim(`(${files} files, ${dirs} dirs)`);
35614
- }
35615
- break;
35616
- case "grep":
35617
- case "search_files":
35618
- if (Array.isArray(data.matches)) {
35619
- const n = data.matches.length;
35620
- return n === 0 ? chalk2.yellow("\xB7 no matches") : chalk2.dim(`\xB7 ${n} match${n === 1 ? "" : "es"}`);
35621
- }
35622
- break;
35623
- case "bash_exec":
35624
- if (data.exitCode !== void 0 && data.exitCode !== 0) {
35625
- return chalk2.red(`(exit ${data.exitCode})`);
35626
- }
35627
- break;
35628
- case "write_file":
35629
- case "edit_file":
35630
- return chalk2.dim("(saved)");
35631
- }
35632
- } catch {
35633
- }
35634
- return "";
35635
- }
35636
- function formatResultDetails(result) {
35637
- if (!result.result.success) return "";
35638
- const { name, result: toolResult } = result;
35639
- const maxWidth = Math.max(getTerminalWidth2() - 8, 40);
35640
- try {
35641
- const data = JSON.parse(toolResult.output);
35642
- if ((name === "grep" || name === "search_files") && Array.isArray(data.matches)) {
35643
- const matches = data.matches;
35644
- if (matches.length === 0) return "";
35645
- const MAX_SHOWN = 3;
35646
- const shown = matches.slice(0, MAX_SHOWN);
35647
- const lines = shown.map(({ file, line, content }) => {
35648
- const location = chalk2.cyan(`${file}:${line}`);
35649
- const snippet = content.trim();
35650
- const truncated = snippet.length > maxWidth ? snippet.slice(0, maxWidth - 1) + "\u2026" : snippet;
35651
- return ` ${chalk2.dim("\u2502")} ${location} ${chalk2.dim(truncated)}`;
35652
- });
35653
- if (matches.length > MAX_SHOWN) {
35654
- lines.push(` ${chalk2.dim(`\u2502 \u2026 +${matches.length - MAX_SHOWN} more`)}`);
35655
- }
35656
- return lines.join("\n");
35657
- }
35658
- if (name === "bash_exec" && data.exitCode === 0) {
35659
- const stdout = String(data.stdout || "").trimEnd();
35660
- if (!stdout) return "";
35661
- const outputLines = stdout.split("\n").filter((l) => l.trim());
35662
- if (outputLines.length > 6) return "";
35663
- const shown = outputLines.slice(0, 4);
35664
- const lines = shown.map((l) => {
35665
- const truncated = l.length > maxWidth ? l.slice(0, maxWidth - 1) + "\u2026" : l;
35666
- return ` ${chalk2.dim("\u2502")} ${chalk2.dim(truncated)}`;
35667
- });
35668
- if (outputLines.length > 4) {
35669
- lines.push(` ${chalk2.dim(`\u2502 \u2026 +${outputLines.length - 4} more`)}`);
35670
- }
35671
- return lines.join("\n");
35672
- }
35673
- } catch {
35674
- }
35675
- return "";
35676
- }
35677
- function formatToolInput(input) {
35678
- const entries = Object.entries(input);
35679
- if (entries.length === 0) return "";
35680
- const parts = entries.slice(0, 3).map(([key, value]) => {
35681
- let str;
35682
- if (typeof value === "string") {
35683
- str = value;
35684
- } else if (value === void 0 || value === null) {
35685
- str = String(value);
35686
- } else {
35687
- str = JSON.stringify(value);
35688
- }
35689
- const truncated = str.length > 40 ? str.slice(0, 37) + "..." : str;
35690
- return `${key}=${truncated}`;
35691
- });
35692
- if (entries.length > 3) {
35693
- parts.push(`+${entries.length - 3} more`);
35694
- }
35695
- return parts.join(", ");
35696
- }
35697
- function renderUsageStats(inputTokens, outputTokens, toolCallCount) {
35698
- const totalTokens = inputTokens + outputTokens;
35699
- const toolsStr = toolCallCount > 0 ? ` \xB7 ${toolCallCount} tools` : "";
35700
- console.log(chalk2.dim(`\u2500 ${totalTokens.toLocaleString("en-US")} tokens${toolsStr}`));
35701
- }
35702
- function renderError(message) {
35703
- console.error(chalk2.red(`\u2717 Error: ${message}`));
35704
- }
35705
- function renderInfo(message) {
35706
- console.log(chalk2.dim(message));
35707
- }
35708
- function getClipboardCommand() {
35709
- const platform = process.platform;
35710
- if (platform === "darwin") {
35711
- return { command: "pbcopy", args: [] };
35712
- }
35713
- if (platform === "linux") {
35714
- return { command: "xclip", args: ["-selection", "clipboard"] };
35715
- }
35716
- if (platform === "win32") {
35717
- return { command: "clip", args: [] };
35718
- }
35719
- return null;
35720
- }
35721
- async function copyToClipboard(text13) {
35722
- const clipboardCmd = getClipboardCommand();
35723
- if (!clipboardCmd) {
35724
- return false;
35725
- }
35726
- return new Promise((resolve4) => {
35727
- try {
35728
- const proc = spawn(clipboardCmd.command, clipboardCmd.args, {
35729
- stdio: ["pipe", "ignore", "ignore"]
35730
- });
35731
- let resolved = false;
35732
- proc.on("error", () => {
35733
- if (resolved) return;
35734
- resolved = true;
35735
- if (process.platform === "linux") {
35736
- try {
35737
- const xselProc = spawn("xsel", ["--clipboard", "--input"], {
35738
- stdio: ["pipe", "ignore", "ignore"]
35739
- });
35740
- xselProc.on("error", () => resolve4(false));
35741
- xselProc.on("close", (code) => resolve4(code === 0));
35742
- xselProc.stdin.write(text13);
35743
- xselProc.stdin.end();
35744
- } catch {
35745
- resolve4(false);
35746
- }
35747
- } else {
35748
- resolve4(false);
35749
- }
35750
- });
35751
- proc.on("close", (code) => {
35752
- if (resolved) return;
35753
- resolved = true;
35754
- resolve4(code === 0);
35755
- });
35756
- proc.stdin.on("error", () => {
35757
- });
35758
- proc.stdin.write(text13);
35759
- proc.stdin.end();
35760
- } catch {
35761
- resolve4(false);
35762
- }
35763
- });
35764
- }
35765
- async function isClipboardAvailable() {
35766
- const clipboardCmd = getClipboardCommand();
35767
- if (!clipboardCmd) return false;
35768
- return new Promise((resolve4) => {
35769
- const testCmd = process.platform === "win32" ? "where" : "which";
35770
- const proc = spawn(testCmd, [clipboardCmd.command], {
35771
- stdio: ["ignore", "ignore", "ignore"]
35772
- });
35773
- proc.on("error", () => {
35774
- if (process.platform === "linux") {
35775
- const xselProc = spawn("which", ["xsel"], {
35776
- stdio: ["ignore", "ignore", "ignore"]
35777
- });
35778
- xselProc.on("error", () => resolve4(false));
35779
- xselProc.on("close", (code) => resolve4(code === 0));
35780
- } else {
35781
- resolve4(false);
35782
- }
35783
- });
35784
- proc.on("close", (code) => resolve4(code === 0));
35785
- });
35786
- }
35787
- async function readClipboardImage() {
35788
- const platform = process.platform;
35789
- if (platform === "darwin") {
35790
- return readClipboardImageMacOS();
35791
- }
35792
- if (platform === "linux") {
35793
- return readClipboardImageLinux();
35794
- }
35795
- if (platform === "win32") {
35796
- return readClipboardImageWindows();
35797
- }
35798
- return null;
35799
- }
35800
- async function readClipboardImageMacOS() {
35801
- const tmpFile = path36.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
35802
- try {
35803
- const script = `
35804
- set theFile to POSIX file "${tmpFile}"
35805
- try
35806
- set theImage to the clipboard as \xABclass PNGf\xBB
35807
- set fileRef to open for access theFile with write permission
35808
- write theImage to fileRef
35809
- close access fileRef
35810
- return "ok"
35811
- on error errMsg
35812
- return "error: " & errMsg
35813
- end try
35814
- `;
35815
- const result = execFileSync("osascript", ["-e", script], {
35816
- encoding: "utf-8",
35817
- timeout: 1e4
35818
- }).trim();
35819
- if (!result.startsWith("ok")) {
35820
- return null;
35821
- }
35822
- const buffer = await fs34.readFile(tmpFile);
35823
- return {
35824
- data: buffer.toString("base64"),
35825
- media_type: "image/png"
35826
- };
35827
- } catch {
35828
- return null;
35829
- } finally {
35830
- try {
35831
- await fs34.unlink(tmpFile);
35832
- } catch {
35833
- }
35834
- }
35835
- }
35836
- async function readClipboardImageLinux() {
35837
- try {
35838
- const targets = execFileSync("xclip", ["-selection", "clipboard", "-t", "TARGETS", "-o"], {
35839
- encoding: "utf-8",
35840
- timeout: 5e3
35841
- });
35842
- if (!targets.includes("image/png")) {
35843
- return null;
35844
- }
35845
- const buffer = execFileSync("xclip", ["-selection", "clipboard", "-t", "image/png", "-o"], {
35846
- timeout: 5e3
35847
- });
35848
- return {
35849
- data: Buffer.from(buffer).toString("base64"),
35850
- media_type: "image/png"
35851
- };
35852
- } catch {
35853
- return null;
35854
- }
35855
- }
35856
- async function readClipboardImageWindows() {
35857
- const tmpFile = path36.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
35858
- try {
35859
- const escapedPath = tmpFile.replace(/'/g, "''");
35860
- const script = `
35861
- Add-Type -AssemblyName System.Windows.Forms;
35862
- $img = [System.Windows.Forms.Clipboard]::GetImage();
35863
- if ($img -ne $null) {
35864
- $img.Save('${escapedPath}', [System.Drawing.Imaging.ImageFormat]::Png);
35865
- Write-Output 'ok';
35866
- } else {
35867
- Write-Output 'no-image';
35868
- }
35869
- `;
35870
- const result = execFileSync("powershell", ["-Command", script], {
35871
- encoding: "utf-8",
35872
- timeout: 1e4
35873
- }).trim();
35874
- if (result !== "ok") return null;
35875
- const buffer = await fs34.readFile(tmpFile);
35876
- return {
35877
- data: buffer.toString("base64"),
35878
- media_type: "image/png"
35879
- };
35880
- } catch {
35881
- return null;
35882
- } finally {
35883
- try {
35884
- await fs34.unlink(tmpFile);
35885
- } catch {
35886
- }
35887
- }
35888
- }
35889
- function isClipboardImageAvailable() {
35890
- const platform = process.platform;
35891
- return platform === "darwin" || platform === "win32" || platform === "linux";
35892
- }
35893
-
35894
- // src/cli/repl/commands/copy.ts
35895
- var copyCommand = {
35896
- name: "copy",
35897
- aliases: ["cp"],
35898
- description: "Copy last response to clipboard",
35899
- usage: "/copy",
35900
- async execute() {
35901
- const clipboardAvailable = await isClipboardAvailable();
35902
- if (!clipboardAvailable) {
35903
- console.log(chalk2.red(" \u2717 Clipboard not available on this system"));
35904
- console.log(chalk2.dim(" macOS: pbcopy, Linux: xclip or xsel, Windows: clip"));
35905
- return false;
35906
- }
35907
- const rawMarkdown = getRawMarkdown();
35908
- if (!rawMarkdown.trim()) {
35909
- console.log(chalk2.yellow(" \u26A0 No response to copy"));
35910
- console.log(chalk2.dim(" Ask a question first, then use /copy"));
35911
- return false;
35912
- }
35913
- let contentToCopy = rawMarkdown;
35914
- const markdownBlockMatch = rawMarkdown.match(/```(?:markdown|md)?\n([\s\S]*?)```/);
35915
- if (markdownBlockMatch && markdownBlockMatch[1]) {
35916
- contentToCopy = markdownBlockMatch[1].trim();
35917
- }
35918
- const lines = contentToCopy.split("\n").length;
35919
- const chars = contentToCopy.length;
35920
- const success = await copyToClipboard(contentToCopy);
35921
- if (success) {
35922
- console.log(chalk2.green(` \u2713 Copied to clipboard`));
35923
- console.log(chalk2.dim(` ${lines} lines, ${chars} characters`));
35924
- } else {
35925
- console.log(chalk2.red(" \u2717 Failed to copy to clipboard"));
35926
- console.log(chalk2.dim(` Content: ${chars} chars, ${lines} lines`));
35927
- }
35928
- return false;
35929
- }
35930
- };
35931
-
35932
- // src/cli/repl/commands/allow-path.ts
35933
- init_allowed_paths();
35934
- var BLOCKED_SYSTEM_PATHS = [
35935
- "/etc",
35936
- "/var",
35937
- "/usr",
35938
- "/root",
35939
- "/sys",
35940
- "/proc",
35941
- "/boot",
35942
- "/bin",
35943
- "/sbin"
35944
- ];
35945
- var allowPathCommand = {
35946
- name: "allow-path",
35947
- aliases: ["ap"],
35948
- description: "Allow file operations in an additional directory",
35949
- usage: "/allow-path <directory> | /allow-path list | /allow-path revoke <directory>",
35950
- execute: async (args, session) => {
35951
- const subcommand = args[0] ?? "";
35952
- if (subcommand === "list" || subcommand === "ls") {
35953
- showAllowedPaths(session);
35954
- return false;
35955
- }
35956
- if (subcommand === "revoke" || subcommand === "rm") {
35957
- await revokePath(args.slice(1).join(" "));
35958
- return false;
35959
- }
35960
- if (!subcommand) {
35961
- p26.log.info("Usage: /allow-path <directory>");
35962
- p26.log.info(" /allow-path list");
35963
- p26.log.info(" /allow-path revoke <directory>");
35964
- return false;
35965
- }
35966
- const dirPath = args.join(" ");
35967
- await addPath(dirPath, session);
35968
- return false;
35969
- }
35970
- };
35971
- async function addPath(dirPath, session) {
35972
- const absolute = path36__default.resolve(dirPath);
35973
- try {
35974
- const stat2 = await fs34__default.stat(absolute);
35975
- if (!stat2.isDirectory()) {
35976
- p26.log.error(`Not a directory: ${absolute}`);
35977
- return;
35978
- }
35979
- } catch {
35980
- p26.log.error(`Directory not found: ${absolute}`);
35981
- return;
35982
- }
35983
- for (const blocked of BLOCKED_SYSTEM_PATHS) {
35984
- const normalizedBlocked = path36__default.normalize(blocked);
35985
- if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path36__default.sep)) {
35986
- p26.log.error(`System path '${blocked}' cannot be allowed`);
35987
- return;
35988
- }
35989
- }
35990
- const normalizedCwd = path36__default.normalize(session.projectPath);
35991
- if (absolute === normalizedCwd || absolute.startsWith(normalizedCwd + path36__default.sep)) {
35992
- p26.log.info("That path is already within the project directory");
35993
- return;
35994
- }
35995
- const existing = getAllowedPaths();
35996
- if (existing.some((e) => path36__default.normalize(e.path) === path36__default.normalize(absolute))) {
35997
- p26.log.info(`Already allowed: ${absolute}`);
35998
- return;
35999
- }
36000
- console.log();
36001
- console.log(chalk2.yellow(" \u26A0 Grant access to external directory"));
36002
- console.log(chalk2.dim(` \u{1F4C1} ${absolute}`));
36003
- console.log();
36004
- const action = await p26.select({
36005
- message: "Grant access?",
36006
- options: [
36007
- { value: "session-write", label: "\u2713 Write access (this session only)" },
36008
- { value: "session-read", label: "\u25D0 Read-only (this session only)" },
36009
- { value: "persist-write", label: "\u26A1 Write access (remember for this project)" },
36010
- { value: "persist-read", label: "\u{1F4BE} Read-only (remember for this project)" },
36011
- { value: "no", label: "\u2717 Cancel" }
36012
- ]
36013
- });
36014
- if (p26.isCancel(action) || action === "no") {
36015
- p26.log.info("Cancelled");
36016
- return;
36017
- }
36018
- const level = action.includes("read") ? "read" : "write";
36019
- const persist = action.startsWith("persist");
36020
- addAllowedPathToSession(absolute, level);
36021
- if (persist) {
36022
- await persistAllowedPath(absolute, level);
36023
- }
36024
- const levelLabel = level === "write" ? "write" : "read-only";
36025
- const persistLabel = persist ? " (persisted)" : " (session only)";
36026
- p26.log.success(`Access granted: ${levelLabel}${persistLabel}`);
36027
- console.log(chalk2.dim(` \u{1F4C1} ${absolute}`));
36028
- }
36029
- function showAllowedPaths(session) {
36030
- const paths = getAllowedPaths();
36031
- console.log();
36032
- console.log(chalk2.bold(" Allowed Paths"));
36033
- console.log();
36034
- console.log(chalk2.dim(` \u{1F4C1} ${session.projectPath}`) + chalk2.green(" (project root)"));
36035
- if (paths.length === 0) {
36036
- console.log(chalk2.dim(" No additional paths allowed"));
36037
- } else {
36038
- for (const entry of paths) {
36039
- const level = entry.level === "write" ? chalk2.yellow("write") : chalk2.cyan("read");
36040
- console.log(chalk2.dim(` \u{1F4C1} ${entry.path}`) + ` [${level}]`);
36041
- }
36042
- }
36043
- console.log();
36044
- }
36045
- async function revokePath(dirPath, _session) {
36046
- if (!dirPath) {
36047
- const paths = getAllowedPaths();
36048
- if (paths.length === 0) {
36049
- p26.log.info("No additional paths to revoke");
36050
- return;
36051
- }
36052
- const selected = await p26.select({
36053
- message: "Revoke access to:",
36054
- options: [
36055
- ...paths.map((entry) => ({
36056
- value: entry.path,
36057
- label: `${entry.path} [${entry.level}]`
36058
- })),
36059
- { value: "__cancel__", label: "Cancel" }
36060
- ]
36061
- });
36062
- if (p26.isCancel(selected) || selected === "__cancel__") {
36063
- return;
36064
- }
36065
- dirPath = selected;
36066
- }
36067
- const absolute = path36__default.resolve(dirPath);
36068
- const removed = removeAllowedPathFromSession(absolute);
36069
- await removePersistedAllowedPath(absolute);
36070
- if (removed) {
36071
- p26.log.success(`Access revoked: ${absolute}`);
36072
- } else {
36073
- p26.log.error(`Path not found in allowed list: ${absolute}`);
35367
+ dirPath = selected;
35368
+ }
35369
+ const absolute = path36__default.resolve(dirPath);
35370
+ const removed = removeAllowedPathFromSession(absolute);
35371
+ await removePersistedAllowedPath(absolute);
35372
+ if (removed) {
35373
+ p26.log.success(`Access revoked: ${absolute}`);
35374
+ } else {
35375
+ p26.log.error(`Path not found in allowed list: ${absolute}`);
36074
35376
  }
36075
35377
  }
36076
35378
 
@@ -37034,17 +36336,20 @@ var updateCocoCommand = {
37034
36336
  return false;
37035
36337
  }
37036
36338
  };
37037
- var pendingImage = null;
37038
- function consumePendingImage() {
37039
- const img = pendingImage;
37040
- pendingImage = null;
37041
- return img;
36339
+ var pendingImages = [];
36340
+ function consumePendingImages() {
36341
+ const imgs = pendingImages;
36342
+ pendingImages = [];
36343
+ return imgs;
37042
36344
  }
37043
36345
  function hasPendingImage() {
37044
- return pendingImage !== null;
36346
+ return pendingImages.length > 0;
36347
+ }
36348
+ function getPendingImageCount() {
36349
+ return pendingImages.length;
37045
36350
  }
37046
36351
  function setPendingImage(data, media_type, prompt) {
37047
- pendingImage = { data, media_type, prompt };
36352
+ pendingImages.push({ data, media_type, prompt });
37048
36353
  }
37049
36354
  var imageCommand = {
37050
36355
  name: "image",
@@ -37073,11 +36378,7 @@ var imageCommand = {
37073
36378
  chalk2.green(" \u2713 Image captured from clipboard") + chalk2.dim(` (${sizeKB} KB, ${imageData.media_type})`)
37074
36379
  );
37075
36380
  console.log(chalk2.dim(` Prompt: "${prompt}"`));
37076
- pendingImage = {
37077
- data: imageData.data,
37078
- media_type: imageData.media_type,
37079
- prompt
37080
- };
36381
+ setPendingImage(imageData.data, imageData.media_type, prompt);
37081
36382
  return false;
37082
36383
  }
37083
36384
  };
@@ -39183,10 +38484,37 @@ Examples:
39183
38484
  }
39184
38485
  }
39185
38486
  });
38487
+ var TREE_IGNORED_DIRS = /* @__PURE__ */ new Set([
38488
+ "node_modules",
38489
+ "dist",
38490
+ "build",
38491
+ "out",
38492
+ ".next",
38493
+ ".nuxt",
38494
+ ".cache",
38495
+ ".turbo",
38496
+ ".parcel-cache",
38497
+ "coverage",
38498
+ ".nyc_output",
38499
+ "vendor",
38500
+ "__pycache__",
38501
+ ".venv",
38502
+ "venv",
38503
+ "env",
38504
+ "target",
38505
+ ".gradle",
38506
+ ".mvn",
38507
+ "bin",
38508
+ "obj"
38509
+ ]);
38510
+ var MAX_TREE_LINES = 500;
39186
38511
  var treeTool = defineTool({
39187
38512
  name: "tree",
39188
38513
  description: `Display directory structure as a tree.
39189
38514
 
38515
+ Large dependency directories (node_modules, dist, .next, etc.) are excluded
38516
+ automatically. Output is capped at ${MAX_TREE_LINES} lines to keep context lean.
38517
+
39190
38518
  Examples:
39191
38519
  - Current dir: { }
39192
38520
  - Specific dir: { "path": "src" }
@@ -39206,9 +38534,12 @@ Examples:
39206
38534
  let totalFiles = 0;
39207
38535
  let totalDirs = 0;
39208
38536
  const lines = [path36__default.basename(absolutePath) + "/"];
38537
+ let truncated = false;
39209
38538
  async function buildTree(dir, prefix, currentDepth) {
39210
38539
  if (currentDepth > (depth ?? 4)) return;
38540
+ if (lines.length >= MAX_TREE_LINES) return;
39211
38541
  let items = await fs34__default.readdir(dir, { withFileTypes: true });
38542
+ items = items.filter((item) => !TREE_IGNORED_DIRS.has(item.name));
39212
38543
  if (!showHidden) {
39213
38544
  items = items.filter((item) => !item.name.startsWith("."));
39214
38545
  }
@@ -39221,6 +38552,10 @@ Examples:
39221
38552
  return a.name.localeCompare(b.name);
39222
38553
  });
39223
38554
  for (let i = 0; i < items.length; i++) {
38555
+ if (lines.length >= MAX_TREE_LINES) {
38556
+ truncated = true;
38557
+ return;
38558
+ }
39224
38559
  const item = items[i];
39225
38560
  const isLast = i === items.length - 1;
39226
38561
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
@@ -39236,10 +38571,17 @@ Examples:
39236
38571
  }
39237
38572
  }
39238
38573
  await buildTree(absolutePath, "", 1);
38574
+ if (truncated) {
38575
+ lines.push(
38576
+ `
38577
+ [... output truncated at ${MAX_TREE_LINES} lines. Use a deeper path or lower depth to see more.]`
38578
+ );
38579
+ }
39239
38580
  return {
39240
38581
  tree: lines.join("\n"),
39241
38582
  totalFiles,
39242
- totalDirs
38583
+ totalDirs,
38584
+ truncated
39243
38585
  };
39244
38586
  } catch (error) {
39245
38587
  if (isENOENT(error)) {
@@ -41790,7 +41132,7 @@ init_registry4();
41790
41132
  init_errors();
41791
41133
  init_version();
41792
41134
  var DEFAULT_TIMEOUT_MS5 = 3e4;
41793
- var DEFAULT_MAX_LENGTH = 5e4;
41135
+ var DEFAULT_MAX_LENGTH = 8e3;
41794
41136
  var MAX_DOWNLOAD_SIZE = 10 * 1024 * 1024;
41795
41137
  var BLOCKED_SCHEMES = ["file:", "ftp:", "data:", "javascript:"];
41796
41138
  var PRIVATE_IP_PATTERNS = [
@@ -46164,327 +45506,1171 @@ var WorktreeManager = class {
46164
45506
  };
46165
45507
  }
46166
45508
  }
46167
- async mergeViaRebase(worktree, _options) {
46168
- try {
46169
- const { stdout: currentBranch } = await this.git(["rev-parse", "--abbrev-ref", "HEAD"]);
46170
- await this.gitIn(worktree.path, ["rebase", currentBranch.trim()]);
46171
- await this.git(["merge", "--ff-only", worktree.branch]);
46172
- const filesChanged = await this.countChangedFiles(worktree.branch);
46173
- return { success: true, strategy: "rebase", filesChanged };
46174
- } catch (error) {
46175
- return {
46176
- success: false,
46177
- strategy: "rebase",
46178
- error: error instanceof Error ? error.message : String(error)
46179
- };
46180
- }
45509
+ async mergeViaRebase(worktree, _options) {
45510
+ try {
45511
+ const { stdout: currentBranch } = await this.git(["rev-parse", "--abbrev-ref", "HEAD"]);
45512
+ await this.gitIn(worktree.path, ["rebase", currentBranch.trim()]);
45513
+ await this.git(["merge", "--ff-only", worktree.branch]);
45514
+ const filesChanged = await this.countChangedFiles(worktree.branch);
45515
+ return { success: true, strategy: "rebase", filesChanged };
45516
+ } catch (error) {
45517
+ return {
45518
+ success: false,
45519
+ strategy: "rebase",
45520
+ error: error instanceof Error ? error.message : String(error)
45521
+ };
45522
+ }
45523
+ }
45524
+ async mergeViaPR(worktree, options) {
45525
+ try {
45526
+ await this.git(["push", "-u", "origin", worktree.branch]);
45527
+ const title = options.message ?? `Agent: ${worktree.name}`;
45528
+ const { stdout } = await execFileAsync2(
45529
+ "gh",
45530
+ [
45531
+ "pr",
45532
+ "create",
45533
+ "--title",
45534
+ title,
45535
+ "--body",
45536
+ `Automated PR from Coco agent worktree: ${worktree.name}`,
45537
+ "--head",
45538
+ worktree.branch
45539
+ ],
45540
+ { cwd: this.projectRoot }
45541
+ );
45542
+ const prUrl = stdout.trim();
45543
+ return { success: true, strategy: "pr", prUrl };
45544
+ } catch (error) {
45545
+ return {
45546
+ success: false,
45547
+ strategy: "pr",
45548
+ error: error instanceof Error ? error.message : String(error)
45549
+ };
45550
+ }
45551
+ }
45552
+ // ── Helpers ──────────────────────────────────────────────────────
45553
+ async git(args) {
45554
+ return execFileAsync2("git", args, { cwd: this.projectRoot });
45555
+ }
45556
+ async gitIn(cwd, args) {
45557
+ return execFileAsync2("git", args, { cwd });
45558
+ }
45559
+ async countChangedFiles(branch) {
45560
+ try {
45561
+ const { stdout } = await this.git(["diff", "--name-only", `${branch}~1..${branch}`]);
45562
+ return stdout.trim().split("\n").filter(Boolean).length;
45563
+ } catch {
45564
+ return 0;
45565
+ }
45566
+ }
45567
+ };
45568
+
45569
+ // src/cli/repl/best-of-n/orchestrator.ts
45570
+ init_evaluator();
45571
+ init_logger();
45572
+ var DEFAULT_CONFIG5 = {
45573
+ attempts: 3,
45574
+ task: "",
45575
+ autoSelect: true,
45576
+ autoMerge: false,
45577
+ timeoutMs: 5 * 60 * 1e3
45578
+ // 5 minutes
45579
+ };
45580
+ async function runBestOfN(projectRoot, executor, config, callbacks = {}) {
45581
+ const cfg = { ...DEFAULT_CONFIG5, ...config };
45582
+ const logger = getLogger();
45583
+ const startTime = Date.now();
45584
+ if (cfg.attempts < 2) {
45585
+ return {
45586
+ success: false,
45587
+ attempts: [],
45588
+ winner: null,
45589
+ totalDurationMs: 0,
45590
+ error: "Best-of-N requires at least 2 attempts"
45591
+ };
45592
+ }
45593
+ if (cfg.attempts > 10) {
45594
+ return {
45595
+ success: false,
45596
+ attempts: [],
45597
+ winner: null,
45598
+ totalDurationMs: 0,
45599
+ error: "Best-of-N supports at most 10 attempts"
45600
+ };
45601
+ }
45602
+ const worktreeManager = new WorktreeManager(projectRoot);
45603
+ const attempts = [];
45604
+ try {
45605
+ logger.info(`Best-of-N: Creating ${cfg.attempts} worktrees...`);
45606
+ const worktrees = await Promise.all(
45607
+ Array.from(
45608
+ { length: cfg.attempts },
45609
+ (_, i) => worktreeManager.create(`best-of-n-${i + 1}`, {
45610
+ branchPrefix: "coco-best-of-n"
45611
+ })
45612
+ )
45613
+ );
45614
+ for (let i = 0; i < cfg.attempts; i++) {
45615
+ const wt = worktrees[i];
45616
+ attempts.push({
45617
+ id: randomUUID(),
45618
+ index: i + 1,
45619
+ worktreeId: wt.id,
45620
+ worktreePath: wt.path,
45621
+ branch: wt.branch,
45622
+ status: "pending",
45623
+ score: null,
45624
+ output: "",
45625
+ filesChanged: [],
45626
+ durationMs: 0
45627
+ });
45628
+ }
45629
+ logger.info(`Best-of-N: Running ${cfg.attempts} parallel attempts...`);
45630
+ await Promise.all(
45631
+ attempts.map(async (attempt) => {
45632
+ const attemptStart = Date.now();
45633
+ attempt.status = "running";
45634
+ callbacks.onAttemptStart?.(attempt);
45635
+ const abortController = new AbortController();
45636
+ const timeout = setTimeout(() => abortController.abort(), cfg.timeoutMs);
45637
+ try {
45638
+ const result = await executor(attempt.worktreePath, cfg.task, abortController.signal);
45639
+ attempt.output = result.output;
45640
+ attempt.filesChanged = result.filesChanged;
45641
+ attempt.durationMs = Date.now() - attemptStart;
45642
+ attempt.status = "evaluating";
45643
+ callbacks.onEvaluating?.(attempt);
45644
+ try {
45645
+ const evaluator = createQualityEvaluator(attempt.worktreePath);
45646
+ const evaluation = await evaluator.evaluate();
45647
+ attempt.score = evaluation.scores.overall;
45648
+ } catch {
45649
+ attempt.score = 0;
45650
+ }
45651
+ attempt.status = "completed";
45652
+ callbacks.onAttemptComplete?.(attempt);
45653
+ } catch (error) {
45654
+ attempt.durationMs = Date.now() - attemptStart;
45655
+ attempt.status = "failed";
45656
+ attempt.error = error instanceof Error ? error.message : String(error);
45657
+ attempt.score = 0;
45658
+ callbacks.onAttemptFail?.(attempt);
45659
+ } finally {
45660
+ clearTimeout(timeout);
45661
+ }
45662
+ })
45663
+ );
45664
+ const completedAttempts = attempts.filter((a) => a.status === "completed");
45665
+ if (completedAttempts.length === 0) {
45666
+ return {
45667
+ success: false,
45668
+ attempts,
45669
+ winner: null,
45670
+ totalDurationMs: Date.now() - startTime,
45671
+ error: "All attempts failed"
45672
+ };
45673
+ }
45674
+ completedAttempts.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
45675
+ const winner = completedAttempts[0];
45676
+ winner.status = "selected";
45677
+ callbacks.onWinnerSelected?.(winner);
45678
+ for (const attempt of attempts) {
45679
+ if (attempt.id !== winner.id && attempt.status === "completed") {
45680
+ attempt.status = "discarded";
45681
+ }
45682
+ }
45683
+ logger.info(`Best-of-N: Winner is attempt #${winner.index} with score ${winner.score}`);
45684
+ for (const attempt of attempts) {
45685
+ if (attempt.id !== winner.id) {
45686
+ try {
45687
+ await worktreeManager.remove(attempt.worktreeId, true);
45688
+ } catch {
45689
+ }
45690
+ }
45691
+ }
45692
+ if (cfg.autoMerge) {
45693
+ const mergeResult = await worktreeManager.merge(winner.worktreeId, {
45694
+ strategy: "merge",
45695
+ message: `Best-of-N winner (attempt #${winner.index}, score: ${winner.score})`
45696
+ });
45697
+ if (!mergeResult.success) {
45698
+ logger.warn(`Best-of-N: Auto-merge failed: ${mergeResult.error}`);
45699
+ }
45700
+ }
45701
+ return {
45702
+ success: true,
45703
+ attempts,
45704
+ winner,
45705
+ totalDurationMs: Date.now() - startTime
45706
+ };
45707
+ } catch (error) {
45708
+ await worktreeManager.cleanupAll().catch(() => {
45709
+ });
45710
+ return {
45711
+ success: false,
45712
+ attempts,
45713
+ winner: null,
45714
+ totalDurationMs: Date.now() - startTime,
45715
+ error: error instanceof Error ? error.message : String(error)
45716
+ };
45717
+ }
45718
+ }
45719
+ function formatBestOfNResult(result) {
45720
+ const lines = [];
45721
+ lines.push(`
45722
+ ## Best-of-N Results (${result.attempts.length} attempts)
45723
+ `);
45724
+ if (!result.success) {
45725
+ lines.push(`Error: ${result.error}
45726
+ `);
45727
+ return lines.join("\n");
45728
+ }
45729
+ const sorted = [...result.attempts].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
45730
+ for (const attempt of sorted) {
45731
+ const medal = attempt.status === "selected" ? "\u{1F3C6}" : attempt.status === "failed" ? "\u274C" : " ";
45732
+ const score = attempt.score !== null ? `${attempt.score.toFixed(1)}/100` : "N/A";
45733
+ const duration = (attempt.durationMs / 1e3).toFixed(1);
45734
+ const files = attempt.filesChanged.length;
45735
+ lines.push(
45736
+ `${medal} #${attempt.index}: Score ${score} | ${files} files | ${duration}s${attempt.error ? ` (Error: ${attempt.error})` : ""}`
45737
+ );
45738
+ }
45739
+ lines.push(`
45740
+ Total time: ${(result.totalDurationMs / 1e3).toFixed(1)}s`);
45741
+ if (result.winner) {
45742
+ lines.push(
45743
+ `Winner: Attempt #${result.winner.index} (Score: ${result.winner.score?.toFixed(1)})`
45744
+ );
45745
+ }
45746
+ return lines.join("\n");
45747
+ }
45748
+
45749
+ // src/cli/repl/commands/best-of-n.ts
45750
+ function parseArgs6(args) {
45751
+ if (args.length === 0) return null;
45752
+ const attemptsIdx = args.indexOf("--attempts");
45753
+ if (attemptsIdx >= 0 && args[attemptsIdx + 1]) {
45754
+ const n = parseInt(args[attemptsIdx + 1], 10);
45755
+ if (isNaN(n)) return null;
45756
+ const remaining = [...args];
45757
+ remaining.splice(attemptsIdx, 2);
45758
+ return { attempts: n, task: remaining.join(" ") };
45759
+ }
45760
+ const firstNum = parseInt(args[0], 10);
45761
+ if (!isNaN(firstNum)) {
45762
+ return { attempts: firstNum, task: args.slice(1).join(" ") };
45763
+ }
45764
+ return { attempts: 3, task: args.join(" ") };
45765
+ }
45766
+ var bestOfNCommand = {
45767
+ name: "best-of-n",
45768
+ aliases: ["bon"],
45769
+ description: "Run N parallel solution attempts and select the best",
45770
+ usage: "/best-of-n [N] <task description>",
45771
+ async execute(args, session) {
45772
+ const parsed = parseArgs6(args);
45773
+ if (!parsed || !parsed.task) {
45774
+ console.log();
45775
+ console.log(chalk2.yellow(" Usage: /best-of-n [N] <task description>"));
45776
+ console.log(chalk2.dim(" Example: /best-of-n 3 fix the authentication bug"));
45777
+ console.log(chalk2.dim(" Alias: /bon 3 fix the auth bug"));
45778
+ console.log();
45779
+ return false;
45780
+ }
45781
+ console.log();
45782
+ console.log(
45783
+ chalk2.magenta.bold(` Best-of-${parsed.attempts}`) + chalk2.dim(` \u2014 Running ${parsed.attempts} parallel attempts`)
45784
+ );
45785
+ console.log(
45786
+ chalk2.yellow.dim(" \u26A0 Experimental: agent execution in worktrees is a preview feature")
45787
+ );
45788
+ console.log(chalk2.dim(` Task: ${parsed.task}`));
45789
+ console.log();
45790
+ const executor = async (worktreePath, task, _signal) => {
45791
+ return {
45792
+ output: `Executed task "${task}" in ${worktreePath}`,
45793
+ filesChanged: []
45794
+ };
45795
+ };
45796
+ const result = await runBestOfN(
45797
+ session.projectPath,
45798
+ executor,
45799
+ {
45800
+ task: parsed.task,
45801
+ attempts: parsed.attempts
45802
+ },
45803
+ {
45804
+ onAttemptStart: (a) => {
45805
+ console.log(chalk2.dim(` \u25B6 Attempt #${a.index} started...`));
45806
+ },
45807
+ onAttemptComplete: (a) => {
45808
+ console.log(
45809
+ chalk2.green(` \u2713 Attempt #${a.index} completed`) + chalk2.dim(
45810
+ ` (score: ${a.score?.toFixed(1) ?? "?"}, ${(a.durationMs / 1e3).toFixed(1)}s)`
45811
+ )
45812
+ );
45813
+ },
45814
+ onAttemptFail: (a) => {
45815
+ console.log(chalk2.red(` \u2717 Attempt #${a.index} failed: ${a.error}`));
45816
+ },
45817
+ onWinnerSelected: (a) => {
45818
+ console.log();
45819
+ console.log(
45820
+ chalk2.magenta.bold(` \u{1F3C6} Winner: Attempt #${a.index}`) + chalk2.dim(` (score: ${a.score?.toFixed(1)})`)
45821
+ );
45822
+ }
45823
+ }
45824
+ );
45825
+ console.log(formatBestOfNResult(result));
45826
+ console.log();
45827
+ return false;
45828
+ }
45829
+ };
45830
+
45831
+ // src/cli/repl/output/renderer.ts
45832
+ init_syntax();
45833
+ var lineBuffer = "";
45834
+ var rawMarkdownBuffer = "";
45835
+ var inCodeBlock = false;
45836
+ var codeBlockLang = "";
45837
+ var codeBlockLines = [];
45838
+ var inNestedCodeBlock = false;
45839
+ var codeBlockFenceChar = "";
45840
+ var streamingIndicatorActive = false;
45841
+ var streamingIndicatorInterval = null;
45842
+ var streamingIndicatorFrame = 0;
45843
+ var STREAMING_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
45844
+ var getTerminalWidth2 = () => process.stdout.columns || 80;
45845
+ function startStreamingIndicator() {
45846
+ if (streamingIndicatorActive) return;
45847
+ streamingIndicatorActive = true;
45848
+ streamingIndicatorFrame = 0;
45849
+ const frame = STREAMING_FRAMES[0];
45850
+ process.stdout.write(`\r${chalk2.magenta(frame)} ${chalk2.dim("Receiving markdown...")}`);
45851
+ streamingIndicatorInterval = setInterval(() => {
45852
+ streamingIndicatorFrame = (streamingIndicatorFrame + 1) % STREAMING_FRAMES.length;
45853
+ const frame2 = STREAMING_FRAMES[streamingIndicatorFrame];
45854
+ const lines = codeBlockLines.length;
45855
+ const linesText = lines > 0 ? ` (${lines} lines)` : "";
45856
+ process.stdout.write(
45857
+ `\r${chalk2.magenta(frame2)} ${chalk2.dim(`Receiving markdown...${linesText}`)}`
45858
+ );
45859
+ }, 80);
45860
+ }
45861
+ function stopStreamingIndicator() {
45862
+ if (!streamingIndicatorActive) return;
45863
+ streamingIndicatorActive = false;
45864
+ if (streamingIndicatorInterval) {
45865
+ clearInterval(streamingIndicatorInterval);
45866
+ streamingIndicatorInterval = null;
45867
+ }
45868
+ process.stdout.write("\r\x1B[K");
45869
+ }
45870
+ function flushLineBuffer() {
45871
+ if (lineBuffer) {
45872
+ processAndOutputLine(lineBuffer);
45873
+ lineBuffer = "";
45874
+ }
45875
+ if (inCodeBlock && codeBlockLines.length > 0) {
45876
+ stopStreamingIndicator();
45877
+ try {
45878
+ renderCodeBlock(codeBlockLang, codeBlockLines);
45879
+ } finally {
45880
+ stopStreamingIndicator();
45881
+ }
45882
+ inCodeBlock = false;
45883
+ codeBlockFenceChar = "";
45884
+ codeBlockLang = "";
45885
+ codeBlockLines = [];
45886
+ }
45887
+ }
45888
+ function resetLineBuffer() {
45889
+ lineBuffer = "";
45890
+ inCodeBlock = false;
45891
+ inNestedCodeBlock = false;
45892
+ codeBlockFenceChar = "";
45893
+ codeBlockLang = "";
45894
+ codeBlockLines = [];
45895
+ stopStreamingIndicator();
45896
+ resetBlockStore();
45897
+ }
45898
+ function renderStreamChunk(chunk) {
45899
+ if (chunk.type === "text" && chunk.text) {
45900
+ lineBuffer += chunk.text;
45901
+ rawMarkdownBuffer += chunk.text;
45902
+ let newlineIndex;
45903
+ while ((newlineIndex = lineBuffer.indexOf("\n")) !== -1) {
45904
+ const line = lineBuffer.slice(0, newlineIndex);
45905
+ lineBuffer = lineBuffer.slice(newlineIndex + 1);
45906
+ processAndOutputLine(line);
45907
+ }
45908
+ } else if (chunk.type === "done") {
45909
+ flushLineBuffer();
45910
+ }
45911
+ }
45912
+ function processAndOutputLine(line) {
45913
+ line = line.replace(/^(?:\u{200B}|\u{FEFF}|\u{200C}|\u{200D}|\u{2060}|\u{00AD})+/u, "");
45914
+ const tildeFenceMatch = line.match(/^~~~(\w*)$/);
45915
+ if (tildeFenceMatch) {
45916
+ const lang = tildeFenceMatch[1] || "";
45917
+ if (!inCodeBlock) {
45918
+ if (lang) {
45919
+ inCodeBlock = true;
45920
+ inNestedCodeBlock = false;
45921
+ codeBlockFenceChar = "~~~";
45922
+ codeBlockLang = lang;
45923
+ codeBlockLines = [];
45924
+ if (codeBlockLang === "markdown" || codeBlockLang === "md") {
45925
+ startStreamingIndicator();
45926
+ }
45927
+ } else {
45928
+ const formatted = formatMarkdownLine(line);
45929
+ const termWidth = getTerminalWidth2();
45930
+ const wrapped = wrapText(formatted, termWidth);
45931
+ for (const wl of wrapped) {
45932
+ console.log(wl);
45933
+ }
45934
+ }
45935
+ } else if (codeBlockFenceChar === "~~~") {
45936
+ if (lang && !inNestedCodeBlock) {
45937
+ inNestedCodeBlock = true;
45938
+ codeBlockLines.push(line);
45939
+ } else if (!lang && inNestedCodeBlock) {
45940
+ inNestedCodeBlock = false;
45941
+ codeBlockLines.push(line);
45942
+ } else if (!lang && !inNestedCodeBlock) {
45943
+ stopStreamingIndicator();
45944
+ renderCodeBlock(codeBlockLang, codeBlockLines);
45945
+ inCodeBlock = false;
45946
+ inNestedCodeBlock = false;
45947
+ codeBlockFenceChar = "";
45948
+ codeBlockLang = "";
45949
+ codeBlockLines = [];
45950
+ } else {
45951
+ codeBlockLines.push(line);
45952
+ }
45953
+ } else {
45954
+ if (lang && !inNestedCodeBlock) {
45955
+ inNestedCodeBlock = true;
45956
+ codeBlockLines.push(line);
45957
+ } else if (!lang && inNestedCodeBlock) {
45958
+ inNestedCodeBlock = false;
45959
+ codeBlockLines.push(line);
45960
+ } else {
45961
+ codeBlockLines.push(line);
45962
+ }
45963
+ }
45964
+ return;
45965
+ }
45966
+ const codeBlockMatch = line.match(/^(`{3,4})(\w*)$/);
45967
+ if (codeBlockMatch) {
45968
+ const fenceChars = codeBlockMatch[1];
45969
+ const lang = codeBlockMatch[2] || "";
45970
+ if (!inCodeBlock) {
45971
+ inCodeBlock = true;
45972
+ inNestedCodeBlock = false;
45973
+ codeBlockFenceChar = fenceChars;
45974
+ codeBlockLang = lang;
45975
+ codeBlockLines = [];
45976
+ if (codeBlockLang === "markdown" || codeBlockLang === "md") {
45977
+ startStreamingIndicator();
45978
+ }
45979
+ } else if (!lang && inNestedCodeBlock && fenceChars === "```") {
45980
+ inNestedCodeBlock = false;
45981
+ codeBlockLines.push(line);
45982
+ } else if (!inNestedCodeBlock && lang && fenceChars === "```") {
45983
+ inNestedCodeBlock = true;
45984
+ codeBlockLines.push(line);
45985
+ } else if (!lang && !inNestedCodeBlock && codeBlockFenceChar === fenceChars) {
45986
+ stopStreamingIndicator();
45987
+ renderCodeBlock(codeBlockLang, codeBlockLines);
45988
+ inCodeBlock = false;
45989
+ inNestedCodeBlock = false;
45990
+ codeBlockFenceChar = "";
45991
+ codeBlockLang = "";
45992
+ codeBlockLines = [];
45993
+ } else {
45994
+ codeBlockLines.push(line);
45995
+ }
45996
+ return;
45997
+ }
45998
+ if (inCodeBlock) {
45999
+ codeBlockLines.push(line);
46000
+ } else {
46001
+ const formatted = formatMarkdownLine(line);
46002
+ const termWidth = getTerminalWidth2();
46003
+ const wrapped = wrapText(formatted, termWidth);
46004
+ for (const wl of wrapped) {
46005
+ console.log(wl);
46006
+ }
46007
+ }
46008
+ }
46009
+ function renderCodeBlock(lang, lines) {
46010
+ const blockId = storeBlock(lang, lines);
46011
+ if (lang === "markdown" || lang === "md") {
46012
+ renderMarkdownBlock(lines, blockId);
46013
+ return;
46014
+ }
46015
+ renderSimpleCodeBlock(lang, lines, blockId);
46016
+ }
46017
+ function renderMarkdownBlock(lines, blockId) {
46018
+ const width = Math.min(getTerminalWidth2() - 4, 100);
46019
+ const contentWidth = width - 4;
46020
+ const title = "Markdown";
46021
+ const idTag = chalk2.dim(` \xB7 #${blockId}`);
46022
+ console.log(chalk2.magenta("\u256D\u2500\u2500 " + title) + idTag + chalk2.magenta(" \u2500\u2500"));
46023
+ let i = 0;
46024
+ while (i < lines.length) {
46025
+ const line = lines[i];
46026
+ const nestedMatch = line.match(/^(~~~|```)(\w*)$/);
46027
+ if (nestedMatch) {
46028
+ const delimiter = nestedMatch[1];
46029
+ const nestedLang = nestedMatch[2] || "";
46030
+ const nestedLines = [];
46031
+ i++;
46032
+ const closePattern = new RegExp(`^${delimiter}$`);
46033
+ while (i < lines.length && !closePattern.test(lines[i])) {
46034
+ nestedLines.push(lines[i]);
46035
+ i++;
46036
+ }
46037
+ i++;
46038
+ renderNestedCodeBlock(nestedLang, nestedLines, contentWidth);
46039
+ } else if (isTableLine(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
46040
+ const tableLines = [];
46041
+ while (i < lines.length && (isTableLine(lines[i]) || isTableSeparator(lines[i]))) {
46042
+ tableLines.push(lines[i]);
46043
+ i++;
46044
+ }
46045
+ renderNestedTable(tableLines, contentWidth);
46046
+ } else {
46047
+ const formatted = formatMarkdownLine(line);
46048
+ const wrappedLines = wrapText(formatted, contentWidth);
46049
+ for (const wrappedLine of wrappedLines) {
46050
+ console.log(chalk2.magenta("\u2502") + " " + wrappedLine);
46051
+ }
46052
+ i++;
46053
+ }
46054
+ }
46055
+ console.log(chalk2.magenta("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
46056
+ }
46057
+ function isTableLine(line) {
46058
+ const trimmed = line.trim();
46059
+ if (!/^\|.*\|$/.test(trimmed)) return false;
46060
+ if (isTableSeparator(line)) return false;
46061
+ const inner = trimmed.slice(1, -1);
46062
+ return inner.length > 0 && !/^[\s|:-]+$/.test(inner);
46063
+ }
46064
+ function isTableSeparator(line) {
46065
+ const trimmed = line.trim();
46066
+ if (!/^\|.*\|$/.test(trimmed)) return false;
46067
+ const inner = trimmed.slice(1, -1);
46068
+ if (!/^[\s|:-]+$/.test(inner)) return false;
46069
+ return /-{3,}/.test(inner);
46070
+ }
46071
+ function renderNestedTable(lines, parentWidth) {
46072
+ const rows = [];
46073
+ let columnWidths = [];
46074
+ for (const line of lines) {
46075
+ if (isTableSeparator(line)) continue;
46076
+ const cells = line.split("|").slice(1, -1).map((c) => c.trim());
46077
+ rows.push(cells);
46078
+ cells.forEach((cell, idx) => {
46079
+ const cellWidth = cell.length;
46080
+ if (!columnWidths[idx] || cellWidth > columnWidths[idx]) {
46081
+ columnWidths[idx] = cellWidth;
46082
+ }
46083
+ });
46084
+ }
46085
+ if (rows.length === 0) return;
46086
+ const minCellPadding = 2;
46087
+ let totalWidth = columnWidths.reduce((sum, w) => sum + w + minCellPadding, 0) + columnWidths.length + 1;
46088
+ const maxTableWidth = parentWidth - 4;
46089
+ if (totalWidth > maxTableWidth) {
46090
+ const scale = maxTableWidth / totalWidth;
46091
+ columnWidths = columnWidths.map((w) => Math.max(3, Math.floor(w * scale)));
46092
+ }
46093
+ const tableTop = "\u256D" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u256E";
46094
+ const tableMid = "\u251C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524";
46095
+ const tableBot = "\u2570" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u256F";
46096
+ const renderRow = (cells, isHeader) => {
46097
+ const formatted = cells.map((cell, idx) => {
46098
+ const width = columnWidths[idx] || 10;
46099
+ const truncated = cell.length > width ? cell.slice(0, width - 1) + "\u2026" : cell;
46100
+ const padded = truncated.padEnd(width);
46101
+ return isHeader ? chalk2.bold(padded) : padded;
46102
+ });
46103
+ return "\u2502 " + formatted.join(" \u2502 ") + " \u2502";
46104
+ };
46105
+ const outputTableLine = (tableLine) => {
46106
+ console.log(chalk2.magenta("\u2502") + " " + chalk2.cyan(tableLine));
46107
+ };
46108
+ outputTableLine(tableTop);
46109
+ rows.forEach((row, idx) => {
46110
+ outputTableLine(renderRow(row, idx === 0));
46111
+ if (idx === 0 && rows.length > 1) {
46112
+ outputTableLine(tableMid);
46113
+ }
46114
+ });
46115
+ outputTableLine(tableBot);
46116
+ }
46117
+ function renderNestedCodeBlock(lang, lines, parentWidth) {
46118
+ const innerWidth = parentWidth - 4;
46119
+ const title = lang || "code";
46120
+ const isDiff = lang === "diff" || !lang && looksLikeDiff(lines);
46121
+ const innerTopPadding = Math.floor((innerWidth - title.length - 4) / 2);
46122
+ const innerTopRemainder = innerWidth - title.length - 4 - innerTopPadding;
46123
+ console.log(
46124
+ chalk2.magenta("\u2502") + " " + chalk2.cyan(
46125
+ "\u256D" + "\u2500".repeat(Math.max(0, innerTopPadding)) + " " + title + " " + "\u2500".repeat(Math.max(0, innerTopRemainder)) + "\u256E"
46126
+ )
46127
+ );
46128
+ const bgDel = chalk2.bgRgb(80, 20, 20);
46129
+ const bgAdd = chalk2.bgRgb(20, 60, 20);
46130
+ for (const line of lines) {
46131
+ const formatted = formatCodeLine(line, lang);
46132
+ const codeWidth = innerWidth - 4;
46133
+ const wrappedLines = wrapText(formatted, codeWidth);
46134
+ for (const wrappedLine of wrappedLines) {
46135
+ const padding = Math.max(0, codeWidth - stripAnsi2(wrappedLine).length);
46136
+ if (isDiff && isDiffDeletion(line)) {
46137
+ console.log(
46138
+ chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2502") + bgDel(" " + wrappedLine + " ".repeat(padding) + " ") + chalk2.cyan("\u2502")
46139
+ );
46140
+ } else if (isDiff && isDiffAddition(line)) {
46141
+ console.log(
46142
+ chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2502") + bgAdd(" " + wrappedLine + " ".repeat(padding) + " ") + chalk2.cyan("\u2502")
46143
+ );
46144
+ } else {
46145
+ console.log(
46146
+ chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2502") + " " + wrappedLine + " ".repeat(padding) + " " + chalk2.cyan("\u2502")
46147
+ );
46148
+ }
46149
+ }
46150
+ }
46151
+ console.log(chalk2.magenta("\u2502") + " " + chalk2.cyan("\u2570" + "\u2500".repeat(innerWidth - 2) + "\u256F"));
46152
+ }
46153
+ function looksLikeDiff(lines) {
46154
+ const head = lines.slice(0, 5);
46155
+ return head.some((l) => l.startsWith("--- ") || l.startsWith("+++ ") || l.startsWith("@@ "));
46156
+ }
46157
+ function isDiffDeletion(line) {
46158
+ return line.startsWith("-") && !line.startsWith("---");
46159
+ }
46160
+ function isDiffAddition(line) {
46161
+ return line.startsWith("+") && !line.startsWith("+++");
46162
+ }
46163
+ function renderSimpleCodeBlock(lang, lines, blockId) {
46164
+ const width = Math.min(getTerminalWidth2() - 4, 100);
46165
+ const contentWidth = width - 4;
46166
+ const isDiff = lang === "diff" || !lang && looksLikeDiff(lines);
46167
+ const title = lang || "Code";
46168
+ const idSuffix = ` \xB7 #${blockId}`;
46169
+ const titleDisplay = ` ${title}${idSuffix} `;
46170
+ const topPadding = Math.floor((width - titleDisplay.length - 2) / 2);
46171
+ const topRemainder = Math.max(0, width - titleDisplay.length - 2 - topPadding);
46172
+ const titleStyled = ` ${title}` + chalk2.dim(idSuffix) + ` `;
46173
+ console.log(
46174
+ chalk2.magenta("\u256D" + "\u2500".repeat(topPadding) + titleStyled + "\u2500".repeat(topRemainder) + "\u256E")
46175
+ );
46176
+ const bgDel = chalk2.bgRgb(80, 20, 20);
46177
+ const bgAdd = chalk2.bgRgb(20, 60, 20);
46178
+ let oldLineNo = 0;
46179
+ let newLineNo = 0;
46180
+ for (const line of lines) {
46181
+ if (isDiff) {
46182
+ const hunkMatch = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
46183
+ if (hunkMatch) {
46184
+ oldLineNo = parseInt(hunkMatch[1], 10);
46185
+ newLineNo = parseInt(hunkMatch[2], 10);
46186
+ }
46187
+ }
46188
+ const formatted = formatCodeLine(line, lang);
46189
+ const lineNoStr = isDiff ? formatDiffLineNo(line, oldLineNo, newLineNo) : "";
46190
+ const adjustedWidth = isDiff ? contentWidth - 7 : contentWidth;
46191
+ const wrappedLines = wrapText(formatted, adjustedWidth);
46192
+ for (const wrappedLine of wrappedLines) {
46193
+ const fullLine = lineNoStr + wrappedLine;
46194
+ const padding = Math.max(0, contentWidth - stripAnsi2(fullLine).length);
46195
+ if (isDiff && isDiffDeletion(line)) {
46196
+ console.log(
46197
+ chalk2.magenta("\u2502") + bgDel(" " + fullLine + " ".repeat(padding) + " ") + chalk2.magenta("\u2502")
46198
+ );
46199
+ } else if (isDiff && isDiffAddition(line)) {
46200
+ console.log(
46201
+ chalk2.magenta("\u2502") + bgAdd(" " + fullLine + " ".repeat(padding) + " ") + chalk2.magenta("\u2502")
46202
+ );
46203
+ } else {
46204
+ console.log(
46205
+ chalk2.magenta("\u2502") + " " + fullLine + " ".repeat(padding) + " " + chalk2.magenta("\u2502")
46206
+ );
46207
+ }
46208
+ }
46209
+ if (isDiff) {
46210
+ if (isDiffDeletion(line)) {
46211
+ oldLineNo++;
46212
+ } else if (isDiffAddition(line)) {
46213
+ newLineNo++;
46214
+ } else if (!line.startsWith("@@") && !line.startsWith("diff ") && !line.startsWith("index ") && !line.startsWith("---") && !line.startsWith("+++")) {
46215
+ oldLineNo++;
46216
+ newLineNo++;
46217
+ }
46218
+ }
46219
+ }
46220
+ console.log(chalk2.magenta("\u2570" + "\u2500".repeat(width - 2) + "\u256F"));
46221
+ }
46222
+ function formatDiffLineNo(line, oldLineNo, newLineNo) {
46223
+ if (isDiffDeletion(line)) {
46224
+ return chalk2.dim(String(oldLineNo).padStart(5) + " ");
46225
+ } else if (isDiffAddition(line)) {
46226
+ return chalk2.dim(String(newLineNo).padStart(5) + " ");
46227
+ } else if (line.startsWith("@@") || line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++")) {
46228
+ return " ";
46229
+ }
46230
+ return chalk2.dim(String(newLineNo).padStart(5) + " ");
46231
+ }
46232
+ function formatCodeLine(line, lang) {
46233
+ if (lang === "markdown" || lang === "md") {
46234
+ return formatMarkdownLine(line);
46235
+ }
46236
+ return highlightLine(line, lang);
46237
+ }
46238
+ function formatMarkdownLine(line) {
46239
+ if (line.startsWith("# ")) {
46240
+ return chalk2.green.bold(line.slice(2));
46241
+ }
46242
+ if (line.startsWith("## ")) {
46243
+ return chalk2.green.bold(line.slice(3));
46244
+ }
46245
+ if (line.startsWith("### ")) {
46246
+ return chalk2.green.bold(line.slice(4));
46247
+ }
46248
+ if (line.match(/^>\s?/)) {
46249
+ const content = line.replace(/^>\s?/, "");
46250
+ const formatted = formatInlineMarkdown(content);
46251
+ return chalk2.dim("\u258C ") + chalk2.italic(formatted);
46252
+ }
46253
+ if (/^-{3,}$/.test(line) || /^\*{3,}$/.test(line)) {
46254
+ return chalk2.dim("\u2500".repeat(40));
46255
+ }
46256
+ const htmlResult = formatHtmlLine(line);
46257
+ if (htmlResult !== null) {
46258
+ return htmlResult;
46259
+ }
46260
+ if (line.match(/^(\s*)[-*]\s\[x\]\s/i)) {
46261
+ line = line.replace(/^(\s*)[-*]\s\[x\]\s/i, "$1" + chalk2.green("\u2714 "));
46262
+ } else if (line.match(/^(\s*)[-*]\s\[\s?\]\s/)) {
46263
+ line = line.replace(/^(\s*)[-*]\s\[\s?\]\s/, "$1" + chalk2.dim("\u2610 "));
46264
+ }
46265
+ if (line.match(/^(\s*)[-*]\s/)) {
46266
+ line = line.replace(/^(\s*)([-*])\s/, "$1\u2022 ");
46267
+ }
46268
+ if (line.match(/^(\s*)\d+\.\s/)) ;
46269
+ line = formatInlineMarkdown(line);
46270
+ return line;
46271
+ }
46272
+ function formatHtmlLine(line) {
46273
+ const trimmed = line.trim();
46274
+ if (/^<details\s*\/?>$/i.test(trimmed) || /^<details\s+[^>]*>$/i.test(trimmed)) {
46275
+ return chalk2.dim("\u25B6 ") + chalk2.dim.italic("details");
46276
+ }
46277
+ if (/^<\/details>$/i.test(trimmed)) {
46278
+ return chalk2.dim(" \u25C0 end details");
46279
+ }
46280
+ const summaryInlineMatch = trimmed.match(/^<summary>(.*?)<\/summary>$/i);
46281
+ if (summaryInlineMatch) {
46282
+ const content = summaryInlineMatch[1] || "";
46283
+ return chalk2.dim("\u25B6 ") + chalk2.bold(formatInlineMarkdown(content));
46284
+ }
46285
+ if (/^<summary\s*\/?>$/i.test(trimmed) || /^<summary\s+[^>]*>$/i.test(trimmed)) {
46286
+ return chalk2.dim("\u25B6 ") + chalk2.dim.italic("summary:");
46287
+ }
46288
+ if (/^<\/summary>$/i.test(trimmed)) {
46289
+ return "";
46290
+ }
46291
+ if (/^<br\s*\/?>$/i.test(trimmed)) {
46292
+ return "";
46293
+ }
46294
+ if (/^<hr\s*\/?>$/i.test(trimmed)) {
46295
+ return chalk2.dim("\u2500".repeat(40));
46296
+ }
46297
+ const headingMatch = trimmed.match(/^<h([1-6])>(.*?)<\/h\1>$/i);
46298
+ if (headingMatch) {
46299
+ const content = headingMatch[2] || "";
46300
+ return chalk2.green.bold(formatInlineMarkdown(content));
46301
+ }
46302
+ const pMatch = trimmed.match(/^<p>(.*?)<\/p>$/i);
46303
+ if (pMatch) {
46304
+ return formatInlineMarkdown(pMatch[1] || "");
46305
+ }
46306
+ if (/^<\/?p>$/i.test(trimmed)) {
46307
+ return "";
46308
+ }
46309
+ const boldMatch = trimmed.match(/^<(?:strong|b)>(.*?)<\/(?:strong|b)>$/i);
46310
+ if (boldMatch) {
46311
+ return chalk2.bold(formatInlineMarkdown(boldMatch[1] || ""));
46312
+ }
46313
+ const italicMatch = trimmed.match(/^<(?:em|i)>(.*?)<\/(?:em|i)>$/i);
46314
+ if (italicMatch) {
46315
+ return chalk2.italic(formatInlineMarkdown(italicMatch[1] || ""));
46316
+ }
46317
+ const codeMatch = trimmed.match(/^<code>(.*?)<\/code>$/i);
46318
+ if (codeMatch) {
46319
+ return chalk2.cyan(codeMatch[1] || "");
46320
+ }
46321
+ if (/^<blockquote\s*>$/i.test(trimmed)) {
46322
+ return chalk2.dim("\u258C ");
46323
+ }
46324
+ if (/^<\/blockquote>$/i.test(trimmed)) {
46325
+ return "";
46326
+ }
46327
+ if (/^<\/?[uo]l\s*>$/i.test(trimmed)) {
46328
+ return "";
46329
+ }
46330
+ const liMatch = trimmed.match(/^<li>(.*?)<\/li>$/i);
46331
+ if (liMatch) {
46332
+ return "\u2022 " + formatInlineMarkdown(liMatch[1] || "");
46333
+ }
46334
+ if (/^<li\s*>$/i.test(trimmed)) {
46335
+ return "\u2022 ";
46336
+ }
46337
+ if (/^<\/li>$/i.test(trimmed)) {
46338
+ return "";
46339
+ }
46340
+ if (/^<\/?div\s*[^>]*>$/i.test(trimmed)) {
46341
+ return "";
46342
+ }
46343
+ const spanMatch = trimmed.match(/^<span[^>]*>(.*?)<\/span>$/i);
46344
+ if (spanMatch) {
46345
+ return formatInlineMarkdown(spanMatch[1] || "");
46346
+ }
46347
+ const imgMatch = trimmed.match(/^<img\s[^>]*alt=["']([^"']*)["'][^>]*\/?>$/i);
46348
+ if (imgMatch) {
46349
+ return chalk2.dim("[image: ") + chalk2.italic(imgMatch[1] || "") + chalk2.dim("]");
46350
+ }
46351
+ const aMatch = trimmed.match(/^<a\s[^>]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>$/i);
46352
+ if (aMatch) {
46353
+ return chalk2.blue.underline(aMatch[2] || aMatch[1] || "");
46354
+ }
46355
+ if (/^<\/?[a-z][a-z0-9]*(\s[^>]*)?\s*\/?>$/i.test(trimmed)) {
46356
+ return chalk2.dim(trimmed);
46357
+ }
46358
+ if (/<[a-z][a-z0-9]*(\s[^>]*)?\s*\/?>/i.test(trimmed) && /<\/[a-z][a-z0-9]*>/i.test(trimmed)) {
46359
+ let stripped = trimmed;
46360
+ stripped = stripped.replace(/<br\s*\/?>/gi, " ").replace(/<\/?(?:strong|b)>/gi, "**").replace(/<\/?(?:em|i)>/gi, "*").replace(/<\/?code>/gi, "`").replace(/<a\s[^>]*href=["']([^"']*)["'][^>]*>/gi, "").replace(/<\/a>/gi, "");
46361
+ let prevStripped = "";
46362
+ while (prevStripped !== stripped) {
46363
+ prevStripped = stripped;
46364
+ stripped = stripped.replace(/<[^>]*>/g, "");
46365
+ }
46366
+ stripped = stripped.replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#39;/g, "'").replace(/&amp;/g, "&");
46367
+ return formatInlineMarkdown(stripped);
46368
+ }
46369
+ return null;
46370
+ }
46371
+ function formatInlineMarkdown(text13) {
46372
+ text13 = text13.replace(/\*\*\*(.+?)\*\*\*/g, (_, content) => chalk2.bold.italic(content));
46373
+ text13 = text13.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk2.bold(content));
46374
+ text13 = text13.replace(/\*([^*]+)\*/g, (_, content) => chalk2.italic(content));
46375
+ text13 = text13.replace(/_([^_]+)_/g, (_, content) => chalk2.italic(content));
46376
+ text13 = text13.replace(/`([^`]+)`/g, (_, content) => chalk2.cyan(content));
46377
+ text13 = text13.replace(/~~(.+?)~~/g, (_, content) => chalk2.strikethrough(content));
46378
+ text13 = text13.replace(/\[([^\]]+)\]\([^)]+\)/g, (_, linkText) => chalk2.blue.underline(linkText));
46379
+ return text13;
46380
+ }
46381
+ function wrapText(text13, maxWidth) {
46382
+ if (maxWidth <= 0) return [text13];
46383
+ const plainText = stripAnsi2(text13);
46384
+ if (plainText.length <= maxWidth) {
46385
+ return [text13];
46386
+ }
46387
+ const lines = [];
46388
+ let remaining = text13;
46389
+ while (true) {
46390
+ const plain = stripAnsi2(remaining);
46391
+ if (plain.length <= maxWidth) break;
46392
+ let breakPoint = maxWidth;
46393
+ const lastSpace = plain.lastIndexOf(" ", maxWidth);
46394
+ if (lastSpace > maxWidth * 0.5) {
46395
+ breakPoint = lastSpace;
46396
+ }
46397
+ const ansiRegex = /\x1b\[[0-9;]*m/g;
46398
+ let match;
46399
+ const ansiPositions = [];
46400
+ ansiRegex.lastIndex = 0;
46401
+ while ((match = ansiRegex.exec(remaining)) !== null) {
46402
+ ansiPositions.push({ start: match.index, end: match.index + match[0].length });
46403
+ }
46404
+ let rawPos = 0;
46405
+ let visualPos = 0;
46406
+ let ansiIdx = 0;
46407
+ while (visualPos < breakPoint && rawPos < remaining.length) {
46408
+ while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
46409
+ rawPos = ansiPositions[ansiIdx].end;
46410
+ ansiIdx++;
46411
+ }
46412
+ if (rawPos >= remaining.length) break;
46413
+ rawPos++;
46414
+ visualPos++;
46415
+ }
46416
+ while (ansiIdx < ansiPositions.length && ansiPositions[ansiIdx].start === rawPos) {
46417
+ rawPos = ansiPositions[ansiIdx].end;
46418
+ ansiIdx++;
46419
+ }
46420
+ lines.push(remaining.slice(0, rawPos) + "\x1B[0m");
46421
+ remaining = "\x1B[0m" + remaining.slice(rawPos).trimStart();
46422
+ }
46423
+ if (remaining) {
46424
+ lines.push(remaining);
46425
+ }
46426
+ return lines.length > 0 ? lines : [text13];
46427
+ }
46428
+ function stripAnsi2(str) {
46429
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
46430
+ }
46431
+ var TOOL_ICONS = {
46432
+ read_file: "\u{1F4C4}",
46433
+ write_file_create: "\u{1F4DD}+",
46434
+ write_file_modify: "\u270F\uFE0F",
46435
+ edit_file: "\u270F\uFE0F",
46436
+ delete_file: "\u{1F5D1}\uFE0F",
46437
+ list_directory: "\u{1F4C1}",
46438
+ list_dir: "\u{1F4C1}",
46439
+ search_files: "\u{1F50D}",
46440
+ grep: "\u{1F50D}",
46441
+ bash_exec: "\u26A1",
46442
+ web_search: "\u{1F310}",
46443
+ git_status: "\u{1F4CA}",
46444
+ git_commit: "\u{1F4BE}",
46445
+ git_push: "\u2B06\uFE0F",
46446
+ git_pull: "\u2B07\uFE0F",
46447
+ run_tests: "\u{1F9EA}",
46448
+ run_linter: "\u{1F50E}",
46449
+ default: "\u{1F527}"
46450
+ };
46451
+ function getToolIcon(toolName, input) {
46452
+ if (toolName === "write_file" && input) {
46453
+ const wouldCreate = input.wouldCreate === true;
46454
+ return wouldCreate ? TOOL_ICONS.write_file_create : TOOL_ICONS.write_file_modify;
46455
+ }
46456
+ return TOOL_ICONS[toolName] ?? "\u{1F527}";
46457
+ }
46458
+ function renderToolStart(toolName, input, metadata) {
46459
+ const icon = getToolIcon(toolName, { ...input, wouldCreate: metadata?.isCreate });
46460
+ const summary = formatToolSummary(toolName, input);
46461
+ if (toolName === "write_file") {
46462
+ const label = chalk2.yellow.bold("MODIFY") + " " + chalk2.cyan(String(input.path || ""));
46463
+ console.log(`
46464
+ ${icon} ${label}`);
46465
+ const preview = renderContentPreview(String(input.content || ""), 3);
46466
+ if (preview) console.log(preview);
46467
+ return;
46468
+ }
46469
+ if (toolName === "edit_file") {
46470
+ console.log(`
46471
+ ${icon} ${chalk2.yellow.bold("EDIT")} ${chalk2.cyan(String(input.path || ""))}`);
46472
+ const editPreview = renderEditPreview(
46473
+ String(input.old_string || ""),
46474
+ String(input.new_string || "")
46475
+ );
46476
+ if (editPreview) console.log(editPreview);
46477
+ return;
46181
46478
  }
46182
- async mergeViaPR(worktree, options) {
46183
- try {
46184
- await this.git(["push", "-u", "origin", worktree.branch]);
46185
- const title = options.message ?? `Agent: ${worktree.name}`;
46186
- const { stdout } = await execFileAsync2(
46187
- "gh",
46188
- [
46189
- "pr",
46190
- "create",
46191
- "--title",
46192
- title,
46193
- "--body",
46194
- `Automated PR from Coco agent worktree: ${worktree.name}`,
46195
- "--head",
46196
- worktree.branch
46197
- ],
46198
- { cwd: this.projectRoot }
46199
- );
46200
- const prUrl = stdout.trim();
46201
- return { success: true, strategy: "pr", prUrl };
46202
- } catch (error) {
46203
- return {
46204
- success: false,
46205
- strategy: "pr",
46206
- error: error instanceof Error ? error.message : String(error)
46207
- };
46208
- }
46479
+ console.log(`
46480
+ ${icon} ${chalk2.cyan.bold(toolName)} ${chalk2.dim(summary)}`);
46481
+ }
46482
+ function renderContentPreview(content, maxLines) {
46483
+ const maxWidth = Math.max(getTerminalWidth2() - 6, 40);
46484
+ const lines = content.split("\n");
46485
+ const preview = [];
46486
+ for (const line of lines) {
46487
+ if (preview.length >= maxLines) break;
46488
+ const trimmed = line.trimEnd();
46489
+ if (trimmed.length === 0 && preview.length === 0) continue;
46490
+ const truncated = trimmed.length > maxWidth ? trimmed.slice(0, maxWidth - 1) + "\u2026" : trimmed;
46491
+ preview.push(` ${truncated}`);
46209
46492
  }
46210
- // ── Helpers ──────────────────────────────────────────────────────
46211
- async git(args) {
46212
- return execFileAsync2("git", args, { cwd: this.projectRoot });
46493
+ if (preview.length === 0) return "";
46494
+ const totalNonEmpty = lines.filter((l) => l.trim().length > 0).length;
46495
+ const more = totalNonEmpty > maxLines ? chalk2.dim(` \u2026 +${totalNonEmpty - maxLines} lines`) : "";
46496
+ return chalk2.dim(preview.join("\n")) + more;
46497
+ }
46498
+ function renderEditPreview(oldStr, newStr) {
46499
+ const maxWidth = Math.max(getTerminalWidth2() - 8, 30);
46500
+ const MAX_PREVIEW_LINES = 8;
46501
+ const bgDel = chalk2.bgRgb(80, 20, 20);
46502
+ const bgAdd = chalk2.bgRgb(20, 60, 20);
46503
+ const oldLines = oldStr.split("\n").filter((l) => l.trim().length > 0);
46504
+ const newLines = newStr.split("\n").filter((l) => l.trim().length > 0);
46505
+ if (oldLines.length === 0 && newLines.length === 0) return "";
46506
+ const truncate2 = (s) => s.length > maxWidth ? s.slice(0, maxWidth - 1) + "\u2026" : s;
46507
+ const result = [];
46508
+ let shown = 0;
46509
+ for (const line of oldLines) {
46510
+ if (shown >= MAX_PREVIEW_LINES) break;
46511
+ const text13 = `- ${truncate2(line.trim())}`;
46512
+ const pad = Math.max(0, maxWidth - text13.length);
46513
+ result.push(" " + bgDel(text13 + " ".repeat(pad)));
46514
+ shown++;
46213
46515
  }
46214
- async gitIn(cwd, args) {
46215
- return execFileAsync2("git", args, { cwd });
46516
+ for (const line of newLines) {
46517
+ if (shown >= MAX_PREVIEW_LINES) break;
46518
+ const text13 = `+ ${truncate2(line.trim())}`;
46519
+ const pad = Math.max(0, maxWidth - text13.length);
46520
+ result.push(" " + bgAdd(text13 + " ".repeat(pad)));
46521
+ shown++;
46216
46522
  }
46217
- async countChangedFiles(branch) {
46218
- try {
46219
- const { stdout } = await this.git(["diff", "--name-only", `${branch}~1..${branch}`]);
46220
- return stdout.trim().split("\n").filter(Boolean).length;
46221
- } catch {
46222
- return 0;
46223
- }
46523
+ const total = oldLines.length + newLines.length;
46524
+ if (total > MAX_PREVIEW_LINES) {
46525
+ result.push(chalk2.dim(` \u2026 +${total - MAX_PREVIEW_LINES} more lines`));
46224
46526
  }
46225
- };
46226
-
46227
- // src/cli/repl/best-of-n/orchestrator.ts
46228
- init_evaluator();
46229
- init_logger();
46230
- var DEFAULT_CONFIG5 = {
46231
- attempts: 3,
46232
- task: "",
46233
- autoSelect: true,
46234
- autoMerge: false,
46235
- timeoutMs: 5 * 60 * 1e3
46236
- // 5 minutes
46237
- };
46238
- async function runBestOfN(projectRoot, executor, config, callbacks = {}) {
46239
- const cfg = { ...DEFAULT_CONFIG5, ...config };
46240
- const logger = getLogger();
46241
- const startTime = Date.now();
46242
- if (cfg.attempts < 2) {
46243
- return {
46244
- success: false,
46245
- attempts: [],
46246
- winner: null,
46247
- totalDurationMs: 0,
46248
- error: "Best-of-N requires at least 2 attempts"
46249
- };
46527
+ return result.join("\n");
46528
+ }
46529
+ function renderToolEnd(result) {
46530
+ const status = result.result.success ? chalk2.green("\u2713") : chalk2.red("\u2717");
46531
+ const duration = chalk2.dim(`${result.duration.toFixed(0)}ms`);
46532
+ const preview = formatResultPreview(result);
46533
+ console.log(` ${status} ${duration}${preview ? ` ${preview}` : ""}`);
46534
+ if (!result.result.success && result.result.error) {
46535
+ console.log(chalk2.red(` \u2514\u2500 ${result.result.error}`));
46250
46536
  }
46251
- if (cfg.attempts > 10) {
46252
- return {
46253
- success: false,
46254
- attempts: [],
46255
- winner: null,
46256
- totalDurationMs: 0,
46257
- error: "Best-of-N supports at most 10 attempts"
46258
- };
46537
+ const details = formatResultDetails(result);
46538
+ if (details) console.log(details);
46539
+ }
46540
+ function formatToolSummary(toolName, input) {
46541
+ switch (toolName) {
46542
+ case "read_file":
46543
+ case "write_file":
46544
+ case "edit_file":
46545
+ case "delete_file":
46546
+ return String(input.path || "");
46547
+ case "list_directory":
46548
+ return String(input.path || ".");
46549
+ case "grep":
46550
+ case "search_files": {
46551
+ const pattern = String(input.pattern || "");
46552
+ const path57 = input.path ? ` in ${input.path}` : "";
46553
+ return `"${pattern}"${path57}`;
46554
+ }
46555
+ case "bash_exec": {
46556
+ const cmd = String(input.command || "");
46557
+ const max = Math.max(getTerminalWidth2() - 20, 50);
46558
+ return cmd.length > max ? cmd.slice(0, max - 1) + "\u2026" : cmd;
46559
+ }
46560
+ default:
46561
+ return formatToolInput(input);
46259
46562
  }
46260
- const worktreeManager = new WorktreeManager(projectRoot);
46261
- const attempts = [];
46563
+ }
46564
+ function formatResultPreview(result) {
46565
+ if (!result.result.success) return "";
46566
+ const { name, result: toolResult } = result;
46262
46567
  try {
46263
- logger.info(`Best-of-N: Creating ${cfg.attempts} worktrees...`);
46264
- const worktrees = await Promise.all(
46265
- Array.from(
46266
- { length: cfg.attempts },
46267
- (_, i) => worktreeManager.create(`best-of-n-${i + 1}`, {
46268
- branchPrefix: "coco-best-of-n"
46269
- })
46270
- )
46271
- );
46272
- for (let i = 0; i < cfg.attempts; i++) {
46273
- const wt = worktrees[i];
46274
- attempts.push({
46275
- id: randomUUID(),
46276
- index: i + 1,
46277
- worktreeId: wt.id,
46278
- worktreePath: wt.path,
46279
- branch: wt.branch,
46280
- status: "pending",
46281
- score: null,
46282
- output: "",
46283
- filesChanged: [],
46284
- durationMs: 0
46285
- });
46286
- }
46287
- logger.info(`Best-of-N: Running ${cfg.attempts} parallel attempts...`);
46288
- await Promise.all(
46289
- attempts.map(async (attempt) => {
46290
- const attemptStart = Date.now();
46291
- attempt.status = "running";
46292
- callbacks.onAttemptStart?.(attempt);
46293
- const abortController = new AbortController();
46294
- const timeout = setTimeout(() => abortController.abort(), cfg.timeoutMs);
46295
- try {
46296
- const result = await executor(attempt.worktreePath, cfg.task, abortController.signal);
46297
- attempt.output = result.output;
46298
- attempt.filesChanged = result.filesChanged;
46299
- attempt.durationMs = Date.now() - attemptStart;
46300
- attempt.status = "evaluating";
46301
- callbacks.onEvaluating?.(attempt);
46302
- try {
46303
- const evaluator = createQualityEvaluator(attempt.worktreePath);
46304
- const evaluation = await evaluator.evaluate();
46305
- attempt.score = evaluation.scores.overall;
46306
- } catch {
46307
- attempt.score = 0;
46308
- }
46309
- attempt.status = "completed";
46310
- callbacks.onAttemptComplete?.(attempt);
46311
- } catch (error) {
46312
- attempt.durationMs = Date.now() - attemptStart;
46313
- attempt.status = "failed";
46314
- attempt.error = error instanceof Error ? error.message : String(error);
46315
- attempt.score = 0;
46316
- callbacks.onAttemptFail?.(attempt);
46317
- } finally {
46318
- clearTimeout(timeout);
46568
+ const data = JSON.parse(toolResult.output);
46569
+ switch (name) {
46570
+ case "read_file":
46571
+ if (data.lines !== void 0) {
46572
+ return chalk2.dim(`(${data.lines} lines)`);
46319
46573
  }
46320
- })
46321
- );
46322
- const completedAttempts = attempts.filter((a) => a.status === "completed");
46323
- if (completedAttempts.length === 0) {
46324
- return {
46325
- success: false,
46326
- attempts,
46327
- winner: null,
46328
- totalDurationMs: Date.now() - startTime,
46329
- error: "All attempts failed"
46330
- };
46331
- }
46332
- completedAttempts.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
46333
- const winner = completedAttempts[0];
46334
- winner.status = "selected";
46335
- callbacks.onWinnerSelected?.(winner);
46336
- for (const attempt of attempts) {
46337
- if (attempt.id !== winner.id && attempt.status === "completed") {
46338
- attempt.status = "discarded";
46339
- }
46340
- }
46341
- logger.info(`Best-of-N: Winner is attempt #${winner.index} with score ${winner.score}`);
46342
- for (const attempt of attempts) {
46343
- if (attempt.id !== winner.id) {
46344
- try {
46345
- await worktreeManager.remove(attempt.worktreeId, true);
46346
- } catch {
46574
+ break;
46575
+ case "list_directory":
46576
+ if (Array.isArray(data.entries)) {
46577
+ const dirs = data.entries.filter((e) => e.type === "directory").length;
46578
+ const files = data.entries.length - dirs;
46579
+ return chalk2.dim(`(${files} files, ${dirs} dirs)`);
46580
+ }
46581
+ break;
46582
+ case "grep":
46583
+ case "search_files":
46584
+ if (Array.isArray(data.matches)) {
46585
+ const n = data.matches.length;
46586
+ return n === 0 ? chalk2.yellow("\xB7 no matches") : chalk2.dim(`\xB7 ${n} match${n === 1 ? "" : "es"}`);
46587
+ }
46588
+ break;
46589
+ case "bash_exec":
46590
+ if (data.exitCode !== void 0 && data.exitCode !== 0) {
46591
+ return chalk2.red(`(exit ${data.exitCode})`);
46347
46592
  }
46593
+ break;
46594
+ case "write_file":
46595
+ case "edit_file":
46596
+ return chalk2.dim("(saved)");
46597
+ }
46598
+ } catch {
46599
+ }
46600
+ return "";
46601
+ }
46602
+ function formatResultDetails(result) {
46603
+ if (!result.result.success) return "";
46604
+ const { name, result: toolResult } = result;
46605
+ const maxWidth = Math.max(getTerminalWidth2() - 8, 40);
46606
+ try {
46607
+ const data = JSON.parse(toolResult.output);
46608
+ if ((name === "grep" || name === "search_files") && Array.isArray(data.matches)) {
46609
+ const matches = data.matches;
46610
+ if (matches.length === 0) return "";
46611
+ const MAX_SHOWN = 3;
46612
+ const shown = matches.slice(0, MAX_SHOWN);
46613
+ const lines = shown.map(({ file, line, content }) => {
46614
+ const location = chalk2.cyan(`${file}:${line}`);
46615
+ const snippet = content.trim();
46616
+ const truncated = snippet.length > maxWidth ? snippet.slice(0, maxWidth - 1) + "\u2026" : snippet;
46617
+ return ` ${chalk2.dim("\u2502")} ${location} ${chalk2.dim(truncated)}`;
46618
+ });
46619
+ if (matches.length > MAX_SHOWN) {
46620
+ lines.push(` ${chalk2.dim(`\u2502 \u2026 +${matches.length - MAX_SHOWN} more`)}`);
46348
46621
  }
46622
+ return lines.join("\n");
46349
46623
  }
46350
- if (cfg.autoMerge) {
46351
- const mergeResult = await worktreeManager.merge(winner.worktreeId, {
46352
- strategy: "merge",
46353
- message: `Best-of-N winner (attempt #${winner.index}, score: ${winner.score})`
46624
+ if (name === "bash_exec" && data.exitCode === 0) {
46625
+ const stdout = String(data.stdout || "").trimEnd();
46626
+ if (!stdout) return "";
46627
+ const outputLines = stdout.split("\n").filter((l) => l.trim());
46628
+ if (outputLines.length > 6) return "";
46629
+ const shown = outputLines.slice(0, 4);
46630
+ const lines = shown.map((l) => {
46631
+ const truncated = l.length > maxWidth ? l.slice(0, maxWidth - 1) + "\u2026" : l;
46632
+ return ` ${chalk2.dim("\u2502")} ${chalk2.dim(truncated)}`;
46354
46633
  });
46355
- if (!mergeResult.success) {
46356
- logger.warn(`Best-of-N: Auto-merge failed: ${mergeResult.error}`);
46634
+ if (outputLines.length > 4) {
46635
+ lines.push(` ${chalk2.dim(`\u2502 \u2026 +${outputLines.length - 4} more`)}`);
46357
46636
  }
46637
+ return lines.join("\n");
46358
46638
  }
46359
- return {
46360
- success: true,
46361
- attempts,
46362
- winner,
46363
- totalDurationMs: Date.now() - startTime
46364
- };
46365
- } catch (error) {
46366
- await worktreeManager.cleanupAll().catch(() => {
46367
- });
46368
- return {
46369
- success: false,
46370
- attempts,
46371
- winner: null,
46372
- totalDurationMs: Date.now() - startTime,
46373
- error: error instanceof Error ? error.message : String(error)
46374
- };
46639
+ } catch {
46375
46640
  }
46641
+ return "";
46376
46642
  }
46377
- function formatBestOfNResult(result) {
46378
- const lines = [];
46379
- lines.push(`
46380
- ## Best-of-N Results (${result.attempts.length} attempts)
46381
- `);
46382
- if (!result.success) {
46383
- lines.push(`Error: ${result.error}
46384
- `);
46385
- return lines.join("\n");
46386
- }
46387
- const sorted = [...result.attempts].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
46388
- for (const attempt of sorted) {
46389
- const medal = attempt.status === "selected" ? "\u{1F3C6}" : attempt.status === "failed" ? "\u274C" : " ";
46390
- const score = attempt.score !== null ? `${attempt.score.toFixed(1)}/100` : "N/A";
46391
- const duration = (attempt.durationMs / 1e3).toFixed(1);
46392
- const files = attempt.filesChanged.length;
46393
- lines.push(
46394
- `${medal} #${attempt.index}: Score ${score} | ${files} files | ${duration}s${attempt.error ? ` (Error: ${attempt.error})` : ""}`
46395
- );
46396
- }
46397
- lines.push(`
46398
- Total time: ${(result.totalDurationMs / 1e3).toFixed(1)}s`);
46399
- if (result.winner) {
46400
- lines.push(
46401
- `Winner: Attempt #${result.winner.index} (Score: ${result.winner.score?.toFixed(1)})`
46402
- );
46643
+ function formatToolInput(input) {
46644
+ const entries = Object.entries(input);
46645
+ if (entries.length === 0) return "";
46646
+ const parts = entries.slice(0, 3).map(([key, value]) => {
46647
+ let str;
46648
+ if (typeof value === "string") {
46649
+ str = value;
46650
+ } else if (value === void 0 || value === null) {
46651
+ str = String(value);
46652
+ } else {
46653
+ str = JSON.stringify(value);
46654
+ }
46655
+ const truncated = str.length > 40 ? str.slice(0, 37) + "..." : str;
46656
+ return `${key}=${truncated}`;
46657
+ });
46658
+ if (entries.length > 3) {
46659
+ parts.push(`+${entries.length - 3} more`);
46403
46660
  }
46404
- return lines.join("\n");
46661
+ return parts.join(", ");
46405
46662
  }
46406
-
46407
- // src/cli/repl/commands/best-of-n.ts
46408
- function parseArgs6(args) {
46409
- if (args.length === 0) return null;
46410
- const attemptsIdx = args.indexOf("--attempts");
46411
- if (attemptsIdx >= 0 && args[attemptsIdx + 1]) {
46412
- const n = parseInt(args[attemptsIdx + 1], 10);
46413
- if (isNaN(n)) return null;
46414
- const remaining = [...args];
46415
- remaining.splice(attemptsIdx, 2);
46416
- return { attempts: n, task: remaining.join(" ") };
46417
- }
46418
- const firstNum = parseInt(args[0], 10);
46419
- if (!isNaN(firstNum)) {
46420
- return { attempts: firstNum, task: args.slice(1).join(" ") };
46421
- }
46422
- return { attempts: 3, task: args.join(" ") };
46663
+ function renderUsageStats(inputTokens, outputTokens, toolCallCount) {
46664
+ const totalTokens = inputTokens + outputTokens;
46665
+ const toolsStr = toolCallCount > 0 ? ` \xB7 ${toolCallCount} tools` : "";
46666
+ console.log(chalk2.dim(`\u2500 ${totalTokens.toLocaleString("en-US")} tokens${toolsStr}`));
46667
+ }
46668
+ function renderError(message) {
46669
+ console.error(chalk2.red(`\u2717 Error: ${message}`));
46670
+ }
46671
+ function renderInfo(message) {
46672
+ console.log(chalk2.dim(message));
46423
46673
  }
46424
- var bestOfNCommand = {
46425
- name: "best-of-n",
46426
- aliases: ["bon"],
46427
- description: "Run N parallel solution attempts and select the best",
46428
- usage: "/best-of-n [N] <task description>",
46429
- async execute(args, session) {
46430
- const parsed = parseArgs6(args);
46431
- if (!parsed || !parsed.task) {
46432
- console.log();
46433
- console.log(chalk2.yellow(" Usage: /best-of-n [N] <task description>"));
46434
- console.log(chalk2.dim(" Example: /best-of-n 3 fix the authentication bug"));
46435
- console.log(chalk2.dim(" Alias: /bon 3 fix the auth bug"));
46436
- console.log();
46437
- return false;
46438
- }
46439
- console.log();
46440
- console.log(
46441
- chalk2.magenta.bold(` Best-of-${parsed.attempts}`) + chalk2.dim(` \u2014 Running ${parsed.attempts} parallel attempts`)
46442
- );
46443
- console.log(
46444
- chalk2.yellow.dim(" \u26A0 Experimental: agent execution in worktrees is a preview feature")
46445
- );
46446
- console.log(chalk2.dim(` Task: ${parsed.task}`));
46447
- console.log();
46448
- const executor = async (worktreePath, task, _signal) => {
46449
- return {
46450
- output: `Executed task "${task}" in ${worktreePath}`,
46451
- filesChanged: []
46452
- };
46453
- };
46454
- const result = await runBestOfN(
46455
- session.projectPath,
46456
- executor,
46457
- {
46458
- task: parsed.task,
46459
- attempts: parsed.attempts
46460
- },
46461
- {
46462
- onAttemptStart: (a) => {
46463
- console.log(chalk2.dim(` \u25B6 Attempt #${a.index} started...`));
46464
- },
46465
- onAttemptComplete: (a) => {
46466
- console.log(
46467
- chalk2.green(` \u2713 Attempt #${a.index} completed`) + chalk2.dim(
46468
- ` (score: ${a.score?.toFixed(1) ?? "?"}, ${(a.durationMs / 1e3).toFixed(1)}s)`
46469
- )
46470
- );
46471
- },
46472
- onAttemptFail: (a) => {
46473
- console.log(chalk2.red(` \u2717 Attempt #${a.index} failed: ${a.error}`));
46474
- },
46475
- onWinnerSelected: (a) => {
46476
- console.log();
46477
- console.log(
46478
- chalk2.magenta.bold(` \u{1F3C6} Winner: Attempt #${a.index}`) + chalk2.dim(` (score: ${a.score?.toFixed(1)})`)
46479
- );
46480
- }
46481
- }
46482
- );
46483
- console.log(formatBestOfNResult(result));
46484
- console.log();
46485
- return false;
46486
- }
46487
- };
46488
46674
 
46489
46675
  // src/cli/repl/commands/index.ts
46490
46676
  init_skills2();
@@ -46598,6 +46784,14 @@ function getAllCommands() {
46598
46784
 
46599
46785
  // src/cli/repl/input/handler.ts
46600
46786
  var HISTORY_FILE = path36.join(os4.homedir(), ".coco", "history");
46787
+ async function handleOptionC(copyFn = copyToClipboard, getLastBlockFn = getLastBlock) {
46788
+ const block = getLastBlockFn();
46789
+ if (!block) return null;
46790
+ const success = await copyFn(block.content);
46791
+ if (!success) return null;
46792
+ const lang = block.lang || "code";
46793
+ return chalk2.green("\u2713") + chalk2.dim(` ${lang} #${block.id} copied`);
46794
+ }
46601
46795
  function loadHistory() {
46602
46796
  try {
46603
46797
  if (fs52.existsSync(HISTORY_FILE)) {
@@ -46754,6 +46948,13 @@ function computeWordWrap(text13, startCol, termCols) {
46754
46948
  }
46755
46949
  };
46756
46950
  }
46951
+ function buildImageIndicator(count) {
46952
+ if (count === 0) return { str: "", len: 0 };
46953
+ const badges = Array.from({ length: count }, (_, i) => `[\u{1F4CE} #${i + 1}]`);
46954
+ const joined = badges.join(" ");
46955
+ const len = 1 + joined.length + 1;
46956
+ return { str: " " + chalk2.cyan(joined) + " ", len };
46957
+ }
46757
46958
  function createInputHandler(_session) {
46758
46959
  const savedHistory = loadHistory();
46759
46960
  const sessionHistory = [...savedHistory];
@@ -46773,19 +46974,18 @@ function createInputHandler(_session) {
46773
46974
  let pasteBuffer = "";
46774
46975
  let isReadingClipboard = false;
46775
46976
  const getPrompt = () => {
46776
- const imageIndicator = hasPendingImage() ? chalk2.cyan(" \u{1F4CE} 1 image") : "";
46777
- const imageIndicatorLen = hasPendingImage() ? 10 : 0;
46977
+ const { str: imageStr, len: imageLen } = buildImageIndicator(getPendingImageCount());
46778
46978
  if (isQualityLoop()) {
46779
46979
  return {
46780
- str: "\u{1F965} " + chalk2.magenta("[quality]") + " \u203A " + imageIndicator,
46980
+ str: "\u{1F965} " + chalk2.magenta("[quality]") + " \u203A " + imageStr,
46781
46981
  // 🥥=2 + space=1 + [quality]=9 + space=1 + ›=1 + space=1 = 15 + image indicator
46782
- visualLen: 15 + imageIndicatorLen
46982
+ visualLen: 15 + imageLen
46783
46983
  };
46784
46984
  }
46785
46985
  return {
46786
- str: chalk2.green("\u{1F965} \u203A ") + imageIndicator,
46986
+ str: chalk2.green("\u{1F965} \u203A ") + imageStr,
46787
46987
  // 🥥=2 + space=1 + ›=1 + space=1 = 5 + image indicator
46788
- visualLen: 5 + imageIndicatorLen
46988
+ visualLen: 5 + imageLen
46789
46989
  };
46790
46990
  };
46791
46991
  const MAX_ROWS = 8;
@@ -47245,6 +47445,17 @@ function createInputHandler(_session) {
47245
47445
  render();
47246
47446
  return;
47247
47447
  }
47448
+ if (key === "\x1Bc") {
47449
+ handleOptionC().then((msg) => {
47450
+ if (msg) {
47451
+ process.stdout.write(` ${msg}
47452
+ `);
47453
+ render();
47454
+ }
47455
+ }).catch(() => {
47456
+ });
47457
+ return;
47458
+ }
47248
47459
  if (key === "\x1B" && data.length === 1) {
47249
47460
  if (lastMenuLines > 0) {
47250
47461
  clearMenu();
@@ -48443,6 +48654,18 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
48443
48654
  const maxIterations = session.config.agent.maxToolIterations;
48444
48655
  const toolErrorCounts = /* @__PURE__ */ new Map();
48445
48656
  const MAX_CONSECUTIVE_TOOL_ERRORS = 3;
48657
+ const INLINE_RESULT_MAX_CHARS = 8e3;
48658
+ const INLINE_RESULT_HEAD_CHARS = 6500;
48659
+ const INLINE_RESULT_TAIL_CHARS = 1e3;
48660
+ function truncateInlineResult(content, toolName) {
48661
+ if (content.length <= INLINE_RESULT_MAX_CHARS) return content;
48662
+ const head = content.slice(0, INLINE_RESULT_HEAD_CHARS);
48663
+ const tail = content.slice(-INLINE_RESULT_TAIL_CHARS);
48664
+ const omitted = content.length - INLINE_RESULT_HEAD_CHARS - INLINE_RESULT_TAIL_CHARS;
48665
+ return `${head}
48666
+ [... ${omitted.toLocaleString()} characters omitted \u2014 use read_file with offset/limit to retrieve more of '${toolName}' output ...]
48667
+ ${tail}`;
48668
+ }
48446
48669
  while (iteration < maxIterations) {
48447
48670
  iteration++;
48448
48671
  if (options.signal?.aborted) {
@@ -48695,7 +48918,7 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
48695
48918
  toolResults.push({
48696
48919
  type: "tool_result",
48697
48920
  tool_use_id: toolCall.id,
48698
- content: executedCall.result.output,
48921
+ content: truncateInlineResult(executedCall.result.output, toolCall.name),
48699
48922
  is_error: !executedCall.result.success
48700
48923
  });
48701
48924
  } else {
@@ -49617,19 +49840,17 @@ async function startRepl(options = {}) {
49617
49840
  if (commandResult.forkPrompt) {
49618
49841
  agentMessage = commandResult.forkPrompt;
49619
49842
  } else if (hasPendingImage()) {
49620
- const pending = consumePendingImage();
49843
+ const images = consumePendingImages();
49621
49844
  agentMessage = [
49622
- {
49623
- type: "image",
49624
- source: {
49625
- type: "base64",
49626
- media_type: pending.media_type,
49627
- data: pending.data
49628
- }
49629
- },
49845
+ ...images.map(
49846
+ (img) => ({
49847
+ type: "image",
49848
+ source: { type: "base64", media_type: img.media_type, data: img.data }
49849
+ })
49850
+ ),
49630
49851
  {
49631
49852
  type: "text",
49632
- text: pending.prompt
49853
+ text: images.map((img) => img.prompt).join("\n")
49633
49854
  }
49634
49855
  ];
49635
49856
  } else {
@@ -49637,19 +49858,17 @@ async function startRepl(options = {}) {
49637
49858
  }
49638
49859
  }
49639
49860
  if (agentMessage === null && hasPendingImage()) {
49640
- const pending = consumePendingImage();
49861
+ const images = consumePendingImages();
49641
49862
  agentMessage = [
49642
- {
49643
- type: "image",
49644
- source: {
49645
- type: "base64",
49646
- media_type: pending.media_type,
49647
- data: pending.data
49648
- }
49649
- },
49863
+ ...images.map(
49864
+ (img) => ({
49865
+ type: "image",
49866
+ source: { type: "base64", media_type: img.media_type, data: img.data }
49867
+ })
49868
+ ),
49650
49869
  {
49651
49870
  type: "text",
49652
- text: pending.prompt
49871
+ text: images.map((img) => img.prompt).join("\n")
49653
49872
  }
49654
49873
  ];
49655
49874
  }
@@ -50085,10 +50304,14 @@ async function startRepl(options = {}) {
50085
50304
  if (compactionResult?.wasCompacted) {
50086
50305
  console.log(chalk2.green(" \u2713 Context compacted. Please retry your message."));
50087
50306
  } else {
50088
- console.log(chalk2.yellow(" \u26A0 Could not compact context. Use /clear to start fresh."));
50307
+ console.log(
50308
+ chalk2.yellow(" \u26A0 Could not compact context. Use /clear to start fresh.")
50309
+ );
50089
50310
  }
50090
50311
  } catch {
50091
- console.log(chalk2.yellow(" \u26A0 Context compaction failed. Use /clear to start fresh."));
50312
+ console.log(
50313
+ chalk2.yellow(" \u26A0 Context compaction failed. Use /clear to start fresh.")
50314
+ );
50092
50315
  }
50093
50316
  continue;
50094
50317
  }