@cosmicstack/mercury-agent 1.2.4 → 1.2.5

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/index.js CHANGED
@@ -2409,6 +2409,525 @@ var init_github_copilot = __esm({
2409
2409
  }
2410
2410
  });
2411
2411
 
2412
+ // src/utils/highlight.ts
2413
+ import chalk from "chalk";
2414
+ function esc(s) {
2415
+ return s.replace(CONTROL, "");
2416
+ }
2417
+ function highlightLine(line, lang, opts) {
2418
+ if (opts?.uniform) {
2419
+ return opts.uniform === "red" ? chalk.red(line) : chalk.green(line);
2420
+ }
2421
+ try {
2422
+ const ruleset = RULESETS[ALIAS[lang?.toLowerCase() ?? ""] ?? lang?.toLowerCase() ?? ""] ?? C_FAMILY;
2423
+ let rest = esc(line);
2424
+ let out2 = "";
2425
+ let guard = 0;
2426
+ while (rest.length > 0 && guard++ < 400) {
2427
+ let matched = false;
2428
+ for (const rule of ruleset) {
2429
+ const m = rule.re.exec(rest);
2430
+ if (m && m[0].length > 0) {
2431
+ out2 += rule.color(m[0]);
2432
+ rest = rest.slice(m[0].length);
2433
+ matched = true;
2434
+ break;
2435
+ }
2436
+ }
2437
+ if (!matched) {
2438
+ out2 += rest[0];
2439
+ rest = rest.slice(1);
2440
+ }
2441
+ }
2442
+ if (rest.length > 0) out2 += rest;
2443
+ return out2;
2444
+ } catch {
2445
+ return line;
2446
+ }
2447
+ }
2448
+ function highlightDiffLine(line) {
2449
+ if (line.startsWith("+++") || line.startsWith("---")) return chalk.blue(line);
2450
+ if (line.startsWith("+++") || line.startsWith("---")) return chalk.blue(line);
2451
+ if (line.startsWith("diff ")) return chalk.bold.blue(line);
2452
+ if (line.startsWith("@@")) return chalk.cyan(line);
2453
+ if (line.startsWith("+")) return chalk.green(line);
2454
+ if (line.startsWith("-")) return chalk.red(line);
2455
+ return line;
2456
+ }
2457
+ function highlightCodeBlock(body, lang) {
2458
+ const trimmedLang = (lang || "").trim().toLowerCase();
2459
+ if (trimmedLang === "diff" || trimmedLang === "patch" || /^(diff --git|--- a\/|\+\+\+ b\/)/m.test(body)) {
2460
+ return body.split("\n").map(highlightDiffLine);
2461
+ }
2462
+ return body.split("\n").map((l) => highlightLine(l, trimmedLang));
2463
+ }
2464
+ var CONTROL, C_FAMILY, PY_RULES, SHELL_RULES, JSON_RULES, CSS_RULES, GO_RULES, RUST_RULES, RULESETS, ALIAS;
2465
+ var init_highlight = __esm({
2466
+ "src/utils/highlight.ts"() {
2467
+ "use strict";
2468
+ CONTROL = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
2469
+ C_FAMILY = [
2470
+ { re: /^\/\/[^\n]*/, color: (s) => chalk.gray(s) },
2471
+ { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
2472
+ { re: /^`(?:\\.|[^`\\])*`?/, color: (s) => chalk.green(s) },
2473
+ { re: /^'(?:\\.|[^'\\\n])*'?/, color: (s) => chalk.green(s) },
2474
+ { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
2475
+ { re: /^-?\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?/, color: (s) => chalk.magenta(s) },
2476
+ { re: /^(?:abstract|as|break|case|catch|class|const|continue|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|namespace|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|yield|async|await)\b/, color: (s) => chalk.yellow(s) },
2477
+ { re: /^(?:true|false|null|unique|undefined|NaN|Infinity)\b/, color: (s) => chalk.blue(s) },
2478
+ { re: /^[A-Za-z_$][\w$]*(?=\s*\()/, color: (s) => chalk.cyan(s) },
2479
+ { re: /^[A-Z][\w$]*/, color: (s) => chalk.blue(s) }
2480
+ ];
2481
+ PY_RULES = [
2482
+ { re: /^#[^\n]*/, color: (s) => chalk.gray(s) },
2483
+ { re: /^"""[\s\S]*?("""|$)/, color: (s) => chalk.gray(s) },
2484
+ { re: /^'''[\s\S]*?('''|$)/, color: (s) => chalk.gray(s) },
2485
+ { re: /^f?"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
2486
+ { re: /^f?'(?:\\.|[^'\\\n])*'?/, color: (s) => chalk.green(s) },
2487
+ { re: /^\d[\d_]*(?:\.\d+)?/, color: (s) => chalk.magenta(s) },
2488
+ { re: /^(?:def|class|import|from|return|if|elif|else|for|while|try|except|finally|with|as|lambda|yield|raise|pass|break|continue|global|nonlocal|assert|async|await|not|and|or|in|is|del)\b/, color: (s) => chalk.yellow(s) },
2489
+ { re: /^(?:True|False|None|self|cls)\b/, color: (s) => chalk.blue(s) },
2490
+ { re: /^[A-Za-z_]\w*(?=\s*\()/, color: (s) => chalk.cyan(s) },
2491
+ { re: /^[A-Z][\w]*/, color: (s) => chalk.blue(s) },
2492
+ { re: /^@\w[\w.]*/, color: (s) => chalk.green(s) }
2493
+ ];
2494
+ SHELL_RULES = [
2495
+ { re: /^#[^\n]*/, color: (s) => chalk.gray(s) },
2496
+ { re: /^(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|unset|cd|exit)\b/, color: (s) => chalk.yellow(s) },
2497
+ { re: /^\$\{[^}]*\}?\$?/, color: (s) => chalk.magenta(s) },
2498
+ { re: /^\$\w*/, color: (s) => chalk.magenta(s) },
2499
+ { re: /^"(?:\\.|[^"\\])*"?/, color: (s) => chalk.green(s) },
2500
+ { re: /^'(?:[^'\\])*'?/, color: (s) => chalk.green(s) },
2501
+ { re: /^\d+/, color: (s) => chalk.magenta(s) },
2502
+ { re: /^(?:npm|pnpm|yarn|node|npx|git|curl|wget|python|python3|pip|cargo|go|make|brew|ls|cat|echo|mkdir|rm|mv|cp|cd|chmod|docker|kubectl)\b/, color: (s) => chalk.cyan(s) }
2503
+ ];
2504
+ JSON_RULES = [
2505
+ { re: /^"(?:\\.|[^"\\])*"(?=\s*:)/, color: (s) => chalk.blue(s) },
2506
+ { re: /^"(?:\\.|[^"\\])*"?/, color: (s) => chalk.green(s) },
2507
+ { re: /^-?\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?/, color: (s) => chalk.magenta(s) },
2508
+ { re: /^(?:true|false|null)\b/, color: (s) => chalk.blue(s) }
2509
+ ];
2510
+ CSS_RULES = [
2511
+ { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
2512
+ { re: /^@[\w-]+/, color: (s) => chalk.yellow(s) },
2513
+ { re: /^[.#]?[\w-]+(?=\s*\{)/, color: (s) => chalk.cyan(s) },
2514
+ { re: /^[\w-]+(?=\s*:)/, color: (s) => chalk.blue(s) },
2515
+ { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
2516
+ { re: /^'(?:[^'\\\n])*'?/, color: (s) => chalk.green(s) },
2517
+ { re: /^-?\d[\d.]*(?:px|em|rem|%|vh|vw|s|ms|fr)?/, color: (s) => chalk.magenta(s) }
2518
+ ];
2519
+ GO_RULES = [
2520
+ { re: /^\/\/[^\n]*/, color: (s) => chalk.gray(s) },
2521
+ { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
2522
+ { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
2523
+ { re: /^`(?:\\.|[^`\\])*`?/, color: (s) => chalk.green(s) },
2524
+ { re: /^\d[\d_]*(?:\.\d+)?/, color: (s) => chalk.magenta(s) },
2525
+ { re: /^(?:package|import|func|return|if|else|for|range|switch|case|default|type|struct|interface|map|chan|go|defer|var|const|select|break|continue|fallthrough)\b/, color: (s) => chalk.yellow(s) },
2526
+ { re: /^[A-Za-z_]\w*(?=\s*\()/, color: (s) => chalk.cyan(s) },
2527
+ { re: /^[A-Z][\w]*/, color: (s) => chalk.blue(s) }
2528
+ ];
2529
+ RUST_RULES = [
2530
+ { re: /^\/\/[^\n]*/, color: (s) => chalk.gray(s) },
2531
+ { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
2532
+ { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
2533
+ { re: /^\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?/, color: (s) => chalk.magenta(s) },
2534
+ { re: /^(?:as|break|const|continue|crate|dyn|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|Self|static|struct|super|trait|true|type|unsafe|use|where|while|async|await)\b/, color: (s) => chalk.yellow(s) },
2535
+ { re: /^&['\u2019]?\w*\b/, color: (s) => chalk.cyan(s) },
2536
+ { re: /^\w+!/, color: (s) => chalk.cyan(s) },
2537
+ { re: /^[A-Za-z_]\w*(?=\s*[<(])/, color: (s) => chalk.cyan(s) },
2538
+ { re: /^[A-Z][\w]*/, color: (s) => chalk.blue(s) }
2539
+ ];
2540
+ RULESETS = {
2541
+ javascript: C_FAMILY,
2542
+ json: JSON_RULES,
2543
+ python: PY_RULES,
2544
+ shell: SHELL_RULES,
2545
+ css: CSS_RULES,
2546
+ go: GO_RULES,
2547
+ rust: RUST_RULES
2548
+ };
2549
+ ALIAS = {
2550
+ js: "javascript",
2551
+ jsx: "javascript",
2552
+ mjs: "javascript",
2553
+ cjs: "javascript",
2554
+ ts: "javascript",
2555
+ tsx: "javascript",
2556
+ typescript: "javascript",
2557
+ py: "python",
2558
+ python3: "python",
2559
+ sh: "shell",
2560
+ bash: "shell",
2561
+ zsh: "shell",
2562
+ console: "shell",
2563
+ shellscript: "shell",
2564
+ golang: "go",
2565
+ rs: "rust",
2566
+ jsonc: "json",
2567
+ json5: "json",
2568
+ less: "css",
2569
+ scss: "css",
2570
+ sass: "css",
2571
+ html: "css",
2572
+ xml: "css",
2573
+ vue: "css",
2574
+ svelte: "css",
2575
+ yaml: "json",
2576
+ yml: "json",
2577
+ toml: "json",
2578
+ ini: "json"
2579
+ };
2580
+ }
2581
+ });
2582
+
2583
+ // src/utils/markdown.ts
2584
+ import { Marked } from "marked";
2585
+ import chalk2 from "chalk";
2586
+ function decodeHtmlEntities(text) {
2587
+ return text.replace(/&(?:#[xX]?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match) => {
2588
+ if (HTML_ENTITIES[match]) return HTML_ENTITIES[match];
2589
+ if (match.startsWith("&#x") || match.startsWith("&#X")) {
2590
+ const code = parseInt(match.slice(3, -1), 16);
2591
+ return isNaN(code) ? match : String.fromCodePoint(code);
2592
+ }
2593
+ if (match.startsWith("&#")) {
2594
+ const code = parseInt(match.slice(2, -1), 10);
2595
+ return isNaN(code) ? match : String.fromCodePoint(code);
2596
+ }
2597
+ return match;
2598
+ });
2599
+ }
2600
+ function renderMarkdown(text) {
2601
+ try {
2602
+ const tokens = lexer.lexer(text);
2603
+ const result = renderTokens(tokens);
2604
+ return decodeHtmlEntities(result.replace(/\n{3,}/g, "\n\n").trimEnd());
2605
+ } catch {
2606
+ return decodeHtmlEntities(text);
2607
+ }
2608
+ }
2609
+ function renderTokens(tokens) {
2610
+ return tokens.map((t) => renderToken(t)).join("");
2611
+ }
2612
+ function renderToken(t) {
2613
+ if (!t || typeof t !== "object") return String(t ?? "");
2614
+ switch (t.type) {
2615
+ case "heading":
2616
+ return renderHeading(t);
2617
+ case "paragraph":
2618
+ return renderInline(t.tokens) + "\n\n";
2619
+ case "strong":
2620
+ return chalk2.bold(renderInline(t.tokens));
2621
+ case "em":
2622
+ return chalk2.italic(renderInline(t.tokens));
2623
+ case "del":
2624
+ return chalk2.dim.strikethrough(renderInline(t.tokens));
2625
+ case "codespan":
2626
+ return chalk2.yellow(t.text);
2627
+ case "code":
2628
+ return renderCodeBlock(t);
2629
+ case "list":
2630
+ return renderList(t);
2631
+ case "blockquote":
2632
+ return renderBlockquote(t);
2633
+ case "hr":
2634
+ return chalk2.dim("\u2500".repeat(50)) + "\n\n";
2635
+ case "link":
2636
+ return `${chalk2.blue.underline(renderInline(t.tokens))} ${chalk2.dim(`(${t.href})`)}`;
2637
+ case "image":
2638
+ return chalk2.blue(`\u{1F5BC} ${t.title || t.href}`);
2639
+ case "table":
2640
+ return renderTable(t);
2641
+ case "text":
2642
+ if (t.tokens) return renderInline(t.tokens);
2643
+ return t.text || "";
2644
+ case "html":
2645
+ return t.text || "";
2646
+ case "space":
2647
+ return "";
2648
+ default:
2649
+ return t.text || "";
2650
+ }
2651
+ }
2652
+ function renderHeading(t) {
2653
+ const text = renderInline(t.tokens);
2654
+ if (t.depth === 1) return `
2655
+ ${chalk2.bold.cyan(text)}
2656
+
2657
+ `;
2658
+ if (t.depth === 2) return `
2659
+ ${chalk2.bold.cyan(` \u25A0 ${text}`)}
2660
+
2661
+ `;
2662
+ return `
2663
+ ${chalk2.bold(` \u25A0 ${text}`)}
2664
+
2665
+ `;
2666
+ }
2667
+ function renderInline(tokens) {
2668
+ if (!tokens) return "";
2669
+ return tokens.map((t) => {
2670
+ if (typeof t === "string") return t;
2671
+ if (t.type === "strong") return chalk2.bold(renderInline(t.tokens));
2672
+ if (t.type === "em") return chalk2.italic(renderInline(t.tokens));
2673
+ if (t.type === "del") return chalk2.dim.strikethrough(renderInline(t.tokens));
2674
+ if (t.type === "codespan") return chalk2.yellow(t.text);
2675
+ if (t.type === "link") return `${chalk2.blue.underline(renderInline(t.tokens))} ${chalk2.dim(`(${t.href})`)}`;
2676
+ if (t.type === "image") return chalk2.blue(`\u{1F5BC} ${t.title || t.href}`);
2677
+ if (t.type === "text") {
2678
+ return t.tokens ? renderInline(t.tokens) : t.text || "";
2679
+ }
2680
+ if (t.type === "html") return t.text || "";
2681
+ return t.text || "";
2682
+ }).join("");
2683
+ }
2684
+ function renderCodeBlock(t) {
2685
+ const lines = highlightCodeBlock(t.text ?? "", t.lang).map((l) => ` ${l}`).join("\n");
2686
+ const langStr = t.lang ? chalk2.dim(` [${t.lang}]`) : "";
2687
+ return `
2688
+ ${langStr}
2689
+ ${lines}
2690
+
2691
+ `;
2692
+ }
2693
+ function renderList(t) {
2694
+ const lines = [];
2695
+ const items = t.items || [];
2696
+ items.forEach((item, i) => {
2697
+ const bullet = t.ordered ? `${i + 1}.` : "\u2022";
2698
+ const firstLine = renderInline(item.tokens?.[0]?.tokens || [{ text: item.text }]);
2699
+ lines.push(` ${chalk2.dim(bullet)} ${firstLine}`);
2700
+ const restTokens = (item.tokens || []).slice(1);
2701
+ for (const sub of restTokens) {
2702
+ if (sub.type === "list") {
2703
+ const subLines = renderList(sub).split("\n").filter(Boolean).map((l) => ` ${l}`).join("\n");
2704
+ lines.push(subLines);
2705
+ } else if (sub.type === "text") {
2706
+ lines.push(` ${chalk2.dim("\u2022")} ${renderInline(sub.tokens)}`);
2707
+ }
2708
+ }
2709
+ });
2710
+ return lines.join("\n") + "\n\n";
2711
+ }
2712
+ function renderBlockquote(t) {
2713
+ const content = renderTokens(t.tokens || []);
2714
+ const lines = content.split("\n").filter((l) => l.trim()).map((l) => `${chalk2.dim("\u2502 ")}${chalk2.gray(l)}`).join("\n");
2715
+ return `
2716
+ ${lines}
2717
+
2718
+ `;
2719
+ }
2720
+ function renderTable(t) {
2721
+ const headers = (t.header || []).map((h) => chalk2.bold(renderInline(h.tokens)));
2722
+ const colWidths = (t.header || []).map((h, i) => {
2723
+ const hLen = (h.text || "").length;
2724
+ const rowLens = (t.rows || []).map((row) => {
2725
+ const cell = row[i];
2726
+ return cell?.text?.length ?? 0;
2727
+ });
2728
+ return Math.max(hLen, ...rowLens) + 2;
2729
+ });
2730
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(chalk2.dim(" \u2502 "));
2731
+ const separator = colWidths.map((w) => "\u2500".repeat(w)).join(chalk2.dim("\u2500\u253C\u2500"));
2732
+ const dataLines = (t.rows || []).map(
2733
+ (row) => row.map((cell, i) => {
2734
+ const text = renderInline(cell.tokens) || cell.text || "";
2735
+ return text.padEnd(colWidths[i]);
2736
+ }).join(chalk2.dim(" \u2502 "))
2737
+ );
2738
+ return `
2739
+ ${headerLine}
2740
+ ${chalk2.dim(separator)}
2741
+ ${dataLines.join("\n")}
2742
+
2743
+ `;
2744
+ }
2745
+ function escapeHtml(text) {
2746
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2747
+ }
2748
+ function mdToTelegram(text) {
2749
+ let out2 = text;
2750
+ const codeBlocks = [];
2751
+ out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
2752
+ const placeholder = `__CODEBLOCK_${codeBlocks.length}__`;
2753
+ codeBlocks.push(`<pre><code class="${lang}">${escapeHtml(code)}</code></pre>`);
2754
+ return placeholder;
2755
+ });
2756
+ const inlineCodes = [];
2757
+ out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
2758
+ const placeholder = `__INLINECODE_${inlineCodes.length}__`;
2759
+ inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
2760
+ return placeholder;
2761
+ });
2762
+ const links = [];
2763
+ out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
2764
+ const placeholder = `__LINK_${links.length}__`;
2765
+ links.push(`<a href="${escapeHtml(href)}">${escapeHtml(label)}</a>`);
2766
+ return placeholder;
2767
+ });
2768
+ out2 = escapeHtml(out2);
2769
+ out2 = out2.replace(/^### (.+)$/gm, "<b><i>$1</i></b>");
2770
+ out2 = out2.replace(/^## (.+)$/gm, "<b>$1</b>");
2771
+ out2 = out2.replace(/^# (.+)$/gm, "<b>$1</b>");
2772
+ out2 = out2.replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>");
2773
+ out2 = out2.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<i>$1</i>");
2774
+ out2 = out2.replace(/~~([^~]+)~~/g, "<s>$1</s>");
2775
+ for (let i = 0; i < inlineCodes.length; i++) {
2776
+ out2 = out2.replace(`__INLINECODE_${i}__`, inlineCodes[i]);
2777
+ }
2778
+ for (let i = 0; i < codeBlocks.length; i++) {
2779
+ out2 = out2.replace(`__CODEBLOCK_${i}__`, codeBlocks[i]);
2780
+ }
2781
+ for (let i = 0; i < links.length; i++) {
2782
+ out2 = out2.replace(`__LINK_${i}__`, links[i]);
2783
+ }
2784
+ if (out2.length > 4096) {
2785
+ out2 = out2.slice(0, 4090) + "...";
2786
+ }
2787
+ return out2;
2788
+ }
2789
+ function mdToSignal(text) {
2790
+ let out2 = text;
2791
+ const codeBlocks = [];
2792
+ out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, _lang, code) => {
2793
+ const placeholder = `__CB_${codeBlocks.length}__`;
2794
+ codeBlocks.push(code.trim());
2795
+ return placeholder;
2796
+ });
2797
+ const inlineCodes = [];
2798
+ out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
2799
+ const placeholder = `__IC_${inlineCodes.length}__`;
2800
+ inlineCodes.push(code);
2801
+ return placeholder;
2802
+ });
2803
+ const links = [];
2804
+ out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
2805
+ const placeholder = `__LK_${links.length}__`;
2806
+ links.push(`${label} (${href})`);
2807
+ return placeholder;
2808
+ });
2809
+ out2 = out2.replace(/^### (.+)$/gm, "*$1*");
2810
+ out2 = out2.replace(/^## (.+)$/gm, "*$1*");
2811
+ out2 = out2.replace(/^# (.+)$/gm, "*$1*");
2812
+ out2 = out2.replace(/\*\*([^*]+)\*\*/g, "*$1*");
2813
+ out2 = out2.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "_$1_");
2814
+ out2 = out2.replace(/~~([^~]+)~~/g, "~$1~");
2815
+ for (let i = 0; i < inlineCodes.length; i++) {
2816
+ out2 = out2.replace(`__IC_${i}__`, inlineCodes[i]);
2817
+ }
2818
+ for (let i = 0; i < codeBlocks.length; i++) {
2819
+ out2 = out2.replace(`__CB_${i}__`, codeBlocks[i]);
2820
+ }
2821
+ for (let i = 0; i < links.length; i++) {
2822
+ out2 = out2.replace(`__LK_${i}__`, links[i]);
2823
+ }
2824
+ if (out2.length > 4e3) {
2825
+ out2 = out2.slice(0, 3990) + "...";
2826
+ }
2827
+ return out2;
2828
+ }
2829
+ function mdToDiscord(text) {
2830
+ let out2 = text;
2831
+ const codeBlocks = [];
2832
+ out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
2833
+ const placeholder = `__DCB_${codeBlocks.length}__`;
2834
+ codeBlocks.push(`\`\`\`${lang}
2835
+ ${code.trim()}
2836
+ \`\`\``);
2837
+ return placeholder;
2838
+ });
2839
+ const inlineCodes = [];
2840
+ out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
2841
+ const placeholder = `__DIC_${inlineCodes.length}__`;
2842
+ inlineCodes.push(`\`${code}\``);
2843
+ return placeholder;
2844
+ });
2845
+ const links = [];
2846
+ out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
2847
+ const placeholder = `__DLK_${links.length}__`;
2848
+ links.push(`[${label}](${href})`);
2849
+ return placeholder;
2850
+ });
2851
+ out2 = out2.replace(/^### (.+)$/gm, "__$1__");
2852
+ out2 = out2.replace(/^## (.+)$/gm, "**$1**");
2853
+ out2 = out2.replace(/^# (.+)$/gm, "**$1**");
2854
+ out2 = out2.replace(/~~([^~]+)~~/g, "~~$1~~");
2855
+ for (let i = 0; i < inlineCodes.length; i++) {
2856
+ out2 = out2.replace(`__DIC_${i}__`, inlineCodes[i]);
2857
+ }
2858
+ for (let i = 0; i < codeBlocks.length; i++) {
2859
+ out2 = out2.replace(`__DCB_${i}__`, codeBlocks[i]);
2860
+ }
2861
+ for (let i = 0; i < links.length; i++) {
2862
+ out2 = out2.replace(`__DLK_${i}__`, links[i]);
2863
+ }
2864
+ if (out2.length > 2e3) {
2865
+ out2 = out2.slice(0, 1990) + "...";
2866
+ }
2867
+ return out2;
2868
+ }
2869
+ function mdToSlack(text) {
2870
+ let out2 = text;
2871
+ const codeBlocks = [];
2872
+ out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
2873
+ const placeholder = `__SCB_${codeBlocks.length}__`;
2874
+ codeBlocks.push(`\`\`\`${lang}
2875
+ ${code.trim()}
2876
+ \`\`\``);
2877
+ return placeholder;
2878
+ });
2879
+ const inlineCodes = [];
2880
+ out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
2881
+ const placeholder = `__SIC_${inlineCodes.length}__`;
2882
+ inlineCodes.push(`\`${code}\``);
2883
+ return placeholder;
2884
+ });
2885
+ const links = [];
2886
+ out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
2887
+ const placeholder = `__SLK_${links.length}__`;
2888
+ links.push(`<${href}|${label}>`);
2889
+ return placeholder;
2890
+ });
2891
+ out2 = out2.replace(/^### (.+)$/gm, "*$1*");
2892
+ out2 = out2.replace(/^## (.+)$/gm, "*$1*");
2893
+ out2 = out2.replace(/^# (.+)$/gm, "*$1*");
2894
+ out2 = out2.replace(/\*\*([^*]+)\*\*/g, "*$1*");
2895
+ out2 = out2.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "_$1_");
2896
+ out2 = out2.replace(/~~([^~]+)~~/g, "~$1~");
2897
+ out2 = out2.replace(/^- /gm, "\u2022 ");
2898
+ for (let i = 0; i < inlineCodes.length; i++) {
2899
+ out2 = out2.replace(`__SIC_${i}__`, inlineCodes[i]);
2900
+ }
2901
+ for (let i = 0; i < codeBlocks.length; i++) {
2902
+ out2 = out2.replace(`__SCB_${i}__`, codeBlocks[i]);
2903
+ }
2904
+ for (let i = 0; i < links.length; i++) {
2905
+ out2 = out2.replace(`__SLK_${i}__`, links[i]);
2906
+ }
2907
+ if (out2.length > 4e4) {
2908
+ out2 = out2.slice(0, 39990) + "...";
2909
+ }
2910
+ return out2;
2911
+ }
2912
+ var lexer, HTML_ENTITIES;
2913
+ var init_markdown = __esm({
2914
+ "src/utils/markdown.ts"() {
2915
+ "use strict";
2916
+ init_highlight();
2917
+ lexer = new Marked();
2918
+ HTML_ENTITIES = {
2919
+ "&amp;": "&",
2920
+ "&lt;": "<",
2921
+ "&gt;": ">",
2922
+ "&quot;": '"',
2923
+ "&#39;": "'",
2924
+ "&#x27;": "'",
2925
+ "&apos;": "'",
2926
+ "&nbsp;": " "
2927
+ };
2928
+ }
2929
+ });
2930
+
2412
2931
  // src/spotify/ui.ts
2413
2932
  var ui_exports = {};
2414
2933
  __export(ui_exports, {
@@ -6296,9 +6815,358 @@ var init_daemon = __esm({
6296
6815
  }
6297
6816
  });
6298
6817
 
6818
+ // src/ui/attach-tui.tsx
6819
+ var attach_tui_exports = {};
6820
+ __export(attach_tui_exports, {
6821
+ AttachTui: () => AttachTui
6822
+ });
6823
+ import React3 from "react";
6824
+ import { Box as Box2, Text as Text2, Spacer as Spacer2, Static as Static2, useApp as useApp2, useInput as useInput2 } from "ink";
6825
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
6826
+ function AttachMessageBlock({ message }) {
6827
+ const isUser = message.role === "user";
6828
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, flexShrink: 0, children: [
6829
+ /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, children: /* @__PURE__ */ jsxs2(Text2, { bold: true, color: isUser ? "yellow" : message.role === "system" ? "gray" : "cyan", children: [
6830
+ isUser ? "YOU" : message.role === "system" ? "\u2014" : "MERCURY",
6831
+ ":"
6832
+ ] }) }),
6833
+ /* @__PURE__ */ jsx2(Box2, { marginLeft: 2, flexDirection: "column", flexShrink: 0, children: (isUser || message.role === "system" ? message.content : renderMarkdown(message.content)).split("\n").map((line, idx) => /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { children: line.length > 0 ? line : " " }) }, `${message.id}:${idx}`)) })
6834
+ ] });
6835
+ }
6836
+ function AttachPromptView({ prompt, activeIdx }) {
6837
+ return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, flexShrink: 0, children: /* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: "yellow", flexDirection: "column", paddingX: 1, flexShrink: 0, children: [
6838
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", bold: true, children: prompt.kind === "continue" ? "Continue? " : prompt.kind === "choice" ? "Choose " : "Permission: " }),
6839
+ /* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: prompt.text }),
6840
+ prompt.options.map((option, idx) => /* @__PURE__ */ jsx2(Box2, { paddingLeft: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: idx === activeIdx ? "cyan" : "gray", children: [
6841
+ idx === activeIdx ? "\u203A " : " ",
6842
+ option.label
6843
+ ] }) }, option.value)),
6844
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2191\u2193 select \xB7 Enter confirm \xB7 Esc decline" })
6845
+ ] }) });
6846
+ }
6847
+ function AttachTui({ client, pid, onExit }) {
6848
+ const { exit } = useApp2();
6849
+ const [phase, setPhase] = React3.useState("pick");
6850
+ const [threads, setThreads] = React3.useState([]);
6851
+ const [threadsLoaded, setThreadsLoaded] = React3.useState(false);
6852
+ const [pickIdx, setPickIdx] = React3.useState(0);
6853
+ const [thread, setThread] = React3.useState(null);
6854
+ const [history, setHistory] = React3.useState([]);
6855
+ const [streamTail, setStreamTail] = React3.useState("");
6856
+ const [streaming, setStreaming] = React3.useState(false);
6857
+ const [steps, setSteps] = React3.useState([]);
6858
+ const [prompt, setPrompt] = React3.useState(null);
6859
+ const [promptIdx, setPromptIdx] = React3.useState(0);
6860
+ const [provider, setProvider] = React3.useState(null);
6861
+ const [notice, setNotice] = React3.useState(null);
6862
+ const [input, setInput] = React3.useState("");
6863
+ const [cursorPos, setCursorPos] = React3.useState(0);
6864
+ React3.useEffect(() => {
6865
+ client.listThreads().then((list) => {
6866
+ setThreads(list.slice(0, 6));
6867
+ setThreadsLoaded(true);
6868
+ }).catch(() => setThreadsLoaded(true));
6869
+ }, [client]);
6870
+ React3.useEffect(() => {
6871
+ if (!thread) return;
6872
+ const controller = new AbortController();
6873
+ const localEcho = (role, content) => {
6874
+ const id = `a_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
6875
+ setHistory((prev) => [...prev, { id, role, content }]);
6876
+ return id;
6877
+ };
6878
+ void client.streamEvents(thread.id, (event) => {
6879
+ switch (event.type) {
6880
+ case "connected":
6881
+ setProvider({
6882
+ name: String(event.data.provider ?? ""),
6883
+ model: String(event.data.model ?? "")
6884
+ });
6885
+ setNotice(null);
6886
+ break;
6887
+ case "thinking":
6888
+ setStreaming(true);
6889
+ break;
6890
+ case "text_delta": {
6891
+ const chunk = String(event.data.text ?? "");
6892
+ if (chunk) setStreamTail((prev) => (prev + chunk).slice(-ATTACH_STREAM_TAIL_CHARS));
6893
+ setStreaming(true);
6894
+ break;
6895
+ }
6896
+ case "text_done": {
6897
+ const fullText = String(event.data.fullText ?? "");
6898
+ if (fullText) localEcho("assistant", fullText);
6899
+ setStreamTail("");
6900
+ setStreaming(false);
6901
+ setSteps([]);
6902
+ break;
6903
+ }
6904
+ case "step_start":
6905
+ setSteps((prev) => [...prev.slice(-(ATTACH_STEP_WINDOW - 1)), {
6906
+ key: `step_${String(event.data.step ?? prev.length)}`,
6907
+ label: String(event.data.label ?? event.data.tool ?? "working"),
6908
+ done: false
6909
+ }]);
6910
+ break;
6911
+ case "step_done":
6912
+ setSteps((prev) => {
6913
+ const next = [...prev];
6914
+ const running = next.findIndex((step) => !step.done);
6915
+ if (running !== -1) next[running] = { ...next[running], done: true };
6916
+ return next.slice(-ATTACH_STEP_WINDOW);
6917
+ });
6918
+ break;
6919
+ case "permission_request":
6920
+ setPrompt({
6921
+ kind: "permission",
6922
+ id: String(event.data.id ?? ""),
6923
+ text: String(event.data.prompt ?? ""),
6924
+ options: (Array.isArray(event.data.options) ? event.data.options : ["yes", "no"]).map((value) => ({ value, label: value }))
6925
+ });
6926
+ setPromptIdx(0);
6927
+ break;
6928
+ case "permission_continue":
6929
+ setPrompt({
6930
+ kind: "continue",
6931
+ id: String(event.data.id ?? ""),
6932
+ text: String(event.data.question ?? ""),
6933
+ options: (Array.isArray(event.data.options) ? event.data.options : ["yes", "no"]).map((value) => ({ value, label: value }))
6934
+ });
6935
+ setPromptIdx(0);
6936
+ break;
6937
+ case "choice_prompt":
6938
+ setPrompt({
6939
+ kind: "choice",
6940
+ id: String(event.data.id ?? ""),
6941
+ text: String(event.data.question ?? ""),
6942
+ options: (Array.isArray(event.data.options) ? event.data.options : []).map((o) => ({ value: o.value, label: o.label ?? o.value }))
6943
+ });
6944
+ setPromptIdx(0);
6945
+ break;
6946
+ case "permission_resolved":
6947
+ case "choice_resolved":
6948
+ setPrompt((current) => current && String(event.data.id ?? "") === current.id ? null : current);
6949
+ break;
6950
+ case "error":
6951
+ setNotice(`\u26A0 ${String(event.data.message ?? "agent error")}`);
6952
+ setStreaming(false);
6953
+ break;
6954
+ case "attach_disconnected":
6955
+ setNotice("connection lost \u2014 reconnecting\u2026");
6956
+ setStreaming(false);
6957
+ break;
6958
+ case "attach_auth_error":
6959
+ setNotice("\u26A0 runtime restarted (attach token rotated) \u2014 run `mercury attach` again");
6960
+ setStreaming(false);
6961
+ break;
6962
+ default:
6963
+ break;
6964
+ }
6965
+ }, controller.signal);
6966
+ return () => controller.abort();
6967
+ }, [client, thread?.id]);
6968
+ const startThread = React3.useCallback(async (picked) => {
6969
+ if (picked) {
6970
+ setThread(picked);
6971
+ const messages = await client.getThread(picked.id);
6972
+ setHistory(messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => ({ id: m.id, role: m.role, content: m.content })));
6973
+ } else {
6974
+ const created = await client.createThread();
6975
+ setThread(created ? { id: created.id, shortId: created.shortId, alias: created.alias, title: created.title } : { id: "", shortId: "", alias: "new session", title: "New session" });
6976
+ }
6977
+ setPhase("chat");
6978
+ }, [client]);
6979
+ const sendInput = React3.useCallback(async (text) => {
6980
+ if (!thread || !text.trim()) return;
6981
+ const id = `a_local_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
6982
+ setHistory((prev) => [...prev, { id, role: "user", content: text }]);
6983
+ setStreaming(true);
6984
+ const result = await client.send(text, thread.id || void 0);
6985
+ if (!result.ok) {
6986
+ setNotice(`\u26A0 send failed: ${result.error ?? "unknown error"}`);
6987
+ setStreaming(false);
6988
+ return;
6989
+ }
6990
+ if (!thread.id && result.sessionId) {
6991
+ setThread({ ...thread, id: result.sessionId, alias: "session", title: "New session" });
6992
+ }
6993
+ }, [client, thread]);
6994
+ const resolvePrompt = React3.useCallback(async (value) => {
6995
+ const active = prompt;
6996
+ if (!active) return;
6997
+ setPrompt(null);
6998
+ await client.resolvePermission(active.id, value);
6999
+ }, [client, prompt]);
7000
+ useInput2((ch, key) => {
7001
+ if (ch === "" || key.ctrl && key.name === "c") {
7002
+ exit();
7003
+ return;
7004
+ }
7005
+ if (phase === "pick") {
7006
+ if (threadsLoaded && threads.length >= 0) {
7007
+ const rows = threads.length + 1;
7008
+ if (key.upArrow) setPickIdx((i) => Math.max(0, i - 1));
7009
+ else if (key.downArrow) setPickIdx((i) => Math.min(rows - 1, i + 1));
7010
+ else if (key.return) {
7011
+ if (pickIdx < threads.length) {
7012
+ void startThread(threads[pickIdx]);
7013
+ } else {
7014
+ void startThread(null);
7015
+ }
7016
+ } else if (key.escape) {
7017
+ exit();
7018
+ }
7019
+ }
7020
+ return;
7021
+ }
7022
+ if (prompt) {
7023
+ const lower = ch?.toLowerCase?.();
7024
+ if (prompt.kind === "permission" && lower === "y") {
7025
+ void resolvePrompt("yes");
7026
+ return;
7027
+ }
7028
+ if (prompt.kind === "permission" && lower === "a") {
7029
+ void resolvePrompt("always");
7030
+ return;
7031
+ }
7032
+ if ((prompt.kind === "permission" || prompt.kind === "continue") && lower === "n") {
7033
+ void resolvePrompt("no");
7034
+ return;
7035
+ }
7036
+ if (key.escape) {
7037
+ void resolvePrompt(prompt.kind === "choice" ? "" : "no");
7038
+ return;
7039
+ }
7040
+ if (key.upArrow) setPromptIdx((i) => Math.max(0, i - 1));
7041
+ else if (key.downArrow) setPromptIdx((i) => Math.min(prompt.options.length - 1, i + 1));
7042
+ else if (key.return) {
7043
+ const option = prompt.options[promptIdx] ?? prompt.options[0];
7044
+ if (option) void resolvePrompt(option.value);
7045
+ }
7046
+ return;
7047
+ }
7048
+ if (key.return) {
7049
+ const trimmed = input.trim();
7050
+ if (trimmed) {
7051
+ setInput("");
7052
+ setCursorPos(0);
7053
+ void sendInput(trimmed);
7054
+ }
7055
+ return;
7056
+ }
7057
+ if (key.leftArrow) {
7058
+ setCursorPos((p) => Math.max(0, p - 1));
7059
+ return;
7060
+ }
7061
+ if (key.rightArrow) {
7062
+ setCursorPos((p) => Math.min(input.length, p + 1));
7063
+ return;
7064
+ }
7065
+ if (key.backspace || key.delete) {
7066
+ if (cursorPos > 0) {
7067
+ setInput((prev) => prev.slice(0, cursorPos - 1) + prev.slice(cursorPos));
7068
+ setCursorPos((p) => p - 1);
7069
+ }
7070
+ return;
7071
+ }
7072
+ if (key.ctrl || key.meta) return;
7073
+ if (ch && ch.length > 0 && !key.escape) {
7074
+ const clean = ch.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "").split("").filter((c) => {
7075
+ const code = c.charCodeAt(0);
7076
+ return code >= 32 && code <= 126 || code >= 160;
7077
+ }).join("");
7078
+ if (clean) {
7079
+ setInput((prev) => prev.slice(0, cursorPos) + clean + prev.slice(cursorPos));
7080
+ setCursorPos((p) => p + clean.length);
7081
+ }
7082
+ }
7083
+ });
7084
+ if (phase === "pick") {
7085
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7086
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "\u26BF Attach to Mercury" }),
7087
+ pid != null && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
7088
+ "runtime PID ",
7089
+ pid,
7090
+ " \xB7 Ctrl+C cancels"
7091
+ ] }),
7092
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
7093
+ !threadsLoaded ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Loading sessions\u2026" }) : [
7094
+ ...threads.map((t, idx) => /* @__PURE__ */ jsxs2(Box2, { children: [
7095
+ /* @__PURE__ */ jsx2(Text2, { color: idx === pickIdx ? "cyan" : "gray", children: idx === pickIdx ? "\u203A " : " " }),
7096
+ /* @__PURE__ */ jsxs2(Text2, { bold: idx === pickIdx, color: idx === pickIdx ? "white" : "gray", children: [
7097
+ t.alias,
7098
+ " [",
7099
+ t.shortId,
7100
+ "]"
7101
+ ] }),
7102
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
7103
+ " ",
7104
+ t.title
7105
+ ] })
7106
+ ] }, t.id)),
7107
+ /* @__PURE__ */ jsxs2(Box2, { children: [
7108
+ /* @__PURE__ */ jsx2(Text2, { color: threads.length === pickIdx ? "cyan" : "gray", children: threads.length === pickIdx ? "\u203A " : " " }),
7109
+ /* @__PURE__ */ jsx2(Text2, { bold: threads.length === pickIdx, color: threads.length === pickIdx ? "white" : "gray", children: "Start a new session" })
7110
+ ] }, "__new__")
7111
+ ],
7112
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2191\u2193 select \xB7 Enter attach" })
7113
+ ] });
7114
+ }
7115
+ const providerLabel = provider && provider.name ? `${provider.name}${provider.model ? ` ${provider.model}` : ""}` : "connected";
7116
+ const tailLines = streamTail.length > 0 ? streamTail.split("\n").slice(-ATTACH_STREAM_TAIL_MAX_LINES) : [];
7117
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexShrink: 0, children: [
7118
+ /* @__PURE__ */ jsx2(Static2, { items: history, itemKey: attachItemKey, children: (message) => /* @__PURE__ */ jsx2(AttachMessageBlock, { message }, message.id) }),
7119
+ tailLines.length > 0 && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", flexShrink: 0, children: tailLines.map((line, idx) => /* @__PURE__ */ jsxs2(Box2, { paddingX: 2, children: [
7120
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "\u2502 " }),
7121
+ /* @__PURE__ */ jsx2(Text2, { children: line || " " })
7122
+ ] }, `t:${idx}`)) }),
7123
+ steps.length > 0 && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingX: 2, flexShrink: 0, children: steps.map((step) => /* @__PURE__ */ jsxs2(Box2, { children: [
7124
+ /* @__PURE__ */ jsx2(Text2, { color: step.done ? "green" : "cyan", children: step.done ? "\u2713" : "\u2192" }),
7125
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
7126
+ " ",
7127
+ step.label
7128
+ ] })
7129
+ ] }, step.key)) }),
7130
+ streaming && tailLines.length === 0 && steps.length === 0 && /* @__PURE__ */ jsxs2(Box2, { paddingX: 2, flexShrink: 0, children: [
7131
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "\u280B" }),
7132
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " working\u2026" })
7133
+ ] }),
7134
+ prompt && /* @__PURE__ */ jsx2(AttachPromptView, { prompt, activeIdx: promptIdx }),
7135
+ notice && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: notice }) }),
7136
+ /* @__PURE__ */ jsx2(Box2, { paddingX: 2, flexShrink: 0, children: /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: "gray", flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsxs2(Box2, { children: [
7137
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "> " }),
7138
+ /* @__PURE__ */ jsx2(Text2, { children: input.slice(0, cursorPos) }),
7139
+ /* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorPos < input.length ? input[cursorPos] : " " }),
7140
+ /* @__PURE__ */ jsx2(Text2, { children: input.slice(cursorPos + 1) })
7141
+ ] }) }) }),
7142
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 3, flexShrink: 0, children: [
7143
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u21B5 send \xB7 esc esc exit \xB7 ctrl+c detach" }),
7144
+ /* @__PURE__ */ jsx2(Spacer2, {}),
7145
+ /* @__PURE__ */ jsxs2(Text2, { color: "blue", wrap: "truncate-end", children: [
7146
+ "\u26BF ",
7147
+ thread ? `${thread.alias} [${thread.shortId}]` : "",
7148
+ " \xB7 ",
7149
+ providerLabel,
7150
+ pid != null ? ` \xB7 PID ${pid}` : ""
7151
+ ] })
7152
+ ] })
7153
+ ] });
7154
+ }
7155
+ var ATTACH_STREAM_TAIL_CHARS, ATTACH_STREAM_TAIL_MAX_LINES, ATTACH_STEP_WINDOW, attachItemKey;
7156
+ var init_attach_tui = __esm({
7157
+ "src/ui/attach-tui.tsx"() {
7158
+ "use strict";
7159
+ init_markdown();
7160
+ ATTACH_STREAM_TAIL_CHARS = 8 * 1024;
7161
+ ATTACH_STREAM_TAIL_MAX_LINES = 12;
7162
+ ATTACH_STEP_WINDOW = 4;
7163
+ attachItemKey = (message) => message.id;
7164
+ }
7165
+ });
7166
+
6299
7167
  // src/cli/service.ts
