@corbat-tech/coco 2.5.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -7141,9 +7141,12 @@ YOU ARE AN EXECUTION AGENT, NOT A CONVERSATIONAL ASSISTANT.
7141
7141
  - EVERY action requires a TOOL CALL. Text responses are ONLY for brief confirmations AFTER tools execute.
7142
7142
 
7143
7143
  **Execution Process:**
7144
- 1. **Analyze**: Understand what the user wants (in your head, don't output this)
7144
+ 1. **Orient**: Output ONE line stating the *goal* of the next step \u2014 not the tool, the intent.
7145
+ - Good: "Confirming the typo is gone\u2026" / "Checking tests still pass\u2026" / "Reading the config to understand current structure\u2026"
7146
+ - Bad: "I'll use grep to search." (restates the tool, not the goal)
7147
+ - Skip this for obvious single-step tasks ("create hello.js" \u2192 just create it).
7145
7148
  2. **Execute**: IMMEDIATELY CALL THE APPROPRIATE TOOLS (this is mandatory, not optional)
7146
- 3. **Respond**: Brief confirmation of what was done (AFTER tools executed)
7149
+ 3. **Respond**: Brief confirmation of what was done (AFTER all tools executed)
7147
7150
 
7148
7151
  **Critical Rules:**
7149
7152
  - User says "create X with Y" \u2192 Immediately call write_file/edit_file tool, no discussion
@@ -7228,19 +7231,43 @@ If a file tool fails with "outside project directory", the system will automatic
7228
7231
 
7229
7232
  **For structured content** (documentation, tutorials, summaries, explanations with multiple sections, or when the user asks for "markdown"):
7230
7233
 
7231
- 1. Wrap your entire response in a single markdown code block:
7232
- \`\`\`markdown
7234
+ 1. Wrap your entire response in a single tilde markdown block:
7235
+ ~~~markdown
7233
7236
  Your content here...
7237
+ ~~~
7238
+
7239
+ 2. **CRITICAL: Bare ~~~ closes the outer block** \u2014 Only use bare ~~~ (without a lang tag) as the VERY LAST line to close the outer block. Writing ~~~ anywhere else inside the block will break rendering.
7240
+
7241
+ 3. **ALL inner fenced blocks use standard backtick syntax:**
7242
+ - Code: \`\`\`javascript / \`\`\`typescript / \`\`\`python / \`\`\`bash / etc.
7243
+ - Shell commands: \`\`\`bash
7244
+ - ASCII diagrams: \`\`\`ascii
7245
+ - Tree structures / file paths: \`\`\`text
7246
+ - Any other fenced content: \`\`\`<lang>
7247
+
7248
+ Example:
7249
+ ~~~markdown
7250
+ ## Section
7251
+
7252
+ Some text here.
7253
+
7254
+ \`\`\`bash
7255
+ echo "hello"
7256
+ ls -la
7234
7257
  \`\`\`
7235
7258
 
7236
- 2. **CRITICAL: Never close the markdown block prematurely** - The closing \`\`\` must ONLY appear at the very end.
7259
+ \`\`\`ascii
7260
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
7261
+ \u2502 Service \u2502
7262
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
7263
+ \`\`\`
7237
7264
 
7238
- 3. **For code examples inside markdown**, use TILDES (~~~) instead of backticks:
7239
- ~~~javascript
7240
- function example() { return "hello"; }
7265
+ More text after blocks.
7241
7266
  ~~~
7242
7267
 
7243
- 4. **Include all content in ONE block**: headers, lists, tables, quotes, code examples.
7268
+ **Inner blocks open with \`\`\`lang and close with \`\`\`. The only ~~~ inside the markdown block is the final bare ~~~ at the very end.**
7269
+
7270
+ 4. **Include all content in ONE block**: headers, lists, tables, quotes, code, commands, diagrams.
7244
7271
 
7245
7272
  **When to use markdown block:**
7246
7273
  - User asks for documentation, summary, tutorial, guide
@@ -32837,6 +32864,8 @@ var rawMarkdownBuffer = "";
32837
32864
  var inCodeBlock = false;
32838
32865
  var codeBlockLang = "";
32839
32866
  var codeBlockLines = [];
32867
+ var inNestedCodeBlock = false;
32868
+ var codeBlockFenceChar = "";
32840
32869
  var streamingIndicatorActive = false;
32841
32870
  var streamingIndicatorInterval = null;
32842
32871
  var streamingIndicatorFrame = 0;
@@ -32880,6 +32909,7 @@ function flushLineBuffer() {
32880
32909
  stopStreamingIndicator();
32881
32910
  }
32882
32911
  inCodeBlock = false;
32912
+ codeBlockFenceChar = "";
32883
32913
  codeBlockLang = "";
32884
32914
  codeBlockLines = [];
32885
32915
  }
@@ -32887,6 +32917,8 @@ function flushLineBuffer() {
32887
32917
  function resetLineBuffer() {
32888
32918
  lineBuffer = "";
32889
32919
  inCodeBlock = false;
32920
+ inNestedCodeBlock = false;
32921
+ codeBlockFenceChar = "";
32890
32922
  codeBlockLang = "";
32891
32923
  codeBlockLines = [];
32892
32924
  stopStreamingIndicator();
@@ -32909,28 +32941,100 @@ function renderStreamChunk(chunk) {
32909
32941
  }
32910
32942
  }
32911
32943
  function processAndOutputLine(line) {
32912
- const codeBlockMatch = line.match(/^```(\w*)$/);
32944
+ line = line.replace(/^[\u200B\uFEFF\u200C\u200D\u2060\u00AD]+/, "");
32945
+ const tildeFenceMatch = line.match(/^~~~(\w*)$/);
32946
+ if (tildeFenceMatch) {
32947
+ const lang = tildeFenceMatch[1] || "";
32948
+ if (!inCodeBlock) {
32949
+ if (lang) {
32950
+ inCodeBlock = true;
32951
+ inNestedCodeBlock = false;
32952
+ codeBlockFenceChar = "~~~";
32953
+ codeBlockLang = lang;
32954
+ codeBlockLines = [];
32955
+ if (codeBlockLang === "markdown" || codeBlockLang === "md") {
32956
+ startStreamingIndicator();
32957
+ }
32958
+ } else {
32959
+ const formatted = formatMarkdownLine(line);
32960
+ const termWidth = getTerminalWidth2();
32961
+ const wrapped = wrapText(formatted, termWidth);
32962
+ for (const wl of wrapped) {
32963
+ console.log(wl);
32964
+ }
32965
+ }
32966
+ } else if (codeBlockFenceChar === "~~~") {
32967
+ if (lang && !inNestedCodeBlock) {
32968
+ inNestedCodeBlock = true;
32969
+ codeBlockLines.push(line);
32970
+ } else if (!lang && inNestedCodeBlock) {
32971
+ inNestedCodeBlock = false;
32972
+ codeBlockLines.push(line);
32973
+ } else if (!lang && !inNestedCodeBlock) {
32974
+ stopStreamingIndicator();
32975
+ renderCodeBlock(codeBlockLang, codeBlockLines);
32976
+ inCodeBlock = false;
32977
+ inNestedCodeBlock = false;
32978
+ codeBlockFenceChar = "";
32979
+ codeBlockLang = "";
32980
+ codeBlockLines = [];
32981
+ } else {
32982
+ codeBlockLines.push(line);
32983
+ }
32984
+ } else {
32985
+ if (lang && !inNestedCodeBlock) {
32986
+ inNestedCodeBlock = true;
32987
+ codeBlockLines.push(line);
32988
+ } else if (!lang && inNestedCodeBlock) {
32989
+ inNestedCodeBlock = false;
32990
+ codeBlockLines.push(line);
32991
+ } else {
32992
+ codeBlockLines.push(line);
32993
+ }
32994
+ }
32995
+ return;
32996
+ }
32997
+ const codeBlockMatch = line.match(/^(`{3,4})(\w*)$/);
32913
32998
  if (codeBlockMatch) {
32999
+ const fenceChars = codeBlockMatch[1];
33000
+ const lang = codeBlockMatch[2] || "";
32914
33001
  if (!inCodeBlock) {
32915
33002
  inCodeBlock = true;
32916
- codeBlockLang = codeBlockMatch[1] || "";
33003
+ inNestedCodeBlock = false;
33004
+ codeBlockFenceChar = fenceChars;
33005
+ codeBlockLang = lang;
32917
33006
  codeBlockLines = [];
32918
33007
  if (codeBlockLang === "markdown" || codeBlockLang === "md") {
32919
33008
  startStreamingIndicator();
32920
33009
  }
32921
- } else {
33010
+ } else if (!lang && inNestedCodeBlock && fenceChars === "```") {
33011
+ inNestedCodeBlock = false;
33012
+ codeBlockLines.push(line);
33013
+ } else if (!inNestedCodeBlock && lang && fenceChars === "```") {
33014
+ inNestedCodeBlock = true;
33015
+ codeBlockLines.push(line);
33016
+ } else if (!lang && !inNestedCodeBlock && codeBlockFenceChar === fenceChars) {
32922
33017
  stopStreamingIndicator();
32923
33018
  renderCodeBlock(codeBlockLang, codeBlockLines);
32924
33019
  inCodeBlock = false;
33020
+ inNestedCodeBlock = false;
33021
+ codeBlockFenceChar = "";
32925
33022
  codeBlockLang = "";
32926
33023
  codeBlockLines = [];
33024
+ } else {
33025
+ codeBlockLines.push(line);
32927
33026
  }
32928
33027
  return;
32929
33028
  }
