@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/README.md +11 -0
- package/dist/cli/index.js +1751 -1528
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +67 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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,
|
|
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
|
-
|
|
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 = `
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
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
|
-
**
|
|
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
|
|
7872
|
+
Keep the total summary under 600 words. Use bullet points within each section.
|
|
7849
7873
|
|
|
7850
|
-
|
|
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:
|
|
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 =
|
|
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:
|
|
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
|
|
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:
|
|
16397
|
-
- Run npm script:
|
|
16398
|
-
- Check disk space:
|
|
16399
|
-
- Find process:
|
|
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:
|
|
16497
|
-
- Run watcher:
|
|
16498
|
-
- Start database:
|
|
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/
|
|
34868
|
-
|
|
34869
|
-
var
|
|
34870
|
-
var
|
|
34871
|
-
|
|
34872
|
-
|
|
34873
|
-
|
|
34874
|
-
|
|
34875
|
-
|
|
34876
|
-
|
|
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
|
-
|
|
34978
|
+
return block.id;
|
|
34905
34979
|
}
|
|
34906
|
-
function
|
|
34907
|
-
if (
|
|
34908
|
-
|
|
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
|
|
34925
|
-
|
|
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
|
|
34934
|
-
return
|
|
34987
|
+
function getBlockCount() {
|
|
34988
|
+
return blocks.length;
|
|
34935
34989
|
}
|
|
34936
|
-
function
|
|
34937
|
-
|
|
34938
|
-
|
|
34939
|
-
|
|
34940
|
-
|
|
34941
|
-
|
|
34942
|
-
|
|
34943
|
-
|
|
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
|
|
34951
|
-
|
|
34952
|
-
|
|
34953
|
-
|
|
34954
|
-
|
|
34955
|
-
|
|
34956
|
-
|
|
34957
|
-
|
|
34958
|
-
|
|
34959
|
-
|
|
34960
|
-
|
|
34961
|
-
|
|
34962
|
-
if (
|
|
34963
|
-
|
|
34964
|
-
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
34968
|
-
|
|
34969
|
-
|
|
34970
|
-
|
|
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
|
-
|
|
34974
|
-
|
|
34975
|
-
|
|
34976
|
-
|
|
34977
|
-
}
|
|
34978
|
-
|
|
34979
|
-
|
|
34980
|
-
|
|
34981
|
-
|
|
34982
|
-
|
|
34983
|
-
|
|
34984
|
-
|
|
34985
|
-
|
|
34986
|
-
|
|
34987
|
-
|
|
34988
|
-
|
|
34989
|
-
|
|
34990
|
-
|
|
34991
|
-
|
|
34992
|
-
|
|
34993
|
-
|
|
34994
|
-
|
|
34995
|
-
|
|
34996
|
-
|
|
34997
|
-
|
|
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
|
-
|
|
35067
|
+
resolve4(false);
|
|
35000
35068
|
}
|
|
35001
|
-
}
|
|
35002
|
-
|
|
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
|
-
|
|
35005
|
-
|
|
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 (
|
|
35037
|
-
|
|
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
|
|
35048
|
-
|
|
35049
|
-
|
|
35050
|
-
|
|
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
|
|
35055
|
-
|
|
35056
|
-
|
|
35057
|
-
|
|
35058
|
-
|
|
35059
|
-
|
|
35060
|
-
|
|
35061
|
-
|
|
35062
|
-
|
|
35063
|
-
|
|
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
|
|
35094
|
-
const
|
|
35095
|
-
|
|
35096
|
-
|
|
35097
|
-
|
|
35098
|
-
|
|
35099
|
-
|
|
35100
|
-
|
|
35101
|
-
|
|
35102
|
-
|
|
35103
|
-
|
|
35104
|
-
|
|
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
|
-
|
|
35108
|
-
|
|
35109
|
-
|
|
35110
|
-
|
|
35111
|
-
|
|
35112
|
-
|
|
35113
|
-
|
|
35114
|
-
|
|
35115
|
-
|
|
35116
|
-
|
|
35117
|
-
|
|
35118
|
-
|
|
35119
|
-
|
|
35120
|
-
}
|
|
35121
|
-
|
|
35122
|
-
|
|
35123
|
-
|
|
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
|
|
35154
|
-
const
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
|
|
35165
|
-
|
|
35166
|
-
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
35188
|
-
|
|
35189
|
-
|
|
35190
|
-
|
|
35191
|
-
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35206
|
-
|
|
35207
|
-
|
|
35208
|
-
|
|
35209
|
-
|
|
35210
|
-
|
|
35211
|
-
|
|
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
|
-
|
|
35223
|
-
|
|
35224
|
-
|
|
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 (
|
|
35244
|
-
|
|
35245
|
-
|
|
35246
|
-
|
|
35247
|
-
|
|
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
|
-
|
|
35255
|
-
|
|
35256
|
-
|
|
35257
|
-
|
|
35258
|
-
|
|
35259
|
-
|
|
35260
|
-
|
|
35261
|
-
|
|
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
|
-
|
|
35401
|
-
|
|
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
|
|
35422
|
-
|
|
35423
|
-
|
|
35424
|
-
|
|
35425
|
-
|
|
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
|
-
|
|
35491
|
-
|
|
35492
|
-
|
|
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
|
-
|
|
35504
|
-
|
|
35505
|
-
|
|
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
|
-
|
|
35515
|
-
}
|
|
35516
|
-
|
|
35517
|
-
const
|
|
35518
|
-
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
|
|
35522
|
-
|
|
35523
|
-
|
|
35524
|
-
|
|
35525
|
-
|
|
35526
|
-
}
|
|
35527
|
-
if (
|
|
35528
|
-
|
|
35529
|
-
|
|
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
|
|
35558
|
-
|
|
35559
|
-
|
|
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
|
-
|
|
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
|
|
35564
|
-
const
|
|
35565
|
-
|
|
35566
|
-
|
|
35567
|
-
console.log(
|
|
35568
|
-
|
|
35569
|
-
|
|
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
|
-
|
|
35572
|
-
if (details) console.log(details);
|
|
35345
|
+
console.log();
|
|
35573
35346
|
}
|
|
35574
|
-
function
|
|
35575
|
-
|
|
35576
|
-
|
|
35577
|
-
|
|
35578
|
-
|
|
35579
|
-
|
|
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
|
-
|
|
35590
|
-
|
|
35591
|
-
|
|
35592
|
-
|
|
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
|
-
|
|
35595
|
-
|
|
35596
|
-
|
|
35597
|
-
|
|
35598
|
-
|
|
35599
|
-
if (
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
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
|
|
37038
|
-
function
|
|
37039
|
-
const
|
|
37040
|
-
|
|
37041
|
-
return
|
|
36339
|
+
var pendingImages = [];
|
|
36340
|
+
function consumePendingImages() {
|
|
36341
|
+
const imgs = pendingImages;
|
|
36342
|
+
pendingImages = [];
|
|
36343
|
+
return imgs;
|
|
37042
36344
|
}
|
|
37043
36345
|
function hasPendingImage() {
|
|
37044
|
-
return
|
|
36346
|
+
return pendingImages.length > 0;
|
|
36347
|
+
}
|
|
36348
|
+
function getPendingImageCount() {
|
|
36349
|
+
return pendingImages.length;
|
|
37045
36350
|
}
|
|
37046
36351
|
function setPendingImage(data, media_type, prompt) {
|
|
37047
|
-
|
|
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
|
-
|
|
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 =
|
|
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(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'").replace(/&/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
|
-
|
|
46183
|
-
|
|
46184
|
-
|
|
46185
|
-
|
|
46186
|
-
|
|
46187
|
-
|
|
46188
|
-
|
|
46189
|
-
|
|
46190
|
-
|
|
46191
|
-
|
|
46192
|
-
|
|
46193
|
-
|
|
46194
|
-
|
|
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
|
-
|
|
46211
|
-
|
|
46212
|
-
|
|
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
|
-
|
|
46215
|
-
|
|
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
|
-
|
|
46218
|
-
|
|
46219
|
-
|
|
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
|
-
|
|
46228
|
-
|
|
46229
|
-
|
|
46230
|
-
|
|
46231
|
-
|
|
46232
|
-
|
|
46233
|
-
|
|
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
|
-
|
|
46252
|
-
|
|
46253
|
-
|
|
46254
|
-
|
|
46255
|
-
|
|
46256
|
-
|
|
46257
|
-
|
|
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
|
-
|
|
46261
|
-
|
|
46563
|
+
}
|
|
46564
|
+
function formatResultPreview(result) {
|
|
46565
|
+
if (!result.result.success) return "";
|
|
46566
|
+
const { name, result: toolResult } = result;
|
|
46262
46567
|
try {
|
|
46263
|
-
|
|
46264
|
-
|
|
46265
|
-
|
|
46266
|
-
|
|
46267
|
-
|
|
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
|
-
|
|
46323
|
-
|
|
46324
|
-
|
|
46325
|
-
|
|
46326
|
-
|
|
46327
|
-
|
|
46328
|
-
|
|
46329
|
-
|
|
46330
|
-
|
|
46331
|
-
|
|
46332
|
-
|
|
46333
|
-
|
|
46334
|
-
|
|
46335
|
-
|
|
46336
|
-
|
|
46337
|
-
|
|
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 (
|
|
46351
|
-
const
|
|
46352
|
-
|
|
46353
|
-
|
|
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 (
|
|
46356
|
-
|
|
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
|
-
|
|
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
|
|
46378
|
-
const
|
|
46379
|
-
|
|
46380
|
-
|
|
46381
|
-
|
|
46382
|
-
|
|
46383
|
-
|
|
46384
|
-
|
|
46385
|
-
|
|
46386
|
-
|
|
46387
|
-
|
|
46388
|
-
|
|
46389
|
-
const
|
|
46390
|
-
|
|
46391
|
-
|
|
46392
|
-
|
|
46393
|
-
|
|
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
|
|
46661
|
+
return parts.join(", ");
|
|
46405
46662
|
}
|
|
46406
|
-
|
|
46407
|
-
|
|
46408
|
-
|
|
46409
|
-
|
|
46410
|
-
|
|
46411
|
-
|
|
46412
|
-
|
|
46413
|
-
|
|
46414
|
-
|
|
46415
|
-
|
|
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
|
|
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 " +
|
|
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 +
|
|
46982
|
+
visualLen: 15 + imageLen
|
|
46783
46983
|
};
|
|
46784
46984
|
}
|
|
46785
46985
|
return {
|
|
46786
|
-
str: chalk2.green("\u{1F965} \u203A ") +
|
|
46986
|
+
str: chalk2.green("\u{1F965} \u203A ") + imageStr,
|
|
46787
46987
|
// 🥥=2 + space=1 + ›=1 + space=1 = 5 + image indicator
|
|
46788
|
-
visualLen: 5 +
|
|
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
|
|
49843
|
+
const images = consumePendingImages();
|
|
49621
49844
|
agentMessage = [
|
|
49622
|
-
|
|
49623
|
-
|
|
49624
|
-
|
|
49625
|
-
type: "base64",
|
|
49626
|
-
|
|
49627
|
-
|
|
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:
|
|
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
|
|
49861
|
+
const images = consumePendingImages();
|
|
49641
49862
|
agentMessage = [
|
|
49642
|
-
|
|
49643
|
-
|
|
49644
|
-
|
|
49645
|
-
type: "base64",
|
|
49646
|
-
|
|
49647
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
50312
|
+
console.log(
|
|
50313
|
+
chalk2.yellow(" \u26A0 Context compaction failed. Use /clear to start fresh.")
|
|
50314
|
+
);
|
|
50092
50315
|
}
|
|
50093
50316
|
continue;
|
|
50094
50317
|
}
|