import { CLOUD_PLAN_KEY } from "../../shared/cloud-upgrade"; import { ActionRow } from "../../../../components/ui/action-row"; import { getCurrentPluginTarget } from "../../../current-target"; import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react"; import { apiClient } from "../../../../api-client"; import { Box, ScrollBox, Text, TextAttributes, useUiCapabilities, type BoxRenderable, type ScrollBoxRenderable, type TextareaRenderable, } from "../../../../ui"; import { Button, ChoiceDialog, DataTableView, EmptyState, getPaneSidebarWidth, MessageComposer, Prose, QueryBar, shouldShowPaneSidebar, Spinner, usePaneFooter, type DataTableCell, type DataTableColumn, type PaneHint, } from "../../../../components"; import { MarkdownText } from "../../../../components/markdown-text"; import { useShortcut } from "../../../../react/input"; import { useAppDispatch, useAppSelector, usePaneAppConfig, } from "../../../../state/app/context"; import { useInlineTickers } from "../../../../state/hooks/inline-tickers"; import { useRemoteControlHandler } from "../../../../remote/app-host"; import { colors } from "../../../../theme/colors"; import type { PaneProps } from "../../../../types/plugin"; import { collectUniqueTickerSymbols } from "../../../../tickers/tokenizer"; import { isPlainKey } from "../../../../utils/keyboard"; import { truncateWithEllipsis } from "../../../../utils/text-wrap"; import { usePluginAppActions, usePluginTickerActions } from "../../../runtime"; import { usePlanAccess } from "../../shared/plan-access"; import { SignInWall } from "../auth-actions"; import { afterLayout, revealInScrollBox } from "../reveal-in-scroll-box"; import { ASKGSessionController, type ASKGControllerManifest } from "./controller"; import { createASKGRendererToolExecutor, loadASKGClientManifest, resolveToolPaneTarget, } from "./host"; import { askgConversationLabel, askgConversationListStore, } from "./conversation-store"; import { ConfirmDialog } from "../../../../components/ui/confirm-dialog"; import { useDialog, type PromptContext } from "../../../../ui/dialog"; import { subscribeASKGQuestions } from "./pending-question"; import { ASKGConversationSidebar } from "./sidebar"; import { activeTurn, canRetryASKGError, describeASKGError, describeToolStatus, formatCellValue, isTurnRunning, pendingConfirmation, rowSymbol, toolResultTables, type ASKGConversationState, type ASKGResultTable, type ASKGToolRow, type ASKGTurn, } from "./model"; import type { JsonValue } from "./protocol"; export const ASKG_PANE_ID = "askg"; const CLIENT_VERSION = "1"; const MIN_DETAIL_HEIGHT = 8; function clientKind(): "tui" | "desktop" | "web" { const target = getCurrentPluginTarget(); return target === "web" || target === "desktop" ? target : "tui"; } function tierLabel(row: ASKGToolRow): string | null { if (row.writeTier === "read") return null; if (row.writeTier === "ui-write") return "layout"; if (row.writeTier === "user-data") return "your data"; return "broker"; } function statusColor(row: ASKGToolRow): string { switch (row.status) { case "ok": return colors.textDim; case "partial": case "awaiting-confirmation": return colors.warning; case "error": case "timeout": return colors.negative; case "denied": case "cancelled": return colors.textMuted; default: return colors.textDim; } } function previewLines(preview: JsonValue | undefined): string[] { if (preview == null) return []; if (typeof preview === "string") return preview.split("\n"); if (typeof preview === "number" || typeof preview === "boolean") return [String(preview)]; if (Array.isArray(preview)) return preview.map((entry) => formatCellValue(entry)); return Object.entries(preview).map(([key, value]) => `${key}: ${formatCellValue(value)}`); } /** Undo a write; not `u`, which installs an app update whenever one is waiting. */ const UNDO_KEY = "z"; /** Upgrade from a Pro-only answer, on the key every Pro prompt uses. */ const UPGRADE_KEY = CLOUD_PLAN_KEY; function canUndo(row: ASKGToolRow): boolean { return !!row.undoToken && (!row.undo || row.undo.status === "available"); } function ToolTimelineRow({ row, width, selected, expanded, selectedRowRef, onSelect, onToggle, onUndo, }: { row: ASKGToolRow; width: number; selected: boolean; expanded: boolean; /** Takes the row while it is selected, so the transcript can scroll to it. */ selectedRowRef: (node: BoxRenderable | null) => void; onSelect: () => void; onToggle: () => void; onUndo: () => void; }) { const hasRows = row.result !== undefined; const marker = hasRows ? (expanded ? "▾" : "▸") : "·"; const tier = tierLabel(row); const status = describeToolStatus(row); const undoLabel = row.undo?.status === "running" ? "undoing…" : row.undo?.status === "done" ? "undone" : row.undo?.status === "failed" ? "undo failed" : row.undoToken ? "undo" : null; const trailing = ` ${status}${row.origin === "server" ? " · Gloom" : ""}`; const summaryWidth = Math.max( 6, width - marker.length - row.name.length - trailing.length - (tier ? tier.length + 3 : 0) - 4, ); return ( { onSelect(); onToggle(); }} > {row.argumentSummary ? ( {` ${truncateWithEllipsis(row.argumentSummary, summaryWidth)}`} ) : null} {tier ? {`${tier} `} : null} {row.status === "running" || row.status === "pending" ? ( ) : ( {status} )} {row.origin === "server" ? · Gloom : null} {undoLabel ? (