32930
33029
  if (inCodeBlock) {
32931
33030
  codeBlockLines.push(line);
32932
33031
  } else {
32933
- console.log(formatMarkdownLine(line));
33032
+ const formatted = formatMarkdownLine(line);
33033
+ const termWidth = getTerminalWidth2();
33034
+ const wrapped = wrapText(formatted, termWidth);
33035
+ for (const wl of wrapped) {
33036
+ console.log(wl);
33037
+ }
32934
33038
  }
32935
33039
  }
32936
33040
  function renderCodeBlock(lang, lines) {
@@ -33241,8 +33345,9 @@ function wrapText(text13, maxWidth) {
33241
33345
  }
33242
33346
  const lines = [];
33243
33347
  let remaining = text13;
33244
- while (stripAnsi2(remaining).length > maxWidth) {
33348
+ while (true) {
33245
33349
  const plain = stripAnsi2(remaining);
33350
+ if (plain.length <= maxWidth) break;
33246
33351
  let breakPoint = maxWidth;
33247
33352
  const lastSpace = plain.lastIndexOf(" ", maxWidth);
33248
33353
  if (lastSpace > maxWidth * 0.5) {
@@ -33271,8 +33376,8 @@ function wrapText(text13, maxWidth) {
33271
33376
  rawPos = ansiPositions[ansiIdx].end;
33272
33377
  ansiIdx++;
33273
33378
  }
33274
- lines.push(remaining.slice(0, rawPos));
33275
- remaining = remaining.slice(rawPos).trimStart();
33379
+ lines.push(remaining.slice(0, rawPos) + "\x1B[0m");
33380
+ remaining = "\x1B[0m" + remaining.slice(rawPos).trimStart();
33276
33381
  }
33277
33382
  if (remaining) {
33278
33383
  lines.push(remaining);
@@ -33312,16 +33417,54 @@ function getToolIcon(toolName, input) {
33312
33417
  function renderToolStart(toolName, input, metadata) {
33313
33418
  const icon = getToolIcon(toolName, { ...input, wouldCreate: metadata?.isCreate });
33314
33419
  const summary = formatToolSummary(toolName, input);
33315
- let label = toolName;
33316
33420
  if (toolName === "write_file") {
33317
- label = chalk25.yellow.bold("MODIFY") + " " + chalk25.cyan(String(input.path || ""));
33421
+ const label = chalk25.yellow.bold("MODIFY") + " " + chalk25.cyan(String(input.path || ""));
33318
33422
  console.log(`
33319
33423
  ${icon} ${label}`);
33424
+ const preview = renderContentPreview(String(input.content || ""), 3);
33425
+ if (preview) console.log(preview);
33426
+ return;
33427
+ }
33428
+ if (toolName === "edit_file") {
33429
+ console.log(`
33430
+ ${icon} ${chalk25.yellow.bold("EDIT")} ${chalk25.cyan(String(input.path || ""))}`);
33431
+ const editPreview = renderEditPreview(
33432
+ String(input.old_string || ""),
33433
+ String(input.new_string || "")
33434
+ );
33435
+ if (editPreview) console.log(editPreview);
33320
33436
  return;
33321
33437
  }
33322
33438
  console.log(`
33323
33439
  ${icon} ${chalk25.cyan.bold(toolName)} ${chalk25.dim(summary)}`);
33324
33440
  }
33441
+ function renderContentPreview(content, maxLines) {
33442
+ const maxWidth = Math.max(getTerminalWidth2() - 6, 40);
33443
+ const lines = content.split("\n");
33444
+ const preview = [];
33445
+ for (const line of lines) {
33446
+ if (preview.length >= maxLines) break;
33447
+ const trimmed = line.trimEnd();
33448
+ if (trimmed.length === 0 && preview.length === 0) continue;
33449
+ const truncated = trimmed.length > maxWidth ? trimmed.slice(0, maxWidth - 1) + "\u2026" : trimmed;
33450
+ preview.push(` ${truncated}`);
33451
+ }
33452
+ if (preview.length === 0) return "";
33453
+ const totalNonEmpty = lines.filter((l) => l.trim().length > 0).length;
33454
+ const more = totalNonEmpty > maxLines ? chalk25.dim(` \u2026 +${totalNonEmpty - maxLines} lines`) : "";
33455
+ return chalk25.dim(preview.join("\n")) + more;
33456
+ }
33457
+ function renderEditPreview(oldStr, newStr) {
33458
+ const maxWidth = Math.max(getTerminalWidth2() - 8, 30);
33459
+ const firstOld = oldStr.split("\n").find((l) => l.trim().length > 0) ?? "";
33460
+ const firstNew = newStr.split("\n").find((l) => l.trim().length > 0) ?? "";
33461
+ if (!firstOld && !firstNew) return "";
33462
+ const truncate2 = (s) => s.length > maxWidth ? s.slice(0, maxWidth - 1) + "\u2026" : s;
33463
+ const lines = [];
33464
+ if (firstOld) lines.push(chalk25.dim(" ") + chalk25.red(`- ${truncate2(firstOld.trim())}`));
33465
+ if (firstNew) lines.push(chalk25.dim(" ") + chalk25.green(`+ ${truncate2(firstNew.trim())}`));
33466
+ return lines.join("\n");
33467
+ }
33325
33468
  function renderToolEnd(result) {
33326
33469
  const status = result.result.success ? chalk25.green("\u2713") : chalk25.red("\u2717");
33327
33470
  const duration = chalk25.dim(`${result.duration.toFixed(0)}ms`);
@@ -33330,6 +33473,8 @@ function renderToolEnd(result) {
33330
33473
  if (!result.result.success && result.result.error) {
33331
33474
  console.log(chalk25.red(` \u2514\u2500 ${result.result.error}`));
33332
33475
  }
33476
+ const details = formatResultDetails(result);
33477
+ if (details) console.log(details);
33333
33478
  }
33334
33479
  function formatToolSummary(toolName, input) {
33335
33480
  switch (toolName) {
@@ -33340,6 +33485,7 @@ function formatToolSummary(toolName, input) {
33340
33485
  return String(input.path || "");
33341
33486
  case "list_directory":
33342
33487
  return String(input.path || ".");
33488
+ case "grep":
33343
33489
  case "search_files": {
33344
33490
  const pattern = String(input.pattern || "");
33345
33491
  const path54 = input.path ? ` in ${input.path}` : "";
@@ -33347,7 +33493,8 @@ function formatToolSummary(toolName, input) {
33347
33493
  }
33348
33494
  case "bash_exec": {
33349
33495
  const cmd = String(input.command || "");
33350
- return cmd.length > 50 ? cmd.slice(0, 47) + "..." : cmd;
33496
+ const max = Math.max(getTerminalWidth2() - 20, 50);
33497
+ return cmd.length > max ? cmd.slice(0, max - 1) + "\u2026" : cmd;
33351
33498
  }
33352
33499
  default:
33353
33500
  return formatToolInput(input);
@@ -33371,15 +33518,16 @@ function formatResultPreview(result) {
33371
33518
  return chalk25.dim(`(${files} files, ${dirs} dirs)`);
33372
33519
  }
33373
33520
  break;
33521
+ case "grep":
33374
33522
  case "search_files":
33375
33523
  if (Array.isArray(data.matches)) {
33376
- return chalk25.dim(`(${data.matches.length} matches)`);
33524
+ const n = data.matches.length;
33525
+ return n === 0 ? chalk25.yellow("\xB7 no matches") : chalk25.dim(`\xB7 ${n} match${n === 1 ? "" : "es"}`);
33377
33526
  }
33378
33527
  break;
33379
33528
  case "bash_exec":
33380
- if (data.exitCode === 0) {
33381
- const lines = String(data.stdout || "").split("\n").length;
33382
- return chalk25.dim(`(${lines} lines)`);
33529
+ if (data.exitCode !== void 0 && data.exitCode !== 0) {
33530
+ return chalk25.red(`(exit ${data.exitCode})`);
33383
33531
  }
33384
33532
  break;
33385
33533
  case "write_file":
@@ -33390,6 +33538,47 @@ function formatResultPreview(result) {
33390
33538
  }
33391
33539
  return "";
33392
33540
  }
33541
+ function formatResultDetails(result) {
33542
+ if (!result.result.success) return "";
33543
+ const { name, result: toolResult } = result;
33544
+ const maxWidth = Math.max(getTerminalWidth2() - 8, 40);
33545
+ try {
33546
+ const data = JSON.parse(toolResult.output);
33547
+ if ((name === "grep" || name === "search_files") && Array.isArray(data.matches)) {
33548
+ const matches = data.matches;
33549
+ if (matches.length === 0) return "";
33550
+ const MAX_SHOWN = 3;
33551
+ const shown = matches.slice(0, MAX_SHOWN);
33552
+ const lines = shown.map(({ file, line, content }) => {
33553
+ const location = chalk25.cyan(`${file}:${line}`);
33554
+ const snippet = content.trim();
33555
+ const truncated = snippet.length > maxWidth ? snippet.slice(0, maxWidth - 1) + "\u2026" : snippet;
33556
+ return ` ${chalk25.dim("\u2502")} ${location} ${chalk25.dim(truncated)}`;
33557
+ });
33558
+ if (matches.length > MAX_SHOWN) {
33559
+ lines.push(` ${chalk25.dim(`\u2502 \u2026 +${matches.length - MAX_SHOWN} more`)}`);
33560
+ }
33561
+ return lines.join("\n");
33562
+ }
33563
+ if (name === "bash_exec" && data.exitCode === 0) {
33564
+ const stdout = String(data.stdout || "").trimEnd();
33565
+ if (!stdout) return "";
33566
+ const outputLines = stdout.split("\n").filter((l) => l.trim());
33567
+ if (outputLines.length > 6) return "";
33568
+ const shown = outputLines.slice(0, 4);
33569
+ const lines = shown.map((l) => {
33570
+ const truncated = l.length > maxWidth ? l.slice(0, maxWidth - 1) + "\u2026" : l;
33571
+ return ` ${chalk25.dim("\u2502")} ${chalk25.dim(truncated)}`;
33572
+ });
33573
+ if (outputLines.length > 4) {
33574
+ lines.push(` ${chalk25.dim(`\u2502 \u2026 +${outputLines.length - 4} more`)}`);
33575
+ }
33576
+ return lines.join("\n");
33577
+ }
33578
+ } catch {
33579
+ }
33580
+ return "";
33581
+ }
33393
33582
  function formatToolInput(input) {
33394
33583
  const entries = Object.entries(input);
33395
33584
  if (entries.length === 0) return "";
@@ -38786,7 +38975,7 @@ init_errors();
38786
38975
  init_paths();
38787
38976
  var fs36 = await import('fs/promises');
38788
38977
  var path38 = await import('path');
38789
- var crypto3 = await import('crypto');
38978
+ var crypto2 = await import('crypto');
38790
38979
  var GLOBAL_MEMORIES_DIR = path38.join(COCO_HOME, "memories");
38791
38980
  var PROJECT_MEMORIES_DIR = ".coco/memories";
38792
38981
  var DEFAULT_MAX_MEMORIES = 1e3;
@@ -38868,7 +39057,7 @@ Examples:
38868
39057
  { tool: "create_memory" }
38869
39058
  );
38870
39059
  }
38871
- const id = crypto3.randomUUID();
39060
+ const id = crypto2.randomUUID();
38872
39061
  const memory = {
38873
39062
  id,
38874
39063
  key,
@@ -38983,7 +39172,7 @@ var memoryTools = [createMemoryTool, recallMemoryTool, listMemoriesTool];
38983
39172
  init_registry4();
38984
39173
  init_errors();
38985
39174
  var fs37 = await import('fs/promises');
38986
- var crypto4 = await import('crypto');
39175
+ var crypto3 = await import('crypto');
38987
39176
  var CHECKPOINT_FILE = ".coco/checkpoints.json";
38988
39177
  var DEFAULT_MAX_CHECKPOINTS = 50;
38989
39178
  var STASH_PREFIX = "coco-cp";
@@ -39038,7 +39227,7 @@ Examples:
39038
39227
  description: z.string().min(1).max(200).describe("Description of this checkpoint")
39039
39228
  }),
39040
39229
  async execute({ description }) {
39041
- const id = crypto4.randomUUID().slice(0, 8);
39230
+ const id = crypto3.randomUUID().slice(0, 8);
39042
39231
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
39043
39232
  const stashMessage = `${STASH_PREFIX}-${id}-${description.replace(/\s+/g, "-").slice(0, 50)}`;
39044
39233
  const changedFiles = await getChangedFiles();
@@ -42128,6 +42317,115 @@ function findNextWordBoundary(line, pos) {
42128
42317
  while (i < line.length && line[i] === " ") i++;
42129
42318
  return i;
42130
42319
  }
42320
+ function countVisualRows(text13, startCol, termCols) {
42321
+ let rows = 1;
42322
+ let col = startCol;
42323
+ for (const char of text13) {
42324
+ if (char === "\n") {
42325
+ if (col > 0) rows++;
42326
+ col = 0;
42327
+ } else {
42328
+ col++;
42329
+ if (col >= termCols) {
42330
+ rows++;
42331
+ col = 0;
42332
+ }
42333
+ }
42334
+ }
42335
+ return rows;
42336
+ }
42337
+ function getCursorVisualPos(text13, cursorPos, promptLen, termCols) {
42338
+ let row = 0;
42339
+ let col = promptLen;
42340
+ for (let i = 0; i < cursorPos; i++) {
42341
+ if (text13[i] === "\n") {
42342
+ if (col > 0) row++;
42343
+ col = 0;
42344
+ } else {
42345
+ col++;
42346
+ if (col >= termCols) {
42347
+ row++;
42348
+ col = 0;
42349
+ }
42350
+ }
42351
+ }
42352
+ return { row, col };
42353
+ }
42354
+ function computeWordWrap(text13, startCol, termCols) {
42355
+ const passthrough = {
42356
+ display: text13,
42357
+ toDisplayPos: (p45) => p45,
42358
+ toOrigPos: (p45) => p45
42359
+ };
42360
+ if (!text13 || termCols <= 1) return passthrough;
42361
+ const origToDisp = new Int32Array(text13.length + 1);
42362
+ const dispToOrig = [];
42363
+ let display = "";
42364
+ let col = startCol;
42365
+ function emitChar(ch, origIdx) {
42366
+ origToDisp[origIdx] = display.length;
42367
+ dispToOrig.push(origIdx);
42368
+ display += ch;
42369
+ col = ch === "\n" ? 0 : col + 1;
42370
+ }
42371
+ function injectNewline() {
42372
+ dispToOrig.push(-1);
42373
+ display += "\n";
42374
+ col = 0;
42375
+ }
42376
+ let i = 0;
42377
+ while (i < text13.length) {
42378
+ const ch = text13[i];
42379
+ if (ch === "\n") {
42380
+ emitChar("\n", i++);
42381
+ continue;
42382
+ }
42383
+ if (ch !== " ") {
42384
+ let wordEnd = i;
42385
+ while (wordEnd < text13.length && text13[wordEnd] !== " " && text13[wordEnd] !== "\n") {
42386
+ wordEnd++;
42387
+ }
42388
+ const wordLen = wordEnd - i;
42389
+ if (col > 0 && col + wordLen > termCols) {
42390
+ injectNewline();
42391
+ }
42392
+ for (let k = i; k < wordEnd; k++) {
42393
+ emitChar(text13[k], k);
42394
+ if (col >= termCols && k + 1 < wordEnd) {
42395
+ injectNewline();
42396
+ }
42397
+ }
42398
+ i = wordEnd;
42399
+ } else {
42400
+ emitChar(" ", i++);
42401
+ if (col >= termCols) {
42402
+ col = 0;
42403
+ } else {
42404
+ let nextWordEnd = i;
42405
+ while (nextWordEnd < text13.length && text13[nextWordEnd] !== " " && text13[nextWordEnd] !== "\n") {
42406
+ nextWordEnd++;
42407
+ }
42408
+ const nextWordLen = nextWordEnd - i;
42409
+ if (nextWordLen > 0 && col + nextWordLen > termCols) {
42410
+ injectNewline();
42411
+ }
42412
+ }
42413
+ }
42414
+ }
42415
+ origToDisp[text13.length] = display.length;
42416
+ return {
42417
+ display,
42418
+ toDisplayPos: (origPos) => origToDisp[Math.min(origPos, text13.length)] ?? display.length,
42419
+ toOrigPos: (displayPos) => {
42420
+ const dp = Math.max(0, Math.min(displayPos, dispToOrig.length - 1));
42421
+ for (let d = dp; d >= 0; d--) {
42422
+ const orig = dispToOrig[d];
42423
+ if (orig !== void 0 && orig >= 0) return orig;
42424
+ }
42425
+ return 0;
42426
+ }
42427
+ };
42428
+ }
42131
42429
  function createInputHandler(_session) {
42132
42430
  const savedHistory = loadHistory();
42133
42431
  const sessionHistory = [...savedHistory];
@@ -42142,6 +42440,7 @@ function createInputHandler(_session) {
42142
42440
  let lastCursorRow = 0;
42143
42441
  let lastContentRows = 1;
42144
42442
  let isFirstRender = true;
42443
+ let lastCtrlCTime = 0;
42145
42444
  let isPasting = false;
42146
42445
  let pasteBuffer = "";
42147
42446
  let isReadingClipboard = false;
@@ -42183,7 +42482,9 @@ function createInputHandler(_session) {
42183
42482
  process.stdout.write("\r" + ansiEscapes.eraseDown);
42184
42483
  const separator = chalk25.dim("\u2500".repeat(termCols));
42185
42484
  let output = separator + "\n";
42186
- output += prompt.str + currentLine;
42485
+ const ww = computeWordWrap(currentLine, prompt.visualLen, termCols);
42486
+ const displayLine = ww.display;
42487
+ output += prompt.str + displayLine;
42187
42488
  completions = findCompletions(currentLine);
42188
42489
  selectedCompletion = Math.min(selectedCompletion, Math.max(0, completions.length - 1));
42189
42490
  if (cursorPos === currentLine.length && completions.length > 0 && completions[selectedCompletion]) {
@@ -42192,9 +42493,23 @@ function createInputHandler(_session) {
42192
42493
  output += chalk25.dim.gray(ghost);
42193
42494
  }
42194
42495
  }
42195
- const totalContentLen = prompt.visualLen + currentLine.length;
42196
- const contentRows = totalContentLen === 0 ? 1 : Math.ceil(totalContentLen / termCols);
42197
- const contentExactFill = totalContentLen > 0 && totalContentLen % termCols === 0;
42496
+ const hasWrapped = displayLine.includes("\n");
42497
+ const contentRows = hasWrapped ? countVisualRows(displayLine, prompt.visualLen, termCols) : (() => {
42498
+ const len = prompt.visualLen + displayLine.length;
42499
+ return len === 0 ? 1 : Math.ceil(len / termCols);
42500
+ })();
42501
+ const contentExactFill = hasWrapped ? (() => {
42502
+ const { col } = getCursorVisualPos(
42503
+ displayLine,
42504
+ displayLine.length,
42505
+ prompt.visualLen,
42506
+ termCols
42507
+ );
42508
+ return col === 0 && displayLine.length > 0;
42509
+ })() : (() => {
42510
+ const len = prompt.visualLen + displayLine.length;
42511
+ return len > 0 && len % termCols === 0;
42512
+ })();
42198
42513
  output += (contentExactFill ? "" : "\n") + separator;
42199
42514
  const showMenu = completions.length > 0 && currentLine.startsWith("/") && currentLine.length >= 1;
42200
42515
  let extraLinesBelow = 0;
@@ -42251,10 +42566,19 @@ function createInputHandler(_session) {
42251
42566
  const totalUp = extraLinesBelow + 1 + contentRows;
42252
42567
  output += ansiEscapes.cursorUp(totalUp);
42253
42568
  output += ansiEscapes.cursorDown(1);
42254
- const cursorAbsolutePos = prompt.visualLen + cursorPos;
42255
- const onExactBoundary = cursorAbsolutePos > 0 && cursorAbsolutePos % termCols === 0;
42256
- const finalLine = onExactBoundary ? cursorAbsolutePos / termCols - 1 : Math.floor(cursorAbsolutePos / termCols);
42257
- const finalCol = onExactBoundary ? 0 : cursorAbsolutePos % termCols;
42569
+ const displayCursorPos = cursorPos === 0 ? 0 : ww.toDisplayPos(cursorPos);
42570
+ let finalLine;
42571
+ let finalCol;
42572
+ if (hasWrapped) {
42573
+ const pos = getCursorVisualPos(displayLine, displayCursorPos, prompt.visualLen, termCols);
42574
+ finalLine = pos.row;
42575
+ finalCol = pos.col;
42576
+ } else {
42577
+ const cursorAbsolutePos = prompt.visualLen + cursorPos;
42578
+ const onExactBoundary = cursorAbsolutePos > 0 && cursorAbsolutePos % termCols === 0;
42579
+ finalLine = onExactBoundary ? cursorAbsolutePos / termCols - 1 : Math.floor(cursorAbsolutePos / termCols);
42580
+ finalCol = onExactBoundary ? 0 : cursorAbsolutePos % termCols;
42581
+ }
42258
42582
  output += "\r";
42259
42583
  if (finalLine > 0) {
42260
42584
  output += ansiEscapes.cursorDown(finalLine);
@@ -42275,8 +42599,8 @@ function createInputHandler(_session) {
42275
42599
  lastMenuLines = 0;
42276
42600
  }
42277
42601
  function insertTextAtCursor(text13) {
42278
- const cleaned = text13.replace(/[\r\n]+/g, " ");
42279
- const printable = cleaned.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
42602
+ const cleaned = text13.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
42603
+ const printable = cleaned.replace(/[^\n\x20-\x7E\u00A0-\uFFFF]/g, "");
42280
42604
  if (printable.length === 0) return;
42281
42605
  currentLine = currentLine.slice(0, cursorPos) + printable + currentLine.slice(cursorPos);
42282
42606
  cursorPos += printable.length;
@@ -42343,10 +42667,27 @@ function createInputHandler(_session) {
42343
42667
  return;
42344
42668
  }
42345
42669
  if (key === "") {
42670
+ if (currentLine.length > 0) {
42671
+ currentLine = "";
42672
+ cursorPos = 0;
42673
+ selectedCompletion = 0;
42674
+ historyIndex = -1;
42675
+ lastCtrlCTime = 0;
42676
+ render();
42677
+ return;
42678
+ }
42679
+ const now = Date.now();
42680
+ if (now - lastCtrlCTime < 800) {
42681
+ cleanup();
42682
+ console.log("\n\u{1F44B} Goodbye!");
42683
+ saveHistory(sessionHistory);
42684
+ process.exit(0);
42685
+ }
42686
+ lastCtrlCTime = now;
42346
42687
  cleanup();
42347
- console.log("\n\u{1F44B} Goodbye!");
42348
- saveHistory(sessionHistory);
42349
- process.exit(0);
42688
+ console.log(chalk25.dim("(Press Ctrl+C again to exit)"));
42689
+ resolve4("");
42690
+ return;
42350
42691
  }
42351
42692
  if (key === "") {
42352
42693
  if (currentLine.length === 0) {
@@ -42484,7 +42825,10 @@ function createInputHandler(_session) {
42484
42825
  selectedCompletion = Math.min(targetIndex, completions.length - 1);
42485
42826
  }
42486
42827
  render();
42487
- } else if (sessionHistory.length > 0 && completions.length === 0) {
42828
+ } else if (cursorPos > 0) {
42829
+ cursorPos = 0;
42830
+ render();
42831
+ } else if (sessionHistory.length > 0) {
42488
42832
  if (historyIndex === -1) {
42489
42833
  tempLine = currentLine;
42490
42834
  historyIndex = sessionHistory.length - 1;
@@ -42510,7 +42854,10 @@ function createInputHandler(_session) {
42510
42854
  selectedCompletion = currentCol;
42511
42855
  }
42512
42856
  render();
42513
- } else if (historyIndex !== -1 && completions.length === 0) {
42857
+ } else if (cursorPos < currentLine.length) {
42858
+ cursorPos = currentLine.length;
42859
+ render();
42860
+ } else if (historyIndex !== -1) {
42514
42861
  if (historyIndex < sessionHistory.length - 1) {
42515
42862
  historyIndex++;
42516
42863
  currentLine = sessionHistory[historyIndex] ?? "";
@@ -42655,7 +43002,9 @@ function createSpinner(message) {
42655
43002
  const elapsed = startTime ? Math.floor((Date.now() - startTime) / 1e3) : 0;
42656
43003
  const elapsedStr = elapsed > 0 ? chalk25.dim(` (${elapsed}s)`) : "";
42657
43004
  const toolCountStr = formatToolCount();
42658
- spinner19.succeed(`${finalMessage || currentMessage}${toolCountStr}${elapsedStr}`);
43005
+ const rawMsg = finalMessage || currentMessage;
43006
+ const cleanMsg = rawMsg.replace(/\u2026$|\.\.\.$/, "").trimEnd();
43007
+ spinner19.succeed(`${cleanMsg}${toolCountStr}${elapsedStr}`);
42659
43008
  spinner19 = null;
42660
43009
  }
42661
43010
  startTime = null;
@@ -44521,7 +44870,13 @@ function formatGitShort(ctx) {
44521
44870
  return chalk25.dim("\u{1F33F} ") + branch + dirty;
44522
44871
  }
44523
44872
  init_full_access_mode();
44524
- function formatStatusBar(projectPath, config, gitCtx) {
44873
+ function formatContextUsage(percent) {
44874
+ const label = `ctx ${percent.toFixed(0)}%`;
44875
+ if (percent >= 90) return chalk25.red(label);
44876
+ if (percent >= 75) return chalk25.yellow(label);
44877
+ return chalk25.dim(label);
44878
+ }
44879
+ function formatStatusBar(projectPath, config, gitCtx, contextUsagePercent) {
44525
44880
  const parts = [];
44526
44881
  const projectName = path34__default.basename(projectPath);
44527
44882
  parts.push(chalk25.dim("\u{1F4C1}") + chalk25.magenta(projectName));
@@ -44537,10 +44892,13 @@ function formatStatusBar(projectPath, config, gitCtx) {
44537
44892
  if (gitCtx) {
44538
44893
  parts.push(formatGitShort(gitCtx));
44539
44894
  }
44895
+ if (contextUsagePercent !== void 0 && contextUsagePercent > 0) {
44896
+ parts.push(formatContextUsage(contextUsagePercent));
44897
+ }
44540
44898
  return " " + parts.join(chalk25.dim(" \u2022 "));
44541
44899
  }
44542
- function renderStatusBar(projectPath, config, gitCtx) {
44543
- const statusLine = formatStatusBar(projectPath, config, gitCtx);
44900
+ function renderStatusBar(projectPath, config, gitCtx, contextUsagePercent) {
44901
+ const statusLine = formatStatusBar(projectPath, config, gitCtx, contextUsagePercent);
44544
44902
  console.log();
44545
44903
  console.log(statusLine);
44546
44904
  }
@@ -44723,6 +45081,8 @@ async function startRepl(options = {}) {
44723
45081
  }).finally(() => process.exit(0));
44724
45082
  };
44725
45083
  process.once("SIGTERM", sigtermHandler);
45084
+ let warned75 = false;
45085
+ let warned90 = false;
44726
45086
  while (true) {
44727
45087
  let autoInput = null;
44728
45088
  if (pendingQueuedMessages.length > 0) {
@@ -44998,7 +45358,8 @@ async function startRepl(options = {}) {
44998
45358
  },
44999
45359
  onToolEnd: (result2) => {
45000
45360
  const elapsed = activeSpinner && typeof activeSpinner.getElapsed === "function" ? activeSpinner.getElapsed() : 0;
45001
- if (elapsed >= 30) {
45361
+ if (elapsed >= 3) {
45362
+ inputEcho.clear();
45002
45363
  activeSpinner?.stop();
45003
45364
  activeSpinner = null;
45004
45365
  turnActiveSpinner = null;
@@ -45050,7 +45411,15 @@ async function startRepl(options = {}) {
45050
45411
  },
45051
45412
  onThinkingEnd: () => {
45052
45413
  clearThinkingInterval();
45053
- clearSpinner();
45414
+ const thinkingElapsed = activeSpinner?.getElapsed() ?? 0;
45415
+ if (thinkingElapsed >= 2) {
45416
+ inputEcho.clear();
45417
+ activeSpinner?.stop();
45418
+ activeSpinner = null;
45419
+ turnActiveSpinner = null;
45420
+ } else {
45421
+ clearSpinner();
45422
+ }
45054
45423
  },
45055
45424
  onToolPreparing: (toolName) => {
45056
45425
  setSpinner(getToolPreparingDescription(toolName));
@@ -45153,20 +45522,34 @@ async function startRepl(options = {}) {
45153
45522
  if (ctx) gitContext = ctx;
45154
45523
  }).catch(() => {
45155
45524
  });
45156
- renderStatusBar(session.projectPath, session.config, gitContext);
45525
+ const usageBefore = getContextUsagePercent(session);
45526
+ let usageForDisplay = usageBefore;
45157
45527
  try {
45158
- const usageBefore = getContextUsagePercent(session);
45159
45528
  const compactionResult = await checkAndCompactContext(session, provider);
45160
45529
  if (compactionResult?.wasCompacted) {
45161
- const usageAfter = getContextUsagePercent(session);
45530
+ usageForDisplay = getContextUsagePercent(session);
45162
45531
  console.log(
45163
45532
  chalk25.dim(
45164
- `Context compacted (${usageBefore.toFixed(0)}% -> ${usageAfter.toFixed(0)}%)`
45533
+ `Context compacted (${usageBefore.toFixed(0)}% -> ${usageForDisplay.toFixed(0)}%)`
45165
45534
  )
45166
45535
  );
45536
+ warned75 = false;
45537
+ warned90 = false;
45167
45538
  }
45168
45539
  } catch {
45169
45540
  }
45541
+ renderStatusBar(session.projectPath, session.config, gitContext, usageForDisplay);
45542
+ if (usageForDisplay >= 90 && !warned90) {
45543
+ warned90 = true;
45544
+ console.log(
45545
+ chalk25.red(" \u2717 Context critical (" + usageForDisplay.toFixed(0) + "%) \u2014 use /clear to start fresh")
45546
+ );
45547
+ } else if (usageForDisplay >= 75 && !warned75) {
45548
+ warned75 = true;
45549
+ console.log(
45550
+ chalk25.yellow(" \u26A0 Context at " + usageForDisplay.toFixed(0) + "% \u2014 use /clear to start fresh or /compact to summarize")
45551
+ );
45552
+ }
45170
45553
  console.log();
45171
45554
  } catch (error) {
45172
45555
  clearThinkingInterval();