dsh-blue/blue--packages-interaction1

@dsh-blue/blue-interaction

Blue terminal UI interaction layer: input editor, slash commands, approval and user-question overlays over dsh-blue-core

包名
@dsh-blue/blue-interaction
版本
0.1.0-rc.1
最近更新
2026年8月21日

安装

此插件尚未提供可验证的 bundle,或兼容性检查未通过。请先阅读仓库说明。 阅读完整 README ↗

@dsh-blue/blue-interaction

English | 中文

Blue terminal UI interaction layer over dsh-blue-core: the bottom input editor with slash-command dispatch, the built-in /quit, /resume, /new, /fork, /sessions, /help, /theme, /model, /effort, /provider, /yolo, /status, /context, /version, /export, and /copy commands, the ctx.userQuestions overlay provider (one tabbed questionnaire per request), and the interactive four-choice approval/request answerer (with the S24a yolo auto-approve stance and the Shift+Tab three-state cycle — normal → plan → yolo — over it). The package imports no pi-tui: the main editor is the pi-tui Editor behind ctx.blueComponents.createEditor (multi-line, history, kill-ring, undo, and paste markers built in), single-select lists come from ctx.blueComponents.createSelectList, and BlueSelect survives only as the package-internal multi-select list (pi-tui ships no multi-select). Every dialog renders through the core framePanel chrome (S12) as a kimi pull-up panel, and since the S16 dogfood (D30) each dialog takes over the editor's dock slot through mountEditorReplacement instead of floating an overlay: the editor leaves the tree for the panel's lifetime (its buffer, draft, and history survive in the component), only the two-row footer shell remains below the open panel, and dismissing restores the editor with focus. The stack emits the core event 'blue/editor-slot-swapped' on each empty↔occupied transition (nested panels re-emit nothing, and unloading with a panel open releases the occupancy) — blue-pane-activity hides its row while any panel is up and blue-pane-btw re-asserts its editor splice around the editor's absence. BluePanel is the package's sole public component export; dialogs resolve keys through ctx.blueKeymap and style through ctx.blueTheme.

Plugins

