Starting an AI chat in the docked terminal is now a first-class…#412
Merged
Conversation
…I detection (#2325) * docs(open-knowledge): terminal-behaviors SPEC (audited, decisions locked) * [US-001] Reorder TERMINAL_CLI_IDS and add promptless CLI launch build path Reorder TERMINAL_CLI_IDS to claude,codex,opencode,cursor so the visible launch-row order matches the default-CLI auto-pick priority (one constant drives both). Consumer audit confirmed no site depends on the old positional order (display map, VISIBLE_CLIS, and parseStickyCliId find-by-match are all order-independent; no index access). Widen buildCliLaunchArgString's prompt param to string|null|undefined and add a promptless branch: an absent or empty prompt emits a bare <bin> (dropping the positional and any prompt-carrying flag such as OpenCode's --prompt), keeping only Claude's opted-in MCP pre-approval. This is the New chat launch shape. The non-empty prompted path stays byte-identical. * [US-002] Add resolveDefaultCli — shared default-CLI resolution New pure function packages/app/src/lib/default-cli-resolver.ts that both the New chat launch and the Ask X bubble default use, so every entry point agrees on the same CLI. Resolution order: 1. sticky parses to a CLI (parseStickyCliId) and that CLI is installed -> it; 2. else the first installed CLI by TERMINAL_CLI_IDS priority; 3. else claude (surfaces the existing Get Claude install nudge on PATH miss). Priority order is TERMINAL_CLI_IDS itself, so auto-pick order stays in lockstep with the visible launch-row order. An app-target sticky (a HandoffTarget id, not a terminal-cli sentinel) parses to null and falls through — New chat only ever launches a CLI. The installed map is Partial so a not-yet-resolved probe degrades to the claude fallback. Covered by 10 unit tests. * [US-003] Add batched CLI-install probe + renderer bridge exposure New desktop probe answering which of the four CLIs are on PATH, so the New chat default-CLI auto-pick can pick the first installed one. claude-readiness.ts: pure resolveCliInstalledMap(deps) -> Record<TerminalCli, boolean> (installed iff the login-shell command -v probe exits 0). Each entry routes through the existing resolveCliOnPath, so a flaky or rejected probe degrades that one entry to not-installed without crashing the batch; unknown collapses to false since defaulting must resolve to a concrete CLI. Covered by 3 unit tests over the exit-code to installed-map parsing. Renderer exposure co-located with cliPreflight: new cliInstalledMap() bridge method added to all three OkDesktopBridge copies (canonical bridge-contract plus the core and app mirrors, kept in lockstep for the m1-smoke drift catcher), the ok:terminal:cli-installed-map IPC channel, the preload invoke wiring, and a main handler backed by a 60s promise-cache so repeated New chat clicks share one probe batch instead of re-spawning four login shells each time. The stress fixture bridge fake gains the method to satisfy the contract. * [US-004] Wire EditorPane for New chat: terminalAvailable, promptless launch, installed-map Derive a single terminalAvailable = desktopBridge != null && desktopBridge.terminal != null and gate both the terminal host mount and the header terminal buttons on it, so a session-only bridge (no terminal surface) never renders a control that would throw. Widen TerminalLaunchIntent.prompt to string | null — null is a New chat launch. It flows through TerminalSessionsHost, TerminalGate, and TerminalPanel, which already passes it to the now-null-safe buildCliLaunchArgString. Fetch the installed-CLI map once on mount (capability-guarded like the existing getDockState restore, so a partial or older bridge skips the probe rather than throwing a synchronous not-a-function the .catch cannot intercept). launchNewChat resolves the CLI via resolveDefaultCli(sticky, installed) unless one is named, reveals the dock, and threads a promptless one-shot intent through the same nonce-dedup + hide-clear path as Open in terminal. Thread terminalAvailable / terminalVisible / onToggleTerminal / onNewChat to EditorHeader and the installed-CLI map to TerminalSessionsHost (the header buttons render in US-005, the tab-strip New chat in US-006). The EditorPane dom-test stub now models cliInstalledMap. * [US-005] Add New chat + show/hide terminal buttons to the editor header When the terminal is available (desktop bridge + terminal surface), the header now renders, after the presence badges and before the vertical separator, a New chat button and a show/hide-terminal toggle. Both are ghost icon buttons matching the existing Search control: an accessible aria-label, an aria-hidden icon, a tooltip, and the electron no-drag opt-out. New chat (MessageSquarePlus) launches the resolved default CLI promptless via the onNewChat handler. The toggle (Terminal) flips the dock visibility; its aria-label and tooltip name the action they perform, flipping between Show terminal and Hide terminal with dock state (the WAI-ARIA changing-label toggle pattern — no aria-pressed, which would double-announce state alongside a Show/Hide label). All copy routes through Lingui. Neither button renders on web or a bridge without a terminal surface. Covered by four DOM tests: button order, label flip, handler dispatch, and absence when the terminal is unavailable. * [US-006] Split the tab-strip + into New chat and New terminal tab The tab strip's single + button becomes two controls. New chat (keeps the plus icon) hugs the last tab and launches the default CLI promptless; a new New terminal tab button (SquareTerminal) sits in the trailing control group and opens a bare shell, the previous + behavior. Layout: TabsList drops flex-1 (keeps min-w-0 + overflow-x-auto so tabs still scroll internally) so New chat can sit immediately right of the last tab, outside the scroll container's fade mask. A flex-1 spacer then pushes the trailing group (New terminal tab, dock-toggle, collapse) to the far right. TerminalSessionsHost resolves the strip New chat's CLI from the sticky pick plus the installed-CLI map at click time (resolveDefaultCli) and opens a promptless session; New terminal tab keeps openSession(null). The launch nonce comes from a local monotonic ref — it is required by the intent type but only load-bearing for the EditorPane prop-channel dedup, so a direct open just needs a distinct value. Updates TerminalTabStrip's tests for the renamed/added controls and fixes the TerminalDock multi-session tests, which created bare-shell sessions through the old + and now go through New terminal tab. * [US-007] Default the Ask X bubble to the first-installed CLI on desktop On desktop, when the user has made no pick and has no sticky preference, the Ask X bubble now leads with the first-installed CLI (resolveDefaultCli; Claude if none) instead of the first installed app target — matching what New chat would launch. The composer fetches its own installed-CLI map from the terminal bridge (capability-guarded, main-cached) to drive this. The default is derived, never saved, so a freshly-installed CLI can take over mid-session. An explicit CLI pick still launches that CLI regardless of install state (the existing no-probe contract for the picker), a sticky app-target pick is still honored, and the web host keeps the app-target default. Two DOM tests cover the desktop no-pick default: Codex when Claude is absent, and the Claude CLI fallback when nothing is installed. * [US-008] Lead the Open with AI dropdowns with the Terminal section Flip the two sections in all five Open with AI surfaces so Terminal renders before Desktop, leading with the in-app terminal as the first-class path: OpenInAgentMenu, OpenInAgentContextSubmenu, OpenInAgentEmptySpaceSubmenu, AgentSplitButton, and the empty-state CreatePromptComposer. Each swap moves the Terminal block above the Desktop block and relocates the inter-section separator to sit after Terminal (gated on the Terminal section being present) instead of before it. Option lists, labels, test ids, and accessible names are otherwise unchanged; the Command Palette (no Terminal section) is untouched. Adds Terminal-precedes-Desktop DOM-order assertions to the four existing section grouping tests and a new order assertion on the split-button picker in BottomComposer's tests. No new user-facing strings. * [US-009] Regenerate Lingui catalogs for the new terminal strings Runs bun run i18n to extract and compile the en + pseudo catalogs for the strings added by the terminal-behaviors work: New chat, Show terminal, Hide terminal, and New terminal tab. The obsolete New terminal tab-strip tooltip (renamed to New chat / New terminal tab) is dropped by the extractor. Also strips spec-marker citations from a handful of test comments (that linkage belongs in the commit, not the source). * feat(open-knowledge): consolidate header chat control to Open/Close toggle + collapse doc-panel on chat open - Replace the two header buttons (New chat + show/hide terminal) with one stateful toggle: Open chat -> Close chat, reflecting dock visibility. - Opening the chat (header toggle, file-tree right-click, bottom composer) collapses the right doc-panel so the chat gets the full column; deferred a frame so it lands after the terminal column mounts, and it never touches the panel on close. Panel stays user-reopenable. - Report session presence from the host so the toggle bootstraps a default-CLI chat only when none exists. * docs(open-knowledge): record D9/D10 header-chat amendments (supersede D3/D5) * fix(open-knowledge): review fixes — honor sticky CLI pre-probe, transient/dock-guarded doc-panel collapse - resolveDefaultCli: honor a sticky CLI unless KNOWN-absent (false), so a New chat click during the cold-start probe window keeps the remembered pick instead of dropping to claude — matching the Ask-X bubble. Priority auto-pick now requires a positive install (true). - Doc-panel collapse on chat open: stop writing the persistent 'collapsed' pin (it left the panel collapsed across docs + restarts after one chat/ask-AI); now transient. Guard on right-dock (a bottom-docked chat has no right column to clear). Cancel the deferred rAF on cleanup/re-request. * chore(open-knowledge): changeset for terminal chat controls * fix(open-knowledge): warn (not error) for recoverable CLI-install probe failure The cliInstalledMap probe failure degrades to 'none installed' → claude default, so it's recoverable. Log at console.warn with the [terminal] namespace to match the terminal surface's probe-failure convention (TerminalPanel, ClaudeReadinessBanner) rather than error severity + an [ask-ai] namespace that breaks [terminal] triage. * refactor(open-knowledge): extract shared useInstalledClis hook; tidy bridge JSDoc Review feedback: the cliInstalledMap probe effect (state + capability guard + cancel-flag cleanup + error log) was duplicated byte-for-byte in EditorPane and BottomComposer, both feeding the same resolveDefaultCli. Extract a shared useInstalledClis() hook (matching the useGitSyncStatus bridge-hook precedent) so both are a single call and a third consumer reuses it. Strip the lone JSDoc on cliInstalledMap in the core desktop-bridge mirror to match its bare siblings. * test(open-knowledge): pin useInstalledClis contract; evict CLI-map cache on probe rejection Review follow-ups: - Add use-installed-clis.dom.test.tsx covering the hook's three behaviors: the capability guard (web-host / partial-bridge → empty map, no throw), the resolved-map success path, and the .catch degradation (rejected probe → empty map + warn). - resolveTerminalCliInstalledMap now evicts the TTL cache on a rejected probe (defensive — the batch never rejects today) so a transient failure becomes an immediate retry rather than a 60s-cached rejection. --------- GitOrigin-RevId: c4b03c63913e4ec4c42dc36d3dd2d2a01c23ac99
Contributor
There was a problem hiding this comment.
Automated approval from agents-private public-mirror-sync (run: https://github.com/inkeep/agents-private/actions/runs/28500970708). Source of truth is the monorepo; direct edits on inkeep/open-knowledge are overwritten on next sync.
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Starting an AI chat in the docked terminal is now a first-class action. A single Open chat / Close chat button in the editor header reveals the terminal (collapsing the right document panel so the chat gets the full column) and starts your default CLI when no session is open, or hides it when it's showing. The terminal tab strip's + is now New chat (launches your default CLI, next to the last tab) alongside a separate New terminal tab button for a plain shell.
Your default CLI is detected from what's installed — priority Claude → Codex → OpenCode → Cursor — and the one you pick anywhere sticks for next time; if none are installed it falls back to Claude with the existing install prompt. The "Ask AI" bubble and the file-tree / composer "Open in terminal" actions use the same default and also clear the document panel for the chat. Across the app, the Terminal option now leads the "Open with AI" menus ahead of Desktop.