omnius 1.0.109 → 1.0.110

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
@@ -563485,970 +563485,1503 @@ var init_task_complete_box = __esm({
563485
563485
  }
563486
563486
  });
563487
563487
 
563488
- // packages/cli/src/tui/render.ts
563489
- var render_exports = {};
563490
- __export(render_exports, {
563491
- SLASH_COMMANDS: () => SLASH_COMMANDS2,
563492
- c: () => c3,
563493
- fileLink: () => fileLink,
563494
- formatInlineMarkdown: () => formatInlineMarkdown,
563495
- formatMarkdownBlock: () => formatMarkdownBlock,
563496
- formatMarkdownLine: () => formatMarkdownLine,
563497
- getColorsEnabled: () => getColorsEnabled,
563498
- getEmojisEnabled: () => getEmojisEnabled,
563499
- getTermWidth: () => getTermWidth,
563500
- pastel: () => pastel,
563501
- renderAssistantText: () => renderAssistantText,
563502
- renderCompactHeader: () => renderCompactHeader,
563503
- renderConfig: () => renderConfig,
563504
- renderError: () => renderError,
563505
- renderHeader: () => renderHeader,
563506
- renderImageAsciiPreview: () => renderImageAsciiPreview,
563507
- renderInfo: () => renderInfo,
563508
- renderModelList: () => renderModelList,
563509
- renderModelSwitch: () => renderModelSwitch,
563510
- renderRichHeader: () => renderRichHeader,
563511
- renderSlashHelp: () => renderSlashHelp,
563512
- renderTaskAborted: () => renderTaskAborted,
563513
- renderTaskComplete: () => renderTaskComplete,
563514
- renderTaskIncomplete: () => renderTaskIncomplete,
563515
- renderThinking: () => renderThinking,
563516
- renderToolCallStart: () => renderToolCallStart,
563517
- renderToolLine: () => renderToolLine,
563518
- renderToolResult: () => renderToolResult,
563519
- renderUserInterrupt: () => renderUserInterrupt,
563520
- renderUserMessage: () => renderUserMessage,
563521
- renderVerbose: () => renderVerbose,
563522
- renderVoiceText: () => renderVoiceText,
563523
- renderWarning: () => renderWarning,
563524
- setColorsEnabled: () => setColorsEnabled,
563525
- setContentWriteHook: () => setContentWriteHook,
563526
- setEmojisEnabled: () => setEmojisEnabled,
563527
- ui: () => ui
563528
- });
563529
- function accentFg() {
563530
- const a2 = tuiAccent();
563531
- return a2 < 0 ? "\x1B[39m" : `\x1B[38;5;${a2}m`;
563532
- }
563533
- function dimFg() {
563534
- return `\x1B[38;5;${tuiTextDim()}m`;
563535
- }
563536
- function ansi2(code8, text) {
563537
- return isTTY2 ? `\x1B[${code8}m${text}\x1B[0m` : text;
563538
- }
563539
- function fg256(code8, text) {
563540
- return isTTY2 ? `\x1B[38;5;${code8}m${text}\x1B[0m` : text;
563541
- }
563542
- function hyperlink(url, text) {
563543
- if (!isTTY2) return text;
563544
- return `\x1B]8;;${url}\x07${text}\x1B]8;;\x07`;
563488
+ // packages/cli/src/tui/model-picker.ts
563489
+ import { totalmem as totalmem3 } from "node:os";
563490
+ function isImageGenModel(name10, family) {
563491
+ return IMAGE_GEN_PATTERNS.some((p2) => p2.test(name10) || family && p2.test(family));
563545
563492
  }
563546
- function fileLink(filePath) {
563547
- if (!isTTY2) return filePath;
563548
- if (filePath.startsWith("/") || filePath.startsWith("~")) {
563549
- const absPath = filePath.startsWith("~") ? filePath.replace("~", process.env["HOME"] ?? "") : filePath;
563550
- return hyperlink(`file://${absPath}`, filePath);
563493
+ function parseShowNumCtx(show) {
563494
+ const sources = [show.parameters, show.modelfile];
563495
+ for (const source of sources) {
563496
+ if (!source) continue;
563497
+ const match = source.match(/\b(?:PARAMETER\s+)?num_ctx\s+(\d+)/i);
563498
+ if (match) return parseInt(match[1], 10);
563551
563499
  }
563552
- return filePath;
563553
- }
563554
- function setEmojisEnabled(enabled2) {
563555
- _emojisEnabled = enabled2;
563556
- }
563557
- function getEmojisEnabled() {
563558
- return _emojisEnabled;
563559
- }
563560
- function setColorsEnabled(enabled2) {
563561
- _colorsEnabled = enabled2;
563562
- }
563563
- function getColorsEnabled() {
563564
- return _colorsEnabled;
563565
- }
563566
- function getTermWidth() {
563567
- return termCols();
563500
+ return null;
563568
563501
  }
563569
- function formatMarkdownLine(line) {
563570
- const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
563571
- if (headingMatch) {
563572
- const level = headingMatch[1].length;
563573
- const text = headingMatch[2];
563574
- const colors2 = [MD.heading1, MD.heading2, MD.heading3, MD.heading3, 183, 183];
563575
- return c3.bold(fg256(colors2[level - 1] ?? 147, formatInlineMarkdown(text)));
563576
- }
563577
- if (/^[-*_]{3,}\s*$/.test(line)) {
563578
- const w = getTermWidth() - 10;
563579
- return fg256(MD.hr, "─".repeat(Math.min(w, 60)));
563580
- }
563581
- if (/^>\s?/.test(line)) {
563582
- const content = line.replace(/^>\s?/, "");
563583
- return fg256(MD.blockquote, "│ ") + c3.italic(fg256(MD.blockquote, formatInlineMarkdown(content)));
563502
+ async function fetchOllamaModels(baseUrl) {
563503
+ const url = `${normalizeBaseUrl(baseUrl)}/api/tags`;
563504
+ const resp = await fetch(url, {
563505
+ signal: AbortSignal.timeout(1e4)
563506
+ });
563507
+ if (!resp.ok) {
563508
+ throw new Error(`Failed to fetch models: HTTP ${resp.status}`);
563584
563509
  }
563585
- if (/^\|(.+)\|/.test(line)) {
563586
- if (/^\|[\s:_-]+\|/.test(line)) {
563587
- return fg256(MD.tableBar, line);
563510
+ const data = await resp.json();
563511
+ const models = data.models ?? [];
563512
+ const result = models.map((m2) => {
563513
+ const family = m2.details?.family;
563514
+ return {
563515
+ name: m2.name,
563516
+ size: formatBytes3(m2.size),
563517
+ sizeBytes: m2.size,
563518
+ modified: formatRelativeTime(m2.modified_at),
563519
+ parameterSize: m2.details?.parameter_size,
563520
+ contextLength: void 0,
563521
+ caps: void 0,
563522
+ isImageGen: isImageGenModel(m2.name, family),
563523
+ family
563524
+ };
563525
+ }).sort((a2, b) => b.sizeBytes - a2.sizeBytes);
563526
+ const normalized = normalizeBaseUrl(baseUrl);
563527
+ const showResults = await Promise.allSettled(
563528
+ result.map(
563529
+ (m2) => fetch(`${normalized}/api/show`, {
563530
+ method: "POST",
563531
+ headers: { "Content-Type": "application/json" },
563532
+ body: JSON.stringify({ name: m2.name }),
563533
+ signal: AbortSignal.timeout(5e3)
563534
+ }).then((r2) => r2.ok ? r2.json() : null)
563535
+ )
563536
+ );
563537
+ for (let i2 = 0; i2 < result.length; i2++) {
563538
+ const sr = showResults[i2];
563539
+ if (sr?.status !== "fulfilled" || !sr.value) continue;
563540
+ const show = sr.value;
563541
+ const explicitNumCtx = parseShowNumCtx(show);
563542
+ if (explicitNumCtx) {
563543
+ result[i2].contextLength = explicitNumCtx;
563544
+ continue;
563588
563545
  }
563589
- return line.replace(/([^|]+)/g, (cell) => {
563590
- const trimmed = cell.trim();
563591
- if (!trimmed) return cell;
563592
- const leading = cell.match(/^(\s*)/)?.[1] ?? "";
563593
- const trailing = cell.match(/(\s*)$/)?.[1] ?? "";
563594
- return leading + formatInlineMarkdown(trimmed) + trailing;
563595
- });
563596
- }
563597
- const ulMatch = line.match(/^(\s*)([-*+])\s+(.*)/);
563598
- if (ulMatch) {
563599
- return ulMatch[1] + fg256(MD.listBullet, "○") + " " + formatInlineMarkdown(ulMatch[3]);
563600
- }
563601
- const olMatch = line.match(/^(\s*)(\d+[.)])\s+(.*)/);
563602
- if (olMatch) {
563603
- return olMatch[1] + fg256(MD.listBullet, olMatch[2]) + " " + formatInlineMarkdown(olMatch[3]);
563604
- }
563605
- return formatInlineMarkdown(line);
563606
- }
563607
- function formatInlineMarkdown(text) {
563608
- let result = text;
563609
- result = result.replace(/`([^`]+)`/g, (_m, code8) => fg256(MD.inlineCode, code8));
563610
- result = result.replace(/\*{3}([^*]+)\*{3}/g, (_m, t2) => c3.bold(c3.italic(t2)));
563611
- result = result.replace(/\*{2}([^*]+)\*{2}/g, (_m, t2) => c3.bold(t2));
563612
- result = result.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_m, t2) => c3.italic(t2));
563613
- result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, label, url) => c3.bold(fg256(MD.link, label)) + " " + c3.dim(fg256(MD.link, `(${url})`)));
563614
- result = result.replace(/(?<!\w)__([^_]+)__(?!\w)/g, (_m, t2) => c3.bold(t2));
563615
- result = result.replace(/(?<!\w)_([^_]+)_(?!\w)/g, (_m, t2) => c3.italic(t2));
563616
- result = result.replace(/~~([^~]+)~~/g, (_m, t2) => c3.dim(t2));
563617
- return result;
563618
- }
563619
- function formatMarkdownBlock(text) {
563620
- const lines = text.split("\n");
563621
- const result = [];
563622
- let inCodeBlock = false;
563623
- let codeLang = "";
563624
- for (const line of lines) {
563625
- const trimmedLine = line.trimStart();
563626
- if (trimmedLine.startsWith("```")) {
563627
- if (inCodeBlock) {
563628
- result.push(c3.dim(" ```"));
563629
- inCodeBlock = false;
563630
- codeLang = "";
563631
- } else {
563632
- codeLang = trimmedLine.slice(3).trim();
563633
- result.push(c3.dim(" ```" + codeLang));
563634
- inCodeBlock = true;
563546
+ if (show.model_info) {
563547
+ const info = show.model_info;
563548
+ const arch3 = info["general.architecture"];
563549
+ const paramCount = info["general.parameter_count"];
563550
+ const fileSizeGB = result[i2].sizeBytes > 0 ? result[i2].sizeBytes / 1024 ** 3 : paramCount ? paramCount * 0.6 / 1024 ** 3 : 4;
563551
+ if (arch3) {
563552
+ const archMax = info[`${arch3}.context_length`];
563553
+ const nLayers = info[`${arch3}.block_count`];
563554
+ const nKVHeads = info[`${arch3}.attention.head_count_kv`] ?? info[`${arch3}.attention.head_count`];
563555
+ const keyDim = info[`${arch3}.attention.key_length`];
563556
+ const valDim = info[`${arch3}.attention.value_length`] ?? keyDim;
563557
+ if (archMax && nLayers && nKVHeads && keyDim && valDim) {
563558
+ const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
563559
+ result[i2].contextLength = estimateRealisticContext(kvBytesPerToken, archMax, fileSizeGB);
563560
+ } else if (archMax) {
563561
+ const kvEstimate = fileSizeGB <= 5 ? 524288 : fileSizeGB <= 20 ? 1048576 : 1572864;
563562
+ result[i2].contextLength = estimateRealisticContext(kvEstimate, archMax, fileSizeGB);
563563
+ }
563635
563564
  }
563636
- continue;
563637
563565
  }
563638
- if (inCodeBlock) {
563639
- result.push(" " + c3.dim(line));
563640
- } else {
563641
- result.push(formatMarkdownLine(line));
563566
+ const modelCaps = { vision: false, toolUse: false, thinking: false };
563567
+ const nameLower = result[i2].name.toLowerCase();
563568
+ if (Array.isArray(show.capabilities)) {
563569
+ if (show.capabilities.includes("vision")) modelCaps.vision = true;
563570
+ if (show.capabilities.includes("tools")) modelCaps.toolUse = true;
563571
+ if (show.capabilities.includes("thinking")) modelCaps.thinking = true;
563642
563572
  }
563643
- }
563644
- return result.join("\n");
563645
- }
563646
- function renderUserMessage(text) {
563647
- process.stdout.write(`
563648
- ${accentFg()}▹\x1B[0m ${c3.bold(text)}
563649
- `);
563650
- }
563651
- function renderAssistantText(text) {
563652
- if (!text.trim()) return;
563653
- const formatted = formatMarkdownBlock(text);
563654
- const lines = formatted.split("\n");
563655
- for (const line of lines) {
563656
- process.stdout.write(` ${line}
563657
- `);
563658
- }
563659
- }
563660
- function renderThinking(text) {
563661
- process.stdout.write(`
563662
- ${c3.magenta("●")} ${c3.italic(text)}
563663
- `);
563664
- }
563665
- function renderVoiceText(text) {
563666
- process.stdout.write(` ${c3.dim("🔊")} ${c3.italic(c3.dim(text))}
563667
- `);
563668
- }
563669
- function renderUserInterrupt(text) {
563670
- process.stdout.write(`
563671
- ${c3.cyan("↪")} ${c3.bold("Context added:")} ${text}
563672
- `);
563673
- }
563674
- function renderTaskAborted() {
563675
- process.stdout.write(`
563676
- ${c3.yellow("⚠")} ${c3.bold("Task aborted by user")}
563677
- `);
563678
- }
563679
- function normalizeToolOpts(arg) {
563680
- return typeof arg === "boolean" ? { verbose: arg } : arg ?? {};
563681
- }
563682
- function toolColorCode(toolName) {
563683
- return TOOL_COLOR_CODES[toolName] ?? tuiTextDim();
563684
- }
563685
- function toolColorSeq(code8, bold = false) {
563686
- if (!_colorsEnabled || !isTTY2) return "";
563687
- return `\x1B[${bold ? "1;" : ""}38;5;${code8}m`;
563688
- }
563689
- function toolResetSeq() {
563690
- return _colorsEnabled && isTTY2 ? RESET2 : "";
563691
- }
563692
- function visibleLen(text) {
563693
- return stripAnsi(text).length;
563694
- }
563695
- function truncateAnsiToWidth(text, width) {
563696
- if (width <= 0) return "";
563697
- if (visibleLen(text) <= width) return text;
563698
- if (width === 1) return "…";
563699
- const target = Math.max(0, width - 1);
563700
- let out = "";
563701
- let visible = 0;
563702
- let hasAnsi = false;
563703
- const tokenRe = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|./gs;
563704
- for (const token of text.matchAll(tokenRe)) {
563705
- const value2 = token[0];
563706
- if (value2.startsWith("\x1B")) {
563707
- hasAnsi = true;
563708
- out += value2;
563709
- continue;
563573
+ if (show.model_info) {
563574
+ for (const key of Object.keys(show.model_info)) {
563575
+ const k = key.toLowerCase();
563576
+ if (k.includes("vision.block_count") || k.includes("clip.") || k.includes("image_token_id") || k.includes("projector")) {
563577
+ const val = show.model_info[key];
563578
+ if (val !== null && val !== void 0 && val !== 0 && val !== "") {
563579
+ modelCaps.vision = true;
563580
+ }
563581
+ }
563582
+ }
563710
563583
  }
563711
- if (visible >= target) break;
563712
- out += value2;
563713
- visible += 1;
563714
- }
563715
- return `${out}…${hasAnsi ? RESET2 : ""}`;
563716
- }
563717
- function wrapToolTextLine(text, width) {
563718
- if (width <= 0) return [text];
563719
- if (text.length === 0) return [""];
563720
- const out = [];
563721
- const continuationIndent = hangingIndentForPlainLine(text, width);
563722
- let remaining = text;
563723
- while (remaining.length > width) {
563724
- let breakAt = remaining.lastIndexOf(" ", width);
563725
- if (breakAt <= continuationIndent.length) breakAt = width;
563726
- out.push(remaining.slice(0, breakAt).trimEnd());
563727
- remaining = continuationIndent + remaining.slice(breakAt).trimStart();
563728
- }
563729
- out.push(remaining);
563730
- return out;
563731
- }
563732
- function buildToolTopBorder(title, metrics2, width, colorCode) {
563733
- const border = toolColorSeq(colorCode);
563734
- const titleColor = toolColorSeq(colorCode, true);
563735
- const metricColor = toolColorSeq(222);
563736
- const reset = toolResetSeq();
563737
- const inner = Math.max(4, width - 2);
563738
- const titleVisible = stripAnsi(title);
563739
- const metricsVisible = stripAnsi(metrics2);
563740
- const titleChip = ` ${titleVisible} `;
563741
- const titleSpan = titleChip.length + 2;
563742
- const metricsChip = metricsVisible ? ` ${metricsVisible} ` : "";
563743
- const metricsSpan = metricsChip ? metricsChip.length + 2 : 0;
563744
- let titleSegment;
563745
- let metricsSegment = "";
563746
- let fillerWidth;
563747
- if (titleSpan + metricsSpan + 4 <= inner) {
563748
- titleSegment = `${border}┤${titleColor}${titleChip}${reset}${border}├`;
563749
- metricsSegment = metricsChip ? `${border}┤${metricColor}${metricsChip}${reset}${border}├` : "";
563750
- fillerWidth = inner - titleSpan - metricsSpan - 2;
563751
- } else if (titleSpan + 4 <= inner) {
563752
- titleSegment = `${border}┤${titleColor}${titleChip}${reset}${border}├`;
563753
- fillerWidth = inner - titleSpan - 2;
563754
- } else {
563755
- const room = Math.max(3, inner - 8);
563756
- const truncated = titleVisible.length > room ? titleVisible.slice(0, Math.max(1, room - 1)) + "…" : titleVisible;
563757
- titleSegment = `${border}┤${titleColor} ${truncated} ${reset}${border}├`;
563758
- fillerWidth = Math.max(0, inner - (truncated.length + 4) - 2);
563759
- }
563760
- return `${border}${BOX_TL2}${BOX_H2}${titleSegment}${BOX_H2.repeat(Math.max(0, fillerWidth))}${metricsSegment}${BOX_H2}${BOX_TR2}${reset}`;
563761
- }
563762
- function buildToolDivider(width, colorCode) {
563763
- const border = toolColorSeq(colorCode);
563764
- return `${border}${BOX_TJ_L2}${BOX_H2.repeat(Math.max(0, width - 2))}${BOX_TJ_R2}${toolResetSeq()}`;
563765
- }
563766
- function buildToolBottom(width, colorCode) {
563767
- const border = toolColorSeq(colorCode);
563768
- return `${border}${BOX_BL2}${BOX_H2.repeat(Math.max(0, width - 2))}${BOX_BR2}${toolResetSeq()}`;
563769
- }
563770
- function buildToolContentRow(content, width, colorCode) {
563771
- const border = toolColorSeq(colorCode);
563772
- const reset = toolResetSeq();
563773
- const innerWidth = Math.max(1, width - 4);
563774
- let padded = visibleLen(content) > innerWidth ? truncateAnsiToWidth(content, innerWidth) : content;
563775
- const visible = visibleLen(padded);
563776
- if (visible < innerWidth) padded += " ".repeat(innerWidth - visible);
563777
- return `${border}${BOX_V2}${reset} ${padded} ${border}${BOX_V2}${reset}`;
563778
- }
563779
- function formatToolBoxLine(line, kind) {
563780
- switch (kind) {
563781
- case "error":
563782
- return c3.magenta(line);
563783
- case "success":
563784
- return c3.green(line);
563785
- case "dim":
563786
- return c3.dim(line);
563787
- case "markdown": {
563788
- const formatted = formatMarkdownLine(line);
563789
- return formatted === line ? highlightToolOutput(line) : formatted;
563584
+ if (/qwen3|qwen2\.5|llama3\.[13]|mistral|mixtral|command-r|gemma3|devstral|deepseek/.test(nameLower)) {
563585
+ modelCaps.toolUse = true;
563790
563586
  }
563791
- case "tool":
563792
- return /\x1B\[[0-?]*[ -/]*[@-~]/.test(line) ? line : highlightToolOutput(line);
563793
- case "plain":
563794
- default:
563795
- return line;
563587
+ if (show.template && (show.template.includes("<think>") || show.template.includes("thinking"))) {
563588
+ modelCaps.thinking = true;
563589
+ }
563590
+ result[i2].caps = modelCaps;
563796
563591
  }
563592
+ return result;
563797
563593
  }
563798
- function wrapFooterItems(items, width) {
563799
- const sep4 = " · ";
563800
- const lines = [];
563801
- let current = "";
563802
- for (const item of items) {
563803
- const clean5 = item.replace(/\s+/g, " ").trim();
563804
- if (!clean5) continue;
563805
- const candidate = current ? `${current}${sep4}${clean5}` : clean5;
563806
- if (visibleLen(candidate) <= width) {
563807
- current = candidate;
563808
- continue;
563809
- }
563810
- if (current) lines.push(current);
563811
- if (clean5.length > width) {
563812
- const chunks = wrapToolTextLine(clean5, width);
563813
- lines.push(...chunks.slice(0, -1));
563814
- current = chunks[chunks.length - 1] ?? "";
563594
+ async function fetchOpenAIModels(baseUrl, apiKey) {
563595
+ const normalized = normalizeBaseUrl(baseUrl);
563596
+ const url = `${normalized}/v1/models`;
563597
+ const isAnthropic = /api\.anthropic\.com/i.test(baseUrl);
563598
+ const headers = {};
563599
+ if (apiKey) {
563600
+ if (isAnthropic) {
563601
+ headers["x-api-key"] = apiKey;
563602
+ headers["anthropic-version"] = "2023-06-01";
563815
563603
  } else {
563816
- current = clean5;
563604
+ headers["Authorization"] = `Bearer ${apiKey}`;
563817
563605
  }
563818
563606
  }
563819
- if (current) lines.push(current);
563820
- return lines;
563821
- }
563822
- function buildToolFooterRows(footer, width, colorCode) {
563823
- const items = [...new Set(footer.items.map((item) => item.trim()).filter(Boolean))];
563824
- if (items.length === 0) return [];
563825
- const innerWidth = Math.max(8, width - 4);
563826
- const labelVisible = `${footer.label}: `.length;
563827
- const label = `${toolColorSeq(colorCode, true)}${footer.label}:${toolResetSeq()} `;
563828
- const wrapped = wrapFooterItems(items, Math.max(8, innerWidth - labelVisible));
563829
- if (wrapped.length === 0) return [];
563830
- const rows = [buildToolContentRow(`${label}${wrapped[0]}`, width, colorCode)];
563831
- for (const line of wrapped.slice(1)) {
563832
- rows.push(buildToolContentRow(`${" ".repeat(labelVisible)}${line}`, width, colorCode));
563607
+ const resp = await fetch(url, {
563608
+ headers,
563609
+ signal: AbortSignal.timeout(15e3)
563610
+ });
563611
+ if (!resp.ok) {
563612
+ throw new Error(`Failed to fetch models: HTTP ${resp.status}`);
563833
563613
  }
563834
- return rows;
563614
+ const data = await resp.json();
563615
+ const models = data.data ?? [];
563616
+ return models.map((m2) => ({
563617
+ name: m2.id,
563618
+ size: "",
563619
+ sizeBytes: 0,
563620
+ modified: m2.created ? formatRelativeTime(new Date(m2.created * 1e3).toISOString()) : "",
563621
+ parameterSize: m2.owned_by ?? void 0,
563622
+ contextLength: m2.context_length ?? m2.max_model_len ?? void 0
563623
+ })).sort((a2, b) => a2.name.localeCompare(b.name));
563835
563624
  }
563836
- function buildToolBoxLines(data, width) {
563837
- const w = Math.max(40, width);
563838
- const innerWidth = Math.max(1, w - 4);
563839
- const lines = [
563840
- buildToolTopBorder(data.title, data.metrics, w, data.colorCode),
563841
- buildToolDivider(w, data.colorCode)
563842
- ];
563843
- for (const bodyLine of data.body.length > 0 ? data.body : [{ text: "Done", mode: "wrap", kind: "dim" }]) {
563844
- const chunks = bodyLine.mode === "truncate" ? [truncateAnsiToWidth(bodyLine.text, innerWidth)] : wrapToolTextLine(bodyLine.text, innerWidth);
563845
- for (const chunk of chunks) {
563846
- lines.push(buildToolContentRow(formatToolBoxLine(chunk, bodyLine.kind), w, data.colorCode));
563625
+ async function fetchPeerModels(peerId, authKey) {
563626
+ try {
563627
+ const { NexusTool: NexusTool2 } = await Promise.resolve().then(() => (init_dist5(), dist_exports));
563628
+ const { existsSync: existsSync131, readFileSync: readFileSync107 } = await import("node:fs");
563629
+ const { join: join148 } = await import("node:path");
563630
+ const cwd4 = process.cwd();
563631
+ const nexusTool = new NexusTool2(cwd4);
563632
+ const nexusDir = nexusTool.getNexusDir();
563633
+ let isLocalPeer = false;
563634
+ try {
563635
+ const statusPath = join148(nexusDir, "status.json");
563636
+ if (existsSync131(statusPath)) {
563637
+ const status = JSON.parse(readFileSync107(statusPath, "utf8"));
563638
+ if (status.peerId === peerId) isLocalPeer = true;
563639
+ }
563640
+ } catch {
563847
563641
  }
563848
- }
563849
- const footers = (data.footers ?? []).filter((footer) => footer.items.length > 0);
563850
- if (footers.length > 0) {
563851
- lines.push(buildToolDivider(w, data.colorCode));
563852
- for (const footer of footers) {
563853
- lines.push(...buildToolFooterRows(footer, w, data.colorCode));
563642
+ if (isLocalPeer) {
563643
+ const pricingPath = join148(nexusDir, "pricing.json");
563644
+ if (existsSync131(pricingPath)) {
563645
+ try {
563646
+ const pricing = JSON.parse(readFileSync107(pricingPath, "utf8"));
563647
+ const localModels = (pricing.models || []).map((m2) => ({
563648
+ name: m2.model || "unknown",
563649
+ size: m2.parameterSize || "",
563650
+ modified: "",
563651
+ sizeBytes: 0,
563652
+ parameterSize: m2.parameterSize || "remote"
563653
+ }));
563654
+ if (localModels.length > 0) return localModels;
563655
+ } catch {
563656
+ }
563657
+ }
563854
563658
  }
563855
- }
563856
- lines.push(buildToolBottom(w, data.colorCode));
563857
- return lines;
563858
- }
563859
- function renderToolDynamicBlock(kind, render2, opts) {
563860
- const redir = _contentWriteHook?.redirect?.();
563861
- const host = opts.host !== void 0 ? opts.host : _contentWriteHook?.dynamicBlockHost?.();
563862
- if (!redir && host) {
563863
- const id = `${kind}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
563864
- host.registerDynamicBlock(id, render2);
563865
- host.appendDynamicBlock(id);
563866
- return;
563867
- }
563868
- const text = `${render2(getTermWidth()).join("\n")}
563869
- `;
563870
- if (redir) {
563871
- redir(text);
563872
- return;
563873
- }
563874
- process.stdout.write(text);
563875
- }
563876
- function buildToolCallBoxLines(toolName, args, verbose, width) {
563877
- const icon = _emojisEnabled ? `${TOOL_ICONS[toolName] ?? "🔧"} ` : "";
563878
- const label = TOOL_LABELS[toolName] ?? toolName;
563879
- const body = formatToolArgsForBox(toolName, args, verbose).map((text) => ({
563880
- text,
563881
- mode: "wrap",
563882
- kind: "tool"
563883
- }));
563884
- return buildToolBoxLines({
563885
- title: `${icon}${label}`,
563886
- metrics: "call",
563887
- body,
563888
- colorCode: toolColorCode(toolName)
563889
- }, width);
563890
- }
563891
- function formatToolArgsForBox(toolName, args, verbose) {
563892
- switch (toolName) {
563893
- case "shell":
563894
- return [`Command: ${String(args["command"] ?? "")}${args["stdin"] ? " (with stdin)" : ""}`];
563895
- case "background_run":
563896
- return [`Command: ${String(args["command"] ?? "")}`];
563897
- case "file_read":
563898
- case "file_write":
563899
- case "file_edit":
563900
- case "list_directory":
563901
- return [`Path: ${String(args["path"] ?? ".")}`];
563902
- case "grep_search":
563903
- return [
563904
- `Pattern: ${String(args["pattern"] ?? "")}`,
563905
- args["path"] ? `Path: ${String(args["path"])}` : ""
563906
- ].filter(Boolean);
563907
- case "find_files":
563908
- return [`Pattern: ${String(args["pattern"] ?? "")}`];
563909
- case "web_search":
563910
- return [`Query: ${String(args["query"] ?? "")}`];
563911
- case "web_fetch":
563912
- return [`URL: ${String(args["url"] ?? "")}`];
563913
- case "memory_read":
563914
- return [`Key: ${args["topic"]}${args["key"] ? "." + args["key"] : ""}`];
563915
- case "memory_write":
563916
- return [`Key: ${args["topic"]}.${args["key"]}`];
563917
- default: {
563918
- const entries = Object.entries(args ?? {});
563919
- if (entries.length === 0) return ["Starting tool call"];
563920
- const maxValue2 = verbose ? 500 : 160;
563921
- return entries.slice(0, verbose ? 20 : 8).map(([key, value2]) => {
563922
- const rendered = typeof value2 === "string" ? value2 : JSON.stringify(value2);
563923
- return `${key}: ${truncStr(String(rendered ?? ""), maxValue2)}`;
563659
+ const cachePath = join148(nexusDir, "peer-models-cache.json");
563660
+ if (existsSync131(cachePath)) {
563661
+ try {
563662
+ const cache8 = JSON.parse(readFileSync107(cachePath, "utf8"));
563663
+ if (cache8.peerId === peerId && cache8.models?.length > 0) {
563664
+ const age = Date.now() - new Date(cache8.cachedAt).getTime();
563665
+ if (age < 5 * 60 * 1e3) {
563666
+ return cache8.models.map((m2) => ({
563667
+ name: m2.name || "unknown",
563668
+ size: m2.size || m2.parameterSize || "",
563669
+ modified: "",
563670
+ sizeBytes: 0,
563671
+ parameterSize: m2.parameterSize || "remote"
563672
+ }));
563673
+ }
563674
+ }
563675
+ } catch {
563676
+ }
563677
+ }
563678
+ try {
563679
+ const capsResult = await nexusTool.execute({
563680
+ action: "query_peer_caps",
563681
+ peer_id: peerId,
563682
+ ...authKey ? { auth_key: authKey } : {}
563683
+ });
563684
+ if (capsResult.success && capsResult.output) {
563685
+ let capsData = null;
563686
+ try {
563687
+ capsData = JSON.parse(capsResult.output);
563688
+ } catch {
563689
+ }
563690
+ if (capsData?.models && capsData.models.length > 0) {
563691
+ return capsData.models.map((m2) => ({
563692
+ name: m2.name || "unknown",
563693
+ size: m2.parameterSize || "",
563694
+ modified: "",
563695
+ sizeBytes: 0,
563696
+ parameterSize: m2.parameterSize || "remote"
563697
+ }));
563698
+ }
563699
+ if (capsData?.capabilities && capsData.capabilities.length > 0) {
563700
+ const models = [];
563701
+ for (const cap of capsData.capabilities) {
563702
+ if (typeof cap === "string" && cap.startsWith("inference:")) {
563703
+ const capName = cap.slice(10);
563704
+ const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
563705
+ models.push({
563706
+ name: modelName,
563707
+ size: "",
563708
+ modified: "",
563709
+ sizeBytes: 0,
563710
+ parameterSize: "remote"
563711
+ });
563712
+ }
563713
+ }
563714
+ if (models.length > 0) return models;
563715
+ }
563716
+ }
563717
+ } catch {
563718
+ }
563719
+ try {
563720
+ const natsResult = await nexusTool.execute({
563721
+ action: "discover_peer_caps",
563722
+ peer_id: peerId
563723
+ });
563724
+ if (natsResult.success && natsResult.output) {
563725
+ let natsPeer = null;
563726
+ try {
563727
+ natsPeer = JSON.parse(natsResult.output);
563728
+ } catch {
563729
+ }
563730
+ if (natsPeer?.capabilities && natsPeer.capabilities.length > 0) {
563731
+ const models = [];
563732
+ for (const cap of natsPeer.capabilities) {
563733
+ if (typeof cap === "string" && cap.startsWith("inference:")) {
563734
+ const capName = cap.slice(10);
563735
+ const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
563736
+ models.push({
563737
+ name: modelName,
563738
+ size: "",
563739
+ modified: "",
563740
+ sizeBytes: 0,
563741
+ parameterSize: "remote"
563742
+ });
563743
+ }
563744
+ }
563745
+ if (models.length > 0) return models;
563746
+ }
563747
+ }
563748
+ } catch {
563749
+ }
563750
+ try {
563751
+ const result = await nexusTool.execute({
563752
+ action: "find_agent",
563753
+ peer_id: peerId
563924
563754
  });
563755
+ if (result.success && result.output) {
563756
+ const models = [];
563757
+ const capMatches = result.output.matchAll(/inference:([^\s,\]]+)/g);
563758
+ for (const m2 of capMatches) {
563759
+ const capName = m2[1];
563760
+ const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
563761
+ models.push({
563762
+ name: modelName,
563763
+ size: "",
563764
+ modified: "",
563765
+ sizeBytes: 0,
563766
+ parameterSize: "remote"
563767
+ });
563768
+ }
563769
+ if (models.length > 0) return models;
563770
+ }
563771
+ } catch {
563772
+ }
563773
+ if (isLocalPeer) {
563774
+ const pricingPath = join148(nexusDir, "pricing.json");
563775
+ if (existsSync131(pricingPath)) {
563776
+ try {
563777
+ const pricing = JSON.parse(readFileSync107(pricingPath, "utf8"));
563778
+ return (pricing.models || []).map((m2) => ({
563779
+ name: m2.model || "unknown",
563780
+ size: m2.parameterSize || "",
563781
+ modified: "",
563782
+ sizeBytes: 0,
563783
+ parameterSize: m2.parameterSize || "remote"
563784
+ }));
563785
+ } catch {
563786
+ }
563787
+ }
563925
563788
  }
563789
+ return [];
563790
+ } catch {
563791
+ return [];
563926
563792
  }
563927
563793
  }
563928
- function buildToolResultBoxLines(toolName, success, output, opts, width) {
563929
- const label = TOOL_LABELS[toolName] ?? toolName;
563930
- const status = success ? "✔" : "✖";
563931
- const body = buildToolResultBody(toolName, success, output, opts.verbose);
563932
- const rawLines = output.split("\n").filter((line) => line.trim()).length;
563933
- const metrics2 = [
563934
- success ? "ok" : "failed",
563935
- rawLines > 0 ? `${rawLines} line${rawLines === 1 ? "" : "s"}` : "",
563936
- output.length > 0 ? `${output.length.toLocaleString()} chars` : "",
563937
- opts.durationMs && opts.durationMs > 0 ? formatDuration3(opts.durationMs) : ""
563938
- ].filter(Boolean).join(" · ");
563939
- const provenance = detectProvenanceAnchors([output]);
563940
- const artifacts = detectToolArtifacts(output);
563941
- const footers = [
563942
- { label: "Artifacts", items: artifacts },
563943
- { label: "Provenance", items: provenance }
563944
- ];
563945
- return buildToolBoxLines({
563946
- title: `${status} ${label}`,
563947
- metrics: metrics2,
563948
- body,
563949
- footers,
563950
- colorCode: toolColorCode(toolName)
563951
- }, width);
563952
- }
563953
- function buildToolResultBody(toolName, success, output, verbose) {
563954
- const debug = loadConfig()?.debug ?? false;
563955
- if (toolName === "file_write" || toolName === "file_edit") {
563956
- const summary = extractFirstLine(output, Number.MAX_SAFE_INTEGER);
563957
- return [{
563958
- text: summary,
563959
- mode: "wrap",
563960
- kind: success ? "dim" : "error"
563961
- }];
563794
+ async function fetchModels(baseUrl, apiKey) {
563795
+ if (baseUrl.startsWith("peer://")) {
563796
+ return fetchPeerModels(baseUrl.slice(7), apiKey);
563962
563797
  }
563963
- if (toolName === "file_read") {
563964
- if (!success) {
563965
- return [{ text: extractFirstLine(output, Number.MAX_SAFE_INTEGER), mode: "wrap", kind: "error" }];
563798
+ const provider = detectProvider(baseUrl);
563799
+ if (provider.id === "ollama") {
563800
+ let ollamaErr;
563801
+ try {
563802
+ return await fetchOllamaModels(baseUrl);
563803
+ } catch (err) {
563804
+ ollamaErr = err instanceof Error ? err : new Error(String(err));
563805
+ try {
563806
+ return await fetchOpenAIModels(baseUrl, apiKey);
563807
+ } catch {
563808
+ throw new Error(`Cannot reach Ollama at ${baseUrl}: ${ollamaErr.message}`);
563809
+ }
563966
563810
  }
563967
- return codePreviewLines(output, 10);
563968
- }
563969
- if (toolName === "task_complete") {
563970
- const summary = output && output.trim() ? output : "Done";
563971
- return summary.split("\n").map((line) => ({
563972
- text: line,
563973
- mode: "wrap",
563974
- kind: "markdown"
563975
- }));
563976
- }
563977
- const filtered = output.split("\n").filter((line) => {
563978
- const trimmed = line.trim();
563979
- if (!trimmed) return false;
563980
- if (!debug && (trimmed.startsWith("[trust_tier:") || trimmed.startsWith("[SYSTEM]:") || trimmed.includes("tool_output_untrusted") || trimmed.includes("FORCED PROGRESS BLOCK"))) return false;
563981
- return true;
563982
- });
563983
- if (filtered.length === 0) {
563984
- return [{ text: success ? "Done" : "Failed", mode: "wrap", kind: success ? "success" : "error" }];
563985
563811
  }
563986
- const maxLines = verbose ? 200 : toolName === "shell" || toolName === "background_run" || toolName === "grep_search" ? 10 : 6;
563987
- const shown = [];
563988
- for (const line of filtered.slice(0, maxLines)) {
563989
- if (isRawJsonDump(line) && !verbose) {
563990
- shown.push({ text: "(output omitted)", mode: "wrap", kind: "dim" });
563991
- break;
563812
+ let lastErr;
563813
+ for (let attempt = 0; attempt < 2; attempt++) {
563814
+ try {
563815
+ return await fetchOpenAIModels(baseUrl, apiKey);
563816
+ } catch (err) {
563817
+ lastErr = err instanceof Error ? err : new Error(String(err));
563818
+ if (attempt === 0) await new Promise((r2) => setTimeout(r2, 1e3));
563992
563819
  }
563993
- shown.push({
563994
- text: line,
563995
- mode: shouldPreserveToolLine(toolName, line) ? "truncate" : "wrap",
563996
- kind: outputLineKind(toolName, line)
563997
- });
563998
- }
563999
- if (filtered.length > maxLines) {
564000
- shown.push({
564001
- text: `... ${filtered.length - maxLines} more lines`,
564002
- mode: "wrap",
564003
- kind: "dim"
564004
- });
564005
563820
  }
564006
- return shown;
564007
- }
564008
- function codePreviewLines(output, maxLines) {
564009
- const lines = output.split("\n");
564010
- let start2 = 0;
564011
- while (start2 < lines.length && !lines[start2].trim()) start2++;
564012
- const shown = lines.slice(start2, start2 + maxLines);
564013
- if (shown.length === 0) return [{ text: "(empty file)", mode: "wrap", kind: "dim" }];
564014
- const body = shown.map((line) => ({
564015
- text: line,
564016
- mode: "truncate",
564017
- kind: "dim"
564018
- }));
564019
- const remaining = lines.length - start2 - shown.length;
564020
- if (remaining > 0) {
564021
- body.push({ text: `... ${remaining} more lines`, mode: "wrap", kind: "dim" });
563821
+ try {
563822
+ return await fetchOllamaModels(baseUrl);
563823
+ } catch {
563824
+ throw new Error(`Cannot fetch models from ${provider.label} at ${baseUrl}: ${lastErr?.message ?? "unknown error"}`);
564022
563825
  }
564023
- return body;
564024
563826
  }
564025
- function shouldPreserveToolLine(toolName, line) {
564026
- if (toolName === "file_read") return true;
564027
- if (/^\s*\d+\s*(?:[|:│])/.test(line)) return true;
564028
- if (/^\s*(?:@@|\+\+\+|---|\+|-)\s/.test(line)) return true;
564029
- if (/^\s*(?:import|export|const|let|var|function|class|interface|type)\b/.test(line)) return true;
564030
- if (/^\s*[{}[\](),.;]+\s*$/.test(line)) return true;
564031
- return false;
563827
+ function stripLatest(modelName) {
563828
+ return modelName.replace(/:latest$/i, "");
564032
563829
  }
564033
- function outputLineKind(toolName, line) {
564034
- if (/\x1B\[[0-?]*[ -/]*[@-~]/.test(line)) return "plain";
564035
- if (toolName === "shell" || toolName === "background_run" || toolName === "grep_search") return "tool";
564036
- return "markdown";
563830
+ function findModel(models, query) {
563831
+ const exact = models.find((m2) => m2.name === query);
563832
+ if (exact) return exact;
563833
+ const partial = models.find((m2) => m2.name.startsWith(query));
563834
+ if (partial) return partial;
563835
+ const fuzzy = models.find((m2) => m2.name.includes(query));
563836
+ return fuzzy;
564037
563837
  }
564038
- function detectToolArtifacts(output) {
564039
- const out = /* @__PURE__ */ new Set();
564040
- const artifactPattern = /\b(?:Saved to|Written to|Output|File|Path|Artifact):\s*([^\s]+(?:\.(?:png|jpe?g|gif|webp|svg|json|md|txt|log|wav|mp3|mp4|mov|pdf|html|csv|ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|css|scss|yaml|yml)))/gi;
564041
- for (const match of output.matchAll(artifactPattern)) {
564042
- const value2 = match[1]?.replace(/[),.;]+$/, "");
564043
- if (value2) out.add(value2);
563838
+ async function queryModelContextSize(baseUrl, modelName) {
563839
+ try {
563840
+ const normalized = normalizeBaseUrl(baseUrl);
563841
+ const res = await fetch(`${normalized}/api/show`, {
563842
+ method: "POST",
563843
+ headers: { "Content-Type": "application/json" },
563844
+ body: JSON.stringify({ name: modelName }),
563845
+ signal: AbortSignal.timeout(1e4)
563846
+ });
563847
+ if (!res.ok) return null;
563848
+ const data = await res.json();
563849
+ const explicitNumCtx = parseShowNumCtx(data);
563850
+ if (explicitNumCtx) return explicitNumCtx;
563851
+ if (data.model_info) {
563852
+ const info = data.model_info;
563853
+ const arch3 = info["general.architecture"];
563854
+ const paramCount = info["general.parameter_count"];
563855
+ const modelSizeGB2 = paramCount ? paramCount * 0.6 / 1024 ** 3 : 4;
563856
+ if (arch3) {
563857
+ const archMax = info[`${arch3}.context_length`];
563858
+ const nLayers = info[`${arch3}.block_count`];
563859
+ const nKVHeads = info[`${arch3}.attention.head_count_kv`] ?? info[`${arch3}.attention.head_count`];
563860
+ const keyDim = info[`${arch3}.attention.key_length`];
563861
+ const valDim = info[`${arch3}.attention.value_length`] ?? keyDim;
563862
+ if (archMax && nLayers && nKVHeads && keyDim && valDim) {
563863
+ const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
563864
+ return estimateRealisticContext(kvBytesPerToken, archMax, modelSizeGB2);
563865
+ }
563866
+ if (archMax) {
563867
+ const kvEstimate = modelSizeGB2 <= 5 ? 524288 : modelSizeGB2 <= 20 ? 1048576 : 1572864;
563868
+ return estimateRealisticContext(kvEstimate, archMax, modelSizeGB2);
563869
+ }
563870
+ }
563871
+ }
563872
+ return null;
563873
+ } catch {
563874
+ return null;
564044
563875
  }
564045
- return [...out].slice(0, 12);
564046
- }
564047
- function renderToolCallStart(toolName, args, verboseOrOpts) {
564048
- const opts = normalizeToolOpts(verboseOrOpts);
564049
- const frozenArgs = { ...args ?? {} };
564050
- renderToolDynamicBlock(
564051
- "tool-call",
564052
- (width) => buildToolCallBoxLines(toolName, frozenArgs, opts.verbose, width),
564053
- opts
564054
- );
564055
- }
564056
- function renderToolLine(content, isLast = false) {
564057
- const connector = isLast ? "└" : "├";
564058
- process.stdout.write(` ${c3.dim(connector)}─ ${content}
564059
- `);
564060
- }
564061
- function renderToolResult(toolName, success, output, verboseOrOpts) {
564062
- const opts = normalizeToolOpts(verboseOrOpts);
564063
- const frozenOutput = String(output ?? "");
564064
- renderToolDynamicBlock(
564065
- "tool-result",
564066
- (width) => buildToolResultBoxLines(toolName, success, frozenOutput, opts, width),
564067
- opts
564068
- );
564069
563876
  }
564070
- function renderImageAsciiPreview(title, imagePath, ascii2, renderer) {
564071
- const prefix = ` ${c3.dim("│")} `;
564072
- const maxW = Math.max(getTermWidth() - 10, 40);
564073
- const header = `${title}: ${imagePath} (${renderer})`;
564074
- process.stdout.write(`
564075
- ${prefix}${c3.cyan(c3.bold(header.length > maxW ? header.slice(0, maxW - 3) + "..." : header))}
564076
- `);
564077
- for (const line of ascii2.split("\n")) {
564078
- const renderedLine = /\x1B\[[0-?]*[ -/]*[@-~]/.test(line) ? line : c3.dim(line);
564079
- process.stdout.write(`${prefix}${renderedLine}
564080
- `);
563877
+ function estimateRealisticContext(kvBytesPerToken, archMax, modelSizeGB2) {
563878
+ const totalMemGB = totalmem3() / 1024 ** 3;
563879
+ const usableBytes = totalMemGB * 0.7 * 1024 ** 3;
563880
+ const maxTokens = Math.floor(usableBytes / kvBytesPerToken);
563881
+ let numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
563882
+ numCtx = Math.min(numCtx, 131072, archMax);
563883
+ if (modelSizeGB2 && modelSizeGB2 > 0) {
563884
+ const maxKVBytes = modelSizeGB2 * 4 * 1024 ** 3;
563885
+ const budgetCap = Math.max(2048, Math.floor(maxKVBytes / kvBytesPerToken / 1024) * 1024);
563886
+ numCtx = Math.min(numCtx, budgetCap);
564081
563887
  }
563888
+ return numCtx;
564082
563889
  }
564083
- function extractFirstLine(output, maxW) {
564084
- const line = output.split("\n").find((l2) => l2.trim()) ?? output;
564085
- return line.length > maxW ? line.slice(0, maxW - 3) + "..." : line;
564086
- }
564087
- function isRawJsonDump(line) {
564088
- if (/\\u[0-9a-fA-F]{4}/.test(line) && line.length > 200) return true;
564089
- if (/^\s*\{"content"\s*:/.test(line) && line.length > 100) return true;
564090
- if (/\[38;5;\d+m/.test(line) && line.length > 100) return true;
564091
- return false;
564092
- }
564093
- function highlightToolOutput(line) {
564094
- if (line.startsWith("+") && !line.startsWith("+++")) return c3.green(line);
564095
- if (line.startsWith("-") && !line.startsWith("---")) return c3.magenta(line);
564096
- if (line.startsWith("@@")) return c3.cyan(line);
564097
- if (line.startsWith("---") || line.startsWith("+++")) return c3.bold(line);
564098
- if (/error/i.test(line) && !/0 error/i.test(line)) return c3.magenta(line);
564099
- if (/warning/i.test(line) && !/0 warning/i.test(line)) return c3.yellow(line);
564100
- if (/\bpass(ed|ing)?\b/i.test(line)) return c3.green(line);
564101
- if (/\bfail(ed|ing|ure)?\b/i.test(line)) return c3.magenta(line);
564102
- const formatted = formatInlineMarkdown(line);
564103
- if (formatted !== line) return formatted;
564104
- return c3.dim(line);
564105
- }
564106
- function renderTaskComplete(arg1, turns, toolCalls, durationMs, tokens) {
564107
- const opts = typeof arg1 === "string" ? {
564108
- summary: arg1,
564109
- turns: turns ?? 0,
564110
- toolCalls: toolCalls ?? 0,
564111
- durationMs: durationMs ?? 0,
564112
- tokens
564113
- } : arg1;
564114
- if (opts.host) {
564115
- const { renderTaskCompleteBox: renderTaskCompleteBox2 } = (init_task_complete_box(), __toCommonJS(task_complete_box_exports));
564116
- renderTaskCompleteBox2(opts.host, {
564117
- task: opts.task ?? "",
564118
- summary: opts.summary,
564119
- turns: opts.turns,
564120
- toolCalls: opts.toolCalls,
564121
- durationMs: opts.durationMs,
564122
- tokens: opts.tokens ?? null,
564123
- filesEdited: opts.filesEdited,
564124
- testsRun: opts.testsRun,
564125
- provenanceAnchors: opts.provenanceAnchors
564126
- });
564127
- return;
564128
- }
564129
- const duration = formatDuration3(opts.durationMs);
564130
- const tokenStr = opts.tokens ? ` ${formatTokenCount2(opts.tokens)}` : "";
564131
- process.stdout.write(`
564132
- ${c3.green("✔")} ${c3.bold("Task completed")} ${c3.dim(`(${opts.turns} turns, ${opts.toolCalls} tool calls, ${duration})`)}
564133
- `);
564134
- if (tokenStr) {
564135
- process.stdout.write(` ${c3.dim(tokenStr)}
564136
- `);
564137
- }
564138
- if (opts.summary) {
564139
- const formatted = formatMarkdownBlock(wrapTaskCompleteSummary(opts.summary));
564140
- const lines = formatted.split("\n");
564141
- for (const line of lines) {
564142
- process.stdout.write(` ${line}
564143
- `);
563890
+ async function queryOpenAIContextSize(baseUrl, modelName, apiKey) {
563891
+ try {
563892
+ const models = await fetchOpenAIModels(baseUrl, apiKey);
563893
+ const model = models.find((m2) => m2.name === modelName);
563894
+ if (model?.contextLength) return model.contextLength;
563895
+ if (model?.size) {
563896
+ const match = model.size.match(/(\d+)K ctx/);
563897
+ if (match) return parseInt(match[1], 10) * 1024;
564144
563898
  }
563899
+ return null;
563900
+ } catch {
563901
+ return null;
564145
563902
  }
564146
- process.stdout.write("\n");
564147
563903
  }
564148
- function wrapTaskCompleteSummary(summary) {
564149
- const width = Math.max(24, getTermWidth() - 6);
564150
- const lines = [];
564151
- let inFence = false;
564152
- for (const line of summary.split(/\r?\n/)) {
564153
- if (line.trimStart().startsWith("```")) {
564154
- inFence = !inFence;
564155
- lines.push(line);
564156
- continue;
563904
+ async function queryContextSize(baseUrl, modelName, apiKey) {
563905
+ if (baseUrl.startsWith("peer://")) return 32768;
563906
+ const ollamaSize = await queryModelContextSize(baseUrl, modelName);
563907
+ if (ollamaSize) return ollamaSize;
563908
+ return queryOpenAIContextSize(baseUrl, modelName, apiKey);
563909
+ }
563910
+ async function queryModelCapabilities(baseUrl, modelName) {
563911
+ const caps = { vision: false, toolUse: false, thinking: false };
563912
+ if (baseUrl.startsWith("peer://")) {
563913
+ const nameLower = modelName.toLowerCase();
563914
+ if (/qwen3|qwen2\.5|llama3\.[13]|mistral|mixtral|command-r|gemma3|devstral|deepseek/.test(nameLower)) {
563915
+ caps.toolUse = true;
564157
563916
  }
564158
- if (inFence || line.length <= width) {
564159
- lines.push(line);
564160
- continue;
563917
+ if (/qwen3|deepseek-r1/.test(nameLower)) {
563918
+ caps.thinking = true;
564161
563919
  }
564162
- lines.push(...wrapPlainLine(line, width));
563920
+ return caps;
564163
563921
  }
564164
- return lines.join("\n");
564165
- }
564166
- function wrapPlainLine(line, width) {
564167
- const out = [];
564168
- const continuationIndent = hangingIndentForPlainLine(line, width);
564169
- let remaining = line;
564170
- while (remaining.length > width) {
564171
- let breakAt = remaining.lastIndexOf(" ", width);
564172
- if (breakAt <= continuationIndent.length) breakAt = width;
564173
- out.push(remaining.slice(0, breakAt).trimEnd());
564174
- remaining = continuationIndent + remaining.slice(breakAt).trimStart();
563922
+ try {
563923
+ const normalized = normalizeBaseUrl(baseUrl);
563924
+ const res = await fetch(`${normalized}/api/show`, {
563925
+ method: "POST",
563926
+ headers: { "Content-Type": "application/json" },
563927
+ body: JSON.stringify({ name: modelName }),
563928
+ signal: AbortSignal.timeout(1e4)
563929
+ });
563930
+ if (!res.ok) return caps;
563931
+ const data = await res.json();
563932
+ if (Array.isArray(data.capabilities)) {
563933
+ if (data.capabilities.includes("vision")) caps.vision = true;
563934
+ if (data.capabilities.includes("tools")) caps.toolUse = true;
563935
+ if (data.capabilities.includes("thinking")) caps.thinking = true;
563936
+ }
563937
+ if (data.model_info) {
563938
+ for (const key of Object.keys(data.model_info)) {
563939
+ const k = key.toLowerCase();
563940
+ if (k.includes("vision.block_count") || k.includes("clip.") || k.includes("image_token_id") || k.includes("projector") || k.includes("vision.embedding_length")) {
563941
+ const val = data.model_info[key];
563942
+ if (val !== null && val !== void 0 && val !== 0 && val !== "") {
563943
+ caps.vision = true;
563944
+ }
563945
+ }
563946
+ }
563947
+ }
563948
+ const nameLower = modelName.toLowerCase();
563949
+ if (/qwen3|qwen2\.5|llama3\.[13]|mistral|mixtral|command-r|gemma3|devstral|deepseek/.test(nameLower)) {
563950
+ caps.toolUse = true;
563951
+ }
563952
+ if (data.template) {
563953
+ if (data.template.includes("<think>") || data.template.includes("thinking")) {
563954
+ caps.thinking = true;
563955
+ }
563956
+ }
563957
+ return caps;
563958
+ } catch {
563959
+ return caps;
564175
563960
  }
564176
- out.push(remaining);
564177
- return out;
564178
563961
  }
564179
- function hangingIndentForPlainLine(line, width) {
564180
- const capped = (n2) => " ".repeat(Math.max(0, Math.min(n2, width - 4)));
564181
- const patterns = [
564182
- /^(\s*(?:[-*+○•]\s+))/,
564183
- /^(\s*(?:\d+[.)]\s+))/,
564184
- /^(\s*(?:>\s*))/,
564185
- /^(\s*(?:[A-Za-z][\w.-]{0,28}:\s+))/
564186
- ];
564187
- for (const pattern of patterns) {
564188
- const match = line.match(pattern);
564189
- if (match?.[1]) return capped(match[1].length);
563962
+ function formatBytes3(bytes) {
563963
+ if (bytes < 1024) return `${bytes} B`;
563964
+ const units = ["KB", "MB", "GB", "TB"];
563965
+ let size = bytes;
563966
+ let i2 = -1;
563967
+ while (size >= 1024 && i2 < units.length - 1) {
563968
+ size /= 1024;
563969
+ i2++;
564190
563970
  }
564191
- return capped(line.match(/^\s*/)?.[0].length ?? 0);
563971
+ return `${size.toFixed(1)} ${units[i2] ?? "B"}`;
564192
563972
  }
564193
- function renderTaskIncomplete(turns, toolCalls, durationMs, tokens) {
564194
- const duration = formatDuration3(durationMs);
564195
- const tokenStr = tokens ? ` ${formatTokenCount2(tokens)}` : "";
564196
- process.stdout.write(`
564197
- ${c3.yellow("⚠")} ${c3.bold("Task incomplete")} ${c3.dim(`(${turns} turns, ${toolCalls} tool calls, ${duration})`)}
564198
- `);
564199
- if (tokenStr) {
564200
- process.stdout.write(` ${c3.dim(tokenStr)}
564201
- `);
564202
- }
564203
- process.stdout.write("\n");
563973
+ function formatContextLength(tokens) {
563974
+ if (tokens >= 1e6) return `${(tokens / 1e6).toFixed(1)}M ctx`;
563975
+ if (tokens >= 1024) return `${Math.round(tokens / 1024)}K ctx`;
563976
+ return `${tokens} ctx`;
564204
563977
  }
564205
- function formatTokenCount2(tokens) {
564206
- if (tokens.total > 0) {
564207
- return `Tokens: ${tokens.total.toLocaleString()}`;
564208
- }
564209
- if (tokens.estimated > 0) {
564210
- return `Tokens: ~${tokens.estimated.toLocaleString()} (estimated)`;
564211
- }
564212
- return "";
563978
+ function formatCaps(caps) {
563979
+ const tags = [];
563980
+ if (caps.vision) tags.push("vision");
563981
+ if (caps.toolUse) tags.push("tools");
563982
+ if (caps.thinking) tags.push("think");
563983
+ return tags.join("+");
564213
563984
  }
564214
- function setContentWriteHook(hook) {
564215
- _contentWriteHook = hook;
563985
+ function formatRelativeTime(iso2) {
563986
+ const now = Date.now();
563987
+ const then = new Date(iso2).getTime();
563988
+ const diffMs = now - then;
563989
+ const minutes = Math.floor(diffMs / 6e4);
563990
+ if (minutes < 1) return "just now";
563991
+ if (minutes < 60) return `${minutes}m ago`;
563992
+ const hours = Math.floor(minutes / 60);
563993
+ if (hours < 24) return `${hours}h ago`;
563994
+ const days = Math.floor(hours / 24);
563995
+ if (days < 7) return `${days}d ago`;
563996
+ const weeks = Math.floor(days / 7);
563997
+ if (weeks < 5) return `${weeks}w ago`;
563998
+ const months = Math.floor(days / 30);
563999
+ return `${months}mo ago`;
564216
564000
  }
564217
- function renderError(message2) {
564218
- const redir = _contentWriteHook?.redirect?.();
564219
- const icon = _emojisEnabled ? "\x1B[38;5;198m✖\x1B[0m" : "\x1B[38;5;198mE\x1B[0m";
564220
- const text = `
564221
- ${icon} \x1B[38;5;198m${message2}\x1B[0m
564222
- `;
564223
- if (redir) {
564224
- redir(text);
564225
- return;
564001
+ var IMAGE_GEN_PATTERNS;
564002
+ var init_model_picker = __esm({
564003
+ "packages/cli/src/tui/model-picker.ts"() {
564004
+ "use strict";
564005
+ init_dist();
564006
+ IMAGE_GEN_PATTERNS = [
564007
+ /flux/i,
564008
+ /z-image/i,
564009
+ /stable-diffusion/i,
564010
+ /sdxl/i,
564011
+ /dall/i,
564012
+ /kandinsky/i,
564013
+ /midjourney/i,
564014
+ /imagen/i
564015
+ ];
564226
564016
  }
564227
- _contentWriteHook?.begin();
564228
- process.stdout.write(text);
564229
- _contentWriteHook?.end();
564017
+ });
564018
+
564019
+ // packages/cli/src/tui/render.ts
564020
+ var render_exports = {};
564021
+ __export(render_exports, {
564022
+ SLASH_COMMANDS: () => SLASH_COMMANDS2,
564023
+ c: () => c3,
564024
+ fileLink: () => fileLink,
564025
+ formatInlineMarkdown: () => formatInlineMarkdown,
564026
+ formatMarkdownBlock: () => formatMarkdownBlock,
564027
+ formatMarkdownLine: () => formatMarkdownLine,
564028
+ getColorsEnabled: () => getColorsEnabled,
564029
+ getEmojisEnabled: () => getEmojisEnabled,
564030
+ getTermWidth: () => getTermWidth,
564031
+ pastel: () => pastel,
564032
+ renderAssistantText: () => renderAssistantText,
564033
+ renderCompactHeader: () => renderCompactHeader,
564034
+ renderConfig: () => renderConfig,
564035
+ renderError: () => renderError,
564036
+ renderHeader: () => renderHeader,
564037
+ renderImageAsciiPreview: () => renderImageAsciiPreview,
564038
+ renderInfo: () => renderInfo,
564039
+ renderModelList: () => renderModelList,
564040
+ renderModelSwitch: () => renderModelSwitch,
564041
+ renderRichHeader: () => renderRichHeader,
564042
+ renderSlashHelp: () => renderSlashHelp,
564043
+ renderTaskAborted: () => renderTaskAborted,
564044
+ renderTaskComplete: () => renderTaskComplete,
564045
+ renderTaskIncomplete: () => renderTaskIncomplete,
564046
+ renderThinking: () => renderThinking,
564047
+ renderToolCallStart: () => renderToolCallStart,
564048
+ renderToolLine: () => renderToolLine,
564049
+ renderToolResult: () => renderToolResult,
564050
+ renderUserInterrupt: () => renderUserInterrupt,
564051
+ renderUserMessage: () => renderUserMessage,
564052
+ renderVerbose: () => renderVerbose,
564053
+ renderVoiceText: () => renderVoiceText,
564054
+ renderWarning: () => renderWarning,
564055
+ setColorsEnabled: () => setColorsEnabled,
564056
+ setContentWriteHook: () => setContentWriteHook,
564057
+ setEmojisEnabled: () => setEmojisEnabled,
564058
+ ui: () => ui
564059
+ });
564060
+ function accentFg() {
564061
+ const a2 = tuiAccent();
564062
+ return a2 < 0 ? "\x1B[39m" : `\x1B[38;5;${a2}m`;
564230
564063
  }
564231
- function renderInfo(message2) {
564232
- const redir = _contentWriteHook?.redirect?.();
564233
- const dim = dimFg();
564234
- const icon = `${dim}∙\x1B[0m`;
564235
- const text = `${icon} ${dim}${message2}\x1B[0m
564236
- `;
564237
- if (redir) {
564238
- redir(text);
564239
- return;
564240
- }
564241
- _contentWriteHook?.begin();
564242
- process.stdout.write(text);
564243
- _contentWriteHook?.end();
564064
+ function dimFg() {
564065
+ return `\x1B[38;5;${tuiTextDim()}m`;
564244
564066
  }
564245
- function renderWarning(message2) {
564246
- const redir = _contentWriteHook?.redirect?.();
564247
- const icon = "\x1B[38;5;214m!\x1B[0m";
564248
- const text = `${icon} \x1B[38;5;214m${message2}\x1B[0m
564249
- `;
564250
- if (redir) {
564251
- redir(text);
564252
- return;
564253
- }
564254
- _contentWriteHook?.begin();
564255
- process.stdout.write(text);
564256
- _contentWriteHook?.end();
564067
+ function ansi2(code8, text) {
564068
+ return isTTY2 ? `\x1B[${code8}m${text}\x1B[0m` : text;
564257
564069
  }
564258
- function renderVerbose(message2) {
564259
- const redir = _contentWriteHook?.redirect?.();
564260
- const text = ` ${accentFg()}▹\x1B[0m ${c3.dim(message2)}
564261
- `;
564262
- if (redir) {
564263
- redir(text);
564264
- return;
564070
+ function fg256(code8, text) {
564071
+ return isTTY2 ? `\x1B[38;5;${code8}m${text}\x1B[0m` : text;
564072
+ }
564073
+ function hyperlink(url, text) {
564074
+ if (!isTTY2) return text;
564075
+ return `\x1B]8;;${url}\x07${text}\x1B]8;;\x07`;
564076
+ }
564077
+ function fileLink(filePath) {
564078
+ if (!isTTY2) return filePath;
564079
+ if (filePath.startsWith("/") || filePath.startsWith("~")) {
564080
+ const absPath = filePath.startsWith("~") ? filePath.replace("~", process.env["HOME"] ?? "") : filePath;
564081
+ return hyperlink(`file://${absPath}`, filePath);
564265
564082
  }
564266
- _contentWriteHook?.begin();
564267
- process.stdout.write(text);
564268
- _contentWriteHook?.end();
564083
+ return filePath;
564269
564084
  }
564270
- function renderRichHeader(opts) {
564271
- const w = getTermWidth();
564272
- const divider = c3.dim("─".repeat(Math.min(w - 4, 72)));
564273
- let lines = 0;
564274
- process.stdout.write("\n");
564275
- lines++;
564276
- const title = c3.bold(c3.cyan("omnius"));
564277
- const ver = c3.dim(`v${opts.version}`);
564278
- const model = c3.dim(`model: ${c3.white(opts.model)}`);
564279
- process.stdout.write(` ${title} ${ver} ${c3.dim("·")} ${model}
564280
- `);
564281
- lines++;
564282
- const ws = opts.workspace.length > 60 ? "..." + opts.workspace.slice(-57) : opts.workspace;
564283
- process.stdout.write(` ${c3.dim("workspace:")} ${c3.white(ws)}
564284
- `);
564285
- lines++;
564286
- process.stdout.write(` ${divider}
564287
- `);
564288
- lines++;
564289
- process.stdout.write(` ${c3.bold("Tools")} ${c3.dim(`(${TOOL_NAMES.length}):`)} `);
564290
- let lineLen = 14;
564291
- for (let i2 = 0; i2 < TOOL_NAMES.length; i2++) {
564292
- const name10 = TOOL_NAMES[i2];
564293
- const sep4 = i2 < TOOL_NAMES.length - 1 ? " " : "";
564294
- if (lineLen + name10.length + 1 > w - 4) {
564295
- process.stdout.write(`
564296
- ${" ".repeat(14)}`);
564297
- lineLen = 14;
564298
- lines++;
564085
+ function setEmojisEnabled(enabled2) {
564086
+ _emojisEnabled = enabled2;
564087
+ }
564088
+ function getEmojisEnabled() {
564089
+ return _emojisEnabled;
564090
+ }
564091
+ function setColorsEnabled(enabled2) {
564092
+ _colorsEnabled = enabled2;
564093
+ }
564094
+ function getColorsEnabled() {
564095
+ return _colorsEnabled;
564096
+ }
564097
+ function getTermWidth() {
564098
+ return termCols();
564099
+ }
564100
+ function formatMarkdownLine(line) {
564101
+ const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
564102
+ if (headingMatch) {
564103
+ const level = headingMatch[1].length;
564104
+ const text = headingMatch[2];
564105
+ const colors2 = [MD.heading1, MD.heading2, MD.heading3, MD.heading3, 183, 183];
564106
+ return c3.bold(fg256(colors2[level - 1] ?? 147, formatInlineMarkdown(text)));
564107
+ }
564108
+ if (/^[-*_]{3,}\s*$/.test(line)) {
564109
+ const w = getTermWidth() - 10;
564110
+ return fg256(MD.hr, "─".repeat(Math.min(w, 60)));
564111
+ }
564112
+ if (/^>\s?/.test(line)) {
564113
+ const content = line.replace(/^>\s?/, "");
564114
+ return fg256(MD.blockquote, "│ ") + c3.italic(fg256(MD.blockquote, formatInlineMarkdown(content)));
564115
+ }
564116
+ if (/^\|(.+)\|/.test(line)) {
564117
+ if (/^\|[\s:_-]+\|/.test(line)) {
564118
+ return fg256(MD.tableBar, line);
564299
564119
  }
564300
- process.stdout.write(`${c3.cyan(name10)}${sep4}`);
564301
- lineLen += name10.length + 1;
564120
+ return line.replace(/([^|]+)/g, (cell) => {
564121
+ const trimmed = cell.trim();
564122
+ if (!trimmed) return cell;
564123
+ const leading = cell.match(/^(\s*)/)?.[1] ?? "";
564124
+ const trailing = cell.match(/(\s*)$/)?.[1] ?? "";
564125
+ return leading + formatInlineMarkdown(trimmed) + trailing;
564126
+ });
564302
564127
  }
564303
- process.stdout.write("\n");
564304
- lines++;
564305
- process.stdout.write(` ${c3.bold("Commands:")} ${COMMAND_NAMES.map((cmd) => c3.yellow(cmd)).join(" ")}
564306
- `);
564307
- lines++;
564308
- process.stdout.write(` ${divider}
564309
- `);
564310
- lines++;
564311
- const hintIdx = Math.floor(Date.now() / 1e4) % HINTS.length;
564312
- const hint = HINTS[hintIdx];
564313
- process.stdout.write(` ${c3.dim("💡")} ${c3.italic(c3.dim(hint))}
564314
- `);
564315
- lines++;
564316
- process.stdout.write(` ${c3.dim("Type a task to begin, or /help for all commands.")}
564317
-
564318
- `);
564319
- lines += 2;
564320
- return lines;
564128
+ const ulMatch = line.match(/^(\s*)([-*+])\s+(.*)/);
564129
+ if (ulMatch) {
564130
+ return ulMatch[1] + fg256(MD.listBullet, "") + " " + formatInlineMarkdown(ulMatch[3]);
564131
+ }
564132
+ const olMatch = line.match(/^(\s*)(\d+[.)])\s+(.*)/);
564133
+ if (olMatch) {
564134
+ return olMatch[1] + fg256(MD.listBullet, olMatch[2]) + " " + formatInlineMarkdown(olMatch[3]);
564135
+ }
564136
+ return formatInlineMarkdown(line);
564321
564137
  }
564322
- function renderHeader(model) {
564323
- const w = getTermWidth();
564324
- process.stdout.write("\n");
564325
- process.stdout.write(` ${c3.bold(c3.cyan("omnius"))} ${c3.dim(model)}
564326
- `);
564327
- process.stdout.write(` ${c3.dim("─".repeat(Math.min(w - 4, 72)))}
564328
- `);
564329
- process.stdout.write(` ${c3.dim("Type a task, or use /help for commands. /quit to exit.")}
564330
-
564331
- `);
564138
+ function formatInlineMarkdown(text) {
564139
+ let result = text;
564140
+ result = result.replace(/`([^`]+)`/g, (_m, code8) => fg256(MD.inlineCode, code8));
564141
+ result = result.replace(/\*{3}([^*]+)\*{3}/g, (_m, t2) => c3.bold(c3.italic(t2)));
564142
+ result = result.replace(/\*{2}([^*]+)\*{2}/g, (_m, t2) => c3.bold(t2));
564143
+ result = result.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_m, t2) => c3.italic(t2));
564144
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, label, url) => c3.bold(fg256(MD.link, label)) + " " + c3.dim(fg256(MD.link, `(${url})`)));
564145
+ result = result.replace(/(?<!\w)__([^_]+)__(?!\w)/g, (_m, t2) => c3.bold(t2));
564146
+ result = result.replace(/(?<!\w)_([^_]+)_(?!\w)/g, (_m, t2) => c3.italic(t2));
564147
+ result = result.replace(/~~([^~]+)~~/g, (_m, t2) => c3.dim(t2));
564148
+ return result;
564332
564149
  }
564333
- function renderCompactHeader(model) {
564334
- process.stdout.write(`
564335
- ${c3.bold(c3.cyan("omnius"))} ${c3.dim(`(${model})`)}
564336
-
564337
- `);
564150
+ function formatMarkdownBlock(text) {
564151
+ const lines = text.split("\n");
564152
+ const result = [];
564153
+ let inCodeBlock = false;
564154
+ let codeLang = "";
564155
+ for (const line of lines) {
564156
+ const trimmedLine = line.trimStart();
564157
+ if (trimmedLine.startsWith("```")) {
564158
+ if (inCodeBlock) {
564159
+ result.push(c3.dim(" ```"));
564160
+ inCodeBlock = false;
564161
+ codeLang = "";
564162
+ } else {
564163
+ codeLang = trimmedLine.slice(3).trim();
564164
+ result.push(c3.dim(" ```" + codeLang));
564165
+ inCodeBlock = true;
564166
+ }
564167
+ continue;
564168
+ }
564169
+ if (inCodeBlock) {
564170
+ result.push(" " + c3.dim(line));
564171
+ } else {
564172
+ result.push(formatMarkdownLine(line));
564173
+ }
564174
+ }
564175
+ return result.join("\n");
564338
564176
  }
564339
- function renderSlashHelp() {
564177
+ function renderUserMessage(text) {
564340
564178
  process.stdout.write(`
564341
- ${c3.bold("Available commands:")}
564342
-
564179
+ ${accentFg()}▹\x1B[0m ${c3.bold(text)}
564343
564180
  `);
564344
- const entries = getSlashHelpEntries();
564345
- for (const [cmd, desc] of entries) {
564346
- process.stdout.write(` ${c3.cyan(cmd.padEnd(30))} ${c3.dim(desc)}
564181
+ }
564182
+ function renderAssistantText(text) {
564183
+ if (!text.trim()) return;
564184
+ const formatted = formatMarkdownBlock(text);
564185
+ const lines = formatted.split("\n");
564186
+ for (const line of lines) {
564187
+ process.stdout.write(` ${line}
564347
564188
  `);
564348
564189
  }
564349
- process.stdout.write("\n");
564350
564190
  }
564351
- function renderModelList(models, current) {
564191
+ function renderThinking(text) {
564352
564192
  process.stdout.write(`
564353
- ${c3.bold("Available models:")}
564354
-
564193
+ ${c3.magenta("")} ${c3.italic(text)}
564355
564194
  `);
564356
- for (const m2 of models) {
564357
- const isCurrent = m2.name === current;
564358
- const marker = isCurrent ? c3.green("") : c3.dim("○");
564359
- const name10 = isCurrent ? c3.bold(c3.green(m2.name)) : m2.name;
564360
- const size = c3.dim(m2.size);
564361
- const modified = c3.dim(m2.modified);
564362
- process.stdout.write(` ${marker} ${name10.padEnd(50)} ${size.padEnd(12)} ${modified}
564195
+ }
564196
+ function renderVoiceText(text) {
564197
+ process.stdout.write(` ${c3.dim("🔊")} ${c3.italic(c3.dim(text))}
564363
564198
  `);
564364
- }
564199
+ }
564200
+ function renderUserInterrupt(text) {
564365
564201
  process.stdout.write(`
564366
- ${c3.dim("Use /model <name> to switch models.")}
564367
-
564202
+ ${c3.cyan("↪")} ${c3.bold("Context added:")} ${text}
564368
564203
  `);
564369
564204
  }
564370
- function renderModelSwitch(oldModel, newModel) {
564371
- const redir = _contentWriteHook?.redirect?.();
564372
- const text = `
564373
- ${c3.green("✔")} Switched model: ${c3.dim(oldModel)} → ${c3.bold(c3.green(newModel))}
564374
-
564375
- `;
564376
- if (redir) {
564377
- redir(text);
564378
- return;
564379
- }
564380
- _contentWriteHook?.begin();
564381
- process.stdout.write(text);
564382
- _contentWriteHook?.end();
564383
- }
564384
- function renderConfig(config) {
564205
+ function renderTaskAborted() {
564385
564206
  process.stdout.write(`
564386
- ${c3.bold("Configuration:")}
564387
-
564388
- `);
564389
- for (const [key, value2] of Object.entries(config)) {
564390
- process.stdout.write(` ${c3.cyan(key.padEnd(20))} ${value2}
564207
+ ${c3.yellow("⚠")} ${c3.bold("Task aborted by user")}
564391
564208
  `);
564392
- }
564393
- process.stdout.write("\n");
564394
564209
  }
564395
- function truncStr(s2, max) {
564396
- return s2.length > max ? s2.slice(0, max) + "..." : s2;
564210
+ function normalizeToolOpts(arg) {
564211
+ return typeof arg === "boolean" ? { verbose: arg } : arg ?? {};
564397
564212
  }
564398
- function formatDuration3(ms) {
564399
- if (ms < 1e3) return `${ms}ms`;
564400
- const totalSecs = ms / 1e3;
564401
- if (totalSecs < 60) return `${totalSecs.toFixed(1)}s`;
564402
- const mins = Math.floor(totalSecs / 60);
564403
- const secs = Math.floor(totalSecs % 60);
564404
- return `${mins}m ${secs}s`;
564213
+ function toolColorCode(toolName) {
564214
+ return TOOL_COLOR_CODES[toolName] ?? tuiTextDim();
564405
564215
  }
564406
- var isTTY2, c3, ui, pastel, _emojisEnabled, _colorsEnabled, MD, TOOL_ICONS, TOOL_LABELS, TOOL_COLOR_CODES, BOX_TL2, BOX_TR2, BOX_BL2, BOX_BR2, BOX_H2, BOX_V2, BOX_TJ_L2, BOX_TJ_R2, RESET2, _contentWriteHook, HINTS, TOOL_NAMES, COMMAND_NAMES, SLASH_COMMANDS2;
564407
- var init_render = __esm({
564408
- "packages/cli/src/tui/render.ts"() {
564409
- "use strict";
564410
- init_theme();
564411
- init_layout2();
564412
- init_command_registry();
564413
- init_config();
564414
- init_text_selection();
564415
- init_task_complete_box();
564416
- isTTY2 = process.stdout.isTTY ?? false;
564417
- c3 = {
564418
- bold: (t2) => ansi2("1", t2),
564419
- dim: (t2) => isTTY2 ? `${dimFg()}${t2}\x1B[0m` : t2,
564420
- italic: (t2) => ansi2("3", t2),
564421
- red: (t2) => ansi2("31", t2),
564422
- green: (t2) => ansi2("32", t2),
564423
- yellow: (t2) => ansi2("33", t2),
564424
- blue: (t2) => ansi2("34", t2),
564425
- magenta: (t2) => ansi2("38;5;198", t2),
564426
- // deep pink
564427
- cyan: (t2) => ansi2("36", t2),
564428
- white: (t2) => ansi2("37", t2),
564429
- bgBlue: (t2) => ansi2("44", t2),
564430
- bgGreen: (t2) => ansi2("42", t2)
564431
- };
564432
- ui = {
564433
- /** Primary text — lighter grey (252) for main content */
564434
- primary: (t2) => fg256(252, t2),
564435
- /** Sub-text — readable grey for secondary info */
564436
- sub: (t2) => fg256(tuiTextDim(), t2),
564437
- /** Dim text — readable grey for hints/placeholders */
564438
- hint: (t2) => fg256(tuiTextDim(), t2),
564439
- /** Error text deep pink for errors */
564440
- error: (t2) => fg256(198, t2),
564441
- /** Warning text — warm orange for warnings */
564442
- warn: (t2) => fg256(214, t2),
564443
- /** Success text — green for confirmations */
564444
- ok: (t2) => fg256(78, t2),
564445
- /** Accent teal matching header/banner */
564446
- accent: (t2) => fg256(37, t2),
564447
- /** Muted accent — dim teal for secondary accent */
564448
- accentDim: (t2) => fg256(30, t2)
564449
- };
564450
- pastel = {
564451
- pink: (t2) => fg256(218, t2),
564216
+ function toolColorSeq(code8, bold = false) {
564217
+ if (!_colorsEnabled || !isTTY2) return "";
564218
+ return `\x1B[${bold ? "1;" : ""}38;5;${code8}m`;
564219
+ }
564220
+ function toolResetSeq() {
564221
+ return _colorsEnabled && isTTY2 ? RESET2 : "";
564222
+ }
564223
+ function visibleLen(text) {
564224
+ return stripAnsi(text).length;
564225
+ }
564226
+ function truncateAnsiToWidth(text, width) {
564227
+ if (width <= 0) return "";
564228
+ if (visibleLen(text) <= width) return text;
564229
+ if (width === 1) return "…";
564230
+ const target = Math.max(0, width - 1);
564231
+ let out = "";
564232
+ let visible = 0;
564233
+ let hasAnsi = false;
564234
+ const tokenRe = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|./gs;
564235
+ for (const token of text.matchAll(tokenRe)) {
564236
+ const value2 = token[0];
564237
+ if (value2.startsWith("\x1B")) {
564238
+ hasAnsi = true;
564239
+ out += value2;
564240
+ continue;
564241
+ }
564242
+ if (visible >= target) break;
564243
+ out += value2;
564244
+ visible += 1;
564245
+ }
564246
+ return `${out}…${hasAnsi ? RESET2 : ""}`;
564247
+ }
564248
+ function wrapToolTextLine(text, width) {
564249
+ if (width <= 0) return [text];
564250
+ if (text.length === 0) return [""];
564251
+ const out = [];
564252
+ const continuationIndent = hangingIndentForPlainLine(text, width);
564253
+ let remaining = text;
564254
+ while (remaining.length > width) {
564255
+ let breakAt = remaining.lastIndexOf(" ", width);
564256
+ if (breakAt <= continuationIndent.length) breakAt = width;
564257
+ out.push(remaining.slice(0, breakAt).trimEnd());
564258
+ remaining = continuationIndent + remaining.slice(breakAt).trimStart();
564259
+ }
564260
+ out.push(remaining);
564261
+ return out;
564262
+ }
564263
+ function buildToolTopBorder(title, metrics2, width, colorCode) {
564264
+ const border = toolColorSeq(colorCode);
564265
+ const titleColor = toolColorSeq(colorCode, true);
564266
+ const metricColor = toolColorSeq(222);
564267
+ const reset = toolResetSeq();
564268
+ const inner = Math.max(4, width - 2);
564269
+ const titleVisible = stripAnsi(title);
564270
+ const metricsVisible = stripAnsi(metrics2);
564271
+ const titleChip = ` ${titleVisible} `;
564272
+ const titleSpan = titleChip.length + 2;
564273
+ const metricsChip = metricsVisible ? ` ${metricsVisible} ` : "";
564274
+ const metricsSpan = metricsChip ? metricsChip.length + 2 : 0;
564275
+ let titleSegment;
564276
+ let metricsSegment = "";
564277
+ let fillerWidth;
564278
+ if (titleSpan + metricsSpan + 4 <= inner) {
564279
+ titleSegment = `${border}┤${titleColor}${titleChip}${reset}${border}├`;
564280
+ metricsSegment = metricsChip ? `${border}┤${metricColor}${metricsChip}${reset}${border}├` : "";
564281
+ fillerWidth = inner - titleSpan - metricsSpan - 2;
564282
+ } else if (titleSpan + 4 <= inner) {
564283
+ titleSegment = `${border}┤${titleColor}${titleChip}${reset}${border}├`;
564284
+ fillerWidth = inner - titleSpan - 2;
564285
+ } else {
564286
+ const room = Math.max(3, inner - 8);
564287
+ const truncated = titleVisible.length > room ? titleVisible.slice(0, Math.max(1, room - 1)) + "…" : titleVisible;
564288
+ titleSegment = `${border}┤${titleColor} ${truncated} ${reset}${border}├`;
564289
+ fillerWidth = Math.max(0, inner - (truncated.length + 4) - 2);
564290
+ }
564291
+ return `${border}${BOX_TL2}${BOX_H2}${titleSegment}${BOX_H2.repeat(Math.max(0, fillerWidth))}${metricsSegment}${BOX_H2}${BOX_TR2}${reset}`;
564292
+ }
564293
+ function buildToolDivider(width, colorCode) {
564294
+ const border = toolColorSeq(colorCode);
564295
+ return `${border}${BOX_TJ_L2}${BOX_H2.repeat(Math.max(0, width - 2))}${BOX_TJ_R2}${toolResetSeq()}`;
564296
+ }
564297
+ function buildToolBottom(width, colorCode) {
564298
+ const border = toolColorSeq(colorCode);
564299
+ return `${border}${BOX_BL2}${BOX_H2.repeat(Math.max(0, width - 2))}${BOX_BR2}${toolResetSeq()}`;
564300
+ }
564301
+ function buildToolContentRow(content, width, colorCode) {
564302
+ const border = toolColorSeq(colorCode);
564303
+ const reset = toolResetSeq();
564304
+ const innerWidth = Math.max(1, width - 4);
564305
+ let padded = visibleLen(content) > innerWidth ? truncateAnsiToWidth(content, innerWidth) : content;
564306
+ const visible = visibleLen(padded);
564307
+ if (visible < innerWidth) padded += " ".repeat(innerWidth - visible);
564308
+ return `${border}${BOX_V2}${reset} ${padded} ${border}${BOX_V2}${reset}`;
564309
+ }
564310
+ function formatToolBoxLine(line, kind) {
564311
+ switch (kind) {
564312
+ case "error":
564313
+ return c3.magenta(line);
564314
+ case "success":
564315
+ return c3.green(line);
564316
+ case "dim":
564317
+ return c3.dim(line);
564318
+ case "markdown": {
564319
+ const formatted = formatMarkdownLine(line);
564320
+ return formatted === line ? highlightToolOutput(line) : formatted;
564321
+ }
564322
+ case "tool":
564323
+ return /\x1B\[[0-?]*[ -/]*[@-~]/.test(line) ? line : highlightToolOutput(line);
564324
+ case "plain":
564325
+ default:
564326
+ return line;
564327
+ }
564328
+ }
564329
+ function wrapFooterItems(items, width) {
564330
+ const sep4 = " · ";
564331
+ const lines = [];
564332
+ let current = "";
564333
+ for (const item of items) {
564334
+ const clean5 = item.replace(/\s+/g, " ").trim();
564335
+ if (!clean5) continue;
564336
+ const candidate = current ? `${current}${sep4}${clean5}` : clean5;
564337
+ if (visibleLen(candidate) <= width) {
564338
+ current = candidate;
564339
+ continue;
564340
+ }
564341
+ if (current) lines.push(current);
564342
+ if (clean5.length > width) {
564343
+ const chunks = wrapToolTextLine(clean5, width);
564344
+ lines.push(...chunks.slice(0, -1));
564345
+ current = chunks[chunks.length - 1] ?? "";
564346
+ } else {
564347
+ current = clean5;
564348
+ }
564349
+ }
564350
+ if (current) lines.push(current);
564351
+ return lines;
564352
+ }
564353
+ function buildToolFooterRows(footer, width, colorCode) {
564354
+ const items = [...new Set(footer.items.map((item) => item.trim()).filter(Boolean))];
564355
+ if (items.length === 0) return [];
564356
+ const innerWidth = Math.max(8, width - 4);
564357
+ const labelVisible = `${footer.label}: `.length;
564358
+ const label = `${toolColorSeq(colorCode, true)}${footer.label}:${toolResetSeq()} `;
564359
+ const wrapped = wrapFooterItems(items, Math.max(8, innerWidth - labelVisible));
564360
+ if (wrapped.length === 0) return [];
564361
+ const rows = [buildToolContentRow(`${label}${wrapped[0]}`, width, colorCode)];
564362
+ for (const line of wrapped.slice(1)) {
564363
+ rows.push(buildToolContentRow(`${" ".repeat(labelVisible)}${line}`, width, colorCode));
564364
+ }
564365
+ return rows;
564366
+ }
564367
+ function buildToolBoxLines(data, width) {
564368
+ const w = Math.max(40, width);
564369
+ const innerWidth = Math.max(1, w - 4);
564370
+ const lines = [
564371
+ buildToolTopBorder(data.title, data.metrics, w, data.colorCode),
564372
+ buildToolDivider(w, data.colorCode)
564373
+ ];
564374
+ for (const bodyLine of data.body.length > 0 ? data.body : [{ text: "Done", mode: "wrap", kind: "dim" }]) {
564375
+ const chunks = bodyLine.mode === "truncate" ? [truncateAnsiToWidth(bodyLine.text, innerWidth)] : wrapToolTextLine(bodyLine.text, innerWidth);
564376
+ for (const chunk of chunks) {
564377
+ lines.push(buildToolContentRow(formatToolBoxLine(chunk, bodyLine.kind), w, data.colorCode));
564378
+ }
564379
+ }
564380
+ const footers = (data.footers ?? []).filter((footer) => footer.items.length > 0);
564381
+ if (footers.length > 0) {
564382
+ lines.push(buildToolDivider(w, data.colorCode));
564383
+ for (const footer of footers) {
564384
+ lines.push(...buildToolFooterRows(footer, w, data.colorCode));
564385
+ }
564386
+ }
564387
+ lines.push(buildToolBottom(w, data.colorCode));
564388
+ return lines;
564389
+ }
564390
+ function renderToolDynamicBlock(kind, render2, opts) {
564391
+ const redir = _contentWriteHook?.redirect?.();
564392
+ const host = opts.host !== void 0 ? opts.host : _contentWriteHook?.dynamicBlockHost?.();
564393
+ if (!redir && host) {
564394
+ const id = `${kind}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
564395
+ host.registerDynamicBlock(id, render2);
564396
+ host.appendDynamicBlock(id);
564397
+ return;
564398
+ }
564399
+ const text = `${render2(getTermWidth()).join("\n")}
564400
+ `;
564401
+ if (redir) {
564402
+ redir(text);
564403
+ return;
564404
+ }
564405
+ process.stdout.write(text);
564406
+ }
564407
+ function buildToolCallBoxLines(toolName, args, verbose, width) {
564408
+ const icon = _emojisEnabled ? `${TOOL_ICONS[toolName] ?? "🔧"} ` : "";
564409
+ const label = TOOL_LABELS[toolName] ?? toolName;
564410
+ const body = formatToolArgsForBox(toolName, args, verbose).map((text) => ({
564411
+ text,
564412
+ mode: "wrap",
564413
+ kind: "tool"
564414
+ }));
564415
+ return buildToolBoxLines({
564416
+ title: `${icon}${label}`,
564417
+ metrics: "call",
564418
+ body,
564419
+ colorCode: toolColorCode(toolName)
564420
+ }, width);
564421
+ }
564422
+ function formatToolArgsForBox(toolName, args, verbose) {
564423
+ switch (toolName) {
564424
+ case "shell":
564425
+ return [`Command: ${String(args["command"] ?? "")}${args["stdin"] ? " (with stdin)" : ""}`];
564426
+ case "background_run":
564427
+ return [`Command: ${String(args["command"] ?? "")}`];
564428
+ case "file_read":
564429
+ case "file_write":
564430
+ case "file_edit":
564431
+ case "list_directory":
564432
+ return [`Path: ${String(args["path"] ?? ".")}`];
564433
+ case "grep_search":
564434
+ return [
564435
+ `Pattern: ${String(args["pattern"] ?? "")}`,
564436
+ args["path"] ? `Path: ${String(args["path"])}` : ""
564437
+ ].filter(Boolean);
564438
+ case "find_files":
564439
+ return [`Pattern: ${String(args["pattern"] ?? "")}`];
564440
+ case "web_search":
564441
+ return [`Query: ${String(args["query"] ?? "")}`];
564442
+ case "web_fetch":
564443
+ return [`URL: ${String(args["url"] ?? "")}`];
564444
+ case "memory_read":
564445
+ return [`Key: ${args["topic"]}${args["key"] ? "." + args["key"] : ""}`];
564446
+ case "memory_write":
564447
+ return [`Key: ${args["topic"]}.${args["key"]}`];
564448
+ default: {
564449
+ const entries = Object.entries(args ?? {});
564450
+ if (entries.length === 0) return ["Starting tool call"];
564451
+ const maxValue2 = verbose ? 500 : 160;
564452
+ return entries.slice(0, verbose ? 20 : 8).map(([key, value2]) => {
564453
+ const rendered = typeof value2 === "string" ? value2 : JSON.stringify(value2);
564454
+ return `${key}: ${truncStr(String(rendered ?? ""), maxValue2)}`;
564455
+ });
564456
+ }
564457
+ }
564458
+ }
564459
+ function buildToolResultBoxLines(toolName, success, output, opts, width) {
564460
+ const label = TOOL_LABELS[toolName] ?? toolName;
564461
+ const status = success ? "✔" : "✖";
564462
+ const body = buildToolResultBody(toolName, success, output, opts.verbose);
564463
+ const rawLines = output.split("\n").filter((line) => line.trim()).length;
564464
+ const metrics2 = [
564465
+ success ? "ok" : "failed",
564466
+ rawLines > 0 ? `${rawLines} line${rawLines === 1 ? "" : "s"}` : "",
564467
+ output.length > 0 ? `${output.length.toLocaleString()} chars` : "",
564468
+ opts.durationMs && opts.durationMs > 0 ? formatDuration3(opts.durationMs) : ""
564469
+ ].filter(Boolean).join(" · ");
564470
+ const provenance = detectProvenanceAnchors([output]);
564471
+ const artifacts = detectToolArtifacts(output);
564472
+ const footers = [
564473
+ { label: "Artifacts", items: artifacts },
564474
+ { label: "Provenance", items: provenance }
564475
+ ];
564476
+ return buildToolBoxLines({
564477
+ title: `${status} ${label}`,
564478
+ metrics: metrics2,
564479
+ body,
564480
+ footers,
564481
+ colorCode: toolColorCode(toolName)
564482
+ }, width);
564483
+ }
564484
+ function buildToolResultBody(toolName, success, output, verbose) {
564485
+ const debug = loadConfig()?.debug ?? false;
564486
+ if (toolName === "file_write" || toolName === "file_edit") {
564487
+ const summary = extractFirstLine(output, Number.MAX_SAFE_INTEGER);
564488
+ return [{
564489
+ text: summary,
564490
+ mode: "wrap",
564491
+ kind: success ? "dim" : "error"
564492
+ }];
564493
+ }
564494
+ if (toolName === "file_read") {
564495
+ if (!success) {
564496
+ return [{ text: extractFirstLine(output, Number.MAX_SAFE_INTEGER), mode: "wrap", kind: "error" }];
564497
+ }
564498
+ return codePreviewLines(output, 10);
564499
+ }
564500
+ if (toolName === "task_complete") {
564501
+ const summary = output && output.trim() ? output : "Done";
564502
+ return summary.split("\n").map((line) => ({
564503
+ text: line,
564504
+ mode: "wrap",
564505
+ kind: "markdown"
564506
+ }));
564507
+ }
564508
+ const filtered = output.split("\n").filter((line) => {
564509
+ const trimmed = line.trim();
564510
+ if (!trimmed) return false;
564511
+ if (!debug && (trimmed.startsWith("[trust_tier:") || trimmed.startsWith("[SYSTEM]:") || trimmed.includes("tool_output_untrusted") || trimmed.includes("FORCED PROGRESS BLOCK"))) return false;
564512
+ return true;
564513
+ });
564514
+ if (filtered.length === 0) {
564515
+ return [{ text: success ? "Done" : "Failed", mode: "wrap", kind: success ? "success" : "error" }];
564516
+ }
564517
+ const maxLines = verbose ? 200 : toolName === "shell" || toolName === "background_run" || toolName === "grep_search" ? 10 : 6;
564518
+ const shown = [];
564519
+ for (const line of filtered.slice(0, maxLines)) {
564520
+ if (isRawJsonDump(line) && !verbose) {
564521
+ shown.push({ text: "(output omitted)", mode: "wrap", kind: "dim" });
564522
+ break;
564523
+ }
564524
+ shown.push({
564525
+ text: line,
564526
+ mode: shouldPreserveToolLine(toolName, line) ? "truncate" : "wrap",
564527
+ kind: outputLineKind(toolName, line)
564528
+ });
564529
+ }
564530
+ if (filtered.length > maxLines) {
564531
+ shown.push({
564532
+ text: `... ${filtered.length - maxLines} more lines`,
564533
+ mode: "wrap",
564534
+ kind: "dim"
564535
+ });
564536
+ }
564537
+ return shown;
564538
+ }
564539
+ function codePreviewLines(output, maxLines) {
564540
+ const lines = output.split("\n");
564541
+ let start2 = 0;
564542
+ while (start2 < lines.length && !lines[start2].trim()) start2++;
564543
+ const shown = lines.slice(start2, start2 + maxLines);
564544
+ if (shown.length === 0) return [{ text: "(empty file)", mode: "wrap", kind: "dim" }];
564545
+ const body = shown.map((line) => ({
564546
+ text: line,
564547
+ mode: "truncate",
564548
+ kind: "dim"
564549
+ }));
564550
+ const remaining = lines.length - start2 - shown.length;
564551
+ if (remaining > 0) {
564552
+ body.push({ text: `... ${remaining} more lines`, mode: "wrap", kind: "dim" });
564553
+ }
564554
+ return body;
564555
+ }
564556
+ function shouldPreserveToolLine(toolName, line) {
564557
+ if (toolName === "file_read") return true;
564558
+ if (/^\s*\d+\s*(?:[|:│])/.test(line)) return true;
564559
+ if (/^\s*(?:@@|\+\+\+|---|\+|-)\s/.test(line)) return true;
564560
+ if (/^\s*(?:import|export|const|let|var|function|class|interface|type)\b/.test(line)) return true;
564561
+ if (/^\s*[{}[\](),.;]+\s*$/.test(line)) return true;
564562
+ return false;
564563
+ }
564564
+ function outputLineKind(toolName, line) {
564565
+ if (/\x1B\[[0-?]*[ -/]*[@-~]/.test(line)) return "plain";
564566
+ if (toolName === "shell" || toolName === "background_run" || toolName === "grep_search") return "tool";
564567
+ return "markdown";
564568
+ }
564569
+ function detectToolArtifacts(output) {
564570
+ const out = /* @__PURE__ */ new Set();
564571
+ const artifactPattern = /\b(?:Saved to|Written to|Output|File|Path|Artifact):\s*([^\s]+(?:\.(?:png|jpe?g|gif|webp|svg|json|md|txt|log|wav|mp3|mp4|mov|pdf|html|csv|ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|css|scss|yaml|yml)))/gi;
564572
+ for (const match of output.matchAll(artifactPattern)) {
564573
+ const value2 = match[1]?.replace(/[),.;]+$/, "");
564574
+ if (value2) out.add(value2);
564575
+ }
564576
+ return [...out].slice(0, 12);
564577
+ }
564578
+ function renderToolCallStart(toolName, args, verboseOrOpts) {
564579
+ const opts = normalizeToolOpts(verboseOrOpts);
564580
+ const frozenArgs = { ...args ?? {} };
564581
+ renderToolDynamicBlock(
564582
+ "tool-call",
564583
+ (width) => buildToolCallBoxLines(toolName, frozenArgs, opts.verbose, width),
564584
+ opts
564585
+ );
564586
+ }
564587
+ function renderToolLine(content, isLast = false) {
564588
+ const connector = isLast ? "└" : "├";
564589
+ process.stdout.write(` ${c3.dim(connector)}─ ${content}
564590
+ `);
564591
+ }
564592
+ function renderToolResult(toolName, success, output, verboseOrOpts) {
564593
+ const opts = normalizeToolOpts(verboseOrOpts);
564594
+ const frozenOutput = String(output ?? "");
564595
+ renderToolDynamicBlock(
564596
+ "tool-result",
564597
+ (width) => buildToolResultBoxLines(toolName, success, frozenOutput, opts, width),
564598
+ opts
564599
+ );
564600
+ }
564601
+ function renderImageAsciiPreview(title, imagePath, ascii2, renderer) {
564602
+ const prefix = ` ${c3.dim("│")} `;
564603
+ const maxW = Math.max(getTermWidth() - 10, 40);
564604
+ const header = `${title}: ${imagePath} (${renderer})`;
564605
+ process.stdout.write(`
564606
+ ${prefix}${c3.cyan(c3.bold(header.length > maxW ? header.slice(0, maxW - 3) + "..." : header))}
564607
+ `);
564608
+ for (const line of ascii2.split("\n")) {
564609
+ const renderedLine = /\x1B\[[0-?]*[ -/]*[@-~]/.test(line) ? line : c3.dim(line);
564610
+ process.stdout.write(`${prefix}${renderedLine}
564611
+ `);
564612
+ }
564613
+ }
564614
+ function extractFirstLine(output, maxW) {
564615
+ const line = output.split("\n").find((l2) => l2.trim()) ?? output;
564616
+ return line.length > maxW ? line.slice(0, maxW - 3) + "..." : line;
564617
+ }
564618
+ function isRawJsonDump(line) {
564619
+ if (/\\u[0-9a-fA-F]{4}/.test(line) && line.length > 200) return true;
564620
+ if (/^\s*\{"content"\s*:/.test(line) && line.length > 100) return true;
564621
+ if (/\[38;5;\d+m/.test(line) && line.length > 100) return true;
564622
+ return false;
564623
+ }
564624
+ function highlightToolOutput(line) {
564625
+ if (line.startsWith("+") && !line.startsWith("+++")) return c3.green(line);
564626
+ if (line.startsWith("-") && !line.startsWith("---")) return c3.magenta(line);
564627
+ if (line.startsWith("@@")) return c3.cyan(line);
564628
+ if (line.startsWith("---") || line.startsWith("+++")) return c3.bold(line);
564629
+ if (/error/i.test(line) && !/0 error/i.test(line)) return c3.magenta(line);
564630
+ if (/warning/i.test(line) && !/0 warning/i.test(line)) return c3.yellow(line);
564631
+ if (/\bpass(ed|ing)?\b/i.test(line)) return c3.green(line);
564632
+ if (/\bfail(ed|ing|ure)?\b/i.test(line)) return c3.magenta(line);
564633
+ const formatted = formatInlineMarkdown(line);
564634
+ if (formatted !== line) return formatted;
564635
+ return c3.dim(line);
564636
+ }
564637
+ function renderTaskComplete(arg1, turns, toolCalls, durationMs, tokens) {
564638
+ const opts = typeof arg1 === "string" ? {
564639
+ summary: arg1,
564640
+ turns: turns ?? 0,
564641
+ toolCalls: toolCalls ?? 0,
564642
+ durationMs: durationMs ?? 0,
564643
+ tokens
564644
+ } : arg1;
564645
+ if (opts.host) {
564646
+ const { renderTaskCompleteBox: renderTaskCompleteBox2 } = (init_task_complete_box(), __toCommonJS(task_complete_box_exports));
564647
+ renderTaskCompleteBox2(opts.host, {
564648
+ task: opts.task ?? "",
564649
+ summary: opts.summary,
564650
+ turns: opts.turns,
564651
+ toolCalls: opts.toolCalls,
564652
+ durationMs: opts.durationMs,
564653
+ tokens: opts.tokens ?? null,
564654
+ filesEdited: opts.filesEdited,
564655
+ testsRun: opts.testsRun,
564656
+ provenanceAnchors: opts.provenanceAnchors
564657
+ });
564658
+ return;
564659
+ }
564660
+ const duration = formatDuration3(opts.durationMs);
564661
+ const tokenStr = opts.tokens ? ` ${formatTokenCount2(opts.tokens)}` : "";
564662
+ process.stdout.write(`
564663
+ ${c3.green("✔")} ${c3.bold("Task completed")} ${c3.dim(`(${opts.turns} turns, ${opts.toolCalls} tool calls, ${duration})`)}
564664
+ `);
564665
+ if (tokenStr) {
564666
+ process.stdout.write(` ${c3.dim(tokenStr)}
564667
+ `);
564668
+ }
564669
+ if (opts.summary) {
564670
+ const formatted = formatMarkdownBlock(wrapTaskCompleteSummary(opts.summary));
564671
+ const lines = formatted.split("\n");
564672
+ for (const line of lines) {
564673
+ process.stdout.write(` ${line}
564674
+ `);
564675
+ }
564676
+ }
564677
+ process.stdout.write("\n");
564678
+ }
564679
+ function wrapTaskCompleteSummary(summary) {
564680
+ const width = Math.max(24, getTermWidth() - 6);
564681
+ const lines = [];
564682
+ let inFence = false;
564683
+ for (const line of summary.split(/\r?\n/)) {
564684
+ if (line.trimStart().startsWith("```")) {
564685
+ inFence = !inFence;
564686
+ lines.push(line);
564687
+ continue;
564688
+ }
564689
+ if (inFence || line.length <= width) {
564690
+ lines.push(line);
564691
+ continue;
564692
+ }
564693
+ lines.push(...wrapPlainLine(line, width));
564694
+ }
564695
+ return lines.join("\n");
564696
+ }
564697
+ function wrapPlainLine(line, width) {
564698
+ const out = [];
564699
+ const continuationIndent = hangingIndentForPlainLine(line, width);
564700
+ let remaining = line;
564701
+ while (remaining.length > width) {
564702
+ let breakAt = remaining.lastIndexOf(" ", width);
564703
+ if (breakAt <= continuationIndent.length) breakAt = width;
564704
+ out.push(remaining.slice(0, breakAt).trimEnd());
564705
+ remaining = continuationIndent + remaining.slice(breakAt).trimStart();
564706
+ }
564707
+ out.push(remaining);
564708
+ return out;
564709
+ }
564710
+ function hangingIndentForPlainLine(line, width) {
564711
+ const capped = (n2) => " ".repeat(Math.max(0, Math.min(n2, width - 4)));
564712
+ const patterns = [
564713
+ /^(\s*(?:[-*+○•]\s+))/,
564714
+ /^(\s*(?:\d+[.)]\s+))/,
564715
+ /^(\s*(?:>\s*))/,
564716
+ /^(\s*(?:[A-Za-z][\w.-]{0,28}:\s+))/
564717
+ ];
564718
+ for (const pattern of patterns) {
564719
+ const match = line.match(pattern);
564720
+ if (match?.[1]) return capped(match[1].length);
564721
+ }
564722
+ return capped(line.match(/^\s*/)?.[0].length ?? 0);
564723
+ }
564724
+ function renderTaskIncomplete(turns, toolCalls, durationMs, tokens) {
564725
+ const duration = formatDuration3(durationMs);
564726
+ const tokenStr = tokens ? ` ${formatTokenCount2(tokens)}` : "";
564727
+ process.stdout.write(`
564728
+ ${c3.yellow("⚠")} ${c3.bold("Task incomplete")} ${c3.dim(`(${turns} turns, ${toolCalls} tool calls, ${duration})`)}
564729
+ `);
564730
+ if (tokenStr) {
564731
+ process.stdout.write(` ${c3.dim(tokenStr)}
564732
+ `);
564733
+ }
564734
+ process.stdout.write("\n");
564735
+ }
564736
+ function formatTokenCount2(tokens) {
564737
+ if (tokens.total > 0) {
564738
+ return `Tokens: ${tokens.total.toLocaleString()}`;
564739
+ }
564740
+ if (tokens.estimated > 0) {
564741
+ return `Tokens: ~${tokens.estimated.toLocaleString()} (estimated)`;
564742
+ }
564743
+ return "";
564744
+ }
564745
+ function setContentWriteHook(hook) {
564746
+ _contentWriteHook = hook;
564747
+ }
564748
+ function renderError(message2) {
564749
+ const redir = _contentWriteHook?.redirect?.();
564750
+ const icon = _emojisEnabled ? "\x1B[38;5;198m✖\x1B[0m" : "\x1B[38;5;198mE\x1B[0m";
564751
+ const text = `
564752
+ ${icon} \x1B[38;5;198m${message2}\x1B[0m
564753
+ `;
564754
+ if (redir) {
564755
+ redir(text);
564756
+ return;
564757
+ }
564758
+ _contentWriteHook?.begin();
564759
+ process.stdout.write(text);
564760
+ _contentWriteHook?.end();
564761
+ }
564762
+ function renderInfo(message2) {
564763
+ const redir = _contentWriteHook?.redirect?.();
564764
+ const dim = dimFg();
564765
+ const icon = `${dim}∙\x1B[0m`;
564766
+ const text = `${icon} ${dim}${message2}\x1B[0m
564767
+ `;
564768
+ if (redir) {
564769
+ redir(text);
564770
+ return;
564771
+ }
564772
+ _contentWriteHook?.begin();
564773
+ process.stdout.write(text);
564774
+ _contentWriteHook?.end();
564775
+ }
564776
+ function renderWarning(message2) {
564777
+ const redir = _contentWriteHook?.redirect?.();
564778
+ const icon = "\x1B[38;5;214m!\x1B[0m";
564779
+ const text = `${icon} \x1B[38;5;214m${message2}\x1B[0m
564780
+ `;
564781
+ if (redir) {
564782
+ redir(text);
564783
+ return;
564784
+ }
564785
+ _contentWriteHook?.begin();
564786
+ process.stdout.write(text);
564787
+ _contentWriteHook?.end();
564788
+ }
564789
+ function renderVerbose(message2) {
564790
+ const redir = _contentWriteHook?.redirect?.();
564791
+ const text = ` ${accentFg()}▹\x1B[0m ${c3.dim(message2)}
564792
+ `;
564793
+ if (redir) {
564794
+ redir(text);
564795
+ return;
564796
+ }
564797
+ _contentWriteHook?.begin();
564798
+ process.stdout.write(text);
564799
+ _contentWriteHook?.end();
564800
+ }
564801
+ function renderRichHeader(opts) {
564802
+ const w = getTermWidth();
564803
+ const divider = c3.dim("─".repeat(Math.min(w - 4, 72)));
564804
+ let lines = 0;
564805
+ process.stdout.write("\n");
564806
+ lines++;
564807
+ const title = c3.bold(c3.cyan("omnius"));
564808
+ const ver = c3.dim(`v${opts.version}`);
564809
+ const model = c3.dim(`model: ${c3.white(opts.model)}`);
564810
+ process.stdout.write(` ${title} ${ver} ${c3.dim("·")} ${model}
564811
+ `);
564812
+ lines++;
564813
+ const ws = opts.workspace.length > 60 ? "..." + opts.workspace.slice(-57) : opts.workspace;
564814
+ process.stdout.write(` ${c3.dim("workspace:")} ${c3.white(ws)}
564815
+ `);
564816
+ lines++;
564817
+ process.stdout.write(` ${divider}
564818
+ `);
564819
+ lines++;
564820
+ process.stdout.write(` ${c3.bold("Tools")} ${c3.dim(`(${TOOL_NAMES.length}):`)} `);
564821
+ let lineLen = 14;
564822
+ for (let i2 = 0; i2 < TOOL_NAMES.length; i2++) {
564823
+ const name10 = TOOL_NAMES[i2];
564824
+ const sep4 = i2 < TOOL_NAMES.length - 1 ? " " : "";
564825
+ if (lineLen + name10.length + 1 > w - 4) {
564826
+ process.stdout.write(`
564827
+ ${" ".repeat(14)}`);
564828
+ lineLen = 14;
564829
+ lines++;
564830
+ }
564831
+ process.stdout.write(`${c3.cyan(name10)}${sep4}`);
564832
+ lineLen += name10.length + 1;
564833
+ }
564834
+ process.stdout.write("\n");
564835
+ lines++;
564836
+ process.stdout.write(` ${c3.bold("Commands:")} ${COMMAND_NAMES.map((cmd) => c3.yellow(cmd)).join(" ")}
564837
+ `);
564838
+ lines++;
564839
+ process.stdout.write(` ${divider}
564840
+ `);
564841
+ lines++;
564842
+ const hintIdx = Math.floor(Date.now() / 1e4) % HINTS.length;
564843
+ const hint = HINTS[hintIdx];
564844
+ process.stdout.write(` ${c3.dim("💡")} ${c3.italic(c3.dim(hint))}
564845
+ `);
564846
+ lines++;
564847
+ process.stdout.write(` ${c3.dim("Type a task to begin, or /help for all commands.")}
564848
+
564849
+ `);
564850
+ lines += 2;
564851
+ return lines;
564852
+ }
564853
+ function renderHeader(model) {
564854
+ const w = getTermWidth();
564855
+ process.stdout.write("\n");
564856
+ process.stdout.write(` ${c3.bold(c3.cyan("omnius"))} ${c3.dim(model)}
564857
+ `);
564858
+ process.stdout.write(` ${c3.dim("─".repeat(Math.min(w - 4, 72)))}
564859
+ `);
564860
+ process.stdout.write(` ${c3.dim("Type a task, or use /help for commands. /quit to exit.")}
564861
+
564862
+ `);
564863
+ }
564864
+ function renderCompactHeader(model) {
564865
+ process.stdout.write(`
564866
+ ${c3.bold(c3.cyan("omnius"))} ${c3.dim(`(${model})`)}
564867
+
564868
+ `);
564869
+ }
564870
+ function renderSlashHelp() {
564871
+ process.stdout.write(`
564872
+ ${c3.bold("Available commands:")}
564873
+
564874
+ `);
564875
+ const entries = getSlashHelpEntries();
564876
+ for (const [cmd, desc] of entries) {
564877
+ process.stdout.write(` ${c3.cyan(cmd.padEnd(30))} ${c3.dim(desc)}
564878
+ `);
564879
+ }
564880
+ process.stdout.write("\n");
564881
+ }
564882
+ function renderModelList(models, current) {
564883
+ process.stdout.write(`
564884
+ ${c3.bold("Available models:")}
564885
+
564886
+ `);
564887
+ const currentKey = stripLatest(current);
564888
+ for (const m2 of models) {
564889
+ const isCurrent = stripLatest(m2.name) === currentKey;
564890
+ const marker = isCurrent ? c3.green("●") : c3.dim("○");
564891
+ const name10 = isCurrent ? c3.bold(c3.green(m2.name)) : m2.name;
564892
+ const size = c3.dim(m2.size);
564893
+ const modified = c3.dim(m2.modified);
564894
+ process.stdout.write(` ${marker} ${name10.padEnd(50)} ${size.padEnd(12)} ${modified}
564895
+ `);
564896
+ }
564897
+ process.stdout.write(`
564898
+ ${c3.dim("Use /model <name> to switch models.")}
564899
+
564900
+ `);
564901
+ }
564902
+ function renderModelSwitch(oldModel, newModel) {
564903
+ const redir = _contentWriteHook?.redirect?.();
564904
+ const text = `
564905
+ ${c3.green("✔")} Switched model: ${c3.dim(oldModel)} → ${c3.bold(c3.green(newModel))}
564906
+
564907
+ `;
564908
+ if (redir) {
564909
+ redir(text);
564910
+ return;
564911
+ }
564912
+ _contentWriteHook?.begin();
564913
+ process.stdout.write(text);
564914
+ _contentWriteHook?.end();
564915
+ }
564916
+ function renderConfig(config) {
564917
+ process.stdout.write(`
564918
+ ${c3.bold("Configuration:")}
564919
+
564920
+ `);
564921
+ for (const [key, value2] of Object.entries(config)) {
564922
+ process.stdout.write(` ${c3.cyan(key.padEnd(20))} ${value2}
564923
+ `);
564924
+ }
564925
+ process.stdout.write("\n");
564926
+ }
564927
+ function truncStr(s2, max) {
564928
+ return s2.length > max ? s2.slice(0, max) + "..." : s2;
564929
+ }
564930
+ function formatDuration3(ms) {
564931
+ if (ms < 1e3) return `${ms}ms`;
564932
+ const totalSecs = ms / 1e3;
564933
+ if (totalSecs < 60) return `${totalSecs.toFixed(1)}s`;
564934
+ const mins = Math.floor(totalSecs / 60);
564935
+ const secs = Math.floor(totalSecs % 60);
564936
+ return `${mins}m ${secs}s`;
564937
+ }
564938
+ var isTTY2, c3, ui, pastel, _emojisEnabled, _colorsEnabled, MD, TOOL_ICONS, TOOL_LABELS, TOOL_COLOR_CODES, BOX_TL2, BOX_TR2, BOX_BL2, BOX_BR2, BOX_H2, BOX_V2, BOX_TJ_L2, BOX_TJ_R2, RESET2, _contentWriteHook, HINTS, TOOL_NAMES, COMMAND_NAMES, SLASH_COMMANDS2;
564939
+ var init_render = __esm({
564940
+ "packages/cli/src/tui/render.ts"() {
564941
+ "use strict";
564942
+ init_theme();
564943
+ init_layout2();
564944
+ init_command_registry();
564945
+ init_config();
564946
+ init_text_selection();
564947
+ init_task_complete_box();
564948
+ init_model_picker();
564949
+ isTTY2 = process.stdout.isTTY ?? false;
564950
+ c3 = {
564951
+ bold: (t2) => ansi2("1", t2),
564952
+ dim: (t2) => isTTY2 ? `${dimFg()}${t2}\x1B[0m` : t2,
564953
+ italic: (t2) => ansi2("3", t2),
564954
+ red: (t2) => ansi2("31", t2),
564955
+ green: (t2) => ansi2("32", t2),
564956
+ yellow: (t2) => ansi2("33", t2),
564957
+ blue: (t2) => ansi2("34", t2),
564958
+ magenta: (t2) => ansi2("38;5;198", t2),
564959
+ // deep pink
564960
+ cyan: (t2) => ansi2("36", t2),
564961
+ white: (t2) => ansi2("37", t2),
564962
+ bgBlue: (t2) => ansi2("44", t2),
564963
+ bgGreen: (t2) => ansi2("42", t2)
564964
+ };
564965
+ ui = {
564966
+ /** Primary text — lighter grey (252) for main content */
564967
+ primary: (t2) => fg256(252, t2),
564968
+ /** Sub-text — readable grey for secondary info */
564969
+ sub: (t2) => fg256(tuiTextDim(), t2),
564970
+ /** Dim text — readable grey for hints/placeholders */
564971
+ hint: (t2) => fg256(tuiTextDim(), t2),
564972
+ /** Error text — deep pink for errors */
564973
+ error: (t2) => fg256(198, t2),
564974
+ /** Warning text — warm orange for warnings */
564975
+ warn: (t2) => fg256(214, t2),
564976
+ /** Success text — green for confirmations */
564977
+ ok: (t2) => fg256(78, t2),
564978
+ /** Accent — teal matching header/banner */
564979
+ accent: (t2) => fg256(37, t2),
564980
+ /** Muted accent — dim teal for secondary accent */
564981
+ accentDim: (t2) => fg256(30, t2)
564982
+ };
564983
+ pastel = {
564984
+ pink: (t2) => fg256(218, t2),
564452
564985
  peach: (t2) => fg256(216, t2),
564453
564986
  lavender: (t2) => fg256(183, t2),
564454
564987
  mint: (t2) => fg256(158, t2),
@@ -566137,7 +566670,7 @@ import { spawn as spawn24, exec as exec2 } from "node:child_process";
566137
566670
  import { EventEmitter as EventEmitter7 } from "node:events";
566138
566671
  import { randomBytes as randomBytes18, timingSafeEqual } from "node:crypto";
566139
566672
  import { URL as URL2 } from "node:url";
566140
- import { loadavg, cpus as cpus2, totalmem as totalmem3, freemem as freemem3 } from "node:os";
566673
+ import { loadavg, cpus as cpus2, totalmem as totalmem4, freemem as freemem3 } from "node:os";
566141
566674
  import { existsSync as existsSync84, readFileSync as readFileSync66, writeFileSync as writeFileSync42, unlinkSync as unlinkSync13, mkdirSync as mkdirSync47, readdirSync as readdirSync28, statSync as statSync31, statfsSync as statfsSync4 } from "node:fs";
566142
566675
  import { join as join99 } from "node:path";
566143
566676
  function cleanForwardHeaders(raw, targetHost) {
@@ -566243,7 +566776,7 @@ function parseRateLimitHeaders(headers) {
566243
566776
  async function collectSystemMetricsAsync() {
566244
566777
  const [l1, l5, l15] = loadavg();
566245
566778
  const cores = cpus2().length;
566246
- const totalMem = totalmem3();
566779
+ const totalMem = totalmem4();
566247
566780
  const freeMem = freemem3();
566248
566781
  const usedMem = totalMem - freeMem;
566249
566782
  let disk = {
@@ -568842,709 +569375,181 @@ var init_call_agent = __esm({
568842
569375
  });
568843
569376
  }
568844
569377
  if (event.type === "model_response" && event.content) {
568845
- this.emit("response", event.content);
568846
- }
568847
- });
568848
- }
568849
- /** Process a voice transcript — queues if already processing */
568850
- handleTranscript(text) {
568851
- if (this.disposed) return;
568852
- this.conversationHistory.push({ role: "user", text });
568853
- if (this.processing) {
568854
- this.pendingTranscripts.push(text);
568855
- return;
568856
- }
568857
- this.processTranscript(text).catch((err) => {
568858
- this.emit("error", err instanceof Error ? err : new Error(String(err)));
568859
- });
568860
- }
568861
- /** Dispose and clean up */
568862
- dispose() {
568863
- this.disposed = true;
568864
- this.pendingTranscripts.length = 0;
568865
- this.runner = null;
568866
- }
568867
- // ── Private ──────────────────────────────────────────────────────────
568868
- async processTranscript(text) {
568869
- if (!this.runner || this.disposed) return;
568870
- this.processing = true;
568871
- try {
568872
- const historyContext = this.conversationHistory.slice(-10).map((h) => `${h.role === "user" ? "User" : "You"}: ${h.text}`).join("\n");
568873
- const feed = getActivityFeed();
568874
- const activitySummary = feed.getSummary(
568875
- this.tier === "admin" ? 20 : 10,
568876
- this.tier === "admin"
568877
- );
568878
- const wantsAction = /\b(read|open|show|run|execute|check|look at|find|search|grep|edit|write|fix|test|build|deploy|install|create|delete|remove|update|change|modify|commit|push|pull)\b/i.test(text) && !/\b(how are you|what's up|hello|hi|hey|can you hear|stop|quit|bye|thanks|thank you|ok|okay|sure|yeah|yes|no)\b/i.test(text);
568879
- if (!wantsAction) {
568880
- try {
568881
- const chatMessages = [
568882
- { role: "system", content: this.buildSystemPrompt() },
568883
- ...this.conversationHistory.slice(-6).map((h) => ({
568884
- role: h.role === "user" ? "user" : "assistant",
568885
- content: h.text
568886
- })),
568887
- { role: "user", content: text }
568888
- ];
568889
- const chatResult = await this.backend.chatCompletion({
568890
- messages: chatMessages,
568891
- tools: [],
568892
- temperature: 0.4,
568893
- maxTokens: 256,
568894
- timeoutMs: 15e3
568895
- });
568896
- const reply = (chatResult.choices[0]?.message?.content ?? "").trim();
568897
- if (!reply) return;
568898
- this.conversationHistory.push({ role: "assistant", text: reply });
568899
- this.emit("response", reply);
568900
- } catch {
568901
- this.emit("response", "Sorry, I couldn't process that.");
568902
- }
568903
- } else {
568904
- const taskPrompt = [
568905
- `User said: "${text}"`,
568906
- "",
568907
- historyContext ? `Conversation so far:
568908
- ${historyContext}
568909
- ` : "",
568910
- `Background activity:
568911
- ${activitySummary}
568912
- `,
568913
- "The user is requesting an action. Use tools as needed, then call task_complete with a brief spoken summary of what you did (1-2 sentences)."
568914
- ].join("\n");
568915
- const result = await this.runner.run(taskPrompt, `Working directory: ${this.repoRoot}`);
568916
- if (result.summary) {
568917
- this.conversationHistory.push({ role: "assistant", text: result.summary });
568918
- }
568919
- }
568920
- } catch (err) {
568921
- this.emit("error", err instanceof Error ? err : new Error(String(err)));
568922
- } finally {
568923
- this.processing = false;
568924
- this.emit("done");
568925
- if (this.pendingTranscripts.length > 0) {
568926
- const next = this.pendingTranscripts.shift();
568927
- this.processTranscript(next).catch((err) => {
568928
- this.emit("error", err instanceof Error ? err : new Error(String(err)));
568929
- });
568930
- }
568931
- }
568932
- }
568933
- buildSystemPrompt() {
568934
- const base3 = [
568935
- "You are a voice assistant on a LIVE AUDIO CALL. This is a real-time conversation.",
568936
- "",
568937
- "CRITICAL RULES FOR VOICE CALLS:",
568938
- "1. ALWAYS respond IMMEDIATELY with speech. Do NOT use tools before responding.",
568939
- "2. Your response goes through text-to-speech — keep it SHORT (1-3 sentences).",
568940
- "3. NEVER use code blocks, markdown, or long technical text.",
568941
- "4. Be conversational and natural, like talking to a colleague.",
568942
- "5. Call task_complete with your spoken response as the summary.",
568943
- "6. Only use tools (file_read, grep, shell, etc.) if the user EXPLICITLY asks you to look something up, run a command, or make a change. For normal conversation, NEVER call tools.",
568944
- "7. If the user asks what's happening, summarize from the activity context below — do NOT run tools to find out."
568945
- ];
568946
- if (this.opts.emotionContext) {
568947
- base3.push("", "Mood:", this.opts.emotionContext);
568948
- }
568949
- if (this.tier === "admin") {
568950
- base3.push(
568951
- "",
568952
- "ADMIN call — you CAN use tools IF the user explicitly requests an action (e.g. 'read that file', 'run the tests').",
568953
- "But for general chat, status questions, or greetings — respond immediately WITHOUT tools."
568954
- );
568955
- } else {
568956
- base3.push(
568957
- "",
568958
- "PUBLIC call — read-only access. Answer questions about the project conversationally."
568959
- );
568960
- }
568961
- return base3.join("\n");
568962
- }
568963
- buildTools() {
568964
- if (this.tier === "admin") {
568965
- return this.buildAdminTools();
568966
- }
568967
- return this.buildPublicTools();
568968
- }
568969
- buildAdminTools() {
568970
- const debateAdapter = async (prompt) => {
568971
- const r2 = await this.backend.chatCompletion({
568972
- messages: [{ role: "user", content: prompt }],
568973
- tools: [],
568974
- temperature: 0.7,
568975
- maxTokens: 800,
568976
- timeoutMs: 12e4
568977
- });
568978
- return r2.choices[0]?.message?.content ?? "";
568979
- };
568980
- const replayAdapter = async (prompt) => {
568981
- const r2 = await this.backend.chatCompletion({
568982
- messages: [{ role: "user", content: prompt }],
568983
- tools: [],
568984
- temperature: 0,
568985
- maxTokens: 1500,
568986
- timeoutMs: 12e4
568987
- });
568988
- return r2.choices[0]?.message?.content ?? "";
568989
- };
568990
- const tools = [
568991
- new FileReadTool(this.repoRoot),
568992
- new FileWriteTool(this.repoRoot),
568993
- new FileEditTool(this.repoRoot),
568994
- new ShellTool(this.repoRoot),
568995
- new GrepSearchTool(this.repoRoot),
568996
- new GlobFindTool(this.repoRoot),
568997
- new ListDirectoryTool(this.repoRoot),
568998
- new WebSearchTool(),
568999
- new WebFetchTool(),
569000
- new MemoryReadTool(this.repoRoot),
569001
- new MemoryWriteTool(this.repoRoot),
569002
- new MemorySearchTool(this.repoRoot),
569003
- new DebateTool(debateAdapter),
569004
- new ReplayWithInterventionTool({ workingDir: this.repoRoot, callable: replayAdapter })
569005
- ];
569006
- return tools.map(adaptTool);
569007
- }
569008
- buildPublicTools() {
569009
- const tools = [
569010
- new FileReadTool(this.repoRoot),
569011
- new GrepSearchTool(this.repoRoot),
569012
- new GlobFindTool(this.repoRoot),
569013
- new ListDirectoryTool(this.repoRoot),
569014
- new MemoryReadTool(this.repoRoot),
569015
- new MemorySearchTool(this.repoRoot)
569016
- ];
569017
- return tools.map(adaptTool);
569018
- }
569019
- };
569020
- }
569021
- });
569022
-
569023
- // packages/cli/src/tui/model-picker.ts
569024
- import { totalmem as totalmem4 } from "node:os";
569025
- function isImageGenModel(name10, family) {
569026
- return IMAGE_GEN_PATTERNS.some((p2) => p2.test(name10) || family && p2.test(family));
569027
- }
569028
- function parseShowNumCtx(show) {
569029
- const sources = [show.parameters, show.modelfile];
569030
- for (const source of sources) {
569031
- if (!source) continue;
569032
- const match = source.match(/\b(?:PARAMETER\s+)?num_ctx\s+(\d+)/i);
569033
- if (match) return parseInt(match[1], 10);
569034
- }
569035
- return null;
569036
- }
569037
- async function fetchOllamaModels(baseUrl) {
569038
- const url = `${normalizeBaseUrl(baseUrl)}/api/tags`;
569039
- const resp = await fetch(url, {
569040
- signal: AbortSignal.timeout(1e4)
569041
- });
569042
- if (!resp.ok) {
569043
- throw new Error(`Failed to fetch models: HTTP ${resp.status}`);
569044
- }
569045
- const data = await resp.json();
569046
- const models = data.models ?? [];
569047
- const result = models.map((m2) => {
569048
- const family = m2.details?.family;
569049
- return {
569050
- name: m2.name,
569051
- size: formatBytes3(m2.size),
569052
- sizeBytes: m2.size,
569053
- modified: formatRelativeTime(m2.modified_at),
569054
- parameterSize: m2.details?.parameter_size,
569055
- contextLength: void 0,
569056
- caps: void 0,
569057
- isImageGen: isImageGenModel(m2.name, family),
569058
- family
569059
- };
569060
- }).sort((a2, b) => b.sizeBytes - a2.sizeBytes);
569061
- const normalized = normalizeBaseUrl(baseUrl);
569062
- const showResults = await Promise.allSettled(
569063
- result.map(
569064
- (m2) => fetch(`${normalized}/api/show`, {
569065
- method: "POST",
569066
- headers: { "Content-Type": "application/json" },
569067
- body: JSON.stringify({ name: m2.name }),
569068
- signal: AbortSignal.timeout(5e3)
569069
- }).then((r2) => r2.ok ? r2.json() : null)
569070
- )
569071
- );
569072
- for (let i2 = 0; i2 < result.length; i2++) {
569073
- const sr = showResults[i2];
569074
- if (sr?.status !== "fulfilled" || !sr.value) continue;
569075
- const show = sr.value;
569076
- const explicitNumCtx = parseShowNumCtx(show);
569077
- if (explicitNumCtx) {
569078
- result[i2].contextLength = explicitNumCtx;
569079
- continue;
569080
- }
569081
- if (show.model_info) {
569082
- const info = show.model_info;
569083
- const arch3 = info["general.architecture"];
569084
- const paramCount = info["general.parameter_count"];
569085
- const fileSizeGB = result[i2].sizeBytes > 0 ? result[i2].sizeBytes / 1024 ** 3 : paramCount ? paramCount * 0.6 / 1024 ** 3 : 4;
569086
- if (arch3) {
569087
- const archMax = info[`${arch3}.context_length`];
569088
- const nLayers = info[`${arch3}.block_count`];
569089
- const nKVHeads = info[`${arch3}.attention.head_count_kv`] ?? info[`${arch3}.attention.head_count`];
569090
- const keyDim = info[`${arch3}.attention.key_length`];
569091
- const valDim = info[`${arch3}.attention.value_length`] ?? keyDim;
569092
- if (archMax && nLayers && nKVHeads && keyDim && valDim) {
569093
- const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
569094
- result[i2].contextLength = estimateRealisticContext(kvBytesPerToken, archMax, fileSizeGB);
569095
- } else if (archMax) {
569096
- const kvEstimate = fileSizeGB <= 5 ? 524288 : fileSizeGB <= 20 ? 1048576 : 1572864;
569097
- result[i2].contextLength = estimateRealisticContext(kvEstimate, archMax, fileSizeGB);
569098
- }
569099
- }
569100
- }
569101
- const modelCaps = { vision: false, toolUse: false, thinking: false };
569102
- const nameLower = result[i2].name.toLowerCase();
569103
- if (Array.isArray(show.capabilities)) {
569104
- if (show.capabilities.includes("vision")) modelCaps.vision = true;
569105
- if (show.capabilities.includes("tools")) modelCaps.toolUse = true;
569106
- if (show.capabilities.includes("thinking")) modelCaps.thinking = true;
569107
- }
569108
- if (show.model_info) {
569109
- for (const key of Object.keys(show.model_info)) {
569110
- const k = key.toLowerCase();
569111
- if (k.includes("vision.block_count") || k.includes("clip.") || k.includes("image_token_id") || k.includes("projector")) {
569112
- const val = show.model_info[key];
569113
- if (val !== null && val !== void 0 && val !== 0 && val !== "") {
569114
- modelCaps.vision = true;
569115
- }
569116
- }
569117
- }
569118
- }
569119
- if (/qwen3|qwen2\.5|llama3\.[13]|mistral|mixtral|command-r|gemma3|devstral|deepseek/.test(nameLower)) {
569120
- modelCaps.toolUse = true;
569121
- }
569122
- if (show.template && (show.template.includes("<think>") || show.template.includes("thinking"))) {
569123
- modelCaps.thinking = true;
569124
- }
569125
- result[i2].caps = modelCaps;
569126
- }
569127
- return result;
569128
- }
569129
- async function fetchOpenAIModels(baseUrl, apiKey) {
569130
- const normalized = normalizeBaseUrl(baseUrl);
569131
- const url = `${normalized}/v1/models`;
569132
- const isAnthropic = /api\.anthropic\.com/i.test(baseUrl);
569133
- const headers = {};
569134
- if (apiKey) {
569135
- if (isAnthropic) {
569136
- headers["x-api-key"] = apiKey;
569137
- headers["anthropic-version"] = "2023-06-01";
569138
- } else {
569139
- headers["Authorization"] = `Bearer ${apiKey}`;
569140
- }
569141
- }
569142
- const resp = await fetch(url, {
569143
- headers,
569144
- signal: AbortSignal.timeout(15e3)
569145
- });
569146
- if (!resp.ok) {
569147
- throw new Error(`Failed to fetch models: HTTP ${resp.status}`);
569148
- }
569149
- const data = await resp.json();
569150
- const models = data.data ?? [];
569151
- return models.map((m2) => ({
569152
- name: m2.id,
569153
- size: "",
569154
- sizeBytes: 0,
569155
- modified: m2.created ? formatRelativeTime(new Date(m2.created * 1e3).toISOString()) : "",
569156
- parameterSize: m2.owned_by ?? void 0,
569157
- contextLength: m2.context_length ?? m2.max_model_len ?? void 0
569158
- })).sort((a2, b) => a2.name.localeCompare(b.name));
569159
- }
569160
- async function fetchPeerModels(peerId, authKey) {
569161
- try {
569162
- const { NexusTool: NexusTool2 } = await Promise.resolve().then(() => (init_dist5(), dist_exports));
569163
- const { existsSync: existsSync131, readFileSync: readFileSync107 } = await import("node:fs");
569164
- const { join: join148 } = await import("node:path");
569165
- const cwd4 = process.cwd();
569166
- const nexusTool = new NexusTool2(cwd4);
569167
- const nexusDir = nexusTool.getNexusDir();
569168
- let isLocalPeer = false;
569169
- try {
569170
- const statusPath = join148(nexusDir, "status.json");
569171
- if (existsSync131(statusPath)) {
569172
- const status = JSON.parse(readFileSync107(statusPath, "utf8"));
569173
- if (status.peerId === peerId) isLocalPeer = true;
569378
+ this.emit("response", event.content);
569379
+ }
569380
+ });
569174
569381
  }
569175
- } catch {
569176
- }
569177
- if (isLocalPeer) {
569178
- const pricingPath = join148(nexusDir, "pricing.json");
569179
- if (existsSync131(pricingPath)) {
569180
- try {
569181
- const pricing = JSON.parse(readFileSync107(pricingPath, "utf8"));
569182
- const localModels = (pricing.models || []).map((m2) => ({
569183
- name: m2.model || "unknown",
569184
- size: m2.parameterSize || "",
569185
- modified: "",
569186
- sizeBytes: 0,
569187
- parameterSize: m2.parameterSize || "remote"
569188
- }));
569189
- if (localModels.length > 0) return localModels;
569190
- } catch {
569382
+ /** Process a voice transcript — queues if already processing */
569383
+ handleTranscript(text) {
569384
+ if (this.disposed) return;
569385
+ this.conversationHistory.push({ role: "user", text });
569386
+ if (this.processing) {
569387
+ this.pendingTranscripts.push(text);
569388
+ return;
569191
569389
  }
569390
+ this.processTranscript(text).catch((err) => {
569391
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
569392
+ });
569192
569393
  }
569193
- }
569194
- const cachePath = join148(nexusDir, "peer-models-cache.json");
569195
- if (existsSync131(cachePath)) {
569196
- try {
569197
- const cache8 = JSON.parse(readFileSync107(cachePath, "utf8"));
569198
- if (cache8.peerId === peerId && cache8.models?.length > 0) {
569199
- const age = Date.now() - new Date(cache8.cachedAt).getTime();
569200
- if (age < 5 * 60 * 1e3) {
569201
- return cache8.models.map((m2) => ({
569202
- name: m2.name || "unknown",
569203
- size: m2.size || m2.parameterSize || "",
569204
- modified: "",
569205
- sizeBytes: 0,
569206
- parameterSize: m2.parameterSize || "remote"
569207
- }));
569208
- }
569209
- }
569210
- } catch {
569394
+ /** Dispose and clean up */
569395
+ dispose() {
569396
+ this.disposed = true;
569397
+ this.pendingTranscripts.length = 0;
569398
+ this.runner = null;
569211
569399
  }
569212
- }
569213
- try {
569214
- const capsResult = await nexusTool.execute({
569215
- action: "query_peer_caps",
569216
- peer_id: peerId,
569217
- ...authKey ? { auth_key: authKey } : {}
569218
- });
569219
- if (capsResult.success && capsResult.output) {
569220
- let capsData = null;
569400
+ // ── Private ──────────────────────────────────────────────────────────
569401
+ async processTranscript(text) {
569402
+ if (!this.runner || this.disposed) return;
569403
+ this.processing = true;
569221
569404
  try {
569222
- capsData = JSON.parse(capsResult.output);
569223
- } catch {
569224
- }
569225
- if (capsData?.models && capsData.models.length > 0) {
569226
- return capsData.models.map((m2) => ({
569227
- name: m2.name || "unknown",
569228
- size: m2.parameterSize || "",
569229
- modified: "",
569230
- sizeBytes: 0,
569231
- parameterSize: m2.parameterSize || "remote"
569232
- }));
569233
- }
569234
- if (capsData?.capabilities && capsData.capabilities.length > 0) {
569235
- const models = [];
569236
- for (const cap of capsData.capabilities) {
569237
- if (typeof cap === "string" && cap.startsWith("inference:")) {
569238
- const capName = cap.slice(10);
569239
- const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
569240
- models.push({
569241
- name: modelName,
569242
- size: "",
569243
- modified: "",
569244
- sizeBytes: 0,
569245
- parameterSize: "remote"
569405
+ const historyContext = this.conversationHistory.slice(-10).map((h) => `${h.role === "user" ? "User" : "You"}: ${h.text}`).join("\n");
569406
+ const feed = getActivityFeed();
569407
+ const activitySummary = feed.getSummary(
569408
+ this.tier === "admin" ? 20 : 10,
569409
+ this.tier === "admin"
569410
+ );
569411
+ const wantsAction = /\b(read|open|show|run|execute|check|look at|find|search|grep|edit|write|fix|test|build|deploy|install|create|delete|remove|update|change|modify|commit|push|pull)\b/i.test(text) && !/\b(how are you|what's up|hello|hi|hey|can you hear|stop|quit|bye|thanks|thank you|ok|okay|sure|yeah|yes|no)\b/i.test(text);
569412
+ if (!wantsAction) {
569413
+ try {
569414
+ const chatMessages = [
569415
+ { role: "system", content: this.buildSystemPrompt() },
569416
+ ...this.conversationHistory.slice(-6).map((h) => ({
569417
+ role: h.role === "user" ? "user" : "assistant",
569418
+ content: h.text
569419
+ })),
569420
+ { role: "user", content: text }
569421
+ ];
569422
+ const chatResult = await this.backend.chatCompletion({
569423
+ messages: chatMessages,
569424
+ tools: [],
569425
+ temperature: 0.4,
569426
+ maxTokens: 256,
569427
+ timeoutMs: 15e3
569246
569428
  });
569429
+ const reply = (chatResult.choices[0]?.message?.content ?? "").trim();
569430
+ if (!reply) return;
569431
+ this.conversationHistory.push({ role: "assistant", text: reply });
569432
+ this.emit("response", reply);
569433
+ } catch {
569434
+ this.emit("response", "Sorry, I couldn't process that.");
569247
569435
  }
569248
- }
569249
- if (models.length > 0) return models;
569250
- }
569251
- }
569252
- } catch {
569253
- }
569254
- try {
569255
- const natsResult = await nexusTool.execute({
569256
- action: "discover_peer_caps",
569257
- peer_id: peerId
569258
- });
569259
- if (natsResult.success && natsResult.output) {
569260
- let natsPeer = null;
569261
- try {
569262
- natsPeer = JSON.parse(natsResult.output);
569263
- } catch {
569264
- }
569265
- if (natsPeer?.capabilities && natsPeer.capabilities.length > 0) {
569266
- const models = [];
569267
- for (const cap of natsPeer.capabilities) {
569268
- if (typeof cap === "string" && cap.startsWith("inference:")) {
569269
- const capName = cap.slice(10);
569270
- const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
569271
- models.push({
569272
- name: modelName,
569273
- size: "",
569274
- modified: "",
569275
- sizeBytes: 0,
569276
- parameterSize: "remote"
569277
- });
569436
+ } else {
569437
+ const taskPrompt = [
569438
+ `User said: "${text}"`,
569439
+ "",
569440
+ historyContext ? `Conversation so far:
569441
+ ${historyContext}
569442
+ ` : "",
569443
+ `Background activity:
569444
+ ${activitySummary}
569445
+ `,
569446
+ "The user is requesting an action. Use tools as needed, then call task_complete with a brief spoken summary of what you did (1-2 sentences)."
569447
+ ].join("\n");
569448
+ const result = await this.runner.run(taskPrompt, `Working directory: ${this.repoRoot}`);
569449
+ if (result.summary) {
569450
+ this.conversationHistory.push({ role: "assistant", text: result.summary });
569278
569451
  }
569279
569452
  }
569280
- if (models.length > 0) return models;
569453
+ } catch (err) {
569454
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
569455
+ } finally {
569456
+ this.processing = false;
569457
+ this.emit("done");
569458
+ if (this.pendingTranscripts.length > 0) {
569459
+ const next = this.pendingTranscripts.shift();
569460
+ this.processTranscript(next).catch((err) => {
569461
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
569462
+ });
569463
+ }
569281
569464
  }
569282
569465
  }
569283
- } catch {
569284
- }
569285
- try {
569286
- const result = await nexusTool.execute({
569287
- action: "find_agent",
569288
- peer_id: peerId
569289
- });
569290
- if (result.success && result.output) {
569291
- const models = [];
569292
- const capMatches = result.output.matchAll(/inference:([^\s,\]]+)/g);
569293
- for (const m2 of capMatches) {
569294
- const capName = m2[1];
569295
- const modelName = capName.replace(/_(\d+[bBmMkK])$/, ":$1").replace(/_latest$/, ":latest");
569296
- models.push({
569297
- name: modelName,
569298
- size: "",
569299
- modified: "",
569300
- sizeBytes: 0,
569301
- parameterSize: "remote"
569302
- });
569466
+ buildSystemPrompt() {
569467
+ const base3 = [
569468
+ "You are a voice assistant on a LIVE AUDIO CALL. This is a real-time conversation.",
569469
+ "",
569470
+ "CRITICAL RULES FOR VOICE CALLS:",
569471
+ "1. ALWAYS respond IMMEDIATELY with speech. Do NOT use tools before responding.",
569472
+ "2. Your response goes through text-to-speech — keep it SHORT (1-3 sentences).",
569473
+ "3. NEVER use code blocks, markdown, or long technical text.",
569474
+ "4. Be conversational and natural, like talking to a colleague.",
569475
+ "5. Call task_complete with your spoken response as the summary.",
569476
+ "6. Only use tools (file_read, grep, shell, etc.) if the user EXPLICITLY asks you to look something up, run a command, or make a change. For normal conversation, NEVER call tools.",
569477
+ "7. If the user asks what's happening, summarize from the activity context below — do NOT run tools to find out."
569478
+ ];
569479
+ if (this.opts.emotionContext) {
569480
+ base3.push("", "Mood:", this.opts.emotionContext);
569303
569481
  }
569304
- if (models.length > 0) return models;
569305
- }
569306
- } catch {
569307
- }
569308
- if (isLocalPeer) {
569309
- const pricingPath = join148(nexusDir, "pricing.json");
569310
- if (existsSync131(pricingPath)) {
569311
- try {
569312
- const pricing = JSON.parse(readFileSync107(pricingPath, "utf8"));
569313
- return (pricing.models || []).map((m2) => ({
569314
- name: m2.model || "unknown",
569315
- size: m2.parameterSize || "",
569316
- modified: "",
569317
- sizeBytes: 0,
569318
- parameterSize: m2.parameterSize || "remote"
569319
- }));
569320
- } catch {
569482
+ if (this.tier === "admin") {
569483
+ base3.push(
569484
+ "",
569485
+ "ADMIN call — you CAN use tools IF the user explicitly requests an action (e.g. 'read that file', 'run the tests').",
569486
+ "But for general chat, status questions, or greetings — respond immediately WITHOUT tools."
569487
+ );
569488
+ } else {
569489
+ base3.push(
569490
+ "",
569491
+ "PUBLIC call read-only access. Answer questions about the project conversationally."
569492
+ );
569321
569493
  }
569494
+ return base3.join("\n");
569322
569495
  }
569323
- }
569324
- return [];
569325
- } catch {
569326
- return [];
569327
- }
569328
- }
569329
- async function fetchModels(baseUrl, apiKey) {
569330
- if (baseUrl.startsWith("peer://")) {
569331
- return fetchPeerModels(baseUrl.slice(7), apiKey);
569332
- }
569333
- const provider = detectProvider(baseUrl);
569334
- if (provider.id === "ollama") {
569335
- let ollamaErr;
569336
- try {
569337
- return await fetchOllamaModels(baseUrl);
569338
- } catch (err) {
569339
- ollamaErr = err instanceof Error ? err : new Error(String(err));
569340
- try {
569341
- return await fetchOpenAIModels(baseUrl, apiKey);
569342
- } catch {
569343
- throw new Error(`Cannot reach Ollama at ${baseUrl}: ${ollamaErr.message}`);
569344
- }
569345
- }
569346
- }
569347
- let lastErr;
569348
- for (let attempt = 0; attempt < 2; attempt++) {
569349
- try {
569350
- return await fetchOpenAIModels(baseUrl, apiKey);
569351
- } catch (err) {
569352
- lastErr = err instanceof Error ? err : new Error(String(err));
569353
- if (attempt === 0) await new Promise((r2) => setTimeout(r2, 1e3));
569354
- }
569355
- }
569356
- try {
569357
- return await fetchOllamaModels(baseUrl);
569358
- } catch {
569359
- throw new Error(`Cannot fetch models from ${provider.label} at ${baseUrl}: ${lastErr?.message ?? "unknown error"}`);
569360
- }
569361
- }
569362
- function findModel(models, query) {
569363
- const exact = models.find((m2) => m2.name === query);
569364
- if (exact) return exact;
569365
- const partial = models.find((m2) => m2.name.startsWith(query));
569366
- if (partial) return partial;
569367
- const fuzzy = models.find((m2) => m2.name.includes(query));
569368
- return fuzzy;
569369
- }
569370
- async function queryModelContextSize(baseUrl, modelName) {
569371
- try {
569372
- const normalized = normalizeBaseUrl(baseUrl);
569373
- const res = await fetch(`${normalized}/api/show`, {
569374
- method: "POST",
569375
- headers: { "Content-Type": "application/json" },
569376
- body: JSON.stringify({ name: modelName }),
569377
- signal: AbortSignal.timeout(1e4)
569378
- });
569379
- if (!res.ok) return null;
569380
- const data = await res.json();
569381
- const explicitNumCtx = parseShowNumCtx(data);
569382
- if (explicitNumCtx) return explicitNumCtx;
569383
- if (data.model_info) {
569384
- const info = data.model_info;
569385
- const arch3 = info["general.architecture"];
569386
- const paramCount = info["general.parameter_count"];
569387
- const modelSizeGB2 = paramCount ? paramCount * 0.6 / 1024 ** 3 : 4;
569388
- if (arch3) {
569389
- const archMax = info[`${arch3}.context_length`];
569390
- const nLayers = info[`${arch3}.block_count`];
569391
- const nKVHeads = info[`${arch3}.attention.head_count_kv`] ?? info[`${arch3}.attention.head_count`];
569392
- const keyDim = info[`${arch3}.attention.key_length`];
569393
- const valDim = info[`${arch3}.attention.value_length`] ?? keyDim;
569394
- if (archMax && nLayers && nKVHeads && keyDim && valDim) {
569395
- const kvBytesPerToken = nLayers * nKVHeads * (keyDim + valDim) * 2;
569396
- return estimateRealisticContext(kvBytesPerToken, archMax, modelSizeGB2);
569397
- }
569398
- if (archMax) {
569399
- const kvEstimate = modelSizeGB2 <= 5 ? 524288 : modelSizeGB2 <= 20 ? 1048576 : 1572864;
569400
- return estimateRealisticContext(kvEstimate, archMax, modelSizeGB2);
569496
+ buildTools() {
569497
+ if (this.tier === "admin") {
569498
+ return this.buildAdminTools();
569401
569499
  }
569500
+ return this.buildPublicTools();
569402
569501
  }
569403
- }
569404
- return null;
569405
- } catch {
569406
- return null;
569407
- }
569408
- }
569409
- function estimateRealisticContext(kvBytesPerToken, archMax, modelSizeGB2) {
569410
- const totalMemGB = totalmem4() / 1024 ** 3;
569411
- const usableBytes = totalMemGB * 0.7 * 1024 ** 3;
569412
- const maxTokens = Math.floor(usableBytes / kvBytesPerToken);
569413
- let numCtx = Math.max(2048, Math.floor(maxTokens / 1024) * 1024);
569414
- numCtx = Math.min(numCtx, 131072, archMax);
569415
- if (modelSizeGB2 && modelSizeGB2 > 0) {
569416
- const maxKVBytes = modelSizeGB2 * 4 * 1024 ** 3;
569417
- const budgetCap = Math.max(2048, Math.floor(maxKVBytes / kvBytesPerToken / 1024) * 1024);
569418
- numCtx = Math.min(numCtx, budgetCap);
569419
- }
569420
- return numCtx;
569421
- }
569422
- async function queryOpenAIContextSize(baseUrl, modelName, apiKey) {
569423
- try {
569424
- const models = await fetchOpenAIModels(baseUrl, apiKey);
569425
- const model = models.find((m2) => m2.name === modelName);
569426
- if (model?.contextLength) return model.contextLength;
569427
- if (model?.size) {
569428
- const match = model.size.match(/(\d+)K ctx/);
569429
- if (match) return parseInt(match[1], 10) * 1024;
569430
- }
569431
- return null;
569432
- } catch {
569433
- return null;
569434
- }
569435
- }
569436
- async function queryContextSize(baseUrl, modelName, apiKey) {
569437
- if (baseUrl.startsWith("peer://")) return 32768;
569438
- const ollamaSize = await queryModelContextSize(baseUrl, modelName);
569439
- if (ollamaSize) return ollamaSize;
569440
- return queryOpenAIContextSize(baseUrl, modelName, apiKey);
569441
- }
569442
- async function queryModelCapabilities(baseUrl, modelName) {
569443
- const caps = { vision: false, toolUse: false, thinking: false };
569444
- if (baseUrl.startsWith("peer://")) {
569445
- const nameLower = modelName.toLowerCase();
569446
- if (/qwen3|qwen2\.5|llama3\.[13]|mistral|mixtral|command-r|gemma3|devstral|deepseek/.test(nameLower)) {
569447
- caps.toolUse = true;
569448
- }
569449
- if (/qwen3|deepseek-r1/.test(nameLower)) {
569450
- caps.thinking = true;
569451
- }
569452
- return caps;
569453
- }
569454
- try {
569455
- const normalized = normalizeBaseUrl(baseUrl);
569456
- const res = await fetch(`${normalized}/api/show`, {
569457
- method: "POST",
569458
- headers: { "Content-Type": "application/json" },
569459
- body: JSON.stringify({ name: modelName }),
569460
- signal: AbortSignal.timeout(1e4)
569461
- });
569462
- if (!res.ok) return caps;
569463
- const data = await res.json();
569464
- if (Array.isArray(data.capabilities)) {
569465
- if (data.capabilities.includes("vision")) caps.vision = true;
569466
- if (data.capabilities.includes("tools")) caps.toolUse = true;
569467
- if (data.capabilities.includes("thinking")) caps.thinking = true;
569468
- }
569469
- if (data.model_info) {
569470
- for (const key of Object.keys(data.model_info)) {
569471
- const k = key.toLowerCase();
569472
- if (k.includes("vision.block_count") || k.includes("clip.") || k.includes("image_token_id") || k.includes("projector") || k.includes("vision.embedding_length")) {
569473
- const val = data.model_info[key];
569474
- if (val !== null && val !== void 0 && val !== 0 && val !== "") {
569475
- caps.vision = true;
569476
- }
569477
- }
569502
+ buildAdminTools() {
569503
+ const debateAdapter = async (prompt) => {
569504
+ const r2 = await this.backend.chatCompletion({
569505
+ messages: [{ role: "user", content: prompt }],
569506
+ tools: [],
569507
+ temperature: 0.7,
569508
+ maxTokens: 800,
569509
+ timeoutMs: 12e4
569510
+ });
569511
+ return r2.choices[0]?.message?.content ?? "";
569512
+ };
569513
+ const replayAdapter = async (prompt) => {
569514
+ const r2 = await this.backend.chatCompletion({
569515
+ messages: [{ role: "user", content: prompt }],
569516
+ tools: [],
569517
+ temperature: 0,
569518
+ maxTokens: 1500,
569519
+ timeoutMs: 12e4
569520
+ });
569521
+ return r2.choices[0]?.message?.content ?? "";
569522
+ };
569523
+ const tools = [
569524
+ new FileReadTool(this.repoRoot),
569525
+ new FileWriteTool(this.repoRoot),
569526
+ new FileEditTool(this.repoRoot),
569527
+ new ShellTool(this.repoRoot),
569528
+ new GrepSearchTool(this.repoRoot),
569529
+ new GlobFindTool(this.repoRoot),
569530
+ new ListDirectoryTool(this.repoRoot),
569531
+ new WebSearchTool(),
569532
+ new WebFetchTool(),
569533
+ new MemoryReadTool(this.repoRoot),
569534
+ new MemoryWriteTool(this.repoRoot),
569535
+ new MemorySearchTool(this.repoRoot),
569536
+ new DebateTool(debateAdapter),
569537
+ new ReplayWithInterventionTool({ workingDir: this.repoRoot, callable: replayAdapter })
569538
+ ];
569539
+ return tools.map(adaptTool);
569478
569540
  }
569479
- }
569480
- const nameLower = modelName.toLowerCase();
569481
- if (/qwen3|qwen2\.5|llama3\.[13]|mistral|mixtral|command-r|gemma3|devstral|deepseek/.test(nameLower)) {
569482
- caps.toolUse = true;
569483
- }
569484
- if (data.template) {
569485
- if (data.template.includes("<think>") || data.template.includes("thinking")) {
569486
- caps.thinking = true;
569541
+ buildPublicTools() {
569542
+ const tools = [
569543
+ new FileReadTool(this.repoRoot),
569544
+ new GrepSearchTool(this.repoRoot),
569545
+ new GlobFindTool(this.repoRoot),
569546
+ new ListDirectoryTool(this.repoRoot),
569547
+ new MemoryReadTool(this.repoRoot),
569548
+ new MemorySearchTool(this.repoRoot)
569549
+ ];
569550
+ return tools.map(adaptTool);
569487
569551
  }
569488
- }
569489
- return caps;
569490
- } catch {
569491
- return caps;
569492
- }
569493
- }
569494
- function formatBytes3(bytes) {
569495
- if (bytes < 1024) return `${bytes} B`;
569496
- const units = ["KB", "MB", "GB", "TB"];
569497
- let size = bytes;
569498
- let i2 = -1;
569499
- while (size >= 1024 && i2 < units.length - 1) {
569500
- size /= 1024;
569501
- i2++;
569502
- }
569503
- return `${size.toFixed(1)} ${units[i2] ?? "B"}`;
569504
- }
569505
- function formatContextLength(tokens) {
569506
- if (tokens >= 1e6) return `${(tokens / 1e6).toFixed(1)}M ctx`;
569507
- if (tokens >= 1024) return `${Math.round(tokens / 1024)}K ctx`;
569508
- return `${tokens} ctx`;
569509
- }
569510
- function formatCaps(caps) {
569511
- const tags = [];
569512
- if (caps.vision) tags.push("vision");
569513
- if (caps.toolUse) tags.push("tools");
569514
- if (caps.thinking) tags.push("think");
569515
- return tags.join("+");
569516
- }
569517
- function formatRelativeTime(iso2) {
569518
- const now = Date.now();
569519
- const then = new Date(iso2).getTime();
569520
- const diffMs = now - then;
569521
- const minutes = Math.floor(diffMs / 6e4);
569522
- if (minutes < 1) return "just now";
569523
- if (minutes < 60) return `${minutes}m ago`;
569524
- const hours = Math.floor(minutes / 60);
569525
- if (hours < 24) return `${hours}h ago`;
569526
- const days = Math.floor(hours / 24);
569527
- if (days < 7) return `${days}d ago`;
569528
- const weeks = Math.floor(days / 7);
569529
- if (weeks < 5) return `${weeks}w ago`;
569530
- const months = Math.floor(days / 30);
569531
- return `${months}mo ago`;
569532
- }
569533
- var IMAGE_GEN_PATTERNS;
569534
- var init_model_picker = __esm({
569535
- "packages/cli/src/tui/model-picker.ts"() {
569536
- "use strict";
569537
- init_dist();
569538
- IMAGE_GEN_PATTERNS = [
569539
- /flux/i,
569540
- /z-image/i,
569541
- /stable-diffusion/i,
569542
- /sdxl/i,
569543
- /dall/i,
569544
- /kandinsky/i,
569545
- /midjourney/i,
569546
- /imagen/i
569547
- ];
569552
+ };
569548
569553
  }
569549
569554
  });
569550
569555
 
@@ -598698,8 +598703,8 @@ async function showModelPicker(ctx3, local = false) {
598698
598703
  }
598699
598704
  const items = [];
598700
598705
  const history = loadUsageHistory("model", ctx3.repoRoot);
598701
- const liveModelNames = new Set(models.map((m2) => m2.name));
598702
- const modelMap = new Map(models.map((m2) => [m2.name, m2]));
598706
+ const liveModelNames = new Set(models.map((m2) => stripLatest(m2.name)));
598707
+ const modelMap = new Map(models.map((m2) => [stripLatest(m2.name), m2]));
598703
598708
  if (history.length > 0) {
598704
598709
  items.push({
598705
598710
  key: "__header_recent__",
@@ -598708,8 +598713,9 @@ async function showModelPicker(ctx3, local = false) {
598708
598713
  });
598709
598714
  for (const h of history.slice(0, 8)) {
598710
598715
  const uses = h.localUses > 0 ? `${h.useCount} uses (${h.localUses} local)` : `${h.useCount} uses`;
598711
- const available = liveModelNames.has(h.value) ? "" : c3.yellow(" [offline]");
598712
- const meta = modelMap.get(h.value);
598716
+ const hKey = stripLatest(h.value);
598717
+ const available = liveModelNames.has(hKey) ? "" : c3.yellow(" [offline]");
598718
+ const meta = modelMap.get(hKey);
598713
598719
  const ctx4 = meta?.contextLength ? ` ${formatContextLength(meta.contextLength)}` : "";
598714
598720
  const capStr = meta?.caps ? ` ${formatCaps(meta.caps)}` : "";
598715
598721
  items.push({
@@ -598724,9 +598730,9 @@ async function showModelPicker(ctx3, local = false) {
598724
598730
  detail: ""
598725
598731
  });
598726
598732
  }
598727
- const historyKeys = new Set(history.map((h) => h.value));
598733
+ const historyKeys = new Set(history.map((h) => stripLatest(h.value)));
598728
598734
  for (const m2 of models) {
598729
- if (history.length > 0 && historyKeys.has(m2.name)) continue;
598735
+ if (history.length > 0 && historyKeys.has(stripLatest(m2.name))) continue;
598730
598736
  const ctx4 = m2.contextLength ? formatContextLength(m2.contextLength) : "";
598731
598737
  const capStr = m2.caps ? formatCaps(m2.caps) : "";
598732
598738
  items.push({
@@ -598737,7 +598743,10 @@ async function showModelPicker(ctx3, local = false) {
598737
598743
  }
598738
598744
  const result = await tuiSelect({
598739
598745
  items,
598740
- activeKey: ctx3.config.model,
598746
+ // `activeKey` is the keyed currently-selected row. The picker stores
598747
+ // history/recent entries with tag-less keys, so normalize the active
598748
+ // model from config to match.
598749
+ activeKey: stripLatest(ctx3.config.model),
598741
598750
  title: "Select Model",
598742
598751
  rl: ctx3.rl,
598743
598752
  // Skip header rows
@@ -598748,7 +598757,7 @@ async function showModelPicker(ctx3, local = false) {
598748
598757
  renderInfo("Model selection cancelled.");
598749
598758
  return;
598750
598759
  }
598751
- await switchModel(result.key, ctx3, local);
598760
+ await switchModel(stripLatest(result.key), ctx3, local);
598752
598761
  } catch (err) {
598753
598762
  renderError(
598754
598763
  `Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`