6300
- import { existsSync as existsSync33, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22, unlinkSync as unlinkSync11 } from "fs";
6301
- import { join as join24 } from "path";
7168
+ import { existsSync as existsSync34, mkdirSync as mkdirSync23, writeFileSync as writeFileSync23, unlinkSync as unlinkSync11 } from "fs";
7169
+ import { join as join25 } from "path";
6302
7170
  import { homedir as homedir8 } from "os";
6303
7171
  import chalk8 from "chalk";
6304
7172
  import { execSync as execSync8 } from "child_process";
@@ -6306,9 +7174,9 @@ function isServiceInstalled() {
6306
7174
  const platform = process.platform;
6307
7175
  if (isTermux()) return false;
6308
7176
  if (platform === "darwin") {
6309
- return existsSync33(join24(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist"));
7177
+ return existsSync34(join25(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist"));
6310
7178
  } else if (platform === "linux") {
6311
- return existsSync33(join24(homedir8(), ".config", "systemd", "user", "mercury.service"));
7179
+ return existsSync34(join25(homedir8(), ".config", "systemd", "user", "mercury.service"));
6312
7180
  } else if (platform === "win32") {
6313
7181
  try {
6314
7182
  execSync8(`schtasks /query /tn "${WIN_TASK_NAME}"`, { stdio: "pipe", shell: "cmd.exe" });
@@ -6344,9 +7212,9 @@ function getNodeBinPath() {
6344
7212
  }
6345
7213
  function getDistPath() {
6346
7214
  if (!process.argv[1]) {
6347
- return join24(homedir8(), ".nvm", "versions", "node", `v${process.version.slice(1)}`, "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
7215
+ return join25(homedir8(), ".nvm", "versions", "node", `v${process.version.slice(1)}`, "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
6348
7216
  }
6349
- return join24(process.argv[1], "..", "..", "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
7217
+ return join25(process.argv[1], "..", "..", "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
6350
7218
  }
6351
7219
  function getServiceLaunchArgs() {
6352
7220
  if (isStandaloneBinary()) {
@@ -6409,7 +7277,7 @@ function restartService() {
6409
7277
  try {
6410
7278
  execSync8(`launchctl kickstart -k ${target}`, { stdio: "inherit" });
6411
7279
  } catch {
6412
- const plistPath = join24(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
7280
+ const plistPath = join25(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
6413
7281
  execSync8(`launchctl load "${plistPath}"`, { stdio: "inherit" });
6414
7282
  execSync8(`launchctl kickstart -k ${target}`, { stdio: "inherit" });
6415
7283
  }
@@ -6471,16 +7339,16 @@ function showTermuxServiceHelp(action) {
6471
7339
  console.log("");
6472
7340
  }
6473
7341
  function installMac() {
6474
- const plistDir = join24(homedir8(), "Library", "LaunchAgents");
6475
- const plistPath = join24(plistDir, "com.cosmicstack.mercury.plist");
6476
- if (!existsSync33(plistDir)) {
6477
- mkdirSync22(plistDir, { recursive: true });
7342
+ const plistDir = join25(homedir8(), "Library", "LaunchAgents");
7343
+ const plistPath = join25(plistDir, "com.cosmicstack.mercury.plist");
7344
+ if (!existsSync34(plistDir)) {
7345
+ mkdirSync23(plistDir, { recursive: true });
6478
7346
  }
6479
7347
  const nodeBin = getNodeBinPath();
6480
7348
  const scriptPath = getDistPath();
6481
7349
  const home = getMercuryHome();
6482
- const logPath2 = join24(home, "daemon.log");
6483
- const errPath = join24(home, "daemon-error.log");
7350
+ const logPath2 = join25(home, "daemon.log");
7351
+ const errPath = join25(home, "daemon-error.log");
6484
7352
  const launchArgs = getServiceLaunchArgs();
6485
7353
  const programArgsXml = launchArgs.map((a) => ` <string>${a}</string>`).join("\n");
6486
7354
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
@@ -6515,7 +7383,7 @@ ${programArgsXml}
6515
7383
  <string>${homedir8()}</string>
6516
7384
  </dict>
6517
7385
  </plist>`;
6518
- writeFileSync22(plistPath, plist, "utf-8");
7386
+ writeFileSync23(plistPath, plist, "utf-8");
6519
7387
  try {
6520
7388
  execSync8(`launchctl load ${plistPath}`, { stdio: "inherit" });
6521
7389
  } catch {
@@ -6532,8 +7400,8 @@ ${programArgsXml}
6532
7400
  console.log("");
6533
7401
  }
6534
7402
  function uninstallMac() {
6535
- const plistPath = join24(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
6536
- if (!existsSync33(plistPath)) {
7403
+ const plistPath = join25(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
7404
+ if (!existsSync34(plistPath)) {
6537
7405
  console.log(chalk8.yellow(" Mercury service is not installed."));
6538
7406
  console.log("");
6539
7407
  process.exit(0);
@@ -6553,8 +7421,8 @@ function uninstallMac() {
6553
7421
  console.log("");
6554
7422
  }
6555
7423
  function showMacStatus() {
6556
- const plistPath = join24(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
6557
- if (!existsSync33(plistPath)) {
7424
+ const plistPath = join25(homedir8(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
7425
+ if (!existsSync34(plistPath)) {
6558
7426
  console.log(chalk8.yellow(" Mercury service is not installed."));
6559
7427
  console.log(chalk8.dim(" Run `mercury service install` to set it up."));
6560
7428
  console.log("");
@@ -6571,11 +7439,11 @@ function showMacStatus() {
6571
7439
  console.log("");
6572
7440
  }
6573
7441
  function installLinux() {
6574
- const systemdDir = join24(homedir8(), ".config", "systemd", "user");
6575
- if (!existsSync33(systemdDir)) {
6576
- mkdirSync22(systemdDir, { recursive: true });
7442
+ const systemdDir = join25(homedir8(), ".config", "systemd", "user");
7443
+ if (!existsSync34(systemdDir)) {
7444
+ mkdirSync23(systemdDir, { recursive: true });
6577
7445
  }
6578
- const servicePath = join24(systemdDir, "mercury.service");
7446
+ const servicePath = join25(systemdDir, "mercury.service");
6579
7447
  const nodeBin = getNodeBinPath();
6580
7448
  const scriptPath = getDistPath();
6581
7449
  const home = getMercuryHome();
@@ -6592,12 +7460,12 @@ RestartSec=5
6592
7460
  Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
6593
7461
  Environment=HOME=${homedir8()}
6594
7462
  WorkingDirectory=${homedir8()}
6595
- StandardOutput=append:${join24(home, "daemon.log")}
6596
- StandardError=append:${join24(home, "daemon-error.log")}
7463
+ StandardOutput=append:${join25(home, "daemon.log")}
7464
+ StandardError=append:${join25(home, "daemon-error.log")}
6597
7465
 
6598
7466
  [Install]
6599
7467
  WantedBy=default.target`;
6600
- writeFileSync22(servicePath, service, "utf-8");
7468
+ writeFileSync23(servicePath, service, "utf-8");
6601
7469
  try {
6602
7470
  execSync8("systemctl --user daemon-reload", { stdio: "inherit" });
6603
7471
  execSync8("systemctl --user enable mercury.service", { stdio: "inherit" });
@@ -6617,15 +7485,15 @@ WantedBy=default.target`;
6617
7485
  console.log("");
6618
7486
  console.log(chalk8.green(" Mercury service installed (systemd --user)"));
6619
7487
  console.log(chalk8.dim(` Service: ${servicePath}`));
6620
- console.log(chalk8.dim(` Logs: ${join24(home, "daemon.log")}`));
7488
+ console.log(chalk8.dim(` Logs: ${join25(home, "daemon.log")}`));
6621
7489
  console.log(chalk8.dim(" Auto-starts on login. Auto-restarts on crash (5s delay)."));
6622
7490
  console.log("");
6623
7491
  console.log(chalk8.dim(" Uninstall: mercury service uninstall"));
6624
7492
  console.log("");
6625
7493
  }
6626
7494
  function uninstallLinux() {
6627
- const servicePath = join24(homedir8(), ".config", "systemd", "user", "mercury.service");
6628
- if (!existsSync33(servicePath)) {
7495
+ const servicePath = join25(homedir8(), ".config", "systemd", "user", "mercury.service");
7496
+ if (!existsSync34(servicePath)) {
6629
7497
  console.log(chalk8.yellow(" Mercury service is not installed."));
6630
7498
  console.log("");
6631
7499
  process.exit(0);
@@ -6650,8 +7518,8 @@ function uninstallLinux() {
6650
7518
  console.log("");
6651
7519
  }
6652
7520
  function showLinuxStatus() {
6653
- const servicePath = join24(homedir8(), ".config", "systemd", "user", "mercury.service");
6654
- if (!existsSync33(servicePath)) {
7521
+ const servicePath = join25(homedir8(), ".config", "systemd", "user", "mercury.service");
7522
+ if (!existsSync34(servicePath)) {
6655
7523
  console.log(chalk8.yellow(" Mercury service is not installed."));
6656
7524
  console.log(chalk8.dim(" Run `mercury service install` to set it up."));
6657
7525
  console.log("");
@@ -6670,7 +7538,7 @@ function installWindows() {
6670
7538
  const nodeBin = getNodeBinPath();
6671
7539
  const scriptPath = getDistPath();
6672
7540
  const home = getMercuryHome();
6673
- const logPath2 = join24(home, "daemon.log");
7541
+ const logPath2 = join25(home, "daemon.log");
6674
7542
  const cmd = getServiceLaunchArgs().map((a) => `"${a}"`).join(" ");
6675
7543
  try {
6676
7544
  execSync8(
@@ -6735,21 +7603,21 @@ var init_service = __esm({
6735
7603
  });
6736
7604
 
6737
7605
  // src/cloud/runtime-status.ts
6738
- import { existsSync as existsSync34, readFileSync as readFileSync23, unlinkSync as unlinkSync12, writeFileSync as writeFileSync23 } from "fs";
6739
- import { join as join25 } from "path";
7606
+ import { existsSync as existsSync35, readFileSync as readFileSync24, unlinkSync as unlinkSync12, writeFileSync as writeFileSync24 } from "fs";
7607
+ import { join as join26 } from "path";
6740
7608
  function statusPath() {
6741
- return join25(getMercuryHome(), "cloud-online.json");
7609
+ return join26(getMercuryHome(), "cloud-online.json");
6742
7610
  }
6743
7611
  function markCloudRuntimeOnline(agentId, mode) {
6744
- writeFileSync23(statusPath(), JSON.stringify({ agentId, mode, pid: process.pid, connectedAt: Date.now() }), {
7612
+ writeFileSync24(statusPath(), JSON.stringify({ agentId, mode, pid: process.pid, connectedAt: Date.now() }), {
6745
7613
  encoding: "utf-8",
6746
7614
  mode: 384
6747
7615
  });
6748
7616
  }
6749
7617
  function clearCloudRuntimeOnline(ownerPid) {
6750
- if (ownerPid !== void 0 && existsSync34(statusPath())) {
7618
+ if (ownerPid !== void 0 && existsSync35(statusPath())) {
6751
7619
  try {
6752
- const status2 = JSON.parse(readFileSync23(statusPath(), "utf-8"));
7620
+ const status2 = JSON.parse(readFileSync24(statusPath(), "utf-8"));
6753
7621
  if (status2.pid !== ownerPid) return;
6754
7622
  } catch {
6755
7623
  return;
@@ -6761,9 +7629,9 @@ function clearCloudRuntimeOnline(ownerPid) {
6761
7629
  }
6762
7630
  }
6763
7631
  function isCloudRuntimeOnline(agentId, mode) {
6764
- if (!existsSync34(statusPath())) return false;
7632
+ if (!existsSync35(statusPath())) return false;
6765
7633
  try {
6766
- const status2 = JSON.parse(readFileSync23(statusPath(), "utf-8"));
7634
+ const status2 = JSON.parse(readFileSync24(statusPath(), "utf-8"));
6767
7635
  if (status2.agentId !== agentId || !status2.pid || mode && status2.mode !== mode) return false;
6768
7636
  process.kill(status2.pid, 0);
6769
7637
  return true;
@@ -13239,517 +14107,11 @@ function formatNarrative(steps, current, maxVisible) {
13239
14107
  }
13240
14108
 
13241
14109
  // src/ui/App.tsx
14110
+ init_markdown();
14111
+ init_highlight();
13242
14112
  import React, { useSyncExternalStore } from "react";
13243
14113
  import { Box, Text, Spacer, Static, useApp, useInput, useStdout } from "ink";
13244
14114
 
13245
- // src/utils/markdown.ts
13246
- import { Marked } from "marked";
13247
- import chalk2 from "chalk";
13248
-
13249
- // src/utils/highlight.ts
13250
- import chalk from "chalk";
13251
- var CONTROL = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
13252
- function esc(s) {
13253
- return s.replace(CONTROL, "");
13254
- }
13255
- var C_FAMILY = [
13256
- { re: /^\/\/[^\n]*/, color: (s) => chalk.gray(s) },
13257
- { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
13258
- { re: /^`(?:\\.|[^`\\])*`?/, color: (s) => chalk.green(s) },
13259
- { re: /^'(?:\\.|[^'\\\n])*'?/, color: (s) => chalk.green(s) },
13260
- { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
13261
- { re: /^-?\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?/, color: (s) => chalk.magenta(s) },
13262
- { re: /^(?:abstract|as|break|case|catch|class|const|continue|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|namespace|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|yield|async|await)\b/, color: (s) => chalk.yellow(s) },
13263
- { re: /^(?:true|false|null|unique|undefined|NaN|Infinity)\b/, color: (s) => chalk.blue(s) },
13264
- { re: /^[A-Za-z_$][\w$]*(?=\s*\()/, color: (s) => chalk.cyan(s) },
13265
- { re: /^[A-Z][\w$]*/, color: (s) => chalk.blue(s) }
13266
- ];
13267
- var PY_RULES = [
13268
- { re: /^#[^\n]*/, color: (s) => chalk.gray(s) },
13269
- { re: /^"""[\s\S]*?("""|$)/, color: (s) => chalk.gray(s) },
13270
- { re: /^'''[\s\S]*?('''|$)/, color: (s) => chalk.gray(s) },
13271
- { re: /^f?"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
13272
- { re: /^f?'(?:\\.|[^'\\\n])*'?/, color: (s) => chalk.green(s) },
13273
- { re: /^\d[\d_]*(?:\.\d+)?/, color: (s) => chalk.magenta(s) },
13274
- { re: /^(?:def|class|import|from|return|if|elif|else|for|while|try|except|finally|with|as|lambda|yield|raise|pass|break|continue|global|nonlocal|assert|async|await|not|and|or|in|is|del)\b/, color: (s) => chalk.yellow(s) },
13275
- { re: /^(?:True|False|None|self|cls)\b/, color: (s) => chalk.blue(s) },
13276
- { re: /^[A-Za-z_]\w*(?=\s*\()/, color: (s) => chalk.cyan(s) },
13277
- { re: /^[A-Z][\w]*/, color: (s) => chalk.blue(s) },
13278
- { re: /^@\w[\w.]*/, color: (s) => chalk.green(s) }
13279
- ];
13280
- var SHELL_RULES = [
13281
- { re: /^#[^\n]*/, color: (s) => chalk.gray(s) },
13282
- { re: /^(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|unset|cd|exit)\b/, color: (s) => chalk.yellow(s) },
13283
- { re: /^\$\{[^}]*\}?\$?/, color: (s) => chalk.magenta(s) },
13284
- { re: /^\$\w*/, color: (s) => chalk.magenta(s) },
13285
- { re: /^"(?:\\.|[^"\\])*"?/, color: (s) => chalk.green(s) },
13286
- { re: /^'(?:[^'\\])*'?/, color: (s) => chalk.green(s) },
13287
- { re: /^\d+/, color: (s) => chalk.magenta(s) },
13288
- { re: /^(?:npm|pnpm|yarn|node|npx|git|curl|wget|python|python3|pip|cargo|go|make|brew|ls|cat|echo|mkdir|rm|mv|cp|cd|chmod|docker|kubectl)\b/, color: (s) => chalk.cyan(s) }
13289
- ];
13290
- var JSON_RULES = [
13291
- { re: /^"(?:\\.|[^"\\])*"(?=\s*:)/, color: (s) => chalk.blue(s) },
13292
- { re: /^"(?:\\.|[^"\\])*"?/, color: (s) => chalk.green(s) },
13293
- { re: /^-?\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?/, color: (s) => chalk.magenta(s) },
13294
- { re: /^(?:true|false|null)\b/, color: (s) => chalk.blue(s) }
13295
- ];
13296
- var CSS_RULES = [
13297
- { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
13298
- { re: /^@[\w-]+/, color: (s) => chalk.yellow(s) },
13299
- { re: /^[.#]?[\w-]+(?=\s*\{)/, color: (s) => chalk.cyan(s) },
13300
- { re: /^[\w-]+(?=\s*:)/, color: (s) => chalk.blue(s) },
13301
- { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
13302
- { re: /^'(?:[^'\\\n])*'?/, color: (s) => chalk.green(s) },
13303
- { re: /^-?\d[\d.]*(?:px|em|rem|%|vh|vw|s|ms|fr)?/, color: (s) => chalk.magenta(s) }
13304
- ];
13305
- var GO_RULES = [
13306
- { re: /^\/\/[^\n]*/, color: (s) => chalk.gray(s) },
13307
- { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
13308
- { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
13309
- { re: /^`(?:\\.|[^`\\])*`?/, color: (s) => chalk.green(s) },
13310
- { re: /^\d[\d_]*(?:\.\d+)?/, color: (s) => chalk.magenta(s) },
13311
- { re: /^(?:package|import|func|return|if|else|for|range|switch|case|default|type|struct|interface|map|chan|go|defer|var|const|select|break|continue|fallthrough)\b/, color: (s) => chalk.yellow(s) },
13312
- { re: /^[A-Za-z_]\w*(?=\s*\()/, color: (s) => chalk.cyan(s) },
13313
- { re: /^[A-Z][\w]*/, color: (s) => chalk.blue(s) }
13314
- ];
13315
- var RUST_RULES = [
13316
- { re: /^\/\/[^\n]*/, color: (s) => chalk.gray(s) },
13317
- { re: /^\/\*[\s\S]*?\*\//, color: (s) => chalk.gray(s) },
13318
- { re: /^"(?:\\.|[^"\\\n])*"?/, color: (s) => chalk.green(s) },
13319
- { re: /^\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?/, color: (s) => chalk.magenta(s) },
13320
- { re: /^(?:as|break|const|continue|crate|dyn|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|Self|static|struct|super|trait|true|type|unsafe|use|where|while|async|await)\b/, color: (s) => chalk.yellow(s) },
13321
- { re: /^&['\u2019]?\w*\b/, color: (s) => chalk.cyan(s) },
13322
- { re: /^\w+!/, color: (s) => chalk.cyan(s) },
13323
- { re: /^[A-Za-z_]\w*(?=\s*[<(])/, color: (s) => chalk.cyan(s) },
13324
- { re: /^[A-Z][\w]*/, color: (s) => chalk.blue(s) }
13325
- ];
13326
- var RULESETS = {
13327
- javascript: C_FAMILY,
13328
- json: JSON_RULES,
13329
- python: PY_RULES,
13330
- shell: SHELL_RULES,
13331
- css: CSS_RULES,
13332
- go: GO_RULES,
13333
- rust: RUST_RULES
13334
- };
13335
- var ALIAS = {
13336
- js: "javascript",
13337
- jsx: "javascript",
13338
- mjs: "javascript",
13339
- cjs: "javascript",
13340
- ts: "javascript",
13341
- tsx: "javascript",
13342
- typescript: "javascript",
13343
- py: "python",
13344
- python3: "python",
13345
- sh: "shell",
13346
- bash: "shell",
13347
- zsh: "shell",
13348
- console: "shell",
13349
- shellscript: "shell",
13350
- golang: "go",
13351
- rs: "rust",
13352
- jsonc: "json",
13353
- json5: "json",
13354
- less: "css",
13355
- scss: "css",
13356
- sass: "css",
13357
- html: "css",
13358
- xml: "css",
13359
- vue: "css",
13360
- svelte: "css",
13361
- yaml: "json",
13362
- yml: "json",
13363
- toml: "json",
13364
- ini: "json"
13365
- };
13366
- function highlightLine(line, lang, opts) {
13367
- if (opts?.uniform) {
13368
- return opts.uniform === "red" ? chalk.red(line) : chalk.green(line);
13369
- }
13370
- try {
13371
- const ruleset = RULESETS[ALIAS[lang?.toLowerCase() ?? ""] ?? lang?.toLowerCase() ?? ""] ?? C_FAMILY;
13372
- let rest = esc(line);
13373
- let out2 = "";
13374
- let guard = 0;
13375
- while (rest.length > 0 && guard++ < 400) {
13376
- let matched = false;
13377
- for (const rule of ruleset) {
13378
- const m = rule.re.exec(rest);
13379
- if (m && m[0].length > 0) {
13380
- out2 += rule.color(m[0]);
13381
- rest = rest.slice(m[0].length);
13382
- matched = true;
13383
- break;
13384
- }
13385
- }
13386
- if (!matched) {
13387
- out2 += rest[0];
13388
- rest = rest.slice(1);
13389
- }
13390
- }
13391
- if (rest.length > 0) out2 += rest;
13392
- return out2;
13393
- } catch {
13394
- return line;
13395
- }
13396
- }
13397
- function highlightDiffLine(line) {
13398
- if (line.startsWith("+++") || line.startsWith("---")) return chalk.blue(line);
13399
- if (line.startsWith("+++") || line.startsWith("---")) return chalk.blue(line);
13400
- if (line.startsWith("diff ")) return chalk.bold.blue(line);
13401
- if (line.startsWith("@@")) return chalk.cyan(line);
13402
- if (line.startsWith("+")) return chalk.green(line);
13403
- if (line.startsWith("-")) return chalk.red(line);
13404
- return line;
13405
- }
13406
- function highlightCodeBlock(body, lang) {
13407
- const trimmedLang = (lang || "").trim().toLowerCase();
13408
- if (trimmedLang === "diff" || trimmedLang === "patch" || /^(diff --git|--- a\/|\+\+\+ b\/)/m.test(body)) {
13409
- return body.split("\n").map(highlightDiffLine);
13410
- }
13411
- return body.split("\n").map((l) => highlightLine(l, trimmedLang));
13412
- }
13413
-
13414
- // src/utils/markdown.ts
13415
- var lexer = new Marked();
13416
- var HTML_ENTITIES = {
13417
- "&amp;": "&",
13418
- "&lt;": "<",
13419
- "&gt;": ">",
13420
- "&quot;": '"',
13421
- "&#39;": "'",
13422
- "&#x27;": "'",
13423
- "&apos;": "'",
13424
- "&nbsp;": " "
13425
- };
13426
- function decodeHtmlEntities(text) {
13427
- return text.replace(/&(?:#[xX]?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match) => {
13428
- if (HTML_ENTITIES[match]) return HTML_ENTITIES[match];
13429
- if (match.startsWith("&#x") || match.startsWith("&#X")) {
13430
- const code = parseInt(match.slice(3, -1), 16);
13431
- return isNaN(code) ? match : String.fromCodePoint(code);
13432
- }
13433
- if (match.startsWith("&#")) {
13434
- const code = parseInt(match.slice(2, -1), 10);
13435
- return isNaN(code) ? match : String.fromCodePoint(code);
13436
- }
13437
- return match;
13438
- });
13439
- }
13440
- function renderMarkdown(text) {
13441
- try {
13442
- const tokens = lexer.lexer(text);
13443
- const result = renderTokens(tokens);
13444
- return decodeHtmlEntities(result.replace(/\n{3,}/g, "\n\n").trimEnd());
13445
- } catch {
13446
- return decodeHtmlEntities(text);
13447
- }
13448
- }
13449
- function renderTokens(tokens) {
13450
- return tokens.map((t) => renderToken(t)).join("");
13451
- }
13452
- function renderToken(t) {
13453
- if (!t || typeof t !== "object") return String(t ?? "");
13454
- switch (t.type) {
13455
- case "heading":
13456
- return renderHeading(t);
13457
- case "paragraph":
13458
- return renderInline(t.tokens) + "\n\n";
13459
- case "strong":
13460
- return chalk2.bold(renderInline(t.tokens));
13461
- case "em":
13462
- return chalk2.italic(renderInline(t.tokens));
13463
- case "del":
13464
- return chalk2.dim.strikethrough(renderInline(t.tokens));
13465
- case "codespan":
13466
- return chalk2.yellow(t.text);
13467
- case "code":
13468
- return renderCodeBlock(t);
13469
- case "list":
13470
- return renderList(t);
13471
- case "blockquote":
13472
- return renderBlockquote(t);
13473
- case "hr":
13474
- return chalk2.dim("\u2500".repeat(50)) + "\n\n";
13475
- case "link":
13476
- return `${chalk2.blue.underline(renderInline(t.tokens))} ${chalk2.dim(`(${t.href})`)}`;
13477
- case "image":
13478
- return chalk2.blue(`\u{1F5BC} ${t.title || t.href}`);
13479
- case "table":
13480
- return renderTable(t);
13481
- case "text":
13482
- if (t.tokens) return renderInline(t.tokens);
13483
- return t.text || "";
13484
- case "html":
13485
- return t.text || "";
13486
- case "space":
13487
- return "";
13488
- default:
13489
- return t.text || "";
13490
- }
13491
- }
13492
- function renderHeading(t) {
13493
- const text = renderInline(t.tokens);
13494
- if (t.depth === 1) return `
13495
- ${chalk2.bold.cyan(text)}
13496
-
13497
- `;
13498
- if (t.depth === 2) return `
13499
- ${chalk2.bold.cyan(` \u25A0 ${text}`)}
13500
-
13501
- `;
13502
- return `
13503
- ${chalk2.bold(` \u25A0 ${text}`)}
13504
-
13505
- `;
13506
- }
13507
- function renderInline(tokens) {
13508
- if (!tokens) return "";
13509
- return tokens.map((t) => {
13510
- if (typeof t === "string") return t;
13511
- if (t.type === "strong") return chalk2.bold(renderInline(t.tokens));
13512
- if (t.type === "em") return chalk2.italic(renderInline(t.tokens));
13513
- if (t.type === "del") return chalk2.dim.strikethrough(renderInline(t.tokens));
13514
- if (t.type === "codespan") return chalk2.yellow(t.text);
13515
- if (t.type === "link") return `${chalk2.blue.underline(renderInline(t.tokens))} ${chalk2.dim(`(${t.href})`)}`;
13516
- if (t.type === "image") return chalk2.blue(`\u{1F5BC} ${t.title || t.href}`);
13517
- if (t.type === "text") {
13518
- return t.tokens ? renderInline(t.tokens) : t.text || "";
13519
- }
13520
- if (t.type === "html") return t.text || "";
13521
- return t.text || "";
13522
- }).join("");
13523
- }
13524
- function renderCodeBlock(t) {
13525
- const lines = highlightCodeBlock(t.text ?? "", t.lang).map((l) => ` ${l}`).join("\n");
13526
- const langStr = t.lang ? chalk2.dim(` [${t.lang}]`) : "";
13527
- return `
13528
- ${langStr}
13529
- ${lines}
13530
-
13531
- `;
13532
- }
13533
- function renderList(t) {
13534
- const lines = [];
13535
- const items = t.items || [];
13536
- items.forEach((item, i) => {
13537
- const bullet = t.ordered ? `${i + 1}.` : "\u2022";
13538
- const firstLine = renderInline(item.tokens?.[0]?.tokens || [{ text: item.text }]);
13539
- lines.push(` ${chalk2.dim(bullet)} ${firstLine}`);
13540
- const restTokens = (item.tokens || []).slice(1);
13541
- for (const sub of restTokens) {
13542
- if (sub.type === "list") {
13543
- const subLines = renderList(sub).split("\n").filter(Boolean).map((l) => ` ${l}`).join("\n");
13544
- lines.push(subLines);
13545
- } else if (sub.type === "text") {
13546
- lines.push(` ${chalk2.dim("\u2022")} ${renderInline(sub.tokens)}`);
13547
- }
13548
- }
13549
- });
13550
- return lines.join("\n") + "\n\n";
13551
- }
13552
- function renderBlockquote(t) {
13553
- const content = renderTokens(t.tokens || []);
13554
- const lines = content.split("\n").filter((l) => l.trim()).map((l) => `${chalk2.dim("\u2502 ")}${chalk2.gray(l)}`).join("\n");
13555
- return `
13556
- ${lines}
13557
-
13558
- `;
13559
- }
13560
- function renderTable(t) {
13561
- const headers = (t.header || []).map((h) => chalk2.bold(renderInline(h.tokens)));
13562
- const colWidths = (t.header || []).map((h, i) => {
13563
- const hLen = (h.text || "").length;
13564
- const rowLens = (t.rows || []).map((row) => {
13565
- const cell = row[i];
13566
- return cell?.text?.length ?? 0;
13567
- });
13568
- return Math.max(hLen, ...rowLens) + 2;
13569
- });
13570
- const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(chalk2.dim(" \u2502 "));
13571
- const separator = colWidths.map((w) => "\u2500".repeat(w)).join(chalk2.dim("\u2500\u253C\u2500"));
13572
- const dataLines = (t.rows || []).map(
13573
- (row) => row.map((cell, i) => {
13574
- const text = renderInline(cell.tokens) || cell.text || "";
13575
- return text.padEnd(colWidths[i]);
13576
- }).join(chalk2.dim(" \u2502 "))
13577
- );
13578
- return `
13579
- ${headerLine}
13580
- ${chalk2.dim(separator)}
13581
- ${dataLines.join("\n")}
13582
-
13583
- `;
13584
- }
13585
- function escapeHtml(text) {
13586
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13587
- }
13588
- function mdToTelegram(text) {
13589
- let out2 = text;
13590
- const codeBlocks = [];
13591
- out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
13592
- const placeholder = `__CODEBLOCK_${codeBlocks.length}__`;
13593
- codeBlocks.push(`<pre><code class="${lang}">${escapeHtml(code)}</code></pre>`);
13594
- return placeholder;
13595
- });
13596
- const inlineCodes = [];
13597
- out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
13598
- const placeholder = `__INLINECODE_${inlineCodes.length}__`;
13599
- inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
13600
- return placeholder;
13601
- });
13602
- const links = [];
13603
- out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
13604
- const placeholder = `__LINK_${links.length}__`;
13605
- links.push(`<a href="${escapeHtml(href)}">${escapeHtml(label)}</a>`);
13606
- return placeholder;
13607
- });
13608
- out2 = escapeHtml(out2);
13609
- out2 = out2.replace(/^### (.+)$/gm, "<b><i>$1</i></b>");
13610
- out2 = out2.replace(/^## (.+)$/gm, "<b>$1</b>");
13611
- out2 = out2.replace(/^# (.+)$/gm, "<b>$1</b>");
13612
- out2 = out2.replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>");
13613
- out2 = out2.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<i>$1</i>");
13614
- out2 = out2.replace(/~~([^~]+)~~/g, "<s>$1</s>");
13615
- for (let i = 0; i < inlineCodes.length; i++) {
13616
- out2 = out2.replace(`__INLINECODE_${i}__`, inlineCodes[i]);
13617
- }
13618
- for (let i = 0; i < codeBlocks.length; i++) {
13619
- out2 = out2.replace(`__CODEBLOCK_${i}__`, codeBlocks[i]);
13620
- }
13621
- for (let i = 0; i < links.length; i++) {
13622
- out2 = out2.replace(`__LINK_${i}__`, links[i]);
13623
- }
13624
- if (out2.length > 4096) {
13625
- out2 = out2.slice(0, 4090) + "...";
13626
- }
13627
- return out2;
13628
- }
13629
- function mdToSignal(text) {
13630
- let out2 = text;
13631
- const codeBlocks = [];
13632
- out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, _lang, code) => {
13633
- const placeholder = `__CB_${codeBlocks.length}__`;
13634
- codeBlocks.push(code.trim());
13635
- return placeholder;
13636
- });
13637
- const inlineCodes = [];
13638
- out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
13639
- const placeholder = `__IC_${inlineCodes.length}__`;
13640
- inlineCodes.push(code);
13641
- return placeholder;
13642
- });
13643
- const links = [];
13644
- out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
13645
- const placeholder = `__LK_${links.length}__`;
13646
- links.push(`${label} (${href})`);
13647
- return placeholder;
13648
- });
13649
- out2 = out2.replace(/^### (.+)$/gm, "*$1*");
13650
- out2 = out2.replace(/^## (.+)$/gm, "*$1*");
13651
- out2 = out2.replace(/^# (.+)$/gm, "*$1*");
13652
- out2 = out2.replace(/\*\*([^*]+)\*\*/g, "*$1*");
13653
- out2 = out2.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "_$1_");
13654
- out2 = out2.replace(/~~([^~]+)~~/g, "~$1~");
13655
- for (let i = 0; i < inlineCodes.length; i++) {
13656
- out2 = out2.replace(`__IC_${i}__`, inlineCodes[i]);
13657
- }
13658
- for (let i = 0; i < codeBlocks.length; i++) {
13659
- out2 = out2.replace(`__CB_${i}__`, codeBlocks[i]);
13660
- }
13661
- for (let i = 0; i < links.length; i++) {
13662
- out2 = out2.replace(`__LK_${i}__`, links[i]);
13663
- }
13664
- if (out2.length > 4e3) {
13665
- out2 = out2.slice(0, 3990) + "...";
13666
- }
13667
- return out2;
13668
- }
13669
- function mdToDiscord(text) {
13670
- let out2 = text;
13671
- const codeBlocks = [];
13672
- out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
13673
- const placeholder = `__DCB_${codeBlocks.length}__`;
13674
- codeBlocks.push(`\`\`\`${lang}
13675
- ${code.trim()}
13676
- \`\`\``);
13677
- return placeholder;
13678
- });
13679
- const inlineCodes = [];
13680
- out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
13681
- const placeholder = `__DIC_${inlineCodes.length}__`;
13682
- inlineCodes.push(`\`${code}\``);
13683
- return placeholder;
13684
- });
13685
- const links = [];
13686
- out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
13687
- const placeholder = `__DLK_${links.length}__`;
13688
- links.push(`[${label}](${href})`);
13689
- return placeholder;
13690
- });
13691
- out2 = out2.replace(/^### (.+)$/gm, "__$1__");
13692
- out2 = out2.replace(/^## (.+)$/gm, "**$1**");
13693
- out2 = out2.replace(/^# (.+)$/gm, "**$1**");
13694
- out2 = out2.replace(/~~([^~]+)~~/g, "~~$1~~");
13695
- for (let i = 0; i < inlineCodes.length; i++) {
13696
- out2 = out2.replace(`__DIC_${i}__`, inlineCodes[i]);
13697
- }
13698
- for (let i = 0; i < codeBlocks.length; i++) {
13699
- out2 = out2.replace(`__DCB_${i}__`, codeBlocks[i]);
13700
- }
13701
- for (let i = 0; i < links.length; i++) {
13702
- out2 = out2.replace(`__DLK_${i}__`, links[i]);
13703
- }
13704
- if (out2.length > 2e3) {
13705
- out2 = out2.slice(0, 1990) + "...";
13706
- }
13707
- return out2;
13708
- }
13709
- function mdToSlack(text) {
13710
- let out2 = text;
13711
- const codeBlocks = [];
13712
- out2 = out2.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
13713
- const placeholder = `__SCB_${codeBlocks.length}__`;
13714
- codeBlocks.push(`\`\`\`${lang}
13715
- ${code.trim()}
13716
- \`\`\``);
13717
- return placeholder;
13718
- });
13719
- const inlineCodes = [];
13720
- out2 = out2.replace(/`([^`]+)`/g, (_match, code) => {
13721
- const placeholder = `__SIC_${inlineCodes.length}__`;
13722
- inlineCodes.push(`\`${code}\``);
13723
- return placeholder;
13724
- });
13725
- const links = [];
13726
- out2 = out2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
13727
- const placeholder = `__SLK_${links.length}__`;
13728
- links.push(`<${href}|${label}>`);
13729
- return placeholder;
13730
- });
13731
- out2 = out2.replace(/^### (.+)$/gm, "*$1*");
13732
- out2 = out2.replace(/^## (.+)$/gm, "*$1*");
13733
- out2 = out2.replace(/^# (.+)$/gm, "*$1*");
13734
- out2 = out2.replace(/\*\*([^*]+)\*\*/g, "*$1*");
13735
- out2 = out2.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "_$1_");
13736
- out2 = out2.replace(/~~([^~]+)~~/g, "~$1~");
13737
- out2 = out2.replace(/^- /gm, "\u2022 ");
13738
- for (let i = 0; i < inlineCodes.length; i++) {
13739
- out2 = out2.replace(`__SIC_${i}__`, inlineCodes[i]);
13740
- }
13741
- for (let i = 0; i < codeBlocks.length; i++) {
13742
- out2 = out2.replace(`__SCB_${i}__`, codeBlocks[i]);
13743
- }
13744
- for (let i = 0; i < links.length; i++) {
13745
- out2 = out2.replace(`__SLK_${i}__`, links[i]);
13746
- }
13747
- if (out2.length > 4e4) {
13748
- out2 = out2.slice(0, 39990) + "...";
13749
- }
13750
- return out2;
13751
- }
13752
-
13753
14115
  // src/ui/terminal-viewport.ts
13754
14116
  function normalizeTerminalText(text) {
13755
14117
  return text.replace(/\r\n?/g, "\n");
@@ -13767,10 +14129,9 @@ function getViewportWindow(totalLines, viewportLines, distanceFromBottom) {
13767
14129
  maxDistanceFromBottom
13768
14130
  };
13769
14131
  }
13770
- function anchorViewportDistance(distanceFromBottom, previousTotal, nextTotal) {
13771
- if (distanceFromBottom <= 0 || nextTotal <= previousTotal) return distanceFromBottom;
13772
- return distanceFromBottom + (nextTotal - previousTotal);
13773
- }
14132
+
14133
+ // src/ui/mercury-transcript.ts
14134
+ init_markdown();
13774
14135
 
13775
14136
  // src/ui/pixel-logo.ts
13776
14137
  var GLYPHS = {
@@ -14007,6 +14368,25 @@ function buildMercuryBrandLines(version, cols) {
14007
14368
  return rows;
14008
14369
  }
14009
14370
  var CODE_BLOCK_VISIBLE_ROWS = 40;
14371
+ function buildStreamTailLines(message, width, tailChars = 8 * 1024, maxLines = 12) {
14372
+ const content = message.content;
14373
+ if (content.length === 0) return [];
14374
+ const rawStart = Math.max(0, content.length - tailChars);
14375
+ const head2 = content.slice(0, rawStart);
14376
+ const insideFence = (head2.match(/```/g) || []).length % 2 === 1;
14377
+ let start = rawStart === 0 ? 0 : content.indexOf("\n", rawStart) + 1;
14378
+ if (start === 0 && rawStart > 0) start = rawStart;
14379
+ if (insideFence) {
14380
+ const opener = content.lastIndexOf("```", Math.max(0, start - 1));
14381
+ if (opener >= 0 && opener < start) start = opener;
14382
+ }
14383
+ const tailMessage = { ...message, content: content.slice(start) };
14384
+ const lines = buildMercuryMessageLines(tailMessage, width);
14385
+ if (lines.length > maxLines) {
14386
+ return [lines[0], ...lines.slice(-(maxLines - 1))];
14387
+ }
14388
+ return lines;
14389
+ }
14010
14390
  function buildMercuryMessageLines(message, width) {
14011
14391
  if (message.id.startsWith("heartbeat-")) return [];
14012
14392
  const contentWidth = Math.max(12, width - 4);
@@ -14498,40 +14878,45 @@ function TuiApp({ channel, onInput, onPermissionResolve, onExit, spotifyClient:
14498
14878
  return;
14499
14879
  }
14500
14880
  if (key.upArrow) {
14501
- onInput("/mc scroll 1");
14881
+ if (inputHistory.length === 0) return;
14882
+ if (historyIndex === -1) {
14883
+ setHistoryDraft(input);
14884
+ const next2 = inputHistory.length - 1;
14885
+ setHistoryIndex(next2);
14886
+ setInputAndCursor(inputHistory[next2] ?? "");
14887
+ return;
14888
+ }
14889
+ const next = Math.max(0, historyIndex - 1);
14890
+ setHistoryIndex(next);
14891
+ setInputAndCursor(inputHistory[next] ?? "");
14502
14892
  return;
14503
14893
  }
14504
14894
  if (key.downArrow) {
14505
- onInput("/mc scroll -1");
14506
- return;
14507
- }
14508
- const transcriptPage = Math.max(5, terminalSize.rows - 12);
14509
- if (key.pageUp) {
14510
- onInput(`/mc scroll ${transcriptPage}`);
14511
- return;
14512
- }
14513
- if (key.pageDown) {
14514
- onInput(`/mc scroll -${transcriptPage}`);
14895
+ if (historyIndex === -1) return;
14896
+ const next = historyIndex + 1;
14897
+ if (next >= inputHistory.length) {
14898
+ setHistoryIndex(-1);
14899
+ setInputAndCursor(historyDraft);
14900
+ return;
14901
+ }
14902
+ setHistoryIndex(next);
14903
+ setInputAndCursor(inputHistory[next] ?? "");
14515
14904
  return;
14516
14905
  }
14517
14906
  if (key.home) {
14518
- onInput("/mc scroll 1000000000");
14907
+ setCursorPos(0);
14519
14908
  return;
14520
14909
  }
14521
14910
  if (key.end) {
14522
- onInput("/mc live");
14523
- return;
14524
- }
14525
- if (key.ctrl && (ch === "u" || ch === "U")) {
14526
- onInput(`/mc scroll ${transcriptPage}`);
14911
+ setCursorPos(input.length);
14527
14912
  return;
14528
14913
  }
14529
14914
  if (key.ctrl && (ch === "a" || ch === "A")) {
14530
- onInput("/mc scroll 1000000000");
14915
+ setCursorPos(0);
14531
14916
  return;
14532
14917
  }
14533
14918
  if (key.ctrl && (ch === "e" || ch === "E")) {
14534
- onInput("/mc live");
14919
+ setCursorPos(input.length);
14535
14920
  return;
14536
14921
  }
14537
14922
  if (key.backspace || key.delete) {
@@ -15041,20 +15426,23 @@ function TuiApp({ channel, onInput, onPermissionResolve, onExit, spotifyClient:
15041
15426
  MercuryCodeView,
15042
15427
  {
15043
15428
  state,
15044
- height: Math.max(10, terminalSize.rows),
15045
15429
  cols: terminalSize.cols,
15046
15430
  input,
15047
15431
  cursorPos,
15048
- onInput,
15049
- onScrollClamp: (distance) => onInput(`/mc scroll-set ${distance}`),
15050
15432
  permIdx
15051
15433
  }
15052
15434
  ) : null,
15053
15435
  state.mode === "spotify" ? /* @__PURE__ */ jsx(SpotifyBody, { activeIdx: spotifyIdx, nowPlaying: spotifyNow, status: spotifyStatus, volume: spotifyVolume, albumArtAnsi: spotifyArtAnsi }) : null,
15054
15436
  state.mode === "menu" ? /* @__PURE__ */ jsx(MenuBody, { menuIdx }) : null,
15055
- state.mode === "coding" ? /* @__PURE__ */ jsx(CodingBody, { state, maxDynamicLines: Math.max(3, terminalSize.rows - 14) }) : null,
15437
+ state.mode === "coding" ? /* @__PURE__ */ jsx(CodingBody, { state, maxDynamicLines: Math.min(12, Math.max(3, terminalSize.rows - 14)) }) : null,
15056
15438
  state.mode === "workspace" ? /* @__PURE__ */ jsx(WorkspaceBody, { state, gitCursor, height: Math.max(8, terminalSize.rows - 6), cols: terminalSize.cols, onInput }) : null,
15057
- state.mode === "chat" ? /* @__PURE__ */ jsx(ChatBody, { state, maxDynamicLines: Math.max(3, terminalSize.rows - 14) }) : null,
15439
+ state.mode === "chat" ? (
15440
+ // Live-region cap: only the newest few lines of the streaming message
15441
+ // repaint per frame — finalized content is already in <Static>. A
15442
+ // near-full-screen live region was rewritten every 60ms frame, which
15443
+ // read as a visible "on-off" flicker on long chats.
15444
+ /* @__PURE__ */ jsx(ChatBody, { state, maxDynamicLines: Math.min(12, Math.max(3, terminalSize.rows - 14)) })
15445
+ ) : null,
15058
15446
  state.permissionPrompt && state.mode !== "mercury-code" && /* @__PURE__ */ jsx(PermPromptView, { prompt: state.permissionPrompt, activeIdx: permIdx }),
15059
15447
  showInput && state.mode !== "mercury-code" && /* @__PURE__ */ jsx(
15060
15448
  InputBox,
@@ -16065,120 +16453,6 @@ function InputBox({
16065
16453
  /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: inWorkspace ? "Tab switch panels \xB7 Ctrl+J chat \xB7 Ctrl+P Plan \xB7 Ctrl+X Execute \xB7 Esc back/exit" : inCoding ? "Coding chat active. Ctrl+P Plan \xB7 Ctrl+X Execute." : "Enter send \xB7 Ctrl+N newline" }) })
16066
16454
  ] });
16067
16455
  }
16068
- var mercuryTranscriptIndex = /* @__PURE__ */ new Map();
16069
- var MERCURY_INDEX_MAX_ENTRIES = 4096;
16070
- var MERCURY_LINES_MAX_ENTRIES = 64;
16071
- var MERCURY_LINES_MAX_LINES = 6e3;
16072
- var mercuryLineCacheEntries = 0;
16073
- var mercuryLineCacheLines = 0;
16074
- function mercuryCacheKey(msg, width) {
16075
- return `${msg.id}|${msg.role}|${msg.content.length}|${msg.timestamp}|${msg.streaming ? 1 : 0}|${width}`;
16076
- }
16077
- function evictMercuryLineCache() {
16078
- while (mercuryLineCacheEntries > MERCURY_LINES_MAX_ENTRIES || mercuryLineCacheLines > MERCURY_LINES_MAX_LINES) {
16079
- const oldest = mercuryTranscriptIndex.keys().next().value;
16080
- if (oldest === void 0) break;
16081
- const entry = mercuryTranscriptIndex.get(oldest);
16082
- if (entry.lines) {
16083
- mercuryLineCacheLines -= entry.lines.length;
16084
- mercuryLineCacheEntries -= 1;
16085
- entry.lines = void 0;
16086
- mercuryTranscriptIndex.delete(oldest);
16087
- mercuryTranscriptIndex.set(oldest, entry);
16088
- continue;
16089
- }
16090
- if (mercuryLineCacheEntries === 0 && mercuryLineCacheLines === 0) break;
16091
- break;
16092
- }
16093
- while (mercuryTranscriptIndex.size > MERCURY_INDEX_MAX_ENTRIES) {
16094
- const oldest = mercuryTranscriptIndex.keys().next().value;
16095
- if (oldest === void 0) break;
16096
- const entry = mercuryTranscriptIndex.get(oldest);
16097
- if (entry.lines) {
16098
- mercuryLineCacheLines -= entry.lines.length;
16099
- mercuryLineCacheEntries -= 1;
16100
- }
16101
- mercuryTranscriptIndex.delete(oldest);
16102
- }
16103
- }
16104
- function getMercuryEntry(msg, width, wantLines) {
16105
- const key = mercuryCacheKey(msg, width);
16106
- const existing = mercuryTranscriptIndex.get(msg.id);
16107
- if (existing && existing.key === key) {
16108
- if (existing.lines) {
16109
- mercuryTranscriptIndex.delete(msg.id);
16110
- mercuryTranscriptIndex.set(msg.id, existing);
16111
- return existing;
16112
- }
16113
- if (wantLines) {
16114
- const lines2 = buildMercuryMessageLines(msg, width);
16115
- existing.lines = lines2;
16116
- mercuryLineCacheEntries += 1;
16117
- mercuryLineCacheLines += lines2.length;
16118
- evictMercuryLineCache();
16119
- }
16120
- return existing;
16121
- }
16122
- const lines = buildMercuryMessageLines(msg, width);
16123
- const entry = { key, count: lines.length };
16124
- if (existing?.lines) {
16125
- mercuryLineCacheLines -= existing.lines.length;
16126
- mercuryLineCacheEntries -= 1;
16127
- }
16128
- entry.lines = lines;
16129
- mercuryLineCacheEntries += 1;
16130
- mercuryLineCacheLines += lines.length;
16131
- mercuryTranscriptIndex.set(msg.id, entry);
16132
- evictMercuryLineCache();
16133
- return entry;
16134
- }
16135
- function buildMercuryTranscriptIndex(messages, width, brandLines = []) {
16136
- const msgs = [];
16137
- const counts = [];
16138
- let total = brandLines.length;
16139
- for (const msg of messages) {
16140
- if (typeof msg.content !== "string") continue;
16141
- const entry = getMercuryEntry(msg, width, false);
16142
- counts.push(entry.count);
16143
- msgs.push(msg);
16144
- total += entry.count;
16145
- }
16146
- return { msgs, counts, total, brandLines };
16147
- }
16148
- function renderMercuryTranscriptWindow(index, startRow, endRow, width) {
16149
- const out2 = [];
16150
- const brandCount = index.brandLines.length;
16151
- if (startRow < brandCount && endRow > 0) {
16152
- out2.push(...index.brandLines.slice(startRow, Math.min(brandCount, endRow)));
16153
- }
16154
- let offset = brandCount;
16155
- for (let i = 0; i < index.msgs.length; i++) {
16156
- const count = index.counts[i];
16157
- const msgStart = offset;
16158
- const msgEnd = offset + count;
16159
- offset = msgEnd;
16160
- if (msgEnd <= startRow || msgStart >= endRow) continue;
16161
- const entry = getMercuryEntry(index.msgs[i], width, true);
16162
- const lines = entry.lines ?? [];
16163
- const from = Math.max(0, startRow - msgStart);
16164
- const to = Math.max(0, Math.min(count, endRow - msgStart));
16165
- if (to > from) out2.push(...lines.slice(from, to));
16166
- }
16167
- return out2;
16168
- }
16169
- function renderMercuryTranscriptRange(index, tail, startRow, endRow, width) {
16170
- const finalizedTotal = index.total;
16171
- const out2 = [];
16172
- if (startRow < finalizedTotal && endRow > 0) {
16173
- out2.push(...renderMercuryTranscriptWindow(index, startRow, Math.min(endRow, finalizedTotal), width));
16174
- }
16175
- if (endRow > finalizedTotal && tail.length > 0) {
16176
- const from = Math.max(0, startRow - finalizedTotal);
16177
- const to = Math.min(tail.length, endRow - finalizedTotal);
16178
- if (to > from) out2.push(...tail.slice(from, to));
16179
- }
16180
- return out2;
16181
- }
16182
16456
  var CODE_HINTS = [
16183
16457
  ["/code auto", "plan & build automatically \u2014 the default", ""],
16184
16458
  ["/code plan", "analyze & propose before coding", "ctrl+p"],
@@ -16250,7 +16524,7 @@ function PlanProgressView({ steps }) {
16250
16524
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 2, flexShrink: 0, children: rows });
16251
16525
  }
16252
16526
  var STREAM_TAIL_CHARS = 8 * 1024;
16253
- var STREAM_TAIL_MAX_LINES = 40;
16527
+ var STREAM_TAIL_MAX_LINES = 12;
16254
16528
  var WORDMARK_LIGHT_BG = (() => {
16255
16529
  const fgBg = process.env.COLORFGBG;
16256
16530
  if (!fgBg) return false;
@@ -16390,14 +16664,70 @@ function MercuryCodeExitConfirm({ boxWidth }) {
16390
16664
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Enter/Y exit \xB7 Esc/N stay \xB7 Ctrl+D force" })
16391
16665
  ] }) });
16392
16666
  }
16667
+ var MERCURY_BRAND_ITEM_KEY = "__mercury_code_brand__";
16668
+ function MercuryTranscriptRow({ line }) {
16669
+ const roleColor = line.role === "user" ? "yellow" : line.role === "agent" ? "cyan" : "gray";
16670
+ if (line.kind === "brand") {
16671
+ return /* @__PURE__ */ jsx(Box, { children: line.accent && line.accent.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
16672
+ /* @__PURE__ */ jsx(Text, { bold: true, color: WORDMARK_COLORS.mercury, children: line.text }),
16673
+ /* @__PURE__ */ jsx(Text, { bold: true, color: WORDMARK_COLORS.code, children: line.accent })
16674
+ ] }) : /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: line.text }) });
16675
+ }
16676
+ if (line.kind === "spacer") {
16677
+ return /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsx(Text, { children: " " }) });
16678
+ }
16679
+ if (line.kind === "header") {
16680
+ return /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsxs(Text, { bold: true, color: roleColor, children: [
16681
+ "\u25CF ",
16682
+ line.text
16683
+ ] }) });
16684
+ }
16685
+ if (line.kind === "code-label") {
16686
+ return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16687
+ /* @__PURE__ */ jsx(Text, { color: roleColor, children: "\u2502 " }),
16688
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
16689
+ "\u250C\u2500 ",
16690
+ line.text
16691
+ ] })
16692
+ ] });
16693
+ }
16694
+ if (line.kind === "code") {
16695
+ const highlighted = highlightCodeBlock(line.text, line.lang)[0] ?? line.text;
16696
+ return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16697
+ /* @__PURE__ */ jsx(Text, { color: roleColor, children: "\u2502 " }),
16698
+ /* @__PURE__ */ jsx(Text, { children: highlighted || " " })
16699
+ ] });
16700
+ }
16701
+ if (line.kind === "system") {
16702
+ const complete = line.text.startsWith("Task complete");
16703
+ return /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsxs(Text, { color: complete ? "green" : "gray", bold: complete, children: [
16704
+ "\u2500 ",
16705
+ line.text || " "
16706
+ ] }) });
16707
+ }
16708
+ if (line.kind === "file") {
16709
+ return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16710
+ /* @__PURE__ */ jsx(Text, { color: "green", children: " \u21B3 " }),
16711
+ /* @__PURE__ */ jsx(Text, { children: line.text })
16712
+ ] });
16713
+ }
16714
+ return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16715
+ /* @__PURE__ */ jsx(Text, { color: roleColor, children: "\u2502 " }),
16716
+ /* @__PURE__ */ jsx(Text, { children: line.text || " " })
16717
+ ] });
16718
+ }
16719
+ function MercuryBrandBlock({ brandLines }) {
16720
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexShrink: 0, children: brandLines.map((line) => /* @__PURE__ */ jsx(MercuryTranscriptRow, { line }, line.key)) });
16721
+ }
16722
+ function MercuryMessageBlock({ message, width }) {
16723
+ const lines = buildMercuryMessageLines(message, width);
16724
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexShrink: 0, children: lines.map((line) => /* @__PURE__ */ jsx(MercuryTranscriptRow, { line }, line.key)) });
16725
+ }
16393
16726
  function MercuryCodeView({
16394
16727
  state,
16395
- height,
16396
16728
  cols,
16397
- onInput,
16398
16729
  input,
16399
16730
  cursorPos,
16400
- onScrollClamp,
16401
16731
  permIdx
16402
16732
  }) {
16403
16733
  const mc = state.mercuryCode;
@@ -16408,141 +16738,59 @@ function MercuryCodeView({
16408
16738
  [state.chatMessages]
16409
16739
  );
16410
16740
  const streamingMessage = state.chatMessages.find((m) => m.streaming && !m.id.startsWith("heartbeat-"));
16411
- const transcriptIndex = React.useMemo(
16412
- () => buildMercuryTranscriptIndex(finalizedMessages, contentWidth, brandLines),
16413
- [finalizedMessages, contentWidth, brandLines]
16741
+ const streamTail = React.useMemo(
16742
+ () => streamingMessage ? buildStreamTailLines(streamingMessage, contentWidth, STREAM_TAIL_CHARS, STREAM_TAIL_MAX_LINES) : [],
16743
+ [streamingMessage, contentWidth]
16414
16744
  );
16415
- const totalLines = transcriptIndex.total;
16416
16745
  if (!mc) {
16417
16746
  return /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Mercury Code is not active. Type /code to enter." }) });
16418
16747
  }
16419
- const inputLines = Math.max(1, (input ?? "").split("\n").length);
16420
- const inputRows = 2 + inputLines;
16421
- const confirmRows = mc.exitConfirm ? 3 : 0;
16422
- const liveVisible = state.isThinking || state.toolSteps.some((s) => s.status === "running") || state.subAgents.some((a) => a.status === "running");
16423
- const liveRows = liveVisible ? 1 + Math.min(2, state.toolSteps.filter((s) => s.status === "done").slice(-2).length) + (state.subAgents.some((a) => a.status === "running") ? 1 + Math.min(4, state.subAgents.filter((a) => a.status === "running").length) : 0) : 0;
16424
- const statusRows = 1;
16425
- const planRows = state.planProgress && state.planProgress.length > 0 ? Math.min(7, state.planProgress.length + 1) : 0;
16426
- const promptRows = state.permissionPrompt ? 2 + (state.permissionPrompt.options?.length ?? 0) : 0;
16427
- const transcriptHeight = Math.max(3, height - inputRows - 1 - liveRows - confirmRows - planRows - promptRows);
16428
- const streamTail = React.useMemo(() => {
16429
- if (!streamingMessage) return [];
16430
- const content = streamingMessage.content;
16431
- const tail = content.length > STREAM_TAIL_CHARS ? content.slice(-STREAM_TAIL_CHARS) : content;
16432
- const lines = [{ key: `${streamingMessage.id}:hdr`, kind: "header", role: streamingMessage.role, text: "MERCURY" }];
16433
- for (const row of tail.split("\n")) {
16434
- for (const chunk of wrapMercuryText(row, contentWidth)) {
16435
- lines.push({ key: `${streamingMessage.id}:${lines.length}`, kind: "text", role: streamingMessage.role, text: chunk });
16436
- if (lines.length > STREAM_TAIL_MAX_LINES) {
16437
- lines.splice(1, lines.length - STREAM_TAIL_MAX_LINES);
16438
- }
16439
- }
16440
- }
16441
- return lines;
16442
- }, [streamingMessage, contentWidth]);
16443
- const totalWithTail = totalLines + streamTail.length;
16444
- const previousLineCount = React.useRef(totalWithTail);
16445
- const anchoredOffset = anchorViewportDistance(mc.scrollOffset, previousLineCount.current, totalWithTail);
16446
- const preliminaryViewport = getViewportWindow(totalWithTail, transcriptHeight, anchoredOffset);
16447
- const wordmarkOnScreen = preliminaryViewport.start < brandLines.length;
16448
- const effectiveViewportRows = wordmarkOnScreen ? transcriptHeight : transcriptHeight - 1;
16449
- const viewport = getViewportWindow(totalWithTail, effectiveViewportRows, anchoredOffset);
16450
- const adjustedVisible = renderMercuryTranscriptRange(transcriptIndex, streamTail, viewport.start, viewport.end, contentWidth);
16451
- React.useEffect(() => {
16452
- previousLineCount.current = totalWithTail;
16453
- if (onScrollClamp && viewport.distanceFromBottom !== mc.scrollOffset) {
16454
- onScrollClamp(viewport.distanceFromBottom);
16455
- }
16456
- }, [totalWithTail, onScrollClamp, mc.scrollOffset, viewport.distanceFromBottom]);
16748
+ const staticItems = [
16749
+ MERCURY_BRAND_ITEM_KEY,
16750
+ ...finalizedMessages.slice(-MAX_STATIC_MESSAGES)
16751
+ ];
16457
16752
  const mode = state.programmingMode;
16458
16753
  const modeLabel = mode === "execute" ? "EXECUTE" : mode === "plan" ? "PLAN" : mode === "auto" ? "AUTO" : "CHAT";
16459
- const modeColor = mode === "execute" ? "green" : mode === "plan" ? "yellow" : "cyan";
16460
16754
  const git2 = mc.git;
16461
- const gitBits = [];
16462
- if (git2.branch !== "no-git") {
16463
- gitBits.push(`\u2387 ${git2.branch}`);
16755
+ const hasGit = git2.branch !== "no-git" && git2.branch !== "not-a-git-repo";
16756
+ const rightSegs = [{ text: mc.dirName, color: "cyan" }];
16757
+ if (hasGit) {
16758
+ const gitBits = [`\u2387 ${git2.branch}`];
16464
16759
  if (git2.ahead > 0) gitBits.push(`\u2191${git2.ahead}`);
16465
16760
  if (git2.behind > 0) gitBits.push(`\u2193${git2.behind}`);
16466
- gitBits.push(git2.dirty > 0 ? `\xB1${git2.dirty}` : "\u2713");
16467
- }
16468
- const rightParts = [mc.dirName, ...gitBits, modeLabel];
16469
- if (state.tokenInfo) {
16470
- const pct = Math.round(state.tokenInfo.percentage);
16471
- rightParts.push(`\u26A1 ${pct}%`);
16761
+ if (git2.dirty > 0) gitBits.push(`\xB1${git2.dirty}`);
16762
+ rightSegs.push({ text: gitBits.join(" "), color: "blue" });
16763
+ }
16764
+ rightSegs.push({ text: modeLabel, color: mode === "execute" ? "green" : mode === "plan" ? "yellow" : "cyan" });
16765
+ if (state.provider) rightSegs.push({ text: `${state.provider.name} ${state.provider.model}`, color: "magenta" });
16766
+ if (state.tokenInfo) rightSegs.push({ text: `\u26A1 ${Math.round(state.tokenInfo.percentage)}%`, color: "green" });
16767
+ const SEP = " \xB7 ";
16768
+ let segWidth = 4;
16769
+ rightSegs.forEach((seg, i) => {
16770
+ segWidth += seg.text.length + (i > 0 ? SEP.length : 0);
16771
+ });
16772
+ while (segWidth > cols - 2 && rightSegs.length > 1) {
16773
+ const removed = rightSegs.pop();
16774
+ segWidth -= removed.text.length + SEP.length;
16472
16775
  }
16473
- if (state.provider) rightParts.push(`${state.provider.name} ${state.provider.model}`);
16474
- const rightStr = rightParts.join(" \xB7 ");
16475
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height, overflow: "hidden", children: [
16476
- !wordmarkOnScreen && /* @__PURE__ */ jsxs(Box, { paddingX: 1, flexShrink: 0, children: [
16477
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u263F MERCURY " }),
16478
- /* @__PURE__ */ jsx(Text, { bold: true, color: WORDMARK_COLORS.code, children: "CODE" }),
16479
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
16480
- " v",
16481
- state.version
16482
- ] }),
16483
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
16484
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: mc.dirName })
16485
- ] }),
16486
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", height: effectiveViewportRows, overflow: "hidden", children: adjustedVisible.length === 0 && streamTail.length === 0 && totalWithTail === 0 ? /* @__PURE__ */ jsx(MercuryCodeHints, { cols }) : adjustedVisible.map((line) => {
16487
- const roleColor = line.role === "user" ? "yellow" : line.role === "agent" ? "cyan" : "gray";
16488
- if (line.kind === "brand") {
16489
- return /* @__PURE__ */ jsx(Box, { children: line.accent && line.accent.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
16490
- /* @__PURE__ */ jsx(Text, { bold: true, color: WORDMARK_COLORS.mercury, children: line.text }),
16491
- /* @__PURE__ */ jsx(Text, { bold: true, color: WORDMARK_COLORS.code, children: line.accent })
16492
- ] }) : /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: line.text }) }, line.key);
16493
- }
16494
- if (line.kind === "spacer") {
16495
- return /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsx(Text, { children: " " }) }, line.key);
16496
- }
16497
- if (line.kind === "header") {
16498
- return /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsxs(Text, { bold: true, color: roleColor, children: [
16499
- "\u25CF ",
16500
- line.text
16501
- ] }) }, line.key);
16502
- }
16503
- if (line.kind === "code-label") {
16504
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16505
- /* @__PURE__ */ jsx(Text, { color: roleColor, children: "\u2502 " }),
16506
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
16507
- "\u250C\u2500 ",
16508
- line.text
16509
- ] })
16510
- ] }, line.key);
16511
- }
16512
- if (line.kind === "code") {
16513
- const highlighted = highlightCodeBlock(line.text, line.lang)[0] ?? line.text;
16514
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16515
- /* @__PURE__ */ jsx(Text, { color: roleColor, children: "\u2502 " }),
16516
- /* @__PURE__ */ jsx(Text, { children: highlighted || " " })
16517
- ] }, line.key);
16518
- }
16519
- if (line.kind === "system") {
16520
- const complete = line.text.startsWith("Task complete");
16521
- return /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsxs(Text, { color: complete ? "green" : "gray", bold: complete, children: [
16522
- "\u2500 ",
16523
- line.text || " "
16524
- ] }) }, line.key);
16525
- }
16526
- if (line.kind === "file") {
16527
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16528
- /* @__PURE__ */ jsx(Text, { color: "green", children: " \u21B3 " }),
16529
- /* @__PURE__ */ jsx(Text, { children: line.text })
16530
- ] }, line.key);
16531
- }
16532
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
16533
- /* @__PURE__ */ jsx(Text, { color: roleColor, children: "\u2502 " }),
16534
- /* @__PURE__ */ jsx(Text, { children: line.text || " " })
16535
- ] }, line.key);
16536
- }) }),
16776
+ const leftHint = mc.exitConfirm || state.permissionPrompt ? "" : state.isThinking || state.toolSteps.some((s) => s.status === "running") || state.subAgents.some((a) => a.status === "running") ? "" : "\u21B5 send \xB7 /help";
16777
+ const showHints = finalizedMessages.length === 0 && streamTail.length === 0;
16778
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [
16779
+ /* @__PURE__ */ jsx(Static, { items: staticItems, itemKey: staticItemKey, children: (item) => typeof item === "string" ? /* @__PURE__ */ jsx(MercuryBrandBlock, { brandLines }, item) : /* @__PURE__ */ jsx(MercuryMessageBlock, { message: item, width: contentWidth }, item.id) }),
16780
+ streamTail.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexShrink: 0, children: streamTail.map((line) => /* @__PURE__ */ jsx(MercuryTranscriptRow, { line }, line.key)) }),
16781
+ showHints && /* @__PURE__ */ jsx(MercuryCodeHints, { cols }),
16537
16782
  state.planProgress && state.planProgress.length > 0 && /* @__PURE__ */ jsx(PlanProgressView, { steps: state.planProgress }),
