nastech-tui 0.0.1
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/.prettierrc +11 -0
- package/README.md +346 -0
- package/eslint.config.mjs +111 -0
- package/package.json +51 -0
- package/packages/nastech-ink/ambient.d.ts +83 -0
- package/packages/nastech-ink/index.d.ts +40 -0
- package/packages/nastech-ink/index.js +1 -0
- package/packages/nastech-ink/nastech-ink/ambient.d.ts +83 -0
- package/packages/nastech-ink/nastech-ink/index.d.ts +40 -0
- package/packages/nastech-ink/nastech-ink/index.js +1 -0
- package/packages/nastech-ink/nastech-ink/package.json +54 -0
- package/packages/nastech-ink/nastech-ink/src/bootstrap/state.ts +9 -0
- package/packages/nastech-ink/nastech-ink/src/entry-exports.ts +32 -0
- package/packages/nastech-ink/nastech-ink/src/hooks/use-stderr.ts +15 -0
- package/packages/nastech-ink/nastech-ink/src/hooks/use-stdout.ts +15 -0
- package/packages/nastech-ink/nastech-ink/src/ink/Ansi.tsx +435 -0
- package/packages/nastech-ink/nastech-ink/src/ink/app-mouse.test.ts +123 -0
- package/packages/nastech-ink/nastech-ink/src/ink/bidi.ts +145 -0
- package/packages/nastech-ink/nastech-ink/src/ink/cache-eviction.ts +45 -0
- package/packages/nastech-ink/nastech-ink/src/ink/clearTerminal.ts +68 -0
- package/packages/nastech-ink/nastech-ink/src/ink/colorize.test.ts +60 -0
- package/packages/nastech-ink/nastech-ink/src/ink/colorize.ts +277 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/App.tsx +830 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/AppContext.ts +20 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Box.tsx +294 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Button.tsx +236 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Link.tsx +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Newline.tsx +43 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Spacer.tsx +23 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/StdinContext.ts +25 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Text.test.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Text.tsx +336 -0
- package/packages/nastech-ink/nastech-ink/src/ink/constants.ts +6 -0
- package/packages/nastech-ink/nastech-ink/src/ink/cursor.ts +5 -0
- package/packages/nastech-ink/nastech-ink/src/ink/devtools.ts +2 -0
- package/packages/nastech-ink/nastech-ink/src/ink/dom.ts +495 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/click-event.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/dispatcher.ts +242 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/emitter.ts +40 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/event-handlers.ts +84 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/event.ts +11 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/focus-event.ts +18 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/input-event.ts +176 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/mouse-event.ts +18 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/paste-event.ts +10 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/resize-event.ts +12 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-event.ts +107 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
- package/packages/nastech-ink/nastech-ink/src/ink/focus.ts +219 -0
- package/packages/nastech-ink/nastech-ink/src/ink/frame.ts +124 -0
- package/packages/nastech-ink/nastech-ink/src/ink/get-max-width.ts +27 -0
- package/packages/nastech-ink/nastech-ink/src/ink/global.d.ts +1 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hit-test.test.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hit-test.ts +224 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-app.ts +9 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-input.ts +95 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
- package/packages/nastech-ink/package.json +57 -0
- package/packages/nastech-ink/src/bootstrap/state.ts +9 -0
- package/packages/nastech-ink/src/entry-exports.ts +32 -0
- package/packages/nastech-ink/src/hooks/use-stderr.ts +15 -0
- package/packages/nastech-ink/src/hooks/use-stdout.ts +15 -0
- package/packages/nastech-ink/src/ink/Ansi.tsx +435 -0
- package/packages/nastech-ink/src/ink/app-mouse.test.ts +123 -0
- package/packages/nastech-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/packages/nastech-ink/src/ink/bidi.ts +145 -0
- package/packages/nastech-ink/src/ink/cache-eviction.ts +45 -0
- package/packages/nastech-ink/src/ink/clearTerminal.ts +68 -0
- package/packages/nastech-ink/src/ink/colorize.test.ts +60 -0
- package/packages/nastech-ink/src/ink/colorize.ts +277 -0
- package/packages/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
- package/packages/nastech-ink/src/ink/components/App.tsx +855 -0
- package/packages/nastech-ink/src/ink/components/AppContext.ts +20 -0
- package/packages/nastech-ink/src/ink/components/Box.tsx +294 -0
- package/packages/nastech-ink/src/ink/components/Button.tsx +236 -0
- package/packages/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
- package/packages/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
- package/packages/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
- package/packages/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
- package/packages/nastech-ink/src/ink/components/Link.tsx +38 -0
- package/packages/nastech-ink/src/ink/components/Newline.tsx +43 -0
- package/packages/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
- package/packages/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
- package/packages/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
- package/packages/nastech-ink/src/ink/components/Spacer.tsx +23 -0
- package/packages/nastech-ink/src/ink/components/StdinContext.ts +25 -0
- package/packages/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
- package/packages/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
- package/packages/nastech-ink/src/ink/components/Text.test.ts +38 -0
- package/packages/nastech-ink/src/ink/components/Text.tsx +336 -0
- package/packages/nastech-ink/src/ink/constants.ts +6 -0
- package/packages/nastech-ink/src/ink/cursor.ts +5 -0
- package/packages/nastech-ink/src/ink/devtools.ts +2 -0
- package/packages/nastech-ink/src/ink/dom.ts +495 -0
- package/packages/nastech-ink/src/ink/events/click-event.ts +38 -0
- package/packages/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
- package/packages/nastech-ink/src/ink/events/dispatcher.ts +242 -0
- package/packages/nastech-ink/src/ink/events/emitter.ts +40 -0
- package/packages/nastech-ink/src/ink/events/event-handlers.ts +84 -0
- package/packages/nastech-ink/src/ink/events/event.ts +11 -0
- package/packages/nastech-ink/src/ink/events/focus-event.ts +18 -0
- package/packages/nastech-ink/src/ink/events/input-event.ts +176 -0
- package/packages/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
- package/packages/nastech-ink/src/ink/events/mouse-event.ts +18 -0
- package/packages/nastech-ink/src/ink/events/paste-event.ts +10 -0
- package/packages/nastech-ink/src/ink/events/resize-event.ts +12 -0
- package/packages/nastech-ink/src/ink/events/terminal-event.ts +107 -0
- package/packages/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
- package/packages/nastech-ink/src/ink/focus.ts +219 -0
- package/packages/nastech-ink/src/ink/frame.ts +124 -0
- package/packages/nastech-ink/src/ink/get-max-width.ts +27 -0
- package/packages/nastech-ink/src/ink/global.d.ts +1 -0
- package/packages/nastech-ink/src/ink/hit-test.test.ts +38 -0
- package/packages/nastech-ink/src/ink/hit-test.ts +224 -0
- package/packages/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
- package/packages/nastech-ink/src/ink/hooks/use-app.ts +9 -0
- package/packages/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
- package/packages/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
- package/packages/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
- package/packages/nastech-ink/src/ink/hooks/use-input.ts +95 -0
- package/packages/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
- package/packages/nastech-ink/src/ink/hooks/use-search-highlight.ts +56 -0
- package/packages/nastech-ink/src/ink/hooks/use-selection.ts +101 -0
- package/packages/nastech-ink/src/ink/hooks/use-stdin.ts +9 -0
- package/packages/nastech-ink/src/ink/hooks/use-tab-status.ts +71 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-focus.ts +18 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-title.ts +34 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-viewport.ts +100 -0
- package/packages/nastech-ink/src/ink/hyperlinkHover.ts +52 -0
- package/packages/nastech-ink/src/ink/ink-cursor-advance.test.ts +234 -0
- package/packages/nastech-ink/src/ink/ink-resize.test.ts +50 -0
- package/packages/nastech-ink/src/ink/ink.tsx +2705 -0
- package/packages/nastech-ink/src/ink/instances.ts +10 -0
- package/packages/nastech-ink/src/ink/layout/engine.ts +6 -0
- package/packages/nastech-ink/src/ink/layout/geometry.ts +98 -0
- package/packages/nastech-ink/src/ink/layout/node.ts +145 -0
- package/packages/nastech-ink/src/ink/layout/yoga.ts +313 -0
- package/packages/nastech-ink/src/ink/line-width-cache.ts +38 -0
- package/packages/nastech-ink/src/ink/log-update.test.ts +223 -0
- package/packages/nastech-ink/src/ink/log-update.ts +752 -0
- package/packages/nastech-ink/src/ink/lru.ts +14 -0
- package/packages/nastech-ink/src/ink/measure-element.ts +23 -0
- package/packages/nastech-ink/src/ink/measure-text.ts +50 -0
- package/packages/nastech-ink/src/ink/node-cache.ts +53 -0
- package/packages/nastech-ink/src/ink/optimizer.ts +99 -0
- package/packages/nastech-ink/src/ink/output.ts +845 -0
- package/packages/nastech-ink/src/ink/parse-keypress.test.ts +133 -0
- package/packages/nastech-ink/src/ink/parse-keypress.ts +848 -0
- package/packages/nastech-ink/src/ink/reconciler.ts +382 -0
- package/packages/nastech-ink/src/ink/render-border.ts +206 -0
- package/packages/nastech-ink/src/ink/render-node-to-output.ts +1582 -0
- package/packages/nastech-ink/src/ink/render-to-screen.ts +236 -0
- package/packages/nastech-ink/src/ink/renderer.ts +169 -0
- package/packages/nastech-ink/src/ink/root.ts +204 -0
- package/packages/nastech-ink/src/ink/screen.ts +1590 -0
- package/packages/nastech-ink/src/ink/searchHighlight.ts +91 -0
- package/packages/nastech-ink/src/ink/selection.test.ts +82 -0
- package/packages/nastech-ink/src/ink/selection.ts +1143 -0
- package/packages/nastech-ink/src/ink/squash-text-nodes.ts +74 -0
- package/packages/nastech-ink/src/ink/stringWidth.ts +341 -0
- package/packages/nastech-ink/src/ink/styles.ts +750 -0
- package/packages/nastech-ink/src/ink/supports-hyperlinks.ts +51 -0
- package/packages/nastech-ink/src/ink/tabstops.ts +44 -0
- package/packages/nastech-ink/src/ink/terminal-focus-state.ts +52 -0
- package/packages/nastech-ink/src/ink/terminal-querier.ts +222 -0
- package/packages/nastech-ink/src/ink/terminal.test.ts +15 -0
- package/packages/nastech-ink/src/ink/terminal.ts +299 -0
- package/packages/nastech-ink/src/ink/termio/ansi.ts +75 -0
- package/packages/nastech-ink/src/ink/termio/csi.ts +334 -0
- package/packages/nastech-ink/src/ink/termio/dec.ts +99 -0
- package/packages/nastech-ink/src/ink/termio/esc.ts +69 -0
- package/packages/nastech-ink/src/ink/termio/osc.test.ts +191 -0
- package/packages/nastech-ink/src/ink/termio/osc.ts +724 -0
- package/packages/nastech-ink/src/ink/termio/parser.ts +467 -0
- package/packages/nastech-ink/src/ink/termio/sgr.ts +362 -0
- package/packages/nastech-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/packages/nastech-ink/src/ink/termio/tokenize.ts +350 -0
- package/packages/nastech-ink/src/ink/termio/types.ts +230 -0
- package/packages/nastech-ink/src/ink/termio.ts +42 -0
- package/packages/nastech-ink/src/ink/useTerminalNotification.ts +110 -0
- package/packages/nastech-ink/src/ink/warn.ts +15 -0
- package/packages/nastech-ink/src/ink/widest-line.ts +22 -0
- package/packages/nastech-ink/src/ink/wrap-text.test.ts +17 -0
- package/packages/nastech-ink/src/ink/wrap-text.ts +144 -0
- package/packages/nastech-ink/src/ink/wrapAnsi.ts +13 -0
- package/packages/nastech-ink/src/native-ts/yoga-layout/enums.ts +112 -0
- package/packages/nastech-ink/src/native-ts/yoga-layout/index.ts +2326 -0
- package/packages/nastech-ink/src/utils/debug.ts +6 -0
- package/packages/nastech-ink/src/utils/earlyInput.ts +131 -0
- package/packages/nastech-ink/src/utils/env.ts +66 -0
- package/packages/nastech-ink/src/utils/envUtils.ts +13 -0
- package/packages/nastech-ink/src/utils/execFileNoThrow.test.ts +146 -0
- package/packages/nastech-ink/src/utils/execFileNoThrow.ts +115 -0
- package/packages/nastech-ink/src/utils/fullscreen.ts +3 -0
- package/packages/nastech-ink/src/utils/intl.ts +87 -0
- package/packages/nastech-ink/src/utils/log.ts +7 -0
- package/packages/nastech-ink/src/utils/semver.ts +57 -0
- package/packages/nastech-ink/src/utils/sliceAnsi.ts +106 -0
- package/packages/nastech-ink/text-input.d.ts +2 -0
- package/packages/nastech-ink/text-input.js +1 -0
- package/scripts/build.mjs +61 -0
- package/scripts/profile-tui.mjs +121 -0
- package/src/__tests__/activeSessionSwitcher.test.ts +157 -0
- package/src/__tests__/appChromeStatusRule.test.tsx +84 -0
- package/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/src/__tests__/approvalAction.test.ts +50 -0
- package/src/__tests__/asCommandDispatch.test.ts +27 -0
- package/src/__tests__/blockLayout.test.ts +122 -0
- package/src/__tests__/clipboard.test.ts +369 -0
- package/src/__tests__/constants.test.ts +53 -0
- package/src/__tests__/createGatewayEventHandler.test.ts +1091 -0
- package/src/__tests__/createSlashHandler.test.ts +822 -0
- package/src/__tests__/creditsCommand.test.ts +144 -0
- package/src/__tests__/cursorDriftRegression.test.ts +114 -0
- package/src/__tests__/details.test.ts +115 -0
- package/src/__tests__/emoji.test.ts +64 -0
- package/src/__tests__/externalLink.test.ts +144 -0
- package/src/__tests__/forceTruecolor.test.ts +191 -0
- package/src/__tests__/gatewayClient.test.ts +394 -0
- package/src/__tests__/gatewayRecovery.test.ts +47 -0
- package/src/__tests__/markdown.test.ts +331 -0
- package/src/__tests__/mathUnicode.test.ts +293 -0
- package/src/__tests__/memoryMonitor.test.ts +102 -0
- package/src/__tests__/messageLine.test.ts +19 -0
- package/src/__tests__/messages.test.ts +92 -0
- package/src/__tests__/orchestratorPromptSession.test.ts +64 -0
- package/src/__tests__/osc52.test.ts +67 -0
- package/src/__tests__/parentLog.test.ts +75 -0
- package/src/__tests__/paths.test.ts +70 -0
- package/src/__tests__/platform.test.ts +556 -0
- package/src/__tests__/precisionWheel.test.ts +44 -0
- package/src/__tests__/prompt.test.ts +31 -0
- package/src/__tests__/providers.test.ts +65 -0
- package/src/__tests__/reasoning.test.ts +76 -0
- package/src/__tests__/rpc.test.ts +27 -0
- package/src/__tests__/scroll.test.ts +99 -0
- package/src/__tests__/slashParity.test.ts +123 -0
- package/src/__tests__/spawnHistoryStore.test.ts +46 -0
- package/src/__tests__/stateIsolation.test.ts +46 -0
- package/src/__tests__/statusBarTicker.test.ts +18 -0
- package/src/__tests__/statusRule.test.ts +32 -0
- package/src/__tests__/streamingMarkdown.test.ts +121 -0
- package/src/__tests__/subagentTree.test.ts +407 -0
- package/src/__tests__/syntax.test.ts +45 -0
- package/src/__tests__/terminalModes.test.ts +39 -0
- package/src/__tests__/terminalParity.test.ts +77 -0
- package/src/__tests__/terminalSetup.test.ts +386 -0
- package/src/__tests__/termux.test.ts +35 -0
- package/src/__tests__/termuxComposerLayout.test.ts +40 -0
- package/src/__tests__/text.test.ts +233 -0
- package/src/__tests__/textInputBurstInput.test.ts +40 -0
- package/src/__tests__/textInputCursorSourceOfTruth.test.ts +50 -0
- package/src/__tests__/textInputFastEcho.test.ts +200 -0
- package/src/__tests__/textInputLineNav.test.ts +55 -0
- package/src/__tests__/textInputPassThrough.test.ts +59 -0
- package/src/__tests__/textInputRightClick.test.ts +48 -0
- package/src/__tests__/textInputWrap.test.ts +151 -0
- package/src/__tests__/theme.test.ts +311 -0
- package/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/src/__tests__/turnStore.test.ts +66 -0
- package/src/__tests__/useCompletion.test.ts +35 -0
- package/src/__tests__/useComposerState.test.ts +59 -0
- package/src/__tests__/useConfigSync.test.ts +460 -0
- package/src/__tests__/useInputHandlers.test.ts +77 -0
- package/src/__tests__/useQueue.test.ts +28 -0
- package/src/__tests__/useSessionLifecycle.test.ts +60 -0
- package/src/__tests__/useVirtualHistoryHeights.test.ts +39 -0
- package/src/__tests__/viewport.test.ts +58 -0
- package/src/__tests__/viewportStore.test.ts +85 -0
- package/src/__tests__/virtualHeights.test.ts +96 -0
- package/src/__tests__/virtualHistoryClamp.test.ts +19 -0
- package/src/__tests__/virtualHistoryOffsetCache.test.ts +282 -0
- package/src/__tests__/wheelAccel.test.ts +138 -0
- package/src/app/createGatewayEventHandler.ts +833 -0
- package/src/app/createSlashHandler.ts +130 -0
- package/src/app/delegationStore.ts +77 -0
- package/src/app/gatewayContext.tsx +19 -0
- package/src/app/gatewayRecovery.ts +35 -0
- package/src/app/inputSelectionStore.ts +15 -0
- package/src/app/interfaces.ts +394 -0
- package/src/app/overlayStore.ts +53 -0
- package/src/app/scroll.ts +71 -0
- package/src/app/setupHandoff.ts +54 -0
- package/src/app/slash/commands/core.ts +648 -0
- package/src/app/slash/commands/credits.ts +57 -0
- package/src/app/slash/commands/debug.ts +48 -0
- package/src/app/slash/commands/ops.ts +717 -0
- package/src/app/slash/commands/session.ts +554 -0
- package/src/app/slash/commands/setup.ts +20 -0
- package/src/app/slash/registry.ts +20 -0
- package/src/app/slash/types.ts +21 -0
- package/src/app/spawnHistoryStore.ts +159 -0
- package/src/app/turnController.ts +866 -0
- package/src/app/turnStore.ts +85 -0
- package/src/app/uiStore.ts +44 -0
- package/src/app/useComposerState.ts +367 -0
- package/src/app/useConfigSync.ts +288 -0
- package/src/app/useInputHandlers.ts +576 -0
- package/src/app/useLongRunToolCharms.ts +69 -0
- package/src/app/useMainApp.ts +1039 -0
- package/src/app/useSessionLifecycle.ts +366 -0
- package/src/app/useSubmission.ts +429 -0
- package/src/app.tsx +25 -0
- package/src/banner.ts +93 -0
- package/src/components/activeSessionSwitcher.tsx +635 -0
- package/src/components/agentsOverlay.tsx +1073 -0
- package/src/components/appChrome.tsx +554 -0
- package/src/components/appLayout.tsx +444 -0
- package/src/components/appOverlays.tsx +254 -0
- package/src/components/branding.tsx +466 -0
- package/src/components/fpsOverlay.tsx +30 -0
- package/src/components/helpHint.tsx +73 -0
- package/src/components/markdown.tsx +1119 -0
- package/src/components/maskedPrompt.tsx +34 -0
- package/src/components/messageLine.tsx +237 -0
- package/src/components/modelPicker.tsx +527 -0
- package/src/components/overlayControls.tsx +50 -0
- package/src/components/pluginsHub.tsx +238 -0
- package/src/components/prompts.tsx +276 -0
- package/src/components/queuedMessages.tsx +64 -0
- package/src/components/sessionPicker.tsx +227 -0
- package/src/components/skillsHub.tsx +308 -0
- package/src/components/streamingAssistant.tsx +110 -0
- package/src/components/streamingMarkdown.tsx +174 -0
- package/src/components/textInput.tsx +1340 -0
- package/src/components/themed.tsx +30 -0
- package/src/components/thinking.tsx +1224 -0
- package/src/components/todoPanel.tsx +93 -0
- package/src/config/env.ts +64 -0
- package/src/config/limits.ts +13 -0
- package/src/config/timing.ts +6 -0
- package/src/content/charms.ts +1 -0
- package/src/content/faces.ts +17 -0
- package/src/content/fortunes.ts +30 -0
- package/src/content/hotkeys.ts +37 -0
- package/src/content/placeholders.ts +13 -0
- package/src/content/setup.ts +17 -0
- package/src/content/verbs.ts +38 -0
- package/src/domain/blockLayout.ts +146 -0
- package/src/domain/details.ts +76 -0
- package/src/domain/messages.ts +91 -0
- package/src/domain/paths.ts +16 -0
- package/src/domain/providers.ts +11 -0
- package/src/domain/roles.ts +9 -0
- package/src/domain/slash.ts +10 -0
- package/src/domain/usage.ts +3 -0
- package/src/domain/viewport.ts +51 -0
- package/src/entry.tsx +104 -0
- package/src/gatewayClient.ts +730 -0
- package/src/gatewayTypes.ts +568 -0
- package/src/hooks/useCompletion.ts +112 -0
- package/src/hooks/useGitBranch.ts +72 -0
- package/src/hooks/useInputHistory.ts +11 -0
- package/src/hooks/useQueue.ts +76 -0
- package/src/hooks/useVirtualHistory.ts +554 -0
- package/src/lib/circularBuffer.ts +48 -0
- package/src/lib/clipboard.ts +182 -0
- package/src/lib/editor.test.ts +74 -0
- package/src/lib/editor.ts +47 -0
- package/src/lib/emoji.ts +55 -0
- package/src/lib/externalCli.ts +16 -0
- package/src/lib/externalLink.ts +435 -0
- package/src/lib/forceTruecolor.ts +60 -0
- package/src/lib/fpsStore.ts +51 -0
- package/src/lib/fuzzy.test.ts +109 -0
- package/src/lib/fuzzy.ts +177 -0
- package/src/lib/gracefulExit.ts +47 -0
- package/src/lib/history.ts +82 -0
- package/src/lib/inputMetrics.ts +203 -0
- package/src/lib/liveProgress.test.ts +116 -0
- package/src/lib/liveProgress.ts +79 -0
- package/src/lib/mathUnicode.ts +770 -0
- package/src/lib/memory.test.ts +155 -0
- package/src/lib/memory.ts +188 -0
- package/src/lib/memoryMonitor.ts +109 -0
- package/src/lib/messages.test.ts +29 -0
- package/src/lib/messages.ts +8 -0
- package/src/lib/openExternalUrl.test.ts +217 -0
- package/src/lib/openExternalUrl.ts +158 -0
- package/src/lib/osc52.ts +73 -0
- package/src/lib/parentLog.ts +57 -0
- package/src/lib/perfPane.tsx +107 -0
- package/src/lib/platform.ts +409 -0
- package/src/lib/precisionWheel.ts +48 -0
- package/src/lib/prompt.ts +35 -0
- package/src/lib/reasoning.ts +55 -0
- package/src/lib/rpc.ts +41 -0
- package/src/lib/subagentTree.ts +355 -0
- package/src/lib/syntax.ts +117 -0
- package/src/lib/terminalModes.ts +51 -0
- package/src/lib/terminalParity.ts +78 -0
- package/src/lib/terminalSetup.ts +444 -0
- package/src/lib/termux.ts +29 -0
- package/src/lib/text.test.ts +18 -0
- package/src/lib/text.ts +339 -0
- package/src/lib/todo.test.ts +21 -0
- package/src/lib/todo.ts +9 -0
- package/src/lib/viewportStore.ts +124 -0
- package/src/lib/virtualHeights.ts +145 -0
- package/src/lib/wheelAccel.ts +190 -0
- package/src/protocol/interpolation.ts +3 -0
- package/src/protocol/paste.ts +1 -0
- package/src/theme.ts +589 -0
- package/src/types/nastech-ink.d.ts +176 -0
- package/src/types.ts +212 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
import { STARTUP_IMAGE, STARTUP_QUERY } from '../config/env.js'
|
|
2
|
+
import { STREAM_BATCH_MS } from '../config/timing.js'
|
|
3
|
+
import { buildSetupRequiredSections, SETUP_REQUIRED_TITLE } from '../content/setup.js'
|
|
4
|
+
import type {
|
|
5
|
+
CommandsCatalogResponse,
|
|
6
|
+
ConfigFullResponse,
|
|
7
|
+
DelegationStatusResponse,
|
|
8
|
+
GatewayEvent,
|
|
9
|
+
GatewaySkin,
|
|
10
|
+
SessionMostRecentResponse
|
|
11
|
+
} from '../gatewayTypes.js'
|
|
12
|
+
import { rpcErrorMessage } from '../lib/rpc.js'
|
|
13
|
+
import { topLevelSubagents } from '../lib/subagentTree.js'
|
|
14
|
+
import { formatToolCall, stripAnsi } from '../lib/text.js'
|
|
15
|
+
import { fromSkin } from '../theme.js'
|
|
16
|
+
import type { Msg, SubagentProgress, SubagentStatus } from '../types.js'
|
|
17
|
+
|
|
18
|
+
import { applyDelegationStatus, getDelegationState } from './delegationStore.js'
|
|
19
|
+
import type { GatewayEventHandlerContext } from './interfaces.js'
|
|
20
|
+
import { getOverlayState, patchOverlayState } from './overlayStore.js'
|
|
21
|
+
import { turnController } from './turnController.js'
|
|
22
|
+
import { getUiState, patchUiState } from './uiStore.js'
|
|
23
|
+
|
|
24
|
+
const NO_PROVIDER_RE = /\bNo (?:LLM|inference) provider configured\b/i
|
|
25
|
+
|
|
26
|
+
const statusFromBusy = () => (getUiState().busy ? 'running…' : 'ready')
|
|
27
|
+
|
|
28
|
+
const applySkin = (s: GatewaySkin) =>
|
|
29
|
+
patchUiState({
|
|
30
|
+
theme: fromSkin(
|
|
31
|
+
s.colors ?? {},
|
|
32
|
+
s.branding ?? {},
|
|
33
|
+
s.banner_logo ?? '',
|
|
34
|
+
s.banner_hero ?? '',
|
|
35
|
+
s.tool_prefix ?? '',
|
|
36
|
+
s.help_header ?? ''
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const dropBgTask = (taskId: string) =>
|
|
41
|
+
patchUiState(state => {
|
|
42
|
+
const next = new Set(state.bgTasks)
|
|
43
|
+
next.delete(taskId)
|
|
44
|
+
|
|
45
|
+
return { ...state, bgTasks: next }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const pushUnique =
|
|
49
|
+
(max: number) =>
|
|
50
|
+
<T>(xs: T[], x: T): T[] =>
|
|
51
|
+
xs.at(-1) === x ? xs : [...xs, x].slice(-max)
|
|
52
|
+
|
|
53
|
+
const pushThinking = pushUnique(6)
|
|
54
|
+
const pushNote = pushUnique(6)
|
|
55
|
+
const pushTool = pushUnique(8)
|
|
56
|
+
|
|
57
|
+
const KNOWN_SUBAGENT_STATUSES = new Set<SubagentStatus>([
|
|
58
|
+
'completed',
|
|
59
|
+
'error',
|
|
60
|
+
'failed',
|
|
61
|
+
'interrupted',
|
|
62
|
+
'queued',
|
|
63
|
+
'running',
|
|
64
|
+
'timeout'
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
const normalizeSubagentStatus = (status: unknown, fallback: SubagentStatus): SubagentStatus => {
|
|
68
|
+
if (typeof status !== 'string') {
|
|
69
|
+
return fallback
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const normalized = status.toLowerCase() as SubagentStatus
|
|
73
|
+
|
|
74
|
+
return KNOWN_SUBAGENT_STATUSES.has(normalized) ? normalized : fallback
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev: GatewayEvent) => void {
|
|
78
|
+
const { rpc } = ctx.gateway
|
|
79
|
+
const { STARTUP_RESUME_ID, newSession, resumeById, setCatalog } = ctx.session
|
|
80
|
+
const { bellOnComplete, stdout, sys } = ctx.system
|
|
81
|
+
const { appendMessage, panel, setHistoryItems } = ctx.transcript
|
|
82
|
+
const { setInput } = ctx.composer
|
|
83
|
+
const { submitRef } = ctx.submission
|
|
84
|
+
const { setProcessing: setVoiceProcessing, setRecording: setVoiceRecording, setVoiceEnabled } = ctx.voice
|
|
85
|
+
|
|
86
|
+
let pendingThinkingStatus = ''
|
|
87
|
+
let thinkingStatusTimer: null | ReturnType<typeof setTimeout> = null
|
|
88
|
+
let startupPromptSubmitted = false
|
|
89
|
+
|
|
90
|
+
// Inject the disk-save callback into turnController so recordMessageComplete
|
|
91
|
+
// can fire-and-forget a persist without having to plumb a gateway ref around.
|
|
92
|
+
turnController.persistSpawnTree = async (subagents, sessionId) => {
|
|
93
|
+
try {
|
|
94
|
+
const startedAt = subagents.reduce<number>((min, s) => {
|
|
95
|
+
if (!s.startedAt) {
|
|
96
|
+
return min
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return min === 0 ? s.startedAt : Math.min(min, s.startedAt)
|
|
100
|
+
}, 0)
|
|
101
|
+
|
|
102
|
+
const top = topLevelSubagents(subagents)
|
|
103
|
+
.map(s => s.goal)
|
|
104
|
+
.filter(Boolean)
|
|
105
|
+
.slice(0, 2)
|
|
106
|
+
|
|
107
|
+
const label = top.length ? top.join(' · ') : `${subagents.length} subagents`
|
|
108
|
+
|
|
109
|
+
await rpc('spawn_tree.save', {
|
|
110
|
+
finished_at: Date.now() / 1000,
|
|
111
|
+
label: label.slice(0, 120),
|
|
112
|
+
session_id: sessionId ?? 'default',
|
|
113
|
+
started_at: startedAt ? startedAt / 1000 : null,
|
|
114
|
+
subagents
|
|
115
|
+
})
|
|
116
|
+
} catch {
|
|
117
|
+
// Persistence is best-effort; in-memory history is the authoritative
|
|
118
|
+
// same-session source. A write failure doesn't block the turn.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Refresh delegation caps at most every 5s so the status bar HUD can
|
|
123
|
+
// render a /warning close to the configured cap without spamming the RPC.
|
|
124
|
+
let lastDelegationFetchAt = 0
|
|
125
|
+
|
|
126
|
+
// ── Shared full-config read ──────────────────────────────────────────
|
|
127
|
+
//
|
|
128
|
+
// Several concerns need `display.*` flags at startup (the /agents nudge
|
|
129
|
+
// gate below, the auto-resume check in the `gateway.ready` handler).
|
|
130
|
+
// Memoize the `config.get full` RPC so we make exactly one round-trip
|
|
131
|
+
// instead of one per concern. Resolves to null on RPC failure; callers
|
|
132
|
+
// treat null as "use defaults".
|
|
133
|
+
let fullConfigPromise: null | Promise<ConfigFullResponse | null> = null
|
|
134
|
+
|
|
135
|
+
const getFullConfigOnce = (): Promise<ConfigFullResponse | null> => {
|
|
136
|
+
fullConfigPromise ??= rpc<ConfigFullResponse>('config.get', { key: 'full' }).catch(() => null)
|
|
137
|
+
|
|
138
|
+
return fullConfigPromise
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Nudge toward /agents on delegation ───────────────────────────────
|
|
142
|
+
//
|
|
143
|
+
// When `display.tui_agents_nudge` is enabled (default true), the first
|
|
144
|
+
// time a turn starts delegating we drop a single transient activity hint
|
|
145
|
+
// ("subagents working · /agents to watch live") so the user discovers the
|
|
146
|
+
// spawn-tree dashboard instead of staring at a quiet transcript — without
|
|
147
|
+
// hijacking the screen by force-opening an overlay. Guards:
|
|
148
|
+
// • fires at most once per turn (`agentsNudgedThisTurn`)
|
|
149
|
+
// • silent if the overlay is already open (nothing to advertise)
|
|
150
|
+
// Reset on `message.start`. The config flag is fetched once, lazily;
|
|
151
|
+
// until it resolves we assume the default (on).
|
|
152
|
+
let agentsNudgeEnabled = true
|
|
153
|
+
let agentsNudgeConfigFetched = false
|
|
154
|
+
let agentsNudgedThisTurn = false
|
|
155
|
+
|
|
156
|
+
const ensureAgentsNudgeConfig = () => {
|
|
157
|
+
if (agentsNudgeConfigFetched) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
agentsNudgeConfigFetched = true
|
|
162
|
+
getFullConfigOnce().then(cfg => {
|
|
163
|
+
// Only an explicit `false` disables it; absent/unknown keeps default on.
|
|
164
|
+
if (cfg?.config?.display?.tui_agents_nudge === false) {
|
|
165
|
+
agentsNudgeEnabled = false
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const maybeNudgeAgents = () => {
|
|
171
|
+
ensureAgentsNudgeConfig()
|
|
172
|
+
|
|
173
|
+
if (!agentsNudgeEnabled || agentsNudgedThisTurn) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Already watching → no point advertising the dashboard. Don't burn the
|
|
178
|
+
// turn's nudge credit here: if the user closes the overlay later in the
|
|
179
|
+
// same turn while delegation is still ongoing, a subsequent event should
|
|
180
|
+
// still be allowed to nudge. The flag is only set once we actually push.
|
|
181
|
+
if (getOverlayState().agents) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
agentsNudgedThisTurn = true
|
|
186
|
+
turnController.pushActivity('subagents working · /agents to watch live', 'info')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const resetAgentsNudgeTurnState = () => {
|
|
190
|
+
agentsNudgedThisTurn = false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Kick off the config fetch eagerly at handler creation so the flag is
|
|
194
|
+
// resolved well before the first delegation of any real session (which
|
|
195
|
+
// only happens after gateway.ready + a user turn).
|
|
196
|
+
ensureAgentsNudgeConfig()
|
|
197
|
+
|
|
198
|
+
const refreshDelegationStatus = (force = false) => {
|
|
199
|
+
const now = Date.now()
|
|
200
|
+
|
|
201
|
+
if (!force && now - lastDelegationFetchAt < 5000) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
lastDelegationFetchAt = now
|
|
206
|
+
rpc<DelegationStatusResponse>('delegation.status', {})
|
|
207
|
+
.then(r => applyDelegationStatus(r))
|
|
208
|
+
.catch(() => {})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const setStatus = (status: string) => {
|
|
212
|
+
pendingThinkingStatus = ''
|
|
213
|
+
|
|
214
|
+
if (thinkingStatusTimer) {
|
|
215
|
+
clearTimeout(thinkingStatusTimer)
|
|
216
|
+
thinkingStatusTimer = null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
patchUiState({ status })
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const scheduleThinkingStatus = (status: string) => {
|
|
223
|
+
pendingThinkingStatus = status
|
|
224
|
+
|
|
225
|
+
if (thinkingStatusTimer) {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
thinkingStatusTimer = setTimeout(() => {
|
|
230
|
+
thinkingStatusTimer = null
|
|
231
|
+
patchUiState({ status: pendingThinkingStatus || statusFromBusy() })
|
|
232
|
+
}, STREAM_BATCH_MS)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const restoreStatusAfter = (ms: number) => {
|
|
236
|
+
turnController.clearStatusTimer()
|
|
237
|
+
turnController.statusTimer = setTimeout(() => {
|
|
238
|
+
turnController.statusTimer = null
|
|
239
|
+
patchUiState({ status: statusFromBusy() })
|
|
240
|
+
}, ms)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const scheduleStartupPrompt = () => {
|
|
244
|
+
if (startupPromptSubmitted || (!STARTUP_QUERY && !STARTUP_IMAGE)) {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
startupPromptSubmitted = true
|
|
249
|
+
setTimeout(async () => {
|
|
250
|
+
let sid = getUiState().sid
|
|
251
|
+
|
|
252
|
+
for (let i = 0; !sid && i < 40; i += 1) {
|
|
253
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
254
|
+
sid = getUiState().sid
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!sid) {
|
|
258
|
+
return sys('startup query skipped: no active session')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (STARTUP_IMAGE) {
|
|
262
|
+
try {
|
|
263
|
+
await rpc('image.attach', { path: STARTUP_IMAGE, session_id: sid })
|
|
264
|
+
} catch (e) {
|
|
265
|
+
sys(`startup image attach failed: ${rpcErrorMessage(e)}`)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
submitRef.current(STARTUP_QUERY || 'What do you see in this image?')
|
|
270
|
+
}, 0)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Terminal statuses are never overwritten by late-arriving live events —
|
|
274
|
+
// otherwise a stale `subagent.start` / `spawn_requested` can clobber a
|
|
275
|
+
// terminal state from complete (failed/interrupted/timeout/error).
|
|
276
|
+
const isTerminalStatus = (s: SubagentProgress['status']) =>
|
|
277
|
+
s === 'completed' || s === 'error' || s === 'failed' || s === 'interrupted' || s === 'timeout'
|
|
278
|
+
|
|
279
|
+
const keepTerminalElseRunning = (s: SubagentProgress['status']) => (isTerminalStatus(s) ? s : 'running')
|
|
280
|
+
|
|
281
|
+
const handleReady = (skin?: GatewaySkin) => {
|
|
282
|
+
if (skin) {
|
|
283
|
+
applySkin(skin)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
rpc<CommandsCatalogResponse>('commands.catalog', {})
|
|
287
|
+
.then(r => {
|
|
288
|
+
if (!r?.pairs) {
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
setCatalog({
|
|
293
|
+
canon: (r.canon ?? {}) as Record<string, string>,
|
|
294
|
+
categories: r.categories ?? [],
|
|
295
|
+
pairs: r.pairs as [string, string][],
|
|
296
|
+
skillCount: (r.skill_count ?? 0) as number,
|
|
297
|
+
sub: (r.sub ?? {}) as Record<string, string[]>
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
if (r.warning) {
|
|
301
|
+
turnController.pushActivity(String(r.warning), 'warn')
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
.catch((e: unknown) => turnController.pushActivity(`command catalog unavailable: ${rpcErrorMessage(e)}`, 'info'))
|
|
305
|
+
|
|
306
|
+
if (STARTUP_RESUME_ID) {
|
|
307
|
+
patchUiState({ status: 'resuming…' })
|
|
308
|
+
resumeById(STARTUP_RESUME_ID)
|
|
309
|
+
scheduleStartupPrompt()
|
|
310
|
+
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Opt-in: when `display.tui_auto_resume_recent` is true, look up
|
|
315
|
+
// the most recent human-facing session and resume it instead of
|
|
316
|
+
// forging a brand-new one. Mirrors classic CLI's `nastech -c` /
|
|
317
|
+
// `nastech --tui` muscle memory and addresses the audit's "session
|
|
318
|
+
// unrecoverable after disconnection" gap. Default off so existing
|
|
319
|
+
// users aren't surprised. (Shares the memoized full-config read.)
|
|
320
|
+
getFullConfigOnce()
|
|
321
|
+
.then(cfg => {
|
|
322
|
+
if (!cfg?.config?.display?.tui_auto_resume_recent) {
|
|
323
|
+
patchUiState({ status: 'forging session…' })
|
|
324
|
+
newSession()
|
|
325
|
+
scheduleStartupPrompt()
|
|
326
|
+
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return rpc<SessionMostRecentResponse>('session.most_recent', {}).then(r => {
|
|
331
|
+
const target = r?.session_id
|
|
332
|
+
|
|
333
|
+
if (target) {
|
|
334
|
+
patchUiState({ status: 'resuming most recent…' })
|
|
335
|
+
resumeById(target)
|
|
336
|
+
scheduleStartupPrompt()
|
|
337
|
+
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
patchUiState({ status: 'forging session…' })
|
|
342
|
+
newSession()
|
|
343
|
+
scheduleStartupPrompt()
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
.catch(() => {
|
|
347
|
+
patchUiState({ status: 'forging session…' })
|
|
348
|
+
newSession()
|
|
349
|
+
scheduleStartupPrompt()
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return (ev: GatewayEvent) => {
|
|
354
|
+
const sid = getUiState().sid
|
|
355
|
+
|
|
356
|
+
if (ev.session_id && sid && ev.session_id !== sid && !ev.type.startsWith('gateway.')) {
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
switch (ev.type) {
|
|
361
|
+
case 'gateway.ready':
|
|
362
|
+
handleReady(ev.payload?.skin)
|
|
363
|
+
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
case 'skin.changed':
|
|
367
|
+
if (ev.payload) {
|
|
368
|
+
applySkin(ev.payload)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return
|
|
372
|
+
case 'session.info': {
|
|
373
|
+
const info = ev.payload
|
|
374
|
+
|
|
375
|
+
patchUiState(state => ({
|
|
376
|
+
...state,
|
|
377
|
+
info,
|
|
378
|
+
status: state.status === 'starting agent…' ? 'ready' : state.status,
|
|
379
|
+
usage: info.usage ? { ...state.usage, ...info.usage } : state.usage
|
|
380
|
+
}))
|
|
381
|
+
|
|
382
|
+
setHistoryItems(prev => prev.map(m => (m.kind === 'intro' ? { ...m, info } : m)))
|
|
383
|
+
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
case 'thinking.delta': {
|
|
388
|
+
if (!getUiState().busy) {
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const text = ev.payload?.text
|
|
393
|
+
|
|
394
|
+
if (text !== undefined) {
|
|
395
|
+
const value = String(text)
|
|
396
|
+
scheduleThinkingStatus(value || statusFromBusy())
|
|
397
|
+
|
|
398
|
+
if (value) {
|
|
399
|
+
turnController.recordReasoningDelta(value)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
case 'message.start':
|
|
407
|
+
resetAgentsNudgeTurnState()
|
|
408
|
+
turnController.startMessage()
|
|
409
|
+
|
|
410
|
+
return
|
|
411
|
+
case 'status.update': {
|
|
412
|
+
const p = ev.payload
|
|
413
|
+
|
|
414
|
+
if (!p?.text) {
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (p.kind === 'goal') {
|
|
419
|
+
sys(p.text)
|
|
420
|
+
|
|
421
|
+
const brief = p.text.startsWith('✓')
|
|
422
|
+
? '✓ goal complete'
|
|
423
|
+
: p.text.startsWith('↻')
|
|
424
|
+
? '↻ goal continuing'
|
|
425
|
+
: p.text.startsWith('⏸')
|
|
426
|
+
? '⏸ goal paused'
|
|
427
|
+
: 'ready'
|
|
428
|
+
|
|
429
|
+
setStatus(brief)
|
|
430
|
+
restoreStatusAfter(6000)
|
|
431
|
+
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
setStatus(p.text)
|
|
436
|
+
|
|
437
|
+
if (p.kind === 'compressing') {
|
|
438
|
+
sys(p.text)
|
|
439
|
+
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!p.kind || p.kind === 'status') {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (turnController.lastStatusNote !== p.text) {
|
|
448
|
+
turnController.lastStatusNote = p.text
|
|
449
|
+
turnController.pushActivity(
|
|
450
|
+
p.text,
|
|
451
|
+
p.kind === 'error' ? 'error' : p.kind === 'warn' || p.kind === 'approval' ? 'warn' : 'info'
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
restoreStatusAfter(4000)
|
|
456
|
+
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
case 'gateway.stderr': {
|
|
461
|
+
const line = String(ev.payload.line).slice(0, 120)
|
|
462
|
+
|
|
463
|
+
turnController.pushActivity(line, 'info')
|
|
464
|
+
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
case 'browser.progress': {
|
|
469
|
+
const message = String(ev.payload?.message ?? '').trim()
|
|
470
|
+
|
|
471
|
+
if (message) {
|
|
472
|
+
sys(message)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
case 'voice.status': {
|
|
479
|
+
// Continuous VAD loop reports its internal state so the status bar
|
|
480
|
+
// can show listening / transcribing / idle without polling.
|
|
481
|
+
const state = String(ev.payload?.state ?? '')
|
|
482
|
+
|
|
483
|
+
if (state === 'listening') {
|
|
484
|
+
setVoiceRecording(true)
|
|
485
|
+
setVoiceProcessing(false)
|
|
486
|
+
} else if (state === 'transcribing') {
|
|
487
|
+
setVoiceRecording(false)
|
|
488
|
+
setVoiceProcessing(true)
|
|
489
|
+
} else {
|
|
490
|
+
setVoiceRecording(false)
|
|
491
|
+
setVoiceProcessing(false)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
case 'voice.transcript': {
|
|
498
|
+
// CLI parity: the 3-strikes silence detector flipped off automatically.
|
|
499
|
+
// Mirror that on the UI side and tell the user why the mode is off.
|
|
500
|
+
if (ev.payload?.no_speech_limit) {
|
|
501
|
+
setVoiceEnabled(false)
|
|
502
|
+
setVoiceRecording(false)
|
|
503
|
+
setVoiceProcessing(false)
|
|
504
|
+
sys('voice: no speech detected 3 times, continuous mode stopped')
|
|
505
|
+
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const text = String(ev.payload?.text ?? '').trim()
|
|
510
|
+
|
|
511
|
+
if (!text) {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// CLI parity: _pending_input.put(transcript) unconditionally feeds
|
|
516
|
+
// the transcript to the agent as its next turn — draft handling
|
|
517
|
+
// doesn't apply because voice-mode users are speaking, not typing.
|
|
518
|
+
//
|
|
519
|
+
// We can't branch on composer input from inside a setInput updater
|
|
520
|
+
// (React strict mode double-invokes it, duplicating the submit).
|
|
521
|
+
// Just clear + defer submit so the cleared input is committed before
|
|
522
|
+
// submit reads it.
|
|
523
|
+
setInput('')
|
|
524
|
+
setTimeout(() => submitRef.current(text), 0)
|
|
525
|
+
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
case 'gateway.start_timeout': {
|
|
530
|
+
const { cwd, python, stderr_tail: stderrTail } = ev.payload ?? {}
|
|
531
|
+
const trace = python || cwd ? ` · ${String(python || '')} ${String(cwd || '')}`.trim() : ''
|
|
532
|
+
|
|
533
|
+
setStatus('gateway startup timeout')
|
|
534
|
+
turnController.pushActivity(`gateway startup timed out${trace} · /logs to inspect`, 'error')
|
|
535
|
+
|
|
536
|
+
// Surface the most useful stderr lines inline so users can tell
|
|
537
|
+
// "wrong python", "missing dep", and "config parse failure"
|
|
538
|
+
// apart without leaving the TUI. Filter blank rows BEFORE
|
|
539
|
+
// taking the last N so trailing empty lines in the buffer
|
|
540
|
+
// don't crowd out actual content; truncate to match the
|
|
541
|
+
// 120-char clip used for `gateway.stderr` activity entries.
|
|
542
|
+
const STDERR_LINE_CAP = 120
|
|
543
|
+
const STDERR_LINES_MAX = 8
|
|
544
|
+
|
|
545
|
+
const tailLines = (stderrTail ?? '')
|
|
546
|
+
.split('\n')
|
|
547
|
+
.map(l => l.trim())
|
|
548
|
+
.filter(Boolean)
|
|
549
|
+
.slice(-STDERR_LINES_MAX)
|
|
550
|
+
|
|
551
|
+
for (const line of tailLines) {
|
|
552
|
+
turnController.pushActivity(line.slice(0, STDERR_LINE_CAP), 'error')
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
case 'gateway.protocol_error':
|
|
559
|
+
setStatus('protocol warning')
|
|
560
|
+
restoreStatusAfter(4000)
|
|
561
|
+
|
|
562
|
+
if (!turnController.protocolWarned) {
|
|
563
|
+
turnController.protocolWarned = true
|
|
564
|
+
turnController.pushActivity('protocol noise detected · /logs to inspect', 'info')
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (ev.payload?.preview) {
|
|
568
|
+
turnController.pushActivity(`protocol noise: ${String(ev.payload.preview).slice(0, 120)}`, 'info')
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
case 'reasoning.delta':
|
|
574
|
+
if (ev.payload?.text) {
|
|
575
|
+
turnController.recordReasoningDelta(ev.payload.text, Boolean(ev.payload.verbose))
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return
|
|
579
|
+
|
|
580
|
+
case 'reasoning.available':
|
|
581
|
+
turnController.recordReasoningAvailable(String(ev.payload?.text ?? ''), Boolean(ev.payload?.verbose))
|
|
582
|
+
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
case 'tool.progress':
|
|
586
|
+
if (ev.payload?.preview && ev.payload.name) {
|
|
587
|
+
turnController.recordToolProgress(ev.payload.name, ev.payload.preview)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return
|
|
591
|
+
|
|
592
|
+
case 'tool.generating':
|
|
593
|
+
if (ev.payload?.name) {
|
|
594
|
+
turnController.pushTrail(`drafting ${ev.payload.name}…`)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return
|
|
598
|
+
|
|
599
|
+
case 'tool.start':
|
|
600
|
+
turnController.recordTodos(ev.payload.todos)
|
|
601
|
+
turnController.recordToolStart(
|
|
602
|
+
ev.payload.tool_id,
|
|
603
|
+
ev.payload.name ?? 'tool',
|
|
604
|
+
ev.payload.context ?? '',
|
|
605
|
+
ev.payload.args_text ? stripAnsi(String(ev.payload.args_text)) : undefined
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
return
|
|
609
|
+
case 'tool.complete': {
|
|
610
|
+
const inlineDiffText =
|
|
611
|
+
ev.payload.inline_diff && getUiState().inlineDiffs ? stripAnsi(String(ev.payload.inline_diff)).trim() : ''
|
|
612
|
+
|
|
613
|
+
const resultText = ev.payload.result_text ? stripAnsi(String(ev.payload.result_text)) : undefined
|
|
614
|
+
|
|
615
|
+
if (inlineDiffText) {
|
|
616
|
+
turnController.recordInlineDiffToolComplete(
|
|
617
|
+
inlineDiffText,
|
|
618
|
+
ev.payload.tool_id,
|
|
619
|
+
ev.payload.name,
|
|
620
|
+
ev.payload.error,
|
|
621
|
+
ev.payload.duration_s,
|
|
622
|
+
resultText
|
|
623
|
+
)
|
|
624
|
+
} else {
|
|
625
|
+
turnController.recordToolComplete(
|
|
626
|
+
ev.payload.tool_id,
|
|
627
|
+
ev.payload.name,
|
|
628
|
+
ev.payload.error,
|
|
629
|
+
ev.payload.summary,
|
|
630
|
+
ev.payload.duration_s,
|
|
631
|
+
ev.payload.todos,
|
|
632
|
+
resultText
|
|
633
|
+
)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
case 'clarify.request':
|
|
640
|
+
patchOverlayState({
|
|
641
|
+
clarify: { choices: ev.payload.choices, question: ev.payload.question, requestId: ev.payload.request_id }
|
|
642
|
+
})
|
|
643
|
+
setStatus('waiting for input…')
|
|
644
|
+
|
|
645
|
+
return
|
|
646
|
+
case 'approval.request': {
|
|
647
|
+
const description = String(ev.payload.description ?? 'dangerous command')
|
|
648
|
+
|
|
649
|
+
patchOverlayState({ approval: { command: String(ev.payload.command ?? ''), description } })
|
|
650
|
+
setStatus('approval needed')
|
|
651
|
+
|
|
652
|
+
return
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
case 'sudo.request':
|
|
656
|
+
patchOverlayState({ sudo: { requestId: ev.payload.request_id } })
|
|
657
|
+
setStatus('sudo password needed')
|
|
658
|
+
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
case 'secret.request':
|
|
662
|
+
patchOverlayState({
|
|
663
|
+
secret: { envVar: ev.payload.env_var, prompt: ev.payload.prompt, requestId: ev.payload.request_id }
|
|
664
|
+
})
|
|
665
|
+
setStatus('secret input needed')
|
|
666
|
+
|
|
667
|
+
return
|
|
668
|
+
|
|
669
|
+
case 'background.complete':
|
|
670
|
+
dropBgTask(ev.payload.task_id)
|
|
671
|
+
sys(`[bg ${ev.payload.task_id}] ${ev.payload.text}`)
|
|
672
|
+
|
|
673
|
+
return
|
|
674
|
+
case 'review.summary': {
|
|
675
|
+
// Self-improvement background review emitted a persistent summary
|
|
676
|
+
// of what it saved to memory/skills. Surface it as a system line
|
|
677
|
+
// in the transcript so it never gets lost to a transient status
|
|
678
|
+
// flash. Python-side already formats it as "💾 Self-improvement
|
|
679
|
+
// review: …".
|
|
680
|
+
const text = String(ev.payload?.text ?? '').trim()
|
|
681
|
+
|
|
682
|
+
if (text) {
|
|
683
|
+
sys(text)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
case 'subagent.spawn_requested':
|
|
690
|
+
// Child built but not yet running (waiting on ThreadPoolExecutor slot).
|
|
691
|
+
// Preserve completed state if a later event races in before this one.
|
|
692
|
+
turnController.upsertSubagent(ev.payload, c => (isTerminalStatus(c.status) ? {} : { status: 'queued' }))
|
|
693
|
+
|
|
694
|
+
// First sign of delegation this turn → nudge toward /agents.
|
|
695
|
+
maybeNudgeAgents()
|
|
696
|
+
|
|
697
|
+
// Prime the status-bar HUD: fetch caps (once every 5s) so we can
|
|
698
|
+
// warn as depth/concurrency approaches the configured ceiling.
|
|
699
|
+
if (getDelegationState().maxSpawnDepth === null) {
|
|
700
|
+
refreshDelegationStatus(true)
|
|
701
|
+
} else {
|
|
702
|
+
refreshDelegationStatus()
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return
|
|
706
|
+
|
|
707
|
+
case 'subagent.start':
|
|
708
|
+
turnController.upsertSubagent(ev.payload, c => (isTerminalStatus(c.status) ? {} : { status: 'running' }))
|
|
709
|
+
|
|
710
|
+
// `subagent.start` is the first delegation event the TUI reliably
|
|
711
|
+
// receives (the delegate callback drops `spawn_requested` in the
|
|
712
|
+
// CLI→gateway path), so nudge here too. Once-per-turn guarded, so
|
|
713
|
+
// hooking both events is safe.
|
|
714
|
+
maybeNudgeAgents()
|
|
715
|
+
|
|
716
|
+
return
|
|
717
|
+
case 'subagent.thinking': {
|
|
718
|
+
const text = String(ev.payload.text ?? '').trim()
|
|
719
|
+
|
|
720
|
+
if (!text) {
|
|
721
|
+
return
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Update-only: never resurrect subagents whose spawn_requested/start
|
|
725
|
+
// we missed or that already flushed via message.complete.
|
|
726
|
+
turnController.upsertSubagent(
|
|
727
|
+
ev.payload,
|
|
728
|
+
c => ({
|
|
729
|
+
status: keepTerminalElseRunning(c.status),
|
|
730
|
+
thinking: pushThinking(c.thinking, text)
|
|
731
|
+
}),
|
|
732
|
+
{ createIfMissing: false }
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
return
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
case 'subagent.tool': {
|
|
739
|
+
const line = formatToolCall(
|
|
740
|
+
ev.payload.tool_name ?? 'delegate_task',
|
|
741
|
+
ev.payload.tool_preview ?? ev.payload.text ?? ''
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
turnController.upsertSubagent(
|
|
745
|
+
ev.payload,
|
|
746
|
+
c => ({
|
|
747
|
+
status: keepTerminalElseRunning(c.status),
|
|
748
|
+
tools: pushTool(c.tools, line)
|
|
749
|
+
}),
|
|
750
|
+
{ createIfMissing: false }
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
return
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
case 'subagent.progress': {
|
|
757
|
+
const text = String(ev.payload.text ?? '').trim()
|
|
758
|
+
|
|
759
|
+
if (!text) {
|
|
760
|
+
return
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
turnController.upsertSubagent(
|
|
764
|
+
ev.payload,
|
|
765
|
+
c => ({
|
|
766
|
+
notes: pushNote(c.notes, text),
|
|
767
|
+
status: keepTerminalElseRunning(c.status)
|
|
768
|
+
}),
|
|
769
|
+
{ createIfMissing: false }
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
return
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
case 'subagent.complete':
|
|
776
|
+
turnController.upsertSubagent(
|
|
777
|
+
ev.payload,
|
|
778
|
+
c => ({
|
|
779
|
+
durationSeconds: ev.payload.duration_seconds ?? c.durationSeconds,
|
|
780
|
+
status: normalizeSubagentStatus(ev.payload.status, 'completed'),
|
|
781
|
+
summary: ev.payload.summary || ev.payload.text || c.summary
|
|
782
|
+
}),
|
|
783
|
+
{ createIfMissing: false }
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
return
|
|
787
|
+
|
|
788
|
+
case 'message.delta':
|
|
789
|
+
turnController.recordMessageDelta(ev.payload ?? {})
|
|
790
|
+
|
|
791
|
+
return
|
|
792
|
+
case 'message.complete': {
|
|
793
|
+
const { finalMessages, finalText, wasInterrupted } = turnController.recordMessageComplete(ev.payload ?? {})
|
|
794
|
+
|
|
795
|
+
if (!wasInterrupted) {
|
|
796
|
+
const msgs: Msg[] = finalMessages.length ? finalMessages : [{ role: 'assistant', text: finalText }]
|
|
797
|
+
msgs.forEach(appendMessage)
|
|
798
|
+
|
|
799
|
+
if (bellOnComplete && stdout?.isTTY) {
|
|
800
|
+
stdout.write('\x07')
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
setStatus('ready')
|
|
805
|
+
|
|
806
|
+
if (ev.payload?.usage) {
|
|
807
|
+
patchUiState(state => ({ ...state, usage: { ...state.usage, ...ev.payload!.usage } }))
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
case 'error':
|
|
814
|
+
turnController.recordError()
|
|
815
|
+
|
|
816
|
+
{
|
|
817
|
+
const message = String(ev.payload?.message || 'unknown error')
|
|
818
|
+
|
|
819
|
+
turnController.pushActivity(message, 'error')
|
|
820
|
+
|
|
821
|
+
if (NO_PROVIDER_RE.test(message)) {
|
|
822
|
+
panel(SETUP_REQUIRED_TITLE, buildSetupRequiredSections())
|
|
823
|
+
setStatus('setup required')
|
|
824
|
+
|
|
825
|
+
return
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
sys(`error: ${message}`)
|
|
829
|
+
setStatus('ready')
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|