import type { MossAgentEvent, Tool, ToolResultOutcome } from '../core/index.js'; import type { SessionMeta } from '../core/session/session.js'; import { SkillRegistry, type SkillMeta } from '../skills/index.js'; import type { PreparedPromptAttachment, PromptAttachmentBlock } from './attachments.js'; import type { CliRuntimeStatus } from './onboarding.js'; import type { ModelChoiceList } from './model-catalog.js'; import type { CommandSpec } from './commands/registry.js'; export type TranscriptKind = 'user' | 'assistant' | 'system' | 'error' | 'shell' | 'tool'; export type TuiRunState = 'ready' | 'running' | 'approval'; export interface TranscriptItem { id: number; kind: TranscriptKind; text: string; turnId?: number; status?: 'running' | 'ok' | 'failed'; toolName?: string; toolCallId?: string; toolInput?: string; toolInputRaw?: unknown; startedAt?: number; elapsedMs?: number; outcome?: ToolResultOutcome; result?: string; finalized?: boolean; channel?: 'btw'; /** Accumulated reasoning/thinking text for an assistant turn (rendered as a collapsible block). */ thinking?: string; } export interface ActivityItem { id: string; toolName: string; toolCallId: string; startedAt: number; status: 'running' | 'ok' | 'failed'; inputSummary?: string; /** CC-style sub-line shown below the headline (e.g. "Added 7 lines, removed 1 line"). */ inputSubline?: string; elapsedMs?: number; outcome?: ToolResultOutcome; inputRaw?: unknown; result?: string; } export interface ModelPickerState { list: ModelChoiceList; selectedIndex: number; } export interface SessionPickerState { sessions: SessionMeta[]; selectedIndex: number; } export interface ApprovalState { question: string; selectedIndex: number; resolve: (answer: string) => void; } /** TUI state for ask_user_question (not tool-approval y/a/n). */ export interface UserQuestionState { question: string; /** Parsed option labels (empty = freeform-only). */ options: { label: string; description?: string; }[]; multiSelect: boolean; selectedIndex: number; /** Multi-select: toggled option indices. */ selectedIndices: number[]; /** Freeform draft (Other / freeform-only). */ freeform: string; resolve: (answer: string) => void; } export interface GoalActivityState { objective: string; startedAt: number; runCount: number; /** Live counters so long goal runs read as structured progress, not a spinner. */ turns?: number; toolCalls?: number; lastCheckpoint?: { status: string; nextAction: string; }; } export interface RunPromptOptions { echoUser?: boolean; autoGoal?: boolean; ephemeralTools?: Tool[]; } export interface GoalAutoRefState { running: boolean; suspended: boolean; scheduled: boolean; startedAt: number; runCount: number; objective: string; } export interface QueuedInput { raw: string; message: string; enqueuedAt?: number; attachments?: PreparedPromptAttachment[]; attachmentBlocks?: PromptAttachmentBlock[]; } export interface QueueDrainState { busy: boolean; approvalActive: boolean; pausedAfterCancel: boolean; queueLength: number; } export interface TranscriptViewportRowsOptions { transcriptLength: number; terminalRows: number; headerRows: number; promptRows: number; queueRows: number; footerRows: number; approvalRows: number; noticeRows: number; } export interface AttachmentRef { index: number; kind: 'image' | 'file'; label: string; } export declare function emojiEnabled(): boolean; export declare function createTranscriptId(now?: number): number; export declare function nextId(): number; export declare const ANSI_RE: RegExp; export declare const CONTROL_CHAR_RE: RegExp; export declare const LONG_TOKEN_RE: RegExp; export declare const CJK_CHAR_RE: RegExp; export declare const COPY_SENSITIVE_TOKEN_RE: RegExp; export declare const RTL_RE: RegExp; export declare const LOCAL_SHELL_OUTPUT_LIMIT = 40000; export declare const MAX_INPUT_HISTORY = 100; export declare const WELCOME_PANEL_ROWS_ESTIMATE = 18; export declare const HEADLINE_MAX = 72; export declare const DEFAULT_MARKDOWN_TABLE_WIDTH = 96; export declare const MIN_MARKDOWN_TABLE_WIDTH = 40; export declare const MAX_MARKDOWN_TABLE_WIDTH = 160; export declare const MARKDOWN_TABLE_CELL = "\u001F"; export declare const MARKDOWN_TABLE_ROW = "\u001E"; export declare const AGENTS_MD_TEMPLATE = "# AGENTS.md\n\nProject memory for Moss and coding agents. Auto-loaded at the start of every session.\n\n## Overview\n\n\n## Build / test / run\n\n\n## Layout\n\n\n## Conventions\n\n"; export declare const KNOWN_COMMANDS: readonly string[]; export { cliLocale, isZhLocale } from './cli-locale.js'; export declare function sanitizeTextForTerminal(text: string, options: { breakLongTokens: boolean; }): string; export declare function sanitizeRenderableText(text: string): string; export declare function sanitizePromptEditorText(text: string): string; export declare function isLocalShellLine(raw: string): boolean; export declare function appendLimited(current: string, chunk: string, limit?: number): string; export declare function killProcessTree(child: import('node:child_process').ChildProcess): void; export declare function runLocalShellCommand(options: { command: string; cwd: string; signal?: AbortSignal; onChunk?: (chunk: string) => void; }): Promise<{ output: string; exitCode: number | null; signal: NodeJS.Signals | null; }>; export declare function visibleText(text: string, maxLines?: number): string; export declare function truncateTerminalText(text: string, maxWidth: number): string; /** * A stored conversation message, structurally typed so the resume-replay helpers * don't need to import the core session schema. * @internal */ export type ResumableMessage = { role: string; content: string | Array<{ type: string; text?: string; name?: string; input?: unknown; }>; }; /** * The human-readable text a user typed or the assistant said — drops tool_use / * tool_result blocks and internal goal checkpoints — so a resumed session can be * re-displayed as the conversation, not as raw protocol. * @internal */ export declare function resumedMessageText(message: ResumableMessage): string; /** * One-line summaries of the tool_use blocks in an assistant message, so a * resumed session can show WHAT the agent did on each turn (not just the prose) * — e.g. `⎿ edit_file (hello.js)`, `⎿ exec (node verify.js)`. Reuses toolHeadline * for the input summary. Returns [] for user messages or text-only turns. * @internal */ export declare function resumedToolLines(message: ResumableMessage): string[]; /** How many recent conversation turns /resume replays into the transcript. @internal */ export declare const RESUME_REPLAY_MAX = 24; /** * Build the transcript rows that replay a resumed conversation's recent turns, so * resuming SHOWS the conversation (like Claude Code / Codex / opencode) instead of * a blank screen. Tool/checkpoint-only turns are dropped; returns the visible rows * plus how many older conversation turns were elided. * * Each assistant turn also emits a `system` row per tool_use block (via * resumedToolLines) so the user can see the agent's prior actions, not just its * prose — previously the replay stripped all tool calls and the user had no idea * what the agent had already done after resuming. * @internal */ export declare function buildResumeReplay(messages: ReadonlyArray, max?: number): { items: Array<{ kind: 'user' | 'assistant' | 'system'; text: string; }>; hiddenCount: number; }; export declare function formatQueueWait(enqueuedAt: number | undefined, now?: number): string | null; export declare function queueItemKind(item: QueuedInput): string; export declare function dropLastQueuedInput(items: QueuedInput[]): { next: QueuedInput[]; dropped?: QueuedInput; }; export declare function queueItemMeta(item: QueuedInput, now?: number): string; export declare function shouldDrainQueue(state: QueueDrainState): boolean; export declare class SerialQueueDrain { private running; isRunning(): boolean; run(task: () => Promise): Promise; } export declare function stopRequestedMessage(queueLength: number): string; export declare function queueResumedMessage(queueLength: number): string; export declare function queuePausedSubmissionMessage(queueLength: number, message: string): string; export declare function isQueueControlCommand(message: string): boolean; export declare function isImmediateGoalCommand(message: string): boolean; export declare function availableTranscriptRows(options: TranscriptViewportRowsOptions): number; export declare function shouldRenderCompactWelcome(options: TranscriptViewportRowsOptions): boolean; export declare function transcriptViewportRows(options: TranscriptViewportRowsOptions): number | undefined; export declare function formatSessionTimestamp(updatedAt: number): string; export declare function formatTuiSessions(sessions: SessionMeta[], currentSessionKey: string, options?: { limit?: number; }): string; export declare function extractAttachmentRefs(text: string): AttachmentRef[]; export declare function attachmentRefIndexes(text: string): Set; export declare function removeAttachmentRefsFromInput(value: string): string; export declare function inputWithAttachmentRefs(value: string, attachments: PreparedPromptAttachment[]): string; export declare function blockCountForAttachment(item: PreparedPromptAttachment): number; export declare function selectReferencedPromptAttachments(text: string, attachments: PreparedPromptAttachment[], blocks: PromptAttachmentBlock[]): { attachments: PreparedPromptAttachment[]; blocks: PromptAttachmentBlock[]; }; export declare function formatAttachmentChip(ref: AttachmentRef): string; export declare function statusLine(options: { state: TuiRunState; model: string; device: string; workspace: string; cacheMode?: string; profile?: string; }): string; export declare function promptCacheModeLabel(runtime?: CliRuntimeStatus): string; export type ExecutionMode = 'pc-host' | 'on-board' | 'hybrid'; export interface DeviceContextSummary { mode: ExecutionMode; runningOn: string; targetDevice: string; inference: string; permissions: string; policy: string; deviceContext: string; lockedCapabilities: string; } export declare const GETTING_STARTED_WORKFLOWS: readonly [{ readonly title: "Host Code"; readonly description: "inspect files, explain architecture, edit safely, review changes"; }, { readonly title: "Host Commands"; readonly description: "build, typecheck, lint, test, reproduce failures, collect logs"; }, { readonly title: "Board Diagnostics"; readonly description: "connect over SSH, check OS, NPU, memory, services, network"; }, { readonly title: "Board Workflows"; readonly description: "deploy model, bring up sensors, debug ROS/tros, gather evidence"; }]; export declare function isLikelyBoardRuntime(): boolean; export declare function readFirstExisting(paths: readonly string[]): string | null; export declare function localBoardModel(): string | null; export declare function localOsName(): string; export declare function localMemoryLabel(): string; export declare function localTemperatureLabel(): string; export declare function localNpuLabel(): string; export declare function localCameraLabel(): string; export declare function localRosLabel(): string; export declare function localServiceLabel(): string; export declare function inferExecutionMode(runtime?: CliRuntimeStatus): ExecutionMode; export declare function modeLabel(mode: ExecutionMode): string; export declare function runningOnLabel(mode: ExecutionMode): string; export declare function boardSurfaceLabel(runtime?: CliRuntimeStatus): string; export declare function inferenceRouteLabel(runtime?: CliRuntimeStatus): string; export declare function permissionBoundaryLabel(runtime?: CliRuntimeStatus): string; export declare function runtimePolicyLabel(runtime?: CliRuntimeStatus): string; export declare function connectUnlockLine(runtime?: CliRuntimeStatus): string; export declare function deviceContextLine(runtime?: CliRuntimeStatus): string; export declare function executionPlaneSummary(runtime?: CliRuntimeStatus): DeviceContextSummary; export declare function boardTip(runtime?: CliRuntimeStatus): string; export declare function compactWelcomeTip(tip: string): string; export declare function footerHint(state: TuiRunState): string; export declare function editorPreviewLines(value: string, placeholder: string, maxLines?: number): string[]; export interface PromptEditState { value: string; cursor: number; } export type PromptEditIntent = { type: 'insert'; text: string; } | { type: 'left'; } | { type: 'right'; } | { type: 'home'; } | { type: 'end'; } | { type: 'backspace'; } | { type: 'delete'; } | { type: 'killBefore'; } | { type: 'killAfter'; } | { type: 'deletePreviousWord'; }; export declare function clampPromptCursor(value: string, cursor: number): number; interface GraphemeSegment { index: number; segment: string; } type GraphemeSegmenter = { segment(input: string): Iterable; }; type GraphemeSegmenterConstructor = new (locales?: string | string[], options?: { granularity: 'grapheme'; }) => GraphemeSegmenter; export declare const NativeSegmenter: { new (locales?: Intl.LocalesArgument, options?: Intl.SegmenterOptions): Intl.Segmenter; prototype: Intl.Segmenter; supportedLocalesOf(locales: Intl.LocalesArgument, options?: Pick): Intl.UnicodeBCP47LocaleIdentifier[]; } & GraphemeSegmenterConstructor; export declare function codePointSegments(value: string): GraphemeSegment[]; export declare function graphemeSegments(value: string): GraphemeSegment[]; export declare function previousGraphemeStart(value: string, cursor: number): number; export declare function nextGraphemeEnd(value: string, cursor: number): number; export declare function previousWordStart(value: string, cursor: number): number; export declare function applyPromptEdit(state: PromptEditState, intent: PromptEditIntent): PromptEditState; export declare function shouldPromptReturnInsertNewline(key: { shift?: boolean; ctrl?: boolean; }): boolean; interface EditorPreviewLine { text: string; /** Terminal display cells from line start to cursor, not a UTF-16 index. */ cursorColumn: number | null; } interface LineViewportResult { text: string; cursorColumn: number; } interface DisplaySegment { segment: string; startColumn: number; endColumn: number; } export declare function displaySegments(value: string): DisplaySegment[]; export declare function lineViewportAroundCursor(text: string, cursorColumn: number, maxWidth?: number): LineViewportResult; export declare function editorPreviewLinesWithCursor(value: string, _placeholder: string, cursor: number, maxLines?: number, maxLineWidth?: number): EditorPreviewLine[]; export declare function commandSuggestion(command: string): string | null; export declare function editDistance(a: string, b: string): number; export declare function commonPrefix(values: readonly string[]): string; export declare function completeSlashCommandInput(value: string, cursor: number): PromptEditState | null; export declare function commandArgumentHint(value: string): string | null; export declare function promptPlaceholder(state: TuiRunState): string; export declare function statusBadge(state: TuiRunState): string; export declare function approvalKeyDecision(inputChar: string, key: { escape?: boolean; }): 'allow-once' | 'allow-always' | 'deny' | null; export declare function renderMemory(workspace: string): string; /** Classify a skill's source for the /skills listing: builtin / rdk / workspace * / global. Helps the user see WHERE a skill comes from (so they know which * file to edit or which scope a /skill disable affects). @internal */ export declare function skillSourceLabel(skill: SkillMeta, workspace?: string): string; export declare function formatSkillLine(skill: SkillMeta, workspace?: string): string; /** Skills auto-injected per turn (cap so a broad query can't flood context). */ export declare const MAX_INJECTED_SKILLS = 3; /** * Read a skill's SKILL.md body (frontmatter stripped). Builtins use a virtual * `builtin://` path and have no readable file, so they return their inlined * `body` field (if any) \u2014 without this, matched builtin skills inject only a * description and no instructions, so they never change model behavior. */ export declare function readSkillBody(skill: SkillMeta): string | undefined; /** * Build the per-turn matched-skill context block. Matches the user's message * against registry skills (name/description/trigger) and inlines the top few * skills' instructions. Returns '' when nothing matches. The caller passes this * via ChatOptions.extraContext (dynamic bucket) so it never breaks prompt cache. * @internal */ export declare function buildMatchedSkillContext(registry: SkillRegistry | null, message: string): string; /** * Returns the skill catalog (name + description for every enabled skill) when * the user's message asks for it, or `''` otherwise. The caller merges the * result into the per-turn `extraContext` (dynamic prompt-cache bucket). */ export declare function buildSkillCatalogContext(registry: SkillRegistry | null, message: string): string; /** Compact skills index budget (dynamic bucket). Kept short — full bodies load via load_skill. */ export declare const SKILL_INDEX_CHAR_BUDGET = 1800; export declare const SKILL_INDEX_DESC_CHARS = 72; /** * Always-on compact skills index (dynamic bucket). Lists name + short description * so the model can call `load_skill` on demand — Claude Code / Grok Skill tool * discovery parity. Bodies are NOT inlined (use matchByText injection or * load_skill for that). Returns '' when the registry is empty. * * When `prioritizePrefixes` is set (e.g. board connected → `['rdk-', 'ros']`), * matching skills float to the top so the limited char budget surfaces * robotics-first guidance before generic coding skills. * @internal */ export declare function buildSkillIndexContext(registry: SkillRegistry | null, options?: { charBudget?: number; maxDescChars?: number; /** Skill name prefixes to float first (case-insensitive). */ prioritizePrefixes?: string[]; }): string; /** * Build `/` slash commands from file-backed registry skills. Mirrors * loadCustomCommands: a skill resolves to a command that expands its SKILL.md * body into a submitted prompt. Builtins (no readable body) and names already * owned by a built-in or custom command are skipped, so a skill can never * shadow a shipped command. * @internal */ export declare function loadSkillCommands(registry: SkillRegistry, reserved: ReadonlySet): CommandSpec[]; export declare function listMarkdownFilenames(dir: string): string[]; export declare function listLearnedSkillFiles(workspace: string): string[]; interface SkillCandidateListing { id: string; name: string; confidence: string; } export declare function listSkillCandidates(workspace: string): SkillCandidateListing[]; /** * Extra skill roots for a session: config `skills.extraRoots` (tilde-expanded, * existence-checked) or the built-in home defaults. Best-effort — a broken * config file must never stop skills from loading, so failures fall back to the * defaults. * @internal */ export declare function resolveSessionSkillRoots(runtime?: CliRuntimeStatus): string[]; export declare function renderSkills(workspace: string, extraDirs?: string[]): string; export declare function summarizeToolInput(input: unknown, maxChars?: number): string; /** * Pull the most informative arg out of a tool input for the headline. * Examples: * { path: 'src/foo.ts' } → 'src/foo.ts' * { command: 'npm run build' } → 'npm run build' * { query: 'authStore' } → 'authStore' * { url: 'https://...' } → 'https://...' * Falls back to summarizeToolInput. */ export declare function toolHeadline(input: unknown): string; export declare function humanTokens(n: number): string; /** Progressive color for context usage: green → amber → orange → red. */ export declare function ctxUsageBarColor(usage: { used: number; total: number; }): string; export declare function activityLabel(event: MossAgentEvent): string | null; export declare function toolOutcomeLabel(item: ActivityItem): string; export declare function transcriptColor(kind: TranscriptKind): 'cyan' | 'red' | 'gray' | 'green' | 'magenta' | undefined; export declare function statusBarColor(state: TuiRunState): string; export declare function resolveMarkdownTableWidth(): number; export declare function markdownTableCellText(content: unknown, context: unknown): string; export declare function markdownTableTokenRows(content: unknown, context: unknown): string; export declare function renderMarkdownTableFromRendererArgs(args: unknown[], context: unknown): string; export declare function cleanMarkdownTableCell(cell: string): string; export declare function splitMarkdownTableRows(text: string): string[][]; export declare function splitWideWord(word: string, width: number): string[]; export declare function wrapMarkdownTableCell(value: string, width: number): string[]; export declare function padMarkdownTableCell(value: string, width: number): string; export declare function markdownTableColumnWidths(rows: string[][], tableWidth: number): number[]; export declare function renderMarkdownTableRows(rows: string[][], widths: number[]): string[]; export declare function shouldStackMarkdownTable(rows: string[][], tableWidth: number): boolean; export declare function renderStackedMarkdownTable(header: string[], rows: string[][]): string; export declare function renderTerminalFriendlyMarkdownTable(headerText: string, bodyText: string): string; export declare function ensureMarkdownRenderer(): void; export declare function renderMarkdown(text: string, options?: { width?: number; }): string; /** * Render markdown for streaming (in-progress) text. * * Strategy: split the text at code block boundaries. Complete code blocks * (opened AND closed with ```) are syntax-highlighted via renderMarkdown. * The incomplete trailing portion (no closing ```) is shown as raw text so * the streaming cursor stays at the natural insertion point rather than * disappearing mid-fence. * * This makes code visible with colors as soon as a block is complete, instead * of waiting for the full message to finalize. */ export declare function renderStreamingMarkdown(text: string): string; //# sourceMappingURL=tui-utils.d.ts.map