16538
16783
  /* @__PURE__ */ jsx(MercuryLiveFeedback, { state }),
16539
16784
  state.permissionPrompt && /* @__PURE__ */ jsx(PermPromptView, { prompt: state.permissionPrompt, activeIdx: permIdx ?? 0 }),
16540
16785
  mc.exitConfirm && /* @__PURE__ */ jsx(MercuryCodeExitConfirm, { boxWidth: Math.max(40, cols - 4) }),
16541
16786
  /* @__PURE__ */ jsx(MercuryCodeInput, { input: input ?? "", cursorPos: cursorPos ?? 0, mode: state.programmingMode, boxWidth: Math.max(40, cols - 4) }),
16542
- /* @__PURE__ */ jsxs(Box, { paddingX: 3, flexShrink: 0, children: [
16543
- viewport.distanceFromBottom > 0 ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u2191\u2193 scroll \xB7 PgUp/PgDn page \xB7 Ctrl+E back to live" }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u21B5 send \xB7 esc esc exit \xB7 \u21E7drag copy \xB7 ctrl+c quit" }),
16787
+ /* @__PURE__ */ jsxs(Box, { height: 1, overflow: "hidden", paddingX: 2, flexShrink: 0, children: [
16788
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: leftHint }),
16544
16789
  /* @__PURE__ */ jsx(Spacer, {}),
16545
- /* @__PURE__ */ jsx(Text, { color: "blue", wrap: "truncate-end", children: rightStr })
16790
+ /* @__PURE__ */ jsx(Text, { wrap: "truncate-end", children: rightSegs.map((seg, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
16791
+ i > 0 && /* @__PURE__ */ jsx(Text, { color: "gray", children: SEP }),
16792
+ /* @__PURE__ */ jsx(Text, { color: seg.color, children: seg.text })
16793
+ ] }, seg.text)) })
16546
16794
  ] })