The single entry plugin blue-interaction mounts five sub-plugins; every registration is effect-bound, so unloading the fiber reverts every contribution (HMR-safe: the provider/command/key registrations disappear with the fiber).

  • blue-interaction-keys — registers the shared key-action batch (blue.interaction.submit/cancel/move-up/move-down/toggle) on ctx.blueKeymap as one validated unit, plus the editor-context actions blue.interaction.interrupt (Ctrl-C) and blue.interaction.steer (Ctrl-S). The latter two carry no handler — they stay contextual and are resolved by the main editor's onKey hook, never by the global dispatcher, so they cannot steal a key from an overlay's escape=cancel route. The multi-select BlueSelect resolves keys against these actions and generates footer hints with getKeys; text-editing keys are owned by the pi-tui Editor itself.
  • blue-input — mounts the focused bottom editor: the pi-tui Editor from ctx.blueComponents.createEditor({ paddingX: 4 }) with the > prompt symbol set at mount (the padding and symbol feed the core adapter's rounded-box chrome), with the hint line as a separate HintLine component pinned below. The hint line carries its transient tiers only — one-shot notices and slash-command discovery in muted — and renders zero rows otherwise (the S15 dogfood verdict retired the persistent key-affordance row: kimi teaches affordances through the footer's rotating tips, and the tips pool already covers every fragment the row carried). Slash-prefixed input recolors the editor frame primary through setBorderColor; any other text restores the neutral border. Submit parses the line with parseCommand: a bare /permission line — but only while the permission-preset service is composed — opens the preset picker instead of dispatching (see Shared panels); any other slash command dispatches through ctx.commands.execute (never reaching the model; success/error text flashes in the hint line), anything else becomes a createUserMessage({ source: { kind: 'user' } }) follow-up on the current agent — queued by the harness inbox when the agent is running. The onSubmit callback argument already carries the paste-expanded, trimmed text. Slash-prefixed input shows up to three fuzzy-matching commands from ctx.commands.list (the same slash-filter helper the dropdown uses) as a discovery hint. The mounted editor and its submit router are published through a package-local shared ref so blue-editor-plus can layer input modes and autocomplete over the same component. The editor's onKey pre-dispatch hook resolves the editor-context key chain: Escape lets a showing autocomplete popup keep the key, then closes the side-question pane when one is docked above the editor (the draft survives), then clears the draft, then interrupts a running agent; Ctrl-C clears the draft, then interrupts a running agent, and a second press within 1 s exits through the launcher-owned appExit(0) (a single press flashes the double-press hint in the hint line); Ctrl-S steers a non-empty draft into the current turn and clears the buffer; Shift-Tab cycles the session mode (normal → plan → yolo, one explicit command per step, also live in bash mode — the input mode and the session mode are orthogonal); Up/Down with an empty buffer scroll the docked side-question pane ahead of the queue recall. The unsubmitted draft, the input mode, and the prompt history are mirrored into a module-level stash (src/draft-stash.ts), so the theme-swap reload that rebuilds this fiber restores all three into the fresh editor (text via setText, history replayed through addToHistory — a /theme submission would otherwise vanish from Up-recall the moment its own command rebuilds the editor); submit and steer clear the draft while the history persists.
  • blue-commands — registers /quit (requests exit through the launcher-owned ctx.appExit; an error result when the launcher provided none; the aliases /q and /exit answer to it through the src/command-meta.ts registry — kimi-style, aliases are not separate registrations, the input layer rewrites an alias line to /quit before dispatch, so discovery surfaces stay canonical-only), /sessions [] (bare opens the persisted-session picker; an id argument emits blue/request-resume directly — /resume is the command's alias since the S24a dogfood merge, rewritten by the input layer), /new (emits blue/request-new), /fork (emits blue/request-fork; an error result while the current agent is not idle), /sessions (lists persisted sessions newest-first from sessionPersistence.list in a framed picker — the shared SelectListPanel rows (src/select-list.ts, S24b) carry a pointer and the live session a ← current badge, picking another emits blue/request-resume, and the outcome flashes through the shared editor's notice seam), /help (the framed, scrollable HelpOverlay: two-column sections of the registered commands from ctx.commands.list (labels in primary; aliases join the label as /quit (/q, /exit)) and the key bindings from ctx.blueKeymap.list() (labels in warning), a showing 1-N of M scroll window with arrows and PageUp/PageDown; Escape, Enter, or q closes it), and /theme (swaps the live theme provider: /theme lists the known themes, marking the current one; /theme dark|light|auto switches to a built-in palette; /theme custom [dark|light] mounts a file-backed palette; the switch disposes the current provider's fibers through ctx.registry.delete and mounts the replacement, and a failed mount restores the built-in dark palette). The model family lives in src/model-commands.ts behind the same registration discipline: /model (no argument opens the ModelPanel picker over ctx.llm's advertised catalog — the kimi tabbed-selector geometry: the title with its (type to search) suffix, the key-hint row beneath it, a provider tab strip (All + each provider, Tab/Shift-Tab toggles) on multi-provider catalogs, a live fuzzy query over Provider Name/model labels (printable keys grow it, Backspace shrinks it, Escape clears it before cancelling — the kimi rule), rows carrying the name column, a muted provider, a · ctx k context suffix, and the ← current badge, the cache-warning row once the session has a logged request header, and the kimi footer thinking-segment control adjusting the highlighted model's effort draft with ←/→; Enter switches and persists the new default through agentDefaultModel.saveSelection, Alt+S switches session-only, an argument switches straight to that model id with the live provider breaking an ambiguity), and /effort (the EffortPanel horizontal segments — Default restores the provider's own default by omitting the effort — plus direct low/high/default arguments listing the valid ids on a miss; /thinking is an alias). Both write through the app layer's blueSession.modelRef seam (a write routes the next step's request; the default service persists the choice for future sessions), resolve the llm/default-model services lazily through ctx.get, and report through the kimi five-state notice family in src/model-commands.ts (Switched to … · thinking … / Thinking set to … / Already using …, with · session only and — failed to save default: … tails). /provider completes the family: the shared SelectListPanel lists the configured routes only (badge ← current; Enter opens the edit flow — a rounded form pre-filled with the stored display name, base URL (hint following the profile's protocol), and a masked API key whose empty field keeps the stored value; Ctrl+D deletes the route after a typed y confirmation, and save keeps every untouched field while normalizing the base URL by protocol), with the trailing + Add provider CTA row (a plain list row since S24b — it windows and wraps like any other, Enter routes to the wizard). The wizard (src/provider-add.ts, the kimi promise-per-panel flow over the slot stack) offers two branches: adopting a known vendor (optional baseURL override plus a masked key), or declaring a custom endpoint (route id, one of the three baseURL protocols, baseURL, masked key — discovery feeds a multi-select adopt step, and the model step is listing-only: discovery must answer or the form re-opens with the classified reason (full cause chain) in its error-red line — fix the URL or key and resubmit, only Escape leaves; manual model entry is disabled for now, documented, and may return behind a flag if a listing-less gateway ever appears). The models step itself is metadata-aware: the adopted ids are matched against the models.dev catalog (src/models-dev.ts, the kimi catalog-fetch port — https://models.dev/api.json with a 10-minute cache and a 4-second best-effort timeout, offline falls back to the manual pass) filling each match's context window and pi-ai-gated effort levels (reasoning_options minus the off tier; a catalog-marked non-reasoning model writes reasoningEfforts: false), and a fully described set skips the optional Model defaults form outright — the form remains for the gap (one context window for undescribed models, one effort set), its subtitle reporting whether the catalog was reached. The Base URL hint follows the protocol's path convention (anthropic-messages: no trailing /v1, the transport appends /v1/messages; the OpenAI pair: include /v1), the discovery probe walks the entered base plus its /v1 and bare variants until one answers, and the written profile base is normalized the same way — an anthropic base never keeps a trailing /v1 (the POST /v1/v1/messages dogfood), an OpenAI base that only listed under the /v1 candidate carries it. The commit is the harness Web Models page's sequence — settings.mutate writes the llm-pi-ai provider profile first (validated at the write by the registering plugin's schema, against the revision read from describe()), credentials.set stores the key under the conventional _API_KEY ref second — and the flow then opens the scoped picker over the fresh route (Escape keeps the provider with no default change). The form surface is src/form-panel.ts (the kimi ApiKeyInput dialog geometry over Blue editors): a rounded box with the bold title, subtitle-or-error line, per-field labels and > input rows (bullets when masked, a trailing cursor block on the active field), and the key footer inside the box; Tab/↑/↓ route fields, Enter advances and submits from the last field, the in-panel error line never closes the panel, and a pasted key is never echoed. The custom branch's model step is listing-only (see above). The mode family lives in src/mode-commands.ts behind the same discipline: /yolo [on|off] (alias /yes) toggles Blue-side auto-approval of tool calls — the harness approval policy stays 'ask' because 'never' resolves asks as rejected before the waterfall dispatch, so the answerer in src/approval-plugin.ts is the auto-approve surface (an aborted request still settles 'cancelled'; questions keep popping — kimi semantics). The live flag and its persistence live in src/mode-state.ts: a per-agent WeakMap plus the pure command/run fold (last yolo run with recorded args decides — off after trim turns it off, anything else on; Blue owns no session-event vocabulary, and the bare toggle turning OFF re-dispatches the explicit /yolo off so the log's last record disambiguates). setupModeTracking restores the flag on session switches (resume/fork fold back, /new starts clean) and enforces the plan/yolo exclusivity: a committed plan entry while yolo is on dispatches /yolo off from a microtask (session.append rejects re-entry and its observers run inside the append). cycleMode answers the contextual shift+tab action (blue.interaction.cycle-mode, matched in blue-input's editor key chain): it cycles normal → plan → yolo → normal — two states when dsh-plan-mode is not composed — dispatching exactly one explicit command per transition (/plan and /plan off belong upstream), with the result text as the single notice. The session-info family (S25) lives in src/session-commands.ts behind the same discipline: /status opens the read-only InfoPanel over the session header (id, cwd, a fixed UTC creation stamp, whole-log turns/steps, the agent's lifecycle state), the live model selection (blueSession.modelRef, falling back to the latest logged request header), the Blue/harness version line, and the context-window bar; /context (named for CC's context panel, not kimi's /usage, by the 2026-08-21 ruling) lists the four disjoint provider token buckets (uncached input, cache read, cache write, output — reasoning included per the provider contract) plus their total, the same context bar, and — projection-only — the CC-style composition section (the contextBreakdown unit's heuristic system/tools/message tokens as a stacked █▓▒░ bar over the window with per-component rows and the free remainder; heading carries the heuristic caveat, sub-half-percent shares read an honest 0%, and a host without the seam omits the section); /version opens the read-only InfoPanel over the two release lines — Blue's own number (BLUE_VERSION, the first release the website advertises) and the independent harness pin line (HARNESS_LINE, rc.7) — version-only, no model; unlike the status family it needs no live session, so the panel opens on an empty slot too. The numbers come from the session-projection seam (ctx.sessionProjections.snapshot — the base composition's dsh-token-meter/dsh-session-stats units fold the whole durable log, so the figures are replay-correct across resumes); src/usage.ts is the thin read layer with a pure assistant/* usage fold as the degraded host's fallback, and Blue owns no accumulator of its own.

The ./mode-status subpath plugin (blue-status-mode, inject ['blueStatus', 'blueScreen', 'blueTheme', 'blueComponents']) is the session-mode foo