16547
16795
  ] });
16548
16796
  }
@@ -17790,9 +18038,10 @@ size: ${sizeStr}`;
17790
18038
  }
17791
18039
  /**
17792
18040
  * Enter Mercury Code: full-screen coding TUI bound to `dir`.
17793
- * Switches to plan mode by default (analyze-first). Transcript scrolling
17794
- * stays keyboard-only: terminal mouse reporting survives native process
17795
- * aborts and leaves the user's shell receiving raw mouse escape sequences.
18041
+ * Switches to plan mode by default (analyze-first). The transcript prints
18042
+ * into the terminal's native scrollback (<Static>), so scrolling and
18043
+ * drag-selection are terminal-native: mouse reporting stays OFF it would
18044
+ * capture wheel/drag events and break both.
17796
18045
  */
17797
18046
  enterMercuryCode(dir, version) {
17798
18047
  const target = path.resolve(dir.replace(/^~(?=$|\/)/, process.env.HOME || "~"));
@@ -17817,10 +18066,6 @@ size: ${sizeStr}`;
17817
18066
  programmingMode: "auto",
17818
18067
  exitEscArmed: false
17819
18068
  });
17820
- this.setMouseEnabled(true, (ev) => {
17821
- if (ev.wheel === "up") this.scrollMercuryCode(3);
17822
- else if (ev.wheel === "down") this.scrollMercuryCode(-3);
17823
- });
17824
18069
  try {
17825
18070
  process.stdout.write("\x1B[2J\x1B[H");
17826
18071
  } catch {
@@ -18319,6 +18564,7 @@ import { Bot, InputFile, InlineKeyboard } from "grammy";
18319
18564
  import { autoRetry } from "@grammyjs/auto-retry";
18320
18565
  init_config();
18321
18566
  init_logger();
18567
+ init_markdown();
18322
18568
  init_registry();
18323
18569
  init_store();
18324
18570
  init_loader();
@@ -19784,6 +20030,7 @@ import fs4 from "fs";
19784
20030
  import path4 from "path";
19785
20031
  init_config();
19786
20032
  init_logger();
20033
+ init_markdown();
19787
20034
  init_redact();
19788
20035
  init_jsonrpc();
19789
20036
  init_binary();
@@ -20704,6 +20951,7 @@ import {
20704
20951
  } from "discord.js";
20705
20952
  init_config();
20706
20953
  init_logger();
20954
+ init_markdown();
20707
20955
  var MAX_MESSAGE_LENGTH3 = 2e3;
20708
20956
  var DISCORD_DM_PREFIX = "discord:dm";
20709
20957
  var DISCORD_GUILD_PREFIX = "discord";
@@ -21740,6 +21988,7 @@ import path6 from "path";
21740
21988
  import { App } from "@slack/bolt";
21741
21989
  init_config();
21742
21990
  init_logger();
21991
+ init_markdown();
21743
21992
  var SLACK_DM_PREFIX = "slack:dm";
21744
21993
  var SlackChannel = class _SlackChannel extends BaseChannel {
21745
21994
  constructor(config2) {
@@ -23736,6 +23985,10 @@ var IMPLEMENTATION_PATTERN = new RegExp(
23736
23985
  );
23737
23986
  var PURE_CONVERSATION_PATTERN = /^(thanks|thank you|thx|ty|cool|nice|great|awesome|perfect|ok|okay|got it|understood|bye|hi|hey|hello|lol|lgtm|sounds good|well done)[\s!,.?]*$/i;
23738
23987
  var QUESTION_PATTERN = /^(what|whats|what's|why|how|when|where|who|which|explain|describe|tell me|walk me through|compare|list)\b/i;
23988
+ var TEXT_DELIVERABLE_PATTERN = /\b(poems?|poetry|haiku|stor(y|ies)|essay|essays|lyrics|song|joke|jokes|slogans?|taglines?|e-?mails?|letters?|blog|blogs|articles?|summaries|summari[sz]e|translat\w*|captions?|tweets?|outlines?|brainstorm|names?|titles?|description|descriptions)\b/i;
23989
+ function isTextDeliverableRequest(taskText) {
23990
+ return TEXT_DELIVERABLE_PATTERN.test(taskText);
23991
+ }
23739
23992
  function shouldForceExecuteContinuation(input) {
23740
23993
  for (const toolName of input.toolsUsed) {
23741
23994
  if (EXECUTE_PAUSE_TOOLS.has(toolName)) return false;
@@ -26632,7 +26885,7 @@ Per-provider:
26632
26885
  const preGuardText = (result.text || "").trim() || "(no text response)";
26633
26886
  this.markProgress("Finalizing response...");
26634
26887
  this.pushLiveActivity("Finalizing response");
26635
- while (this.programmingMode.isExecute() && !loopAbortController.signal.aborted && executeGuardRounds < (narrationSecondWind ? MAX_EXECUTE_CONTINUATIONS * 2 : MAX_EXECUTE_CONTINUATIONS) && !responseAsksUser(result.text || "") && shouldForceExecuteContinuation({
26888
+ while (!loopAbortController.signal.aborted && (this.programmingMode.isExecute() || this.programmingMode.getState() === "off" && msg.channelType !== "internal" && !isTextDeliverableRequest(msg.content)) && executeGuardRounds < (narrationSecondWind ? MAX_EXECUTE_CONTINUATIONS * 2 : MAX_EXECUTE_CONTINUATIONS) && !responseAsksUser(result.text || "") && shouldForceExecuteContinuation({
26636
26889
  taskText: msg.content,
26637
26890
  hasApprovedPlan: this.programmingMode.getLastPlan() != null,
26638
26891
  toolsUsed: executeTurnToolsUsed,
@@ -26848,7 +27101,7 @@ This is real, current state. Use it: create/edit files HERE with your tools.`
26848
27101
  break;
26849
27102
  }
26850
27103
  }
26851
- if (!loopAbortController.signal.aborted && turnEnd() === "text-stop" && this.programmingMode.isExecute() && verificationContinuations < MAX_VERIFICATION_CONTINUATIONS && shouldRequireVerification({
27104
+ if (!loopAbortController.signal.aborted && turnEnd() === "text-stop" && (this.programmingMode.isExecute() || this.programmingMode.getState() === "off" && msg.channelType !== "internal") && verificationContinuations < MAX_VERIFICATION_CONTINUATIONS && shouldRequireVerification({
26852
27105
  taskText: msg.content,
26853
27106
  hasApprovedPlan: this.programmingMode.getLastPlan() != null,
26854
27107
  commandsRun: executeCommandsRun,
@@ -35443,12 +35696,13 @@ init_loader();
35443
35696
  // src/skills/cli.ts
35444
35697
  init_registry();
35445
35698
  init_store();
35699
+ init_markdown();
35700
+ init_config();
35701
+ init_open_url();
35446
35702
  import { existsSync as existsSync31, readFileSync as readFileSync21 } from "fs";
35447
35703
  import { resolve as resolve16 } from "path";
35448
35704
  import { homedir as homedir7 } from "os";
35449
35705
  import chalk6 from "chalk";
35450
- init_config();
35451
- init_open_url();
35452
35706
  function inheritedFlags(cmd) {
35453
35707
  const opts = {};
35454
35708
  let c = cmd;
@@ -35792,76 +36046,23 @@ function registerSkillsCommand(program2) {
35792
36046
 
35793
36047
  // src/index.ts
35794
36048
  init_daemon();
35795
- init_service();
35796
36049
 
35797
- // src/cli/watchdog.ts
35798
- init_logger();
35799
- init_crash_flag();
35800
- var MAX_RESTARTS = 10;
35801
- var RESTART_WINDOW_MS = 6e4;
35802
- var BASE_DELAY_MS = 1e3;
35803
- async function runWithWatchdog(agentFn) {
35804
- const restarts = [];
35805
- async function attempt() {
35806
- try {
35807
- await agentFn();
35808
- } catch (err) {
35809
- const now = Date.now();
35810
- restarts.push(now);
35811
- const recentRestarts = restarts.filter((t) => now - t < RESTART_WINDOW_MS);
35812
- const restartCount = recentRestarts.length;
35813
- if (restartCount >= MAX_RESTARTS) {
35814
- logger.error({ restartCount }, "Max restarts exceeded within 60s. Exiting.");
35815
- writeCrashFlag({
35816
- reason: `Max restarts exceeded (${restartCount} crashes in 60s). Last error: ${err instanceof Error ? err.message : String(err)}`.slice(0, 300),
35817
- timestamp: Date.now()
35818
- });
35819
- process.stderr.write(`[mercury] FATAL: Max restarts exceeded (${restartCount} in 60s). Last error: ${err instanceof Error ? err.message : String(err)}
35820
- `);
35821
- process.exit(1);
35822
- }
35823
- const delay = BASE_DELAY_MS * Math.pow(1.25, restartCount);
35824
- logger.warn({ err, restartCount, delay }, "Crash detected. Restarting with backoff...");
35825
- await sleep2(delay);
35826
- await attempt();
35827
- }
35828
- }
35829
- await attempt();
35830
- }
35831
- function sleep2(ms) {
35832
- return new Promise((resolve19) => setTimeout(resolve19, ms));
35833
- }
35834
-
35835
- // src/index.ts
35836
- init_arrow_select();
35837
- init_provider_models();
35838
- init_token_store();
35839
- init_runtime_status();
35840
-
35841
- // src/web/server.ts
35842
- init_logger();
35843
- import { Hono as Hono12 } from "hono";
35844
- import { createAdaptorServer } from "@hono/node-server";
35845
- import { readFileSync as readFileSync30, existsSync as existsSync41, readdirSync as readdirSync8 } from "fs";
35846
- import { join as join32, dirname as dirname8 } from "path";
35847
- import { fileURLToPath as fileURLToPath2 } from "url";
35848
-
35849
- // src/web/middleware.ts
35850
- import { getCookie } from "hono/cookie";
36050
+ // src/cli/attach.ts
36051
+ init_daemon();
35851
36052
 
35852
36053
  // src/web/auth.ts
35853
36054
  init_config();
35854
36055
  import { compareSync, hashSync, genSaltSync } from "bcryptjs";
35855
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync24, existsSync as existsSync35, mkdirSync as mkdirSync23, chmodSync as chmodSync3 } from "fs";
35856
- import { join as join26 } from "path";
36056
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync22, existsSync as existsSync33, mkdirSync as mkdirSync22, chmodSync as chmodSync3 } from "fs";
36057
+ import { join as join24 } from "path";
35857
36058
  import { randomBytes } from "crypto";
35858
36059
  var SESSION_COOKIE = "mercury_session";
35859
36060
  var SESSION_MAX_AGE = 7 * 24 * 60 * 60;
35860
36061
  function getWebConfigPath() {
35861
- return join26(getMercuryHome(), "web-config.json");
36062
+ return join24(getMercuryHome(), "web-config.json");
35862
36063
  }
35863
36064
  function writeCredentialFile(path9, contents) {
35864
- writeFileSync24(path9, contents, { encoding: "utf-8", mode: 384 });
36065
+ writeFileSync22(path9, contents, { encoding: "utf-8", mode: 384 });
35865
36066
  try {
35866
36067
  chmodSync3(path9, 384);
35867
36068
  } catch {
@@ -35881,11 +36082,19 @@ function getWebPort() {
35881
36082
  }
35882
36083
  var ATTACH_TOKEN_FILE = "attach-token";
35883
36084
  function getAttachTokenPath() {
35884
- return join26(getMercuryHome(), ATTACH_TOKEN_FILE);
36085
+ return join24(getMercuryHome(), ATTACH_TOKEN_FILE);
36086
+ }
36087
+ function writeAttachToken() {
36088
+ try {
36089
+ const home = getMercuryHome();
36090
+ if (!existsSync33(home)) mkdirSync22(home, { recursive: true });
36091
+ writeCredentialFile(getAttachTokenPath(), randomBytes(32).toString("hex"));
36092
+ } catch {
36093
+ }
35885
36094
  }
35886
36095
  function readAttachToken() {
35887
36096
  try {
35888
- const token = readFileSync24(getAttachTokenPath(), "utf-8").trim();
36097
+ const token = readFileSync23(getAttachTokenPath(), "utf-8").trim();
35889
36098
  return token.length > 0 ? token : null;
35890
36099
  } catch {
35891
36100
  return null;
@@ -35893,13 +36102,13 @@ function readAttachToken() {
35893
36102
  }
35894
36103
  function loadWebAuth() {
35895
36104
  const path9 = getWebConfigPath();
35896
- if (!existsSync35(path9)) return null;
36105
+ if (!existsSync33(path9)) return null;
35897
36106
  try {
35898
36107
  try {
35899
36108
  chmodSync3(path9, 384);
35900
36109
  } catch {
35901
36110
  }
35902
- const raw = readFileSync24(path9, "utf-8");
36111
+ const raw = readFileSync23(path9, "utf-8");
35903
36112
  return JSON.parse(raw);
35904
36113
  } catch {
35905
36114
  return null;
@@ -35907,8 +36116,8 @@ function loadWebAuth() {
35907
36116
  }
35908
36117
  function saveWebAuth(auth2) {
35909
36118
  const dir = getMercuryHome();
35910
- if (!existsSync35(dir)) {
35911
- mkdirSync23(dir, { recursive: true });
36119
+ if (!existsSync33(dir)) {
36120
+ mkdirSync22(dir, { recursive: true });
35912
36121
  }
35913
36122
  writeCredentialFile(getWebConfigPath(), JSON.stringify(auth2, null, 2));
35914
36123
  }
@@ -35972,12 +36181,12 @@ function createSessionToken() {
35972
36181
  var sessions = /* @__PURE__ */ new Map();
35973
36182
  var SESSION_FILE = "web-sessions.json";
35974
36183
  function getSessionFilePath() {
35975
- return join26(getMercuryHome(), SESSION_FILE);
36184
+ return join24(getMercuryHome(), SESSION_FILE);
35976
36185
  }
35977
36186
  function persistSessions() {
35978
36187
  try {
35979
36188
  const dir = getMercuryHome();
35980
- if (!existsSync35(dir)) mkdirSync23(dir, { recursive: true });
36189
+ if (!existsSync33(dir)) mkdirSync22(dir, { recursive: true });
35981
36190
  const entries = Object.fromEntries(sessions);
35982
36191
  writeCredentialFile(getSessionFilePath(), JSON.stringify(entries, null, 2));
35983
36192
  } catch {
@@ -35986,8 +36195,8 @@ function persistSessions() {
35986
36195
  function restoreSessions() {
35987
36196
  try {
35988
36197
  const path9 = getSessionFilePath();
35989
- if (!existsSync35(path9)) return;
35990
- const raw = readFileSync24(path9, "utf-8");
36198
+ if (!existsSync33(path9)) return;
36199
+ const raw = readFileSync23(path9, "utf-8");
35991
36200
  const data = JSON.parse(raw);
35992
36201
  const now = Date.now();
35993
36202
  for (const [key, entry] of Object.entries(data)) {
@@ -36028,7 +36237,292 @@ function getSessionMaxAge() {
36028
36237
  return SESSION_MAX_AGE;
36029
36238
  }
36030
36239
 
36240
+ // src/cli/attach.ts
36241
+ function createSseParser() {
36242
+ let buffer = "";
36243
+ let eventName = "";
36244
+ let dataLines = [];
36245
+ const flush = (frames) => {
36246
+ if (eventName || dataLines.length > 0) {
36247
+ frames.push({ event: eventName || "message", data: dataLines.join("\n") });
36248
+ eventName = "";
36249
+ dataLines = [];
36250
+ }
36251
+ };
36252
+ return {
36253
+ push(chunk) {
36254
+ const frames = [];
36255
+ buffer += chunk.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
36256
+ let idx;
36257
+ while ((idx = buffer.indexOf("\n")) !== -1) {
36258
+ const line = buffer.slice(0, idx);
36259
+ buffer = buffer.slice(idx + 1);
36260
+ if (line.length === 0) {
36261
+ flush(frames);
36262
+ continue;
36263
+ }
36264
+ if (line.startsWith(":")) continue;
36265
+ if (line.startsWith("event:")) eventName = line.slice(6).trim();
36266
+ else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
36267
+ }
36268
+ return frames;
36269
+ }
36270
+ };
36271
+ }
36272
+ var AttachClient = class {
36273
+ constructor(baseUrl, token) {
36274
+ this.baseUrl = baseUrl;
36275
+ this.token = token;
36276
+ }
36277
+ baseUrl;
36278
+ token;
36279
+ headers() {
36280
+ return { Authorization: `Bearer ${this.token}` };
36281
+ }
36282
+ async request(path9, init) {
36283
+ return fetch(`${this.baseUrl}${path9}`, {
36284
+ ...init,
36285
+ headers: { ...this.headers(), ...init?.headers }
36286
+ });
36287
+ }
36288
+ /** Cheap authenticated GET to verify the runtime + token before opening the TUI. */
36289
+ async healthCheck(timeoutMs = 3e3) {
36290
+ const controller = new AbortController();
36291
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
36292
+ try {
36293
+ const res = await this.request("/api/chat/models", { signal: controller.signal });
36294
+ if (res.status === 401) throw new Error("auth");
36295
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
36296
+ } finally {
36297
+ clearTimeout(timer);
36298
+ }
36299
+ }
36300
+ async listThreads() {
36301
+ const res = await this.request("/api/chat/threads");
36302
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
36303
+ const body = await res.json();
36304
+ return (body.threads || []).map((t) => ({
36305
+ id: String(t.id ?? ""),
36306
+ shortId: String(t.shortId ?? ""),
36307
+ alias: String(t.alias ?? t.title ?? "session"),
36308
+ title: String(t.title ?? ""),
36309
+ updatedAt: String(t.updatedAt ?? "")
36310
+ })).filter((t) => t.id.length > 0);
36311
+ }
36312
+ async getThread(id) {
36313
+ const res = await this.request(`/api/chat/threads/${encodeURIComponent(id)}`);
36314
+ if (!res.ok) return [];
36315
+ const body = await res.json();
36316
+ return (body.messages || []).map((m) => ({
36317
+ id: String(m.id ?? `h_${Math.random().toString(36).slice(2, 8)}`),
36318
+ role: m.role ?? "system",
36319
+ kind: m.kind,
36320
+ content: String(m.content ?? ""),
36321
+ timestamp: String(m.timestamp ?? "")
36322
+ }));
36323
+ }
36324
+ async createThread() {
36325
+ try {
36326
+ const res = await this.request("/api/chat/threads", {
36327
+ method: "POST",
36328
+ headers: { "Content-Type": "application/json" },
36329
+ body: JSON.stringify({})
36330
+ });
36331
+ if (!res.ok) return null;
36332
+ const t = await res.json();
36333
+ if (!t.id) return null;
36334
+ return { id: t.id, shortId: String(t.shortId ?? ""), alias: String(t.alias ?? t.title ?? "session"), title: String(t.title ?? ""), updatedAt: "" };
36335
+ } catch {
36336
+ return null;
36337
+ }
36338
+ }
36339
+ async send(content, sessionId) {
36340
+ try {
36341
+ const res = await this.request("/api/chat/send", {
36342
+ method: "POST",
36343
+ headers: { "Content-Type": "application/json" },
36344
+ body: JSON.stringify({ content, sessionId })
36345
+ });
36346
+ if (!res.ok) {
36347
+ const body2 = await res.json().catch(() => ({}));
36348
+ return { ok: false, error: body2.error || `HTTP ${res.status}` };
36349
+ }
36350
+ const body = await res.json();
36351
+ return { ok: true, sessionId: body.sessionId };
36352
+ } catch (err) {
36353
+ return { ok: false, error: String(err?.message || err) };
36354
+ }
36355
+ }
36356
+ async resolvePermission(id, action) {
36357
+ try {
36358
+ const res = await this.request(`/api/chat/permission/${encodeURIComponent(id)}`, {
36359
+ method: "POST",
36360
+ headers: { "Content-Type": "application/json" },
36361
+ body: JSON.stringify({ action })
36362
+ });
36363
+ return res.ok;
36364
+ } catch {
36365
+ return false;
36366
+ }
36367
+ }
36368
+ /**
36369
+ * Consume the SSE event feed (reconnecting with backoff until aborted).
36370
+ * Emits transport pseudo-events `attach_disconnected` / `attach_auth_error`
36371
+ * — the auth error is terminal (the token rotates on runtime restart, so
36372
+ * reconnecting cannot recover; the client must re-attach).
36373
+ */
36374
+ async streamEvents(sessionId, onEvent, signal) {
36375
+ const parser = createSseParser();
36376
+ const decoder = new TextDecoder();
36377
+ let backoffMs = 1e3;
36378
+ while (!signal.aborted) {
36379
+ try {
36380
+ const query = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
36381
+ const res = await this.request(`/api/chat/events${query}`, {
36382
+ headers: { Accept: "text/event-stream" },
36383
+ signal
36384
+ });
36385
+ if (res.status === 401) {
36386
+ onEvent({ type: "attach_auth_error", data: {} });
36387
+ return;
36388
+ }
36389
+ if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
36390
+ backoffMs = 1e3;
36391
+ const reader = res.body.getReader();
36392
+ for (; ; ) {
36393
+ const { done, value } = await reader.read();
36394
+ if (done) break;
36395
+ for (const frame of parser.push(decoder.decode(value, { stream: true }))) {
36396
+ let data = {};
36397
+ try {
36398
+ data = frame.data ? JSON.parse(frame.data) : {};
36399
+ } catch {
36400
+ }
36401
+ onEvent({ type: frame.event, data });
36402
+ }
36403
+ }
36404
+ } catch (err) {
36405
+ if (signal.aborted) return;
36406
+ onEvent({ type: "attach_disconnected", data: { message: String(err?.message || err) } });
36407
+ }
36408
+ const aborted = await new Promise((resolve19) => {
36409
+ const timer = setTimeout(() => resolve19(false), backoffMs);
36410
+ signal.addEventListener("abort", () => {
36411
+ clearTimeout(timer);
36412
+ resolve19(true);
36413
+ }, { once: true });
36414
+ });
36415
+ if (aborted) return;
36416
+ backoffMs = Math.min(backoffMs * 2, 1e4);
36417
+ }
36418
+ }
36419
+ };
36420
+ function findAttachTarget() {
36421
+ const foreground = getForegroundRuntimeStatus();
36422
+ const daemon = getDaemonStatus();
36423
+ const pid = foreground.running ? foreground.pid : daemon.running ? daemon.pid : null;
36424
+ if (pid == null) return null;
36425
+ const token = readAttachToken();
36426
+ if (!token) return null;
36427
+ return { baseUrl: `http://127.0.0.1:${getWebPort()}`, token, pid };
36428
+ }
36429
+ async function runAttach() {
36430
+ const { render: render2 } = await import("ink");
36431
+ const React4 = await import("react");
36432
+ const { AttachTui: AttachTui2 } = await Promise.resolve().then(() => (init_attach_tui(), attach_tui_exports));
36433
+ const chalk11 = (await import("chalk")).default;
36434
+ const target = findAttachTarget();
36435
+ if (!target) {
36436
+ const runtimeRunning = getForegroundRuntimeStatus().running || getDaemonStatus().running;
36437
+ if (!runtimeRunning) {
36438
+ console.error(" Mercury is not running. Start it with `mercury` or `mercury start`.");
36439
+ } else {
36440
+ console.error(" Cannot attach: web is disabled or no attach token was written at boot.");
36441
+ console.error(" Enable web (mercury doctor), or `mercury stop` and start again.");
36442
+ }
36443
+ process.exitCode = 1;
36444
+ return;
36445
+ }
36446
+ const client = new AttachClient(target.baseUrl, target.token);
36447
+ try {
36448
+ await client.healthCheck();
36449
+ } catch (err) {
36450
+ const message = String(err?.message || err);
36451
+ console.error(` Runtime is not responding at ${target.baseUrl} (${message}).`);
36452
+ if (message === "auth") {
36453
+ console.error(" The attach token rotated \u2014 the runtime restarted since the token was read.");
36454
+ console.error(" Run `mercury attach` again.");
36455
+ }
36456
+ process.exitCode = 1;
36457
+ return;
36458
+ }
36459
+ const instance = render2(React4.createElement(AttachTui2, {
36460
+ client,
36461
+ pid: target.pid,
36462
+ onExit: () => {
36463
+ }
36464
+ }));
36465
+ await instance.waitUntilExit();
36466
+ console.log(chalk11.dim(` Detached. Runtime still running${target.pid ? ` (PID ${target.pid})` : ""} \u2014 \`mercury stop\` stops it.`));
36467
+ }
36468
+
36469
+ // src/index.ts
36470
+ init_service();
36471
+
36472
+ // src/cli/watchdog.ts
36473
+ init_logger();
36474
+ init_crash_flag();
36475
+ var MAX_RESTARTS = 10;
36476
+ var RESTART_WINDOW_MS = 6e4;
36477
+ var BASE_DELAY_MS = 1e3;
36478
+ async function runWithWatchdog(agentFn) {
36479
+ const restarts = [];
36480
+ async function attempt() {
36481
+ try {
36482
+ await agentFn();
36483
+ } catch (err) {
36484
+ const now = Date.now();
36485
+ restarts.push(now);
36486
+ const recentRestarts = restarts.filter((t) => now - t < RESTART_WINDOW_MS);
36487
+ const restartCount = recentRestarts.length;
36488
+ if (restartCount >= MAX_RESTARTS) {
36489
+ logger.error({ restartCount }, "Max restarts exceeded within 60s. Exiting.");
36490
+ writeCrashFlag({
36491
+ reason: `Max restarts exceeded (${restartCount} crashes in 60s). Last error: ${err instanceof Error ? err.message : String(err)}`.slice(0, 300),
36492
+ timestamp: Date.now()
36493
+ });
36494
+ process.stderr.write(`[mercury] FATAL: Max restarts exceeded (${restartCount} in 60s). Last error: ${err instanceof Error ? err.message : String(err)}
36495
+ `);
36496
+ process.exit(1);
36497
+ }
36498
+ const delay = BASE_DELAY_MS * Math.pow(1.25, restartCount);
36499
+ logger.warn({ err, restartCount, delay }, "Crash detected. Restarting with backoff...");
36500
+ await sleep2(delay);
36501
+ await attempt();
36502
+ }
36503
+ }
36504
+ await attempt();
36505
+ }
36506
+ function sleep2(ms) {
36507
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
36508
+ }
36509
+
36510
+ // src/index.ts
36511
+ init_arrow_select();
36512
+ init_provider_models();
36513
+ init_token_store();
36514
+ init_runtime_status();
36515
+
36516
+ // src/web/server.ts
36517
+ init_logger();
36518
+ import { Hono as Hono12 } from "hono";
36519
+ import { createAdaptorServer } from "@hono/node-server";
36520
+ import { readFileSync as readFileSync30, existsSync as existsSync41, readdirSync as readdirSync8 } from "fs";
36521
+ import { join as join32, dirname as dirname8 } from "path";
36522
+ import { fileURLToPath as fileURLToPath2 } from "url";
36523
+
36031
36524
  // src/web/middleware.ts
36525
+ import { getCookie } from "hono/cookie";
36032
36526
  var PUBLIC_PATHS = /* @__PURE__ */ new Set(["/login", "/api/auth/login", "/api/auth/logout"]);
36033
36527
  function isLoopbackRequest(c) {
36034
36528
  try {
@@ -37911,7 +38405,7 @@ process.on("unhandledRejection", (reason) => {
37911
38405
  try {
37912
38406
  process.stderr.write(`
37913
38407
  \u2717 Mercury cannot start: ${message}
37914
- Stop the other instance with \`mercury stop\` or \`kill <pid>\`.
38408
+ Attach to it with \`mercury attach\`, or stop it with \`mercury stop\` / \`kill <pid>\`.
37915
38409
  `);
37916
38410
  } catch {
37917
38411
  }
@@ -37954,7 +38448,7 @@ var pkgVersion;
37954
38448
  try {
37955
38449
  pkgVersion = JSON.parse(readFileSync31(join33(__dirname2, "..", "package.json"), "utf8")).version;
37956
38450
  } catch {
37957
- pkgVersion = "1.2.4";
38451
+ pkgVersion = "1.2.5";
37958
38452
  }
37959
38453
  function hr() {
37960
38454
  console.log(chalk10.dim("\u2500".repeat(50)));
@@ -39687,7 +40181,7 @@ async function runAgent(isDaemon = false) {
39687
40181
  try {
39688
40182
  process.stderr.write(`
39689
40183
  \u2717 Mercury cannot start: ${message}
39690
- Stop the other instance with \`mercury stop\` or \`kill <pid>\`.
40184
+ Attach to it with \`mercury attach\`, or stop it with \`mercury stop\` / \`kill <pid>\`.
39691
40185
  `);
39692
40186
  } catch {
39693
40187
  }
@@ -40685,6 +41179,7 @@ Assistant: ${assistantMessage.slice(0, 1500)}`,
40685
41179
  console.log(chalk10.dim(" Ctrl+C to exit \xB7 /help for commands"));
40686
41180
  if (config2.web.enabled) {
40687
41181
  startWebServer();
41182
+ writeAttachToken();
40688
41183
  updateStatus({
40689
41184
  running: true,
40690
41185
  pid: process.pid,
@@ -40733,6 +41228,7 @@ Assistant: ${assistantMessage.slice(0, 1500)}`,
40733
41228
  await channels.startAll();
40734
41229
  if (config2.web.enabled) {
40735
41230
  startWebServer();
41231
+ writeAttachToken();
40736
41232
  updateStatus({
40737
41233
  running: true,
40738
41234
  pid: process.pid,
@@ -40816,6 +41312,12 @@ program.name("mercury").description("Mercury \u2014 Soul-driven AI agent with pe
40816
41312
  autoDaemonize();
40817
41313
  return;
40818
41314
  }
41315
+ const foreground = getForegroundRuntimeStatus();
41316
+ if (foreground.running && foreground.pid) {
41317
+ console.log(chalk10.cyan(` \u26BF Mercury is already running (PID: ${foreground.pid}) \u2014 attaching.`));
41318
+ await runAttach();
41319
+ return;
41320
+ }
40819
41321
  autoDaemonize();
40820
41322
  await runAgent();
40821
41323
  });
@@ -40830,11 +41332,20 @@ program.command("start").description("Start Mercury \u2014 runs as a daemon by d
40830
41332
  return;
40831
41333
  }
40832
41334
  if (opts.foreground) {
41335
+ const foreground = getForegroundRuntimeStatus();
41336
+ if (foreground.running && foreground.pid) {
41337
+ console.log(chalk10.cyan(` \u26BF Mercury is already running (PID: ${foreground.pid}) \u2014 attaching.`));
41338
+ await runAttach();
41339
+ return;
41340
+ }
40833
41341
  await runAgent();
40834
41342
  return;
40835
41343
  }
40836
41344
  startBackground();
40837
41345
  });
41346
+ program.command("attach").description("Attach this terminal to an already-running Mercury runtime (chat + live streaming)").action(async () => {
41347
+ await runAttach();
41348
+ });
40838
41349
  program.command("stop").description("Stop a running Mercury process (daemon or foreground)").action(async () => {
40839
41350
  const foreground = getForegroundRuntimeStatus();
40840
41351
  const daemon = getDaemonStatus();