@oh-my-pi/pi-tui 18.2.4 → 18.2.6
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/CHANGELOG.md +23 -0
- package/README.md +43 -0
- package/dist/types/app-keybindings.d.ts +395 -0
- package/dist/types/apps/autoresearch-dashboard.d.ts +43 -0
- package/dist/types/apps/autoresearch-data.d.ts +15 -0
- package/dist/types/apps/cleanse-board.d.ts +66 -0
- package/dist/types/apps/cleanse-picker.d.ts +23 -0
- package/dist/types/apps/debug/log-formatting.d.ts +6 -0
- package/dist/types/apps/debug/log-viewer.d.ts +81 -0
- package/dist/types/apps/debug/protocol-probe.d.ts +42 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +58 -0
- package/dist/types/apps/debug/raw-sse.d.ts +26 -0
- package/dist/types/apps/debug/terminal-info.d.ts +34 -0
- package/dist/types/apps/debug/viewer-frame.d.ts +52 -0
- package/dist/types/apps/git/avatar.d.ts +9 -0
- package/dist/types/apps/git/colors.d.ts +49 -0
- package/dist/types/apps/git/diff-pane.d.ts +188 -0
- package/dist/types/apps/git/git-tui.d.ts +66 -0
- package/dist/types/apps/git/sidebar.d.ts +149 -0
- package/dist/types/apps/git/state.d.ts +117 -0
- package/dist/types/apps/if-bench-board.d.ts +75 -0
- package/dist/types/apps/live-visualizer.d.ts +33 -0
- package/dist/types/apps/ps-data.d.ts +51 -0
- package/dist/types/apps/ps-top.d.ts +33 -0
- package/dist/types/apps/session-picker.d.ts +26 -0
- package/dist/types/apps/setup-model-picker.d.ts +13 -0
- package/dist/types/apps/standalone-picker.d.ts +42 -0
- package/dist/types/autocomplete.d.ts +4 -0
- package/dist/types/chat/advisor-message.d.ts +11 -0
- package/dist/types/chat/assistant-message.d.ts +106 -0
- package/dist/types/chat/background-tan-message.d.ts +9 -0
- package/dist/types/chat/bash-execution.d.ts +47 -0
- package/dist/types/chat/cache-invalidation-marker.d.ts +50 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +50 -0
- package/dist/types/chat/collab-prompt-message.d.ts +10 -0
- package/dist/types/chat/compaction-summary-message.d.ts +54 -0
- package/dist/types/chat/custom-message.d.ts +10 -0
- package/dist/types/chat/display-preferences.d.ts +13 -0
- package/dist/types/chat/eval-execution.d.ts +29 -0
- package/dist/types/chat/execution-shared.d.ts +51 -0
- package/dist/types/chat/extension-types.d.ts +28 -0
- package/dist/types/chat/hook-message.d.ts +10 -0
- package/dist/types/chat/image-loading.d.ts +46 -0
- package/dist/types/chat/late-diagnostics-message.d.ts +21 -0
- package/dist/types/chat/messages.d.ts +189 -0
- package/dist/types/chat/reaction.d.ts +30 -0
- package/dist/types/chat/read-target.d.ts +4 -0
- package/dist/types/chat/read-tool-group.d.ts +67 -0
- package/dist/types/chat/served-model-marker.d.ts +47 -0
- package/dist/types/chat/skill-message.d.ts +33 -0
- package/dist/types/chat/skill-title-input.d.ts +14 -0
- package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +12 -0
- package/dist/types/chat/thinking-display.d.ts +12 -0
- package/dist/types/chat/todo-reminder.d.ts +13 -0
- package/dist/types/chat/tool-execution.d.ts +138 -0
- package/dist/types/chat/transcript-browser.d.ts +93 -0
- package/dist/types/chat/transcript-entry.d.ts +45 -0
- package/dist/types/chat/transcript-outline.d.ts +86 -0
- package/dist/types/chat/transcript-render-helpers.d.ts +92 -0
- package/dist/types/chat/ttsr-notification.d.ts +23 -0
- package/dist/types/chat/user-message.d.ts +52 -0
- package/dist/types/chrome/chat-block.d.ts +64 -0
- package/dist/types/chrome/collab-qrcode.d.ts +28 -0
- package/dist/types/chrome/context-thresholds.d.ts +10 -0
- package/dist/types/chrome/countdown-timer.d.ts +14 -0
- package/dist/types/chrome/diff.d.ts +14 -0
- package/dist/types/chrome/dynamic-border.d.ts +16 -0
- package/dist/types/chrome/error-block.d.ts +17 -0
- package/dist/types/chrome/form-theme.d.ts +3 -0
- package/dist/types/chrome/format.d.ts +12 -0
- package/dist/types/chrome/index.d.ts +20 -0
- package/dist/types/chrome/keybinding-hints.d.ts +40 -0
- package/dist/types/chrome/live-board.d.ts +23 -0
- package/dist/types/chrome/local-date.d.ts +4 -0
- package/dist/types/chrome/message-divider.d.ts +22 -0
- package/dist/types/chrome/message-frame.d.ts +52 -0
- package/dist/types/chrome/message-notice.d.ts +35 -0
- package/dist/types/chrome/overlay-box.d.ts +74 -0
- package/dist/types/chrome/qrcode.d.ts +48 -0
- package/dist/types/chrome/segment-track.d.ts +27 -0
- package/dist/types/chrome/select-list-mouse-routing.d.ts +4 -0
- package/dist/types/chrome/selector-helpers.d.ts +52 -0
- package/dist/types/chrome/shared.d.ts +5 -0
- package/dist/types/chrome/streaming-panel.d.ts +19 -0
- package/dist/types/chrome/tool-activity.d.ts +17 -0
- package/dist/types/chrome/transcript-container.d.ts +129 -0
- package/dist/types/chrome/transcript-status.d.ts +12 -0
- package/dist/types/chrome/visual-truncate.d.ts +19 -0
- package/dist/types/components/disclosure.d.ts +42 -0
- package/dist/types/components/form.d.ts +130 -0
- package/dist/types/components/key-value-list.d.ts +24 -0
- package/dist/types/components/layout/geometry.d.ts +73 -0
- package/dist/types/components/layout/row.d.ts +55 -0
- package/dist/types/components/layout/split-pane.d.ts +80 -0
- package/dist/types/components/layout/stack.d.ts +50 -0
- package/dist/types/components/menu-selection.d.ts +71 -0
- package/dist/types/components/metric.d.ts +44 -0
- package/dist/types/components/progress-bar.d.ts +36 -0
- package/dist/types/components/scroll-view.d.ts +53 -19
- package/dist/types/components/scroll-viewport.d.ts +46 -0
- package/dist/types/components/section.d.ts +19 -0
- package/dist/types/components/select-list.d.ts +46 -2
- package/dist/types/components/settings-list.d.ts +20 -0
- package/dist/types/components/table.d.ts +32 -0
- package/dist/types/components/tree-view.d.ts +130 -0
- package/dist/types/components/wizard-step.d.ts +60 -0
- package/dist/types/hotkeys-markdown.d.ts +7 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/keybinding-matchers.d.ts +35 -0
- package/dist/types/keys.d.ts +2 -2
- package/dist/types/lang-from-path.d.ts +9 -0
- package/dist/types/overlays/advisor-config.d.ts +114 -0
- package/dist/types/overlays/agent-activity.d.ts +41 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +33 -0
- package/dist/types/overlays/agent-hub-renderer.d.ts +53 -0
- package/dist/types/overlays/agent-hub-types.d.ts +99 -0
- package/dist/types/overlays/agent-hub.d.ts +126 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +71 -0
- package/dist/types/overlays/agents-hub.d.ts +72 -0
- package/dist/types/overlays/ask-dialog.d.ts +88 -0
- package/dist/types/overlays/bordered-loader.d.ts +11 -0
- package/dist/types/overlays/btw-history-panel.d.ts +29 -0
- package/dist/types/overlays/btw-history.d.ts +17 -0
- package/dist/types/overlays/btw-panel.d.ts +26 -0
- package/dist/types/overlays/cleanse-panel.d.ts +36 -0
- package/dist/types/overlays/codex-reset-fireworks.d.ts +52 -0
- package/dist/types/overlays/composer-shape-preview.d.ts +47 -0
- package/dist/types/overlays/composer-shape-registry.d.ts +50 -0
- package/dist/types/overlays/copy-selector.d.ts +48 -0
- package/dist/types/overlays/copy-targets.d.ts +63 -0
- package/dist/types/overlays/error-banner.d.ts +13 -0
- package/dist/types/overlays/extensions/display-text.d.ts +10 -0
- package/dist/types/overlays/extensions/extension-dashboard.d.ts +63 -0
- package/dist/types/overlays/extensions/extension-list.d.ts +54 -0
- package/dist/types/overlays/extensions/inspector-model.d.ts +109 -0
- package/dist/types/overlays/extensions/inspector-panel.d.ts +17 -0
- package/dist/types/overlays/extensions/live-tool-session.d.ts +28 -0
- package/dist/types/overlays/extensions/mcp-runtime.d.ts +107 -0
- package/dist/types/overlays/extensions/state-manager.d.ts +42 -0
- package/dist/types/overlays/extensions/types.d.ts +165 -0
- package/dist/types/overlays/history-search.d.ts +16 -0
- package/dist/types/overlays/hook-editor.d.ts +44 -0
- package/dist/types/overlays/hook-input.d.ts +20 -0
- package/dist/types/overlays/hook-selector.d.ts +71 -0
- package/dist/types/overlays/hub-frame.d.ts +77 -0
- package/dist/types/overlays/login-dialog.d.ts +43 -0
- package/dist/types/overlays/logout-account-selector.d.ts +15 -0
- package/dist/types/overlays/mcp-add-wizard.d.ts +87 -0
- package/dist/types/overlays/model-browser.d.ts +213 -0
- package/dist/types/overlays/model-hub.d.ts +72 -0
- package/dist/types/overlays/model-picker.d.ts +74 -0
- package/dist/types/overlays/model-selector.d.ts +52 -0
- package/dist/types/overlays/move-overlay.d.ts +39 -0
- package/dist/types/overlays/oauth-selector.d.ts +41 -0
- package/dist/types/overlays/omfg-panel.d.ts +20 -0
- package/dist/types/overlays/pause-screen.d.ts +43 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +89 -0
- package/dist/types/overlays/plan-save-overlay.d.ts +19 -0
- package/dist/types/overlays/plan-toc.d.ts +41 -0
- package/dist/types/overlays/plugin-selector.d.ts +28 -0
- package/dist/types/overlays/plugin-settings.d.ts +180 -0
- package/dist/types/overlays/queue-mode-selector.d.ts +11 -0
- package/dist/types/overlays/reset-usage-selector.d.ts +23 -0
- package/dist/types/overlays/rewind-selector.d.ts +61 -0
- package/dist/types/overlays/running-subagent-badge.d.ts +6 -0
- package/dist/types/overlays/session-account-selector.d.ts +24 -0
- package/dist/types/overlays/session-info-overlay.d.ts +17 -0
- package/dist/types/overlays/session-observer-registry.d.ts +77 -0
- package/dist/types/overlays/session-selector.d.ts +131 -0
- package/dist/types/overlays/settings-defs.d.ts +119 -0
- package/dist/types/overlays/settings-selector.d.ts +73 -0
- package/dist/types/overlays/show-images-selector.d.ts +11 -0
- package/dist/types/overlays/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/overlays/theme-selector.d.ts +12 -0
- package/dist/types/overlays/thinking-selector.d.ts +12 -0
- package/dist/types/overlays/tiny-title-download-progress.d.ts +21 -0
- package/dist/types/overlays/tree-selector.d.ts +92 -0
- package/dist/types/overlays/usage-dashboard.d.ts +94 -0
- package/dist/types/overlays/usage-display.d.ts +5 -0
- package/dist/types/overlays/usage-row.d.ts +17 -0
- package/dist/types/prompt/attachment-chips.d.ts +19 -0
- package/dist/types/prompt/composer-attachments.d.ts +71 -0
- package/dist/types/prompt/composer-cache.d.ts +37 -0
- package/dist/types/prompt/composer.d.ts +159 -0
- package/dist/types/prompt/custom-editor.d.ts +261 -0
- package/dist/types/prompt/editor-top-gap.d.ts +18 -0
- package/dist/types/prompt/emoji-autocomplete.d.ts +16 -0
- package/dist/types/prompt/github-ref-autocomplete.d.ts +35 -0
- package/dist/types/prompt/gradient-highlight.d.ts +32 -0
- package/dist/types/prompt/image-format.d.ts +4 -0
- package/dist/types/prompt/image-references.d.ts +26 -0
- package/dist/types/prompt/interactive-context-helpers.d.ts +43 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +56 -0
- package/dist/types/prompt/macos-spelling.d.ts +44 -0
- package/dist/types/prompt/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/prompt/magic-keywords.d.ts +26 -0
- package/dist/types/prompt/markdown-prose.d.ts +15 -0
- package/dist/types/prompt/model-mention-autocomplete.d.ts +25 -0
- package/dist/types/prompt/model-mention-syntax.d.ts +24 -0
- package/dist/types/prompt/orchestrate.d.ts +13 -0
- package/dist/types/prompt/prompt-action-autocomplete.d.ts +56 -0
- package/dist/types/prompt/queue-input.d.ts +8 -0
- package/dist/types/prompt/skill-tokens.d.ts +13 -0
- package/dist/types/prompt/tools-markdown.d.ts +12 -0
- package/dist/types/prompt/ultrathink.d.ts +13 -0
- package/dist/types/prompt/usage-amounts.d.ts +26 -0
- package/dist/types/prompt/video.d.ts +18 -0
- package/dist/types/prompt/welcome.d.ts +83 -0
- package/dist/types/prompt/workflow.d.ts +13 -0
- package/dist/types/render/code-cell.d.ts +51 -0
- package/dist/types/render/file-list.d.ts +22 -0
- package/dist/types/render/hyperlink.d.ts +80 -0
- package/dist/types/render/index.d.ts +16 -0
- package/dist/types/render/output-block.d.ts +54 -0
- package/dist/types/render/output-pane.d.ts +80 -0
- package/dist/types/render/render-utils.d.ts +312 -0
- package/dist/types/render/sixel.d.ts +21 -0
- package/dist/types/render/status-line.d.ts +23 -0
- package/dist/types/render/tool-card.d.ts +71 -0
- package/dist/types/render/tree-list.d.ts +29 -0
- package/dist/types/render/types.d.ts +17 -0
- package/dist/types/render/utils.d.ts +43 -0
- package/dist/types/render/width-aware-text.d.ts +23 -0
- package/dist/types/setup/lazy.d.ts +3 -0
- package/dist/types/setup/scenes/composer.d.ts +3 -0
- package/dist/types/setup/scenes/glyph.d.ts +3 -0
- package/dist/types/setup/scenes/model.d.ts +3 -0
- package/dist/types/setup/scenes/outro.d.ts +2 -0
- package/dist/types/setup/scenes/providers.d.ts +3 -0
- package/dist/types/setup/scenes/sign-in.d.ts +21 -0
- package/dist/types/setup/scenes/splash.d.ts +11 -0
- package/dist/types/setup/scenes/theme.d.ts +3 -0
- package/dist/types/setup/scenes/types.d.ts +110 -0
- package/dist/types/setup/scenes/web-search.d.ts +22 -0
- package/dist/types/setup/setup-version.d.ts +11 -0
- package/dist/types/setup/startup-splash.d.ts +9 -0
- package/dist/types/setup/wizard-overlay.d.ts +16 -0
- package/dist/types/setup/wizard.d.ts +25 -0
- package/dist/types/status-line/component.d.ts +182 -0
- package/dist/types/status-line/context-usage.d.ts +156 -0
- package/dist/types/status-line/footer.d.ts +30 -0
- package/dist/types/status-line/git-utils.d.ts +22 -0
- package/dist/types/status-line/host.d.ts +129 -0
- package/dist/types/status-line/index.d.ts +6 -0
- package/dist/types/status-line/loop.d.ts +18 -0
- package/dist/types/status-line/metrics.d.ts +21 -0
- package/dist/types/status-line/presets.d.ts +3 -0
- package/dist/types/status-line/schema.d.ts +15 -0
- package/dist/types/status-line/segments.d.ts +5 -0
- package/dist/types/status-line/separators.d.ts +3 -0
- package/dist/types/status-line/types.d.ts +236 -0
- package/dist/types/stdin-buffer.d.ts +4 -0
- package/dist/types/theme/color.d.ts +24 -0
- package/dist/types/theme/defaults/index.d.ts +9489 -0
- package/dist/types/theme/index.d.ts +4 -0
- package/dist/types/theme/loader.d.ts +23 -0
- package/dist/types/theme/mermaid-cache.d.ts +26 -0
- package/dist/types/theme/schema-validation.d.ts +3 -0
- package/dist/types/theme/schema.d.ts +25 -0
- package/dist/types/theme/session-color.d.ts +43 -0
- package/dist/types/theme/shimmer.d.ts +44 -0
- package/dist/types/theme/symbols.d.ts +34 -0
- package/dist/types/theme/theme-class.d.ts +299 -0
- package/dist/types/theme/theme.d.ts +111 -0
- package/dist/types/theme/tui-adapters.d.ts +30 -0
- package/dist/types/thinking.d.ts +165 -0
- package/dist/types/tools/agent-tree.d.ts +36 -0
- package/dist/types/tools/ask.d.ts +67 -0
- package/dist/types/tools/ast-edit.d.ts +53 -0
- package/dist/types/tools/ast-grep.d.ts +52 -0
- package/dist/types/tools/autoresearch.d.ts +156 -0
- package/dist/types/tools/bash-interactive.d.ts +28 -0
- package/dist/types/tools/bash.d.ts +103 -0
- package/dist/types/tools/conflict-detect.d.ts +43 -0
- package/dist/types/tools/debug.d.ts +60 -0
- package/dist/types/tools/default-renderer.d.ts +24 -0
- package/dist/types/tools/edit.d.ts +158 -0
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +5 -0
- package/dist/types/tools/eval-format/index.d.ts +5 -0
- package/dist/types/tools/eval-format/javascript.d.ts +2 -0
- package/dist/types/tools/eval-format/python.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +94 -0
- package/dist/types/tools/fetch.d.ts +42 -0
- package/dist/types/tools/gh-format.d.ts +8 -0
- package/dist/types/tools/github.d.ts +144 -0
- package/dist/types/tools/glob.d.ts +44 -0
- package/dist/types/tools/goal.d.ts +42 -0
- package/dist/types/tools/grep.d.ts +64 -0
- package/dist/types/tools/grouped-file-output.d.ts +71 -0
- package/dist/types/tools/hashline-format.d.ts +28 -0
- package/dist/types/tools/hub.d.ts +340 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/json-tree.d.ts +59 -0
- package/dist/types/tools/line-ranges.d.ts +29 -0
- package/dist/types/tools/list-limit.d.ts +15 -0
- package/dist/types/tools/lsp.d.ts +72 -0
- package/dist/types/tools/match-line-format.d.ts +11 -0
- package/dist/types/tools/mcp.d.ts +81 -0
- package/dist/types/tools/memory.d.ts +67 -0
- package/dist/types/tools/output-meta.d.ts +151 -0
- package/dist/types/tools/read.d.ts +139 -0
- package/dist/types/tools/renderer.d.ts +98 -0
- package/dist/types/tools/report-tool-issue.d.ts +8 -0
- package/dist/types/tools/resolve.d.ts +47 -0
- package/dist/types/tools/streaming-output.d.ts +282 -0
- package/dist/types/tools/subprocess.d.ts +24 -0
- package/dist/types/tools/task-repair-args.d.ts +53 -0
- package/dist/types/tools/task-yield-assembly.d.ts +20 -0
- package/dist/types/tools/task.d.ts +425 -0
- package/dist/types/tools/terminal-output.d.ts +8 -0
- package/dist/types/tools/think.d.ts +13 -0
- package/dist/types/tools/todo.d.ts +118 -0
- package/dist/types/tools/tool-errors.d.ts +7 -0
- package/dist/types/tools/vibe.d.ts +120 -0
- package/dist/types/tools/web-search.d.ts +195 -0
- package/dist/types/tools/write.d.ts +76 -0
- package/dist/types/tools/xd-url.d.ts +13 -0
- package/dist/types/tools/xdev.d.ts +52 -0
- package/dist/types/tui.d.ts +18 -0
- package/dist/types/utils.d.ts +2 -0
- package/package.json +37 -3
- package/src/app-keybindings.ts +760 -0
- package/src/apps/autoresearch-dashboard.ts +500 -0
- package/src/apps/autoresearch-data.ts +64 -0
- package/src/apps/cleanse-board.ts +341 -0
- package/src/apps/cleanse-picker.ts +57 -0
- package/src/apps/debug/log-formatting.ts +53 -0
- package/src/apps/debug/log-viewer.ts +868 -0
- package/src/apps/debug/protocol-probe.ts +267 -0
- package/src/apps/debug/raw-sse-buffer.ts +449 -0
- package/src/apps/debug/raw-sse.ts +282 -0
- package/src/apps/debug/terminal-info.ts +115 -0
- package/src/apps/debug/viewer-frame.ts +174 -0
- package/src/apps/git/avatar.ts +52 -0
- package/src/apps/git/colors.ts +109 -0
- package/src/apps/git/diff-pane.ts +1512 -0
- package/src/apps/git/git-tui.ts +889 -0
- package/src/apps/git/sidebar.ts +1204 -0
- package/src/apps/git/state.ts +113 -0
- package/src/apps/if-bench-board.ts +324 -0
- package/src/apps/live-visualizer.ts +238 -0
- package/src/apps/ps-data.ts +116 -0
- package/src/apps/ps-top.ts +422 -0
- package/src/apps/session-picker.ts +95 -0
- package/src/apps/setup-model-picker.ts +21 -0
- package/src/apps/standalone-picker.ts +111 -0
- package/src/autocomplete.ts +9 -13
- package/src/chat/advisor-message.ts +175 -0
- package/src/chat/assistant-message.ts +1214 -0
- package/src/chat/background-tan-message.ts +35 -0
- package/src/chat/bash-execution.ts +394 -0
- package/src/chat/cache-invalidation-marker.ts +94 -0
- package/src/chat/chat-transcript-builder.ts +593 -0
- package/src/chat/collab-prompt-message.ts +34 -0
- package/src/chat/compaction-summary-message.ts +261 -0
- package/src/chat/custom-message.ts +23 -0
- package/src/chat/display-preferences.ts +24 -0
- package/src/chat/eval-execution.ts +172 -0
- package/src/chat/execution-shared.ts +113 -0
- package/src/chat/extension-types.ts +46 -0
- package/src/chat/hook-message.ts +20 -0
- package/src/chat/image-loading.ts +122 -0
- package/src/chat/late-diagnostics-message.ts +92 -0
- package/src/chat/messages.ts +305 -0
- package/src/chat/reaction.ts +65 -0
- package/src/chat/read-target.ts +7 -0
- package/src/chat/read-tool-group.ts +873 -0
- package/src/chat/served-model-marker.ts +91 -0
- package/src/chat/skill-message.ts +220 -0
- package/src/chat/skill-title-input.ts +41 -0
- package/src/chat/stripped-tool-calls-placeholder.ts +35 -0
- package/src/chat/thinking-display.ts +312 -0
- package/src/chat/todo-reminder.ts +33 -0
- package/src/chat/tool-execution.ts +1395 -0
- package/src/chat/transcript-browser.ts +268 -0
- package/src/chat/transcript-entry.ts +96 -0
- package/src/chat/transcript-outline.ts +260 -0
- package/src/chat/transcript-render-helpers.ts +315 -0
- package/src/chat/ttsr-notification.ts +123 -0
- package/src/chat/user-message.ts +287 -0
- package/src/chrome/chat-block.ts +110 -0
- package/src/chrome/collab-qrcode.ts +74 -0
- package/src/chrome/context-thresholds.ts +85 -0
- package/src/chrome/countdown-timer.ts +74 -0
- package/src/chrome/diff.ts +266 -0
- package/src/chrome/dynamic-border.ts +36 -0
- package/src/chrome/error-block.ts +63 -0
- package/src/chrome/form-theme.ts +10 -0
- package/src/chrome/format.ts +66 -0
- package/src/chrome/index.ts +21 -0
- package/src/chrome/keybinding-hints.ts +55 -0
- package/src/chrome/live-board.ts +123 -0
- package/src/chrome/local-date.ts +20 -0
- package/src/chrome/message-divider.ts +51 -0
- package/src/chrome/message-frame.ts +164 -0
- package/src/chrome/message-notice.ts +103 -0
- package/src/chrome/overlay-box.ts +301 -0
- package/src/chrome/qrcode.ts +539 -0
- package/src/chrome/segment-track.ts +86 -0
- package/src/chrome/select-list-mouse-routing.ts +11 -0
- package/src/chrome/selector-helpers.ts +119 -0
- package/src/chrome/shared.ts +27 -0
- package/src/chrome/streaming-panel.ts +72 -0
- package/src/chrome/tool-activity.ts +44 -0
- package/src/chrome/transcript-container.ts +902 -0
- package/src/chrome/transcript-status.ts +27 -0
- package/src/chrome/visual-truncate.ts +70 -0
- package/src/components/disclosure.ts +235 -0
- package/src/components/editor.ts +10 -12
- package/src/components/form.ts +566 -0
- package/src/components/input.ts +6 -27
- package/src/components/key-value-list.ts +88 -0
- package/src/components/layout/geometry.ts +202 -0
- package/src/components/layout/row.ts +283 -0
- package/src/components/layout/split-pane.ts +349 -0
- package/src/components/layout/stack.ts +261 -0
- package/src/components/markdown.ts +16 -12
- package/src/components/menu-selection.ts +248 -0
- package/src/components/metric.ts +132 -0
- package/src/components/progress-bar.ts +140 -0
- package/src/components/scroll-view.ts +372 -70
- package/src/components/scroll-viewport.ts +154 -0
- package/src/components/section.ts +77 -0
- package/src/components/select-list.ts +238 -159
- package/src/components/settings-list.ts +103 -80
- package/src/components/table.ts +127 -0
- package/src/components/tree-view.ts +481 -0
- package/src/components/wizard-step.ts +229 -0
- package/src/deccara.ts +1 -3
- package/src/hotkeys-markdown.ts +100 -0
- package/src/index.ts +18 -0
- package/src/keybinding-matchers.ts +99 -0
- package/src/keys.ts +2 -2
- package/src/lang-from-path.ts +252 -0
- package/src/overlays/advisor-config.ts +761 -0
- package/src/overlays/agent-activity.ts +113 -0
- package/src/overlays/agent-hub-projection.ts +250 -0
- package/src/overlays/agent-hub-renderer.ts +281 -0
- package/src/overlays/agent-hub-types.ts +74 -0
- package/src/overlays/agent-hub.ts +1562 -0
- package/src/overlays/agent-transcript-viewer.ts +669 -0
- package/src/overlays/agents-hub.ts +1203 -0
- package/src/overlays/ask-dialog.ts +1202 -0
- package/src/overlays/bordered-loader.ts +41 -0
- package/src/overlays/btw-history-panel.ts +591 -0
- package/src/overlays/btw-history.ts +33 -0
- package/src/overlays/btw-panel.ts +167 -0
- package/src/overlays/cleanse-panel.ts +167 -0
- package/src/overlays/codex-reset-fireworks.ts +375 -0
- package/src/overlays/composer-shape-preview.ts +137 -0
- package/src/overlays/composer-shape-registry.ts +82 -0
- package/src/overlays/copy-selector.ts +618 -0
- package/src/overlays/copy-targets.ts +210 -0
- package/src/overlays/error-banner.ts +39 -0
- package/src/overlays/extensions/display-text.ts +39 -0
- package/src/overlays/extensions/extension-dashboard.ts +645 -0
- package/src/overlays/extensions/extension-list.ts +667 -0
- package/src/overlays/extensions/inspector-model.ts +484 -0
- package/src/overlays/extensions/inspector-panel.ts +669 -0
- package/src/overlays/extensions/live-tool-session.ts +67 -0
- package/src/overlays/extensions/mcp-runtime.ts +289 -0
- package/src/overlays/extensions/state-manager.ts +337 -0
- package/src/overlays/extensions/types.ts +208 -0
- package/src/overlays/history-search.ts +268 -0
- package/src/overlays/hook-editor.ts +272 -0
- package/src/overlays/hook-input.ts +84 -0
- package/src/overlays/hook-selector.ts +635 -0
- package/src/overlays/hub-frame.ts +262 -0
- package/src/overlays/login-dialog.ts +231 -0
- package/src/overlays/logout-account-selector.ts +115 -0
- package/src/overlays/mcp-add-wizard.ts +1436 -0
- package/src/overlays/model-browser.ts +1176 -0
- package/src/overlays/model-hub.ts +2279 -0
- package/src/overlays/model-picker.ts +285 -0
- package/src/overlays/model-selector.ts +107 -0
- package/src/overlays/move-overlay.ts +197 -0
- package/src/overlays/oauth-selector.ts +446 -0
- package/src/overlays/omfg-panel.ts +136 -0
- package/src/overlays/pause-screen.ts +198 -0
- package/src/overlays/plan-review-overlay.ts +1227 -0
- package/src/overlays/plan-save-overlay.ts +74 -0
- package/src/overlays/plan-toc.ts +139 -0
- package/src/overlays/plugin-selector.ts +97 -0
- package/src/overlays/plugin-settings.ts +839 -0
- package/src/overlays/queue-mode-selector.ts +55 -0
- package/src/overlays/reset-usage-selector.ts +170 -0
- package/src/overlays/rewind-selector.ts +499 -0
- package/src/overlays/running-subagent-badge.ts +19 -0
- package/src/overlays/session-account-selector.ts +68 -0
- package/src/overlays/session-info-overlay.ts +96 -0
- package/src/overlays/session-observer-registry.ts +289 -0
- package/src/overlays/session-selector.ts +1137 -0
- package/src/overlays/settings-defs.ts +315 -0
- package/src/overlays/settings-selector.ts +1415 -0
- package/src/overlays/show-images-selector.ts +44 -0
- package/src/overlays/snapcompact-shape-preview-doc.md +14 -0
- package/src/overlays/snapcompact-shape-preview.ts +192 -0
- package/src/overlays/theme-selector.ts +62 -0
- package/src/overlays/thinking-selector.ts +51 -0
- package/src/overlays/tiny-title-download-progress.ts +102 -0
- package/src/overlays/tree-selector.ts +1121 -0
- package/src/overlays/usage-dashboard.ts +629 -0
- package/src/overlays/usage-display.ts +45 -0
- package/src/overlays/usage-row.ts +118 -0
- package/src/prompt/attachment-chips.ts +201 -0
- package/src/prompt/composer-attachments.ts +280 -0
- package/src/prompt/composer-cache.ts +304 -0
- package/src/prompt/composer.ts +934 -0
- package/src/prompt/custom-editor.ts +1392 -0
- package/src/prompt/data/emojis.json +1 -0
- package/src/prompt/editor-top-gap.ts +28 -0
- package/src/prompt/emoji-autocomplete.ts +285 -0
- package/src/prompt/github-ref-autocomplete.ts +75 -0
- package/src/prompt/gradient-highlight.ts +101 -0
- package/src/prompt/image-format.ts +28 -0
- package/src/prompt/image-references.ts +102 -0
- package/src/prompt/interactive-context-helpers.ts +126 -0
- package/src/prompt/internal-url-autocomplete.ts +155 -0
- package/src/prompt/macos-spelling.ts +456 -0
- package/src/prompt/magic-keyword-boundary.ts +23 -0
- package/src/prompt/magic-keywords.ts +42 -0
- package/src/prompt/markdown-prose.ts +245 -0
- package/src/prompt/model-mention-autocomplete.ts +94 -0
- package/src/prompt/model-mention-syntax.ts +41 -0
- package/src/prompt/orchestrate.ts +26 -0
- package/src/prompt/prompt-action-autocomplete.ts +337 -0
- package/src/prompt/queue-input.ts +132 -0
- package/src/prompt/skill-tokens.ts +36 -0
- package/src/prompt/tips.txt +27 -0
- package/src/prompt/tools-markdown.ts +29 -0
- package/src/prompt/ultrathink.ts +26 -0
- package/src/prompt/usage-amounts.ts +99 -0
- package/src/prompt/video.ts +47 -0
- package/src/prompt/welcome.ts +605 -0
- package/src/prompt/workflow.ts +26 -0
- package/src/render/code-cell.ts +244 -0
- package/src/render/file-list.ts +58 -0
- package/src/render/hyperlink.ts +192 -0
- package/src/render/index.ts +17 -0
- package/src/render/output-block.ts +267 -0
- package/src/render/output-pane.ts +299 -0
- package/src/render/render-utils.ts +1182 -0
- package/src/render/sixel.ts +69 -0
- package/src/render/status-line.ts +55 -0
- package/src/render/tool-card.ts +272 -0
- package/src/render/tree-list.ts +188 -0
- package/src/render/types.ts +19 -0
- package/src/render/utils.ts +109 -0
- package/src/render/width-aware-text.ts +60 -0
- package/src/setup/lazy.ts +17 -0
- package/src/setup/scenes/composer.ts +127 -0
- package/src/setup/scenes/glyph.ts +120 -0
- package/src/setup/scenes/model.ts +138 -0
- package/src/setup/scenes/outro.ts +24 -0
- package/src/setup/scenes/providers.ts +107 -0
- package/src/setup/scenes/sign-in.ts +373 -0
- package/src/setup/scenes/splash.ts +192 -0
- package/src/setup/scenes/theme.ts +343 -0
- package/src/setup/scenes/types.ts +111 -0
- package/src/setup/scenes/web-search.ts +165 -0
- package/src/setup/setup-version.ts +11 -0
- package/src/setup/startup-splash.ts +107 -0
- package/src/setup/wizard-overlay.ts +319 -0
- package/src/setup/wizard.ts +101 -0
- package/src/status-line/component.ts +3003 -0
- package/src/status-line/context-usage.ts +688 -0
- package/src/status-line/footer.ts +323 -0
- package/src/status-line/git-utils.ts +42 -0
- package/src/status-line/host.ts +97 -0
- package/src/status-line/index.ts +6 -0
- package/src/status-line/loop.ts +28 -0
- package/src/status-line/metrics.ts +94 -0
- package/src/status-line/presets.ts +107 -0
- package/src/status-line/schema.ts +57 -0
- package/src/status-line/segments.ts +934 -0
- package/src/status-line/separators.ts +55 -0
- package/src/status-line/types.ts +227 -0
- package/src/stdin-buffer.ts +4 -2
- package/src/theme/color.ts +95 -0
- package/src/theme/dark.json +95 -0
- package/src/theme/defaults/alabaster.json +93 -0
- package/src/theme/defaults/amethyst.json +96 -0
- package/src/theme/defaults/anthracite.json +93 -0
- package/src/theme/defaults/basalt.json +91 -0
- package/src/theme/defaults/birch.json +95 -0
- package/src/theme/defaults/dark-abyss.json +91 -0
- package/src/theme/defaults/dark-arctic.json +104 -0
- package/src/theme/defaults/dark-aurora.json +95 -0
- package/src/theme/defaults/dark-catppuccin.json +107 -0
- package/src/theme/defaults/dark-cavern.json +91 -0
- package/src/theme/defaults/dark-celestial.json +106 -0
- package/src/theme/defaults/dark-copper.json +95 -0
- package/src/theme/defaults/dark-cosmos.json +90 -0
- package/src/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/theme/defaults/dark-dracula.json +98 -0
- package/src/theme/defaults/dark-eclipse.json +91 -0
- package/src/theme/defaults/dark-ember.json +95 -0
- package/src/theme/defaults/dark-equinox.json +90 -0
- package/src/theme/defaults/dark-forest.json +96 -0
- package/src/theme/defaults/dark-github.json +105 -0
- package/src/theme/defaults/dark-gruvbox.json +112 -0
- package/src/theme/defaults/dark-lavender.json +95 -0
- package/src/theme/defaults/dark-lunar.json +89 -0
- package/src/theme/defaults/dark-midnight.json +95 -0
- package/src/theme/defaults/dark-monochrome.json +94 -0
- package/src/theme/defaults/dark-monokai.json +98 -0
- package/src/theme/defaults/dark-nebula.json +90 -0
- package/src/theme/defaults/dark-nord.json +98 -0
- package/src/theme/defaults/dark-ocean.json +101 -0
- package/src/theme/defaults/dark-one.json +100 -0
- package/src/theme/defaults/dark-poimandres.json +115 -0
- package/src/theme/defaults/dark-rainforest.json +91 -0
- package/src/theme/defaults/dark-reef.json +91 -0
- package/src/theme/defaults/dark-retro.json +92 -0
- package/src/theme/defaults/dark-rose-pine.json +96 -0
- package/src/theme/defaults/dark-sakura.json +95 -0
- package/src/theme/defaults/dark-slate.json +95 -0
- package/src/theme/defaults/dark-solarized.json +97 -0
- package/src/theme/defaults/dark-solstice.json +90 -0
- package/src/theme/defaults/dark-starfall.json +91 -0
- package/src/theme/defaults/dark-sunset.json +99 -0
- package/src/theme/defaults/dark-swamp.json +90 -0
- package/src/theme/defaults/dark-synthwave.json +103 -0
- package/src/theme/defaults/dark-taiga.json +91 -0
- package/src/theme/defaults/dark-terminal.json +95 -0
- package/src/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/theme/defaults/dark-tundra.json +91 -0
- package/src/theme/defaults/dark-twilight.json +91 -0
- package/src/theme/defaults/dark-volcanic.json +91 -0
- package/src/theme/defaults/graphite.json +92 -0
- package/src/theme/defaults/index.ts +201 -0
- package/src/theme/defaults/light-arctic.json +107 -0
- package/src/theme/defaults/light-aurora-day.json +91 -0
- package/src/theme/defaults/light-canyon.json +91 -0
- package/src/theme/defaults/light-catppuccin.json +106 -0
- package/src/theme/defaults/light-cirrus.json +90 -0
- package/src/theme/defaults/light-coral.json +95 -0
- package/src/theme/defaults/light-cyberpunk.json +96 -0
- package/src/theme/defaults/light-dawn.json +90 -0
- package/src/theme/defaults/light-dunes.json +91 -0
- package/src/theme/defaults/light-eucalyptus.json +95 -0
- package/src/theme/defaults/light-forest.json +100 -0
- package/src/theme/defaults/light-frost.json +95 -0
- package/src/theme/defaults/light-github.json +115 -0
- package/src/theme/defaults/light-glacier.json +91 -0
- package/src/theme/defaults/light-gruvbox.json +108 -0
- package/src/theme/defaults/light-haze.json +90 -0
- package/src/theme/defaults/light-honeycomb.json +95 -0
- package/src/theme/defaults/light-lagoon.json +91 -0
- package/src/theme/defaults/light-lavender.json +95 -0
- package/src/theme/defaults/light-meadow.json +91 -0
- package/src/theme/defaults/light-mint.json +95 -0
- package/src/theme/defaults/light-monochrome.json +101 -0
- package/src/theme/defaults/light-ocean.json +99 -0
- package/src/theme/defaults/light-one.json +99 -0
- package/src/theme/defaults/light-opal.json +91 -0
- package/src/theme/defaults/light-orchard.json +91 -0
- package/src/theme/defaults/light-paper.json +95 -0
- package/src/theme/defaults/light-poimandres.json +115 -0
- package/src/theme/defaults/light-prism.json +90 -0
- package/src/theme/defaults/light-retro.json +98 -0
- package/src/theme/defaults/light-sand.json +95 -0
- package/src/theme/defaults/light-savanna.json +91 -0
- package/src/theme/defaults/light-solarized.json +102 -0
- package/src/theme/defaults/light-soleil.json +90 -0
- package/src/theme/defaults/light-sunset.json +99 -0
- package/src/theme/defaults/light-synthwave.json +98 -0
- package/src/theme/defaults/light-tokyo-night.json +111 -0
- package/src/theme/defaults/light-wetland.json +91 -0
- package/src/theme/defaults/light-zenith.json +89 -0
- package/src/theme/defaults/limestone.json +94 -0
- package/src/theme/defaults/mahogany.json +97 -0
- package/src/theme/defaults/marble.json +93 -0
- package/src/theme/defaults/obsidian.json +91 -0
- package/src/theme/defaults/onyx.json +91 -0
- package/src/theme/defaults/pearl.json +93 -0
- package/src/theme/defaults/porcelain.json +91 -0
- package/src/theme/defaults/quartz.json +96 -0
- package/src/theme/defaults/sandstone.json +95 -0
- package/src/theme/defaults/titanium.json +90 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/light.json +93 -0
- package/src/theme/loader.ts +200 -0
- package/src/theme/mermaid-cache.ts +92 -0
- package/src/theme/schema-validation.ts +119 -0
- package/src/theme/schema.ts +168 -0
- package/src/theme/session-color.ts +253 -0
- package/src/theme/shimmer.ts +307 -0
- package/src/theme/symbols.ts +1433 -0
- package/src/theme/theme-class.ts +787 -0
- package/src/theme/theme-schema.json +463 -0
- package/src/theme/theme.ts +810 -0
- package/src/theme/tui-adapters.ts +339 -0
- package/src/thinking.ts +380 -0
- package/src/tools/agent-tree.ts +128 -0
- package/src/tools/ask.ts +482 -0
- package/src/tools/ast-edit.ts +210 -0
- package/src/tools/ast-grep.ts +171 -0
- package/src/tools/autoresearch.ts +287 -0
- package/src/tools/bash-interactive.ts +295 -0
- package/src/tools/bash.ts +468 -0
- package/src/tools/conflict-detect.ts +77 -0
- package/src/tools/debug.ts +167 -0
- package/src/tools/default-renderer.ts +172 -0
- package/src/tools/edit.ts +1258 -0
- package/src/tools/eval-format/code-mode-declarations.ts +61 -0
- package/src/tools/eval-format/index.ts +16 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval.ts +835 -0
- package/src/tools/fetch.ts +226 -0
- package/src/tools/gh-format.ts +18 -0
- package/src/tools/github.ts +686 -0
- package/src/tools/glob.ts +213 -0
- package/src/tools/goal.ts +153 -0
- package/src/tools/grep.ts +385 -0
- package/src/tools/grouped-file-output.ts +211 -0
- package/src/tools/hashline-format.ts +59 -0
- package/src/tools/hub.ts +1488 -0
- package/src/tools/index.ts +71 -0
- package/src/tools/json-tree.ts +388 -0
- package/src/tools/line-ranges.ts +98 -0
- package/src/tools/list-limit.ts +43 -0
- package/src/tools/lsp.ts +702 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/mcp.ts +309 -0
- package/src/tools/memory.ts +220 -0
- package/src/tools/output-meta.ts +367 -0
- package/src/tools/read.ts +628 -0
- package/src/tools/renderer.ts +107 -0
- package/src/tools/report-tool-issue.ts +13 -0
- package/src/tools/resolve.ts +139 -0
- package/src/tools/streaming-output.ts +1576 -0
- package/src/tools/subprocess.ts +39 -0
- package/src/tools/task-repair-args.ts +118 -0
- package/src/tools/task-yield-assembly.ts +147 -0
- package/src/tools/task.ts +2110 -0
- package/src/tools/terminal-output.ts +153 -0
- package/src/tools/think.ts +28 -0
- package/src/tools/todo.ts +580 -0
- package/src/tools/tool-errors.ts +15 -0
- package/src/tools/vibe.ts +420 -0
- package/src/tools/web-search.ts +409 -0
- package/src/tools/write.ts +511 -0
- package/src/tools/xd-url.ts +28 -0
- package/src/tools/xdev.ts +201 -0
- package/src/tui.ts +57 -4
- package/src/utils.ts +8 -0
package/src/tools/hub.ts
ADDED
|
@@ -0,0 +1,1488 @@
|
|
|
1
|
+
import { styleTerminalRow } from "./terminal-output";
|
|
2
|
+
import { TERMINAL_STATES } from "../apps/ps-data";
|
|
3
|
+
import type { Component } from "../tui";
|
|
4
|
+
import { Text } from "../components/text";
|
|
5
|
+
import { visibleWidth } from "../utils";
|
|
6
|
+
import { formatAge, pluralize } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { shimmerEnabled, shimmerText } from "../theme/shimmer";
|
|
8
|
+
import type { Theme, ThemeColor } from "../theme/theme";
|
|
9
|
+
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../render/index";
|
|
10
|
+
import { framedToolCard } from "../render/tool-card";
|
|
11
|
+
import { formatArtifactErrorNotice, stripOutputNotice, type OutputMeta } from "./output-meta";
|
|
12
|
+
import {
|
|
13
|
+
FEED_MODEL_BADGE_WIDTH,
|
|
14
|
+
formatBadge,
|
|
15
|
+
formatDuration,
|
|
16
|
+
formatEmptyMessage,
|
|
17
|
+
formatFeedModelBadge,
|
|
18
|
+
formatStatusIcon,
|
|
19
|
+
getPreviewLines,
|
|
20
|
+
isFeedModelBadgeEnabled,
|
|
21
|
+
PREVIEW_LIMITS,
|
|
22
|
+
replaceTabs,
|
|
23
|
+
type ToolUIColor,
|
|
24
|
+
type ToolUIStatus,
|
|
25
|
+
capPreviewLines,
|
|
26
|
+
cappedHeadLines,
|
|
27
|
+
createCachedComponent,
|
|
28
|
+
DEFAULT_TERMINAL_PREVIEW_LINES,
|
|
29
|
+
formatMoreItems,
|
|
30
|
+
formatExpandHint,
|
|
31
|
+
previewLine,
|
|
32
|
+
TRUNCATE_LENGTHS,
|
|
33
|
+
shortenPath,
|
|
34
|
+
formatErrorDetail,
|
|
35
|
+
type ConfiguredThinkingLevel,
|
|
36
|
+
} from "../render/render-utils";
|
|
37
|
+
import type { StructuredSubagentOutput } from "./task";
|
|
38
|
+
import type { RenderResultOptions, ToolRenderer, ToolActivitySummary } from "./renderer";
|
|
39
|
+
|
|
40
|
+
/** Whether a wait snapshot contains only running jobs and no cancellations. */
|
|
41
|
+
export function isWaitingPollDetails(details: unknown): boolean {
|
|
42
|
+
const d = details as CoordinationDetails | undefined;
|
|
43
|
+
if (!d || !Array.isArray(d.jobs) || d.jobs.length === 0) return false;
|
|
44
|
+
if (d.cancelled?.length) return false;
|
|
45
|
+
return d.jobs.every(job => job?.status === "running");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Hub operations: messaging (`send`/`wait`/`inbox`/`list`), jobs
|
|
49
|
+
* (`wait`/`cancel`/`jobs`), and process supervision (`start`/`ps`/`logs`/
|
|
50
|
+
* `stop`/`restart`/`describe`, plus `send`/`wait` when they carry `name`).
|
|
51
|
+
*/
|
|
52
|
+
export type HubOp =
|
|
53
|
+
| "send"
|
|
54
|
+
| "wait"
|
|
55
|
+
| "inbox"
|
|
56
|
+
| "list"
|
|
57
|
+
| "jobs"
|
|
58
|
+
| "cancel"
|
|
59
|
+
| "start"
|
|
60
|
+
| "ps"
|
|
61
|
+
| "logs"
|
|
62
|
+
| "stop"
|
|
63
|
+
| "restart"
|
|
64
|
+
| "describe";
|
|
65
|
+
|
|
66
|
+
/** Peer row surfaced by `op:"list"`. */
|
|
67
|
+
export interface HubPeerInfo {
|
|
68
|
+
id: string;
|
|
69
|
+
displayName: string;
|
|
70
|
+
kind: string;
|
|
71
|
+
status: string;
|
|
72
|
+
parentId?: string;
|
|
73
|
+
unread: number;
|
|
74
|
+
lastActivity: number;
|
|
75
|
+
activity?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Status values `op:"list"` can filter on. Advisor is a kind, not a status. */
|
|
79
|
+
export type HubListStatus = "running" | "idle" | "parked";
|
|
80
|
+
|
|
81
|
+
/** Model-facing roster bounds shared by the hub schema and executor. */
|
|
82
|
+
export const DEFAULT_HUB_LIST_LIMIT = 32;
|
|
83
|
+
|
|
84
|
+
/** Maximum number of peers in a hub roster page. */
|
|
85
|
+
export const MAX_HUB_LIST_LIMIT = 100;
|
|
86
|
+
|
|
87
|
+
/** Addressable roster tallies always returned by `op:"list"`. */
|
|
88
|
+
export interface HubRosterCounts {
|
|
89
|
+
running: number;
|
|
90
|
+
idle: number;
|
|
91
|
+
parked: number;
|
|
92
|
+
shown: number;
|
|
93
|
+
truncated: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Background-job row surfaced by `wait`/`cancel`/`jobs` results. */
|
|
97
|
+
export interface JobSnapshot {
|
|
98
|
+
id: string;
|
|
99
|
+
type: "bash" | "task" | "eval";
|
|
100
|
+
status: "running" | "completed" | "failed" | "cancelled";
|
|
101
|
+
label: string;
|
|
102
|
+
durationMs: number;
|
|
103
|
+
/** Effective task model selector, including an explicit reasoning suffix when configured. */
|
|
104
|
+
resolvedModel?: string;
|
|
105
|
+
/** Provider/id including routing, with no added thinking suffix. */
|
|
106
|
+
resolvedModelIdentity?: string;
|
|
107
|
+
/** Explicit thinking metadata, independent of the model identity. */
|
|
108
|
+
resolvedThinkingLevel?: ConfiguredThinkingLevel;
|
|
109
|
+
/** True when the task progress reports an attached live advisor. */
|
|
110
|
+
advisor?: boolean;
|
|
111
|
+
resultText?: string;
|
|
112
|
+
errorText?: string;
|
|
113
|
+
/** Source-output metadata retained for per-job warnings and persisted row rendering. */
|
|
114
|
+
meta?: OutputMeta;
|
|
115
|
+
/** Capture error in historical snapshots; new snapshots store source metadata in `meta`. */
|
|
116
|
+
artifactError?: OutputMeta["artifactError"];
|
|
117
|
+
structured?: StructuredSubagentOutput;
|
|
118
|
+
/**
|
|
119
|
+
* `agent://<id>` handle backing this job's artifacts — the job-row's
|
|
120
|
+
* registry `agentId` when the manager disambiguated a requested job id
|
|
121
|
+
* on collision, else the job id itself. See {@link AsyncJob.agentId}.
|
|
122
|
+
*/
|
|
123
|
+
agentUrlId?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Outcome of cancelling one background job. */
|
|
127
|
+
export type CancelStatus = "cancelled" | "not_found" | "already_completed";
|
|
128
|
+
|
|
129
|
+
/** Cancellation outcome and its model-facing explanation. */
|
|
130
|
+
export interface CancelOutcome {
|
|
131
|
+
id: string;
|
|
132
|
+
status: CancelStatus;
|
|
133
|
+
message: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A live subagent from the AgentRegistry that has no backing job in the
|
|
138
|
+
* AsyncJobManager — e.g. an idle agent woken (or a parked agent revived) via
|
|
139
|
+
* a hub message, or a spawn owned by another agent. Surfaced by `jobs` and
|
|
140
|
+
* empty-wait snapshots so the hub's picture matches the UI's running-agent
|
|
141
|
+
* count.
|
|
142
|
+
*/
|
|
143
|
+
export interface AgentActivitySnapshot {
|
|
144
|
+
id: string;
|
|
145
|
+
parentId?: string;
|
|
146
|
+
/** Latest activity gist recorded by the registry (display-only). */
|
|
147
|
+
activity?: string;
|
|
148
|
+
/** Time since the agent was registered. */
|
|
149
|
+
ageMs: number;
|
|
150
|
+
/**
|
|
151
|
+
* Whether an attached session corroborates the `running` claim. False marks
|
|
152
|
+
* a ref that says `running` with no turn in flight — either a spawn still
|
|
153
|
+
* wiring up or a stale registration that `hub cancel <id>` clears (#8634).
|
|
154
|
+
*/
|
|
155
|
+
live: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Acceptance time of the agent's final result, when the registry recorded
|
|
158
|
+
* one. With `live: false` this is an accepted run the parent may cancel
|
|
159
|
+
* instead of waiting on (#11079).
|
|
160
|
+
*/
|
|
161
|
+
acceptedAt?: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Result details for messaging and job ops; fields are disjoint per op. */
|
|
165
|
+
export interface CoordinationDetails {
|
|
166
|
+
meta?: OutputMeta;
|
|
167
|
+
op: HubOp;
|
|
168
|
+
from?: string;
|
|
169
|
+
to?: string;
|
|
170
|
+
receipts?: IrcDeliveryReceipt[];
|
|
171
|
+
/** Message consumed by `wait` / `send await:true`; null when the wait timed out. */
|
|
172
|
+
waited?: IrcMessage | null;
|
|
173
|
+
inbox?: IrcMessage[];
|
|
174
|
+
peers?: HubPeerInfo[];
|
|
175
|
+
/** Present on `op:"list"`: addressable running/idle/parked plus page size. */
|
|
176
|
+
counts?: HubRosterCounts;
|
|
177
|
+
jobs?: JobSnapshot[];
|
|
178
|
+
cancelled?: { id: string; status: CancelStatus }[];
|
|
179
|
+
/** Running subagents not represented by a job row in this result. */
|
|
180
|
+
agents?: AgentActivitySnapshot[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Hub result details: coordination snapshots or launch (process) state. */
|
|
184
|
+
export type HubDetails = CoordinationDetails | LaunchToolDetails;
|
|
185
|
+
|
|
186
|
+
/** Partially-streamed hub call arguments, as seen by the renderers. */
|
|
187
|
+
export type HubRenderArgs = {
|
|
188
|
+
op?: string;
|
|
189
|
+
to?: string;
|
|
190
|
+
message?: string;
|
|
191
|
+
replyTo?: string;
|
|
192
|
+
await?: boolean;
|
|
193
|
+
from?: string;
|
|
194
|
+
peek?: boolean;
|
|
195
|
+
ids?: string[];
|
|
196
|
+
} & Partial<Omit<LaunchParams, "op">>;
|
|
197
|
+
/** Stable lifecycle states exposed by the launch tool. */
|
|
198
|
+
export type DaemonState = "starting" | "running" | "ready" | "restarting" | "stopping" | "exited" | "failed";
|
|
199
|
+
|
|
200
|
+
/** Restart behavior applied after an unexpected daemon exit. */
|
|
201
|
+
export type DaemonRestartPolicy = "no" | "on-failure" | "always";
|
|
202
|
+
|
|
203
|
+
/** Readiness conditions; every configured condition must pass. */
|
|
204
|
+
export interface DaemonReadySpec {
|
|
205
|
+
log?: string;
|
|
206
|
+
port?: number;
|
|
207
|
+
host?: string;
|
|
208
|
+
timeoutMs: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Immutable launch specification retained for restart and inspection. */
|
|
212
|
+
export interface DaemonSpec {
|
|
213
|
+
name: string;
|
|
214
|
+
application: string;
|
|
215
|
+
args: string[];
|
|
216
|
+
env: Record<string, string>;
|
|
217
|
+
cwd: string;
|
|
218
|
+
pty: boolean;
|
|
219
|
+
ready?: DaemonReadySpec;
|
|
220
|
+
restart: DaemonRestartPolicy;
|
|
221
|
+
persist: boolean;
|
|
222
|
+
detached: boolean;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Serializable daemon state visible to every client in one broker scope. */
|
|
226
|
+
export interface DaemonSnapshot {
|
|
227
|
+
name: string;
|
|
228
|
+
id: string;
|
|
229
|
+
state: DaemonState;
|
|
230
|
+
pid?: number;
|
|
231
|
+
createdAt: number;
|
|
232
|
+
startedAt: number;
|
|
233
|
+
readyAt?: number;
|
|
234
|
+
exitedAt?: number;
|
|
235
|
+
exitCode?: number;
|
|
236
|
+
exitReason?: string;
|
|
237
|
+
restartCount: number;
|
|
238
|
+
outputBytes: number;
|
|
239
|
+
owner?: string;
|
|
240
|
+
readyMatch?: string;
|
|
241
|
+
/** Readiness conditions still unmet while `state` is `starting`; absent once ready or without a ready spec. */
|
|
242
|
+
readyPending?: ("log" | "port")[];
|
|
243
|
+
persist: boolean;
|
|
244
|
+
detached: boolean;
|
|
245
|
+
}
|
|
246
|
+
/** Serializable peer message retained in hub result snapshots. */
|
|
247
|
+
export interface IrcMessage {
|
|
248
|
+
id: string;
|
|
249
|
+
/** Sender agent id. */
|
|
250
|
+
from: string;
|
|
251
|
+
/** Recipient agent id (resolved; "all" is expanded by the tool, not stored). */
|
|
252
|
+
to: string;
|
|
253
|
+
body: string;
|
|
254
|
+
ts: number;
|
|
255
|
+
/** Message id being answered. */
|
|
256
|
+
replyTo?: string;
|
|
257
|
+
/**
|
|
258
|
+
* Automated wake-turn relay of a woken subagent's stop output (task executor
|
|
259
|
+
* `relayWakeTurnOutput`). Relays are answers, never wake sources: the
|
|
260
|
+
* recipient's own wake-turn relay must skip them or two idle peers
|
|
261
|
+
* ping-pong forever.
|
|
262
|
+
*/
|
|
263
|
+
wakeRelay?: boolean;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Delivery outcome for one peer recipient. */
|
|
267
|
+
export interface IrcDeliveryReceipt {
|
|
268
|
+
to: string;
|
|
269
|
+
outcome: "injected" | "woken" | "revived" | "failed";
|
|
270
|
+
error?: string;
|
|
271
|
+
}
|
|
272
|
+
/** Broker-facing launch parameters; the hub adapts its `ps` op to `list` before calling in. */
|
|
273
|
+
export interface LaunchParams {
|
|
274
|
+
op: "start" | "list" | "logs" | "wait" | "send" | "stop" | "restart" | "describe";
|
|
275
|
+
name?: string;
|
|
276
|
+
application?: string;
|
|
277
|
+
args?: string[];
|
|
278
|
+
env?: Record<string, string>;
|
|
279
|
+
cwd?: string;
|
|
280
|
+
pty?: boolean;
|
|
281
|
+
ready?: { log?: string; port?: number; host?: string; timeout?: number };
|
|
282
|
+
restart?: "no" | "on-failure" | "always";
|
|
283
|
+
persist?: boolean;
|
|
284
|
+
detached?: boolean;
|
|
285
|
+
lines?: number;
|
|
286
|
+
head?: boolean;
|
|
287
|
+
grep?: string;
|
|
288
|
+
follow?: boolean;
|
|
289
|
+
cursor?: number;
|
|
290
|
+
for?: "ready" | "exit";
|
|
291
|
+
pattern?: string;
|
|
292
|
+
text?: string;
|
|
293
|
+
enter?: boolean;
|
|
294
|
+
keys?: string[];
|
|
295
|
+
signal?: "SIGINT" | "SIGTERM" | "SIGHUP" | "SIGQUIT" | "SIGKILL";
|
|
296
|
+
timeout?: number;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Structured launch state retained for compact TUI rendering. */
|
|
300
|
+
export interface LaunchToolDetails {
|
|
301
|
+
op: LaunchParams["op"];
|
|
302
|
+
daemon?: DaemonSnapshot;
|
|
303
|
+
daemons?: DaemonSnapshot[];
|
|
304
|
+
cursor?: number;
|
|
305
|
+
timedOut?: boolean;
|
|
306
|
+
/** logs: daemon lifecycle state at read time. */
|
|
307
|
+
state?: DaemonState;
|
|
308
|
+
/** logs: virtual terminal rows for display; model-facing content remains sanitized text. */
|
|
309
|
+
terminalRows?: string[];
|
|
310
|
+
/** wait: output line that satisfied the pattern. */
|
|
311
|
+
matched?: string;
|
|
312
|
+
/** describe: immutable launch spec backing the command/cwd detail lines. */
|
|
313
|
+
spec?: DaemonSpec;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Human sentences for the readiness conditions still unmet, e.g.
|
|
318
|
+
* `port 5173 on 127.0.0.1 never accepted connections`. `ready` (from the start
|
|
319
|
+
* params) adds the concrete pattern/port; absent it falls back to generic labels.
|
|
320
|
+
*/
|
|
321
|
+
export function readyPendingSummary(daemon: DaemonSnapshot, ready?: LaunchParams["ready"]): string[] {
|
|
322
|
+
const parts: string[] = [];
|
|
323
|
+
for (const condition of daemon.readyPending ?? []) {
|
|
324
|
+
if (condition === "log") {
|
|
325
|
+
parts.push(ready?.log ? `log pattern /${ready.log}/ never matched` : "the log pattern never matched");
|
|
326
|
+
} else {
|
|
327
|
+
parts.push(
|
|
328
|
+
ready?.port !== undefined
|
|
329
|
+
? `port ${ready.port} on ${ready.host ?? "127.0.0.1"} never accepted connections`
|
|
330
|
+
: "the port never accepted connections",
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return parts;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* What a timed-out `wait` was actually blocked on: the output `pattern`, the
|
|
339
|
+
* process exiting (`for: "exit"`, the default), or the unmet readiness
|
|
340
|
+
* conditions (`for: "ready"`). Never reports readiness for an exit/pattern
|
|
341
|
+
* wait — the process may well be ready and simply still running.
|
|
342
|
+
*/
|
|
343
|
+
export function waitPendingSummary(daemon: DaemonSnapshot, params: Pick<LaunchParams, "for" | "pattern">): string[] {
|
|
344
|
+
if (params.pattern) return [`output pattern /${params.pattern}/ never matched`];
|
|
345
|
+
if ((params.for ?? "exit") === "exit") return [`process exit (still ${daemon.state})`];
|
|
346
|
+
const pending = readyPendingSummary(daemon);
|
|
347
|
+
return pending.length > 0 ? pending : [`readiness (still ${daemon.state})`];
|
|
348
|
+
}
|
|
349
|
+
/** Hub roster ordering (running before idle before parked) shared with the child prompt's live-row cap. */
|
|
350
|
+
export const LIST_STATUS_ORDER: Record<string, number> = { running: 0, idle: 1, parked: 2 };
|
|
351
|
+
|
|
352
|
+
// =============================================================================
|
|
353
|
+
// TUI Renderer (jobs half)
|
|
354
|
+
// =============================================================================
|
|
355
|
+
|
|
356
|
+
interface JobRenderArgs {
|
|
357
|
+
poll?: string[];
|
|
358
|
+
cancel?: string[];
|
|
359
|
+
list?: boolean;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Hub args → legacy job-renderer arg shape, preserving the exact frame titles. */
|
|
363
|
+
function toJobRenderArgs(args: HubRenderArgs | undefined): JobRenderArgs | undefined {
|
|
364
|
+
if (!args) return undefined;
|
|
365
|
+
switch (args.op) {
|
|
366
|
+
case "wait":
|
|
367
|
+
return { poll: args.ids };
|
|
368
|
+
case "cancel":
|
|
369
|
+
return { cancel: args.ids ?? [] };
|
|
370
|
+
case "jobs":
|
|
371
|
+
return { list: true };
|
|
372
|
+
default:
|
|
373
|
+
return {};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const LABEL_MAX_WIDTH = 60;
|
|
378
|
+
const PREVIEW_LINES_COLLAPSED = 1;
|
|
379
|
+
const PREVIEW_LINES_EXPANDED = 4;
|
|
380
|
+
const LABEL_LINES_COLLAPSED = 1;
|
|
381
|
+
const LABEL_LINES_EXPANDED = 3;
|
|
382
|
+
const PREVIEW_LINE_WIDTH = 80;
|
|
383
|
+
|
|
384
|
+
function statusToIcon(status: JobSnapshot["status"]): ToolUIStatus {
|
|
385
|
+
switch (status) {
|
|
386
|
+
case "completed":
|
|
387
|
+
return "done";
|
|
388
|
+
case "failed":
|
|
389
|
+
return "error";
|
|
390
|
+
case "cancelled":
|
|
391
|
+
return "aborted";
|
|
392
|
+
case "running":
|
|
393
|
+
return "running";
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function statusToColor(status: JobSnapshot["status"]): ToolUIColor {
|
|
398
|
+
switch (status) {
|
|
399
|
+
case "completed":
|
|
400
|
+
return "success";
|
|
401
|
+
case "failed":
|
|
402
|
+
return "error";
|
|
403
|
+
case "cancelled":
|
|
404
|
+
return "warning";
|
|
405
|
+
case "running":
|
|
406
|
+
return "accent";
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Task job results are delivered in the model-facing `<task-result>` envelope
|
|
412
|
+
* (prompts/tools/task-summary.md) so the parent agent can parse status and the
|
|
413
|
+
* `agent://` pointer. The wrapper markup is noise to a human — preview the
|
|
414
|
+
* inner <output>/<preview> body instead.
|
|
415
|
+
*/
|
|
416
|
+
function stripTaskResultEnvelope(text: string): string {
|
|
417
|
+
if (!text.startsWith("<task-result")) return text;
|
|
418
|
+
const body = /<(output|preview)(?:\s[^>]*)?>\n?([\s\S]*?)\n?<\/\1>/.exec(text)?.[2];
|
|
419
|
+
return body?.trim() || text;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Pretty-printed JSON output wastes the collapsed one-line preview on a lone
|
|
424
|
+
* "{" — flatten structured-looking bodies onto a single line. Slice first:
|
|
425
|
+
* downstream truncation keeps at most a few hundred columns, so collapsing
|
|
426
|
+
* whitespace across a multi-KB body would be pure waste.
|
|
427
|
+
*/
|
|
428
|
+
function flattenStructuredPreview(text: string): string {
|
|
429
|
+
const first = text[0];
|
|
430
|
+
if (first !== "{" && first !== "[") return text;
|
|
431
|
+
return text.slice(0, PREVIEW_LINES_EXPANDED * PREVIEW_LINE_WIDTH * 2).replace(/\s+/g, " ");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function describeTarget(args: JobRenderArgs | undefined): string {
|
|
435
|
+
if (args?.list) return "background jobs";
|
|
436
|
+
const poll = args?.poll ?? [];
|
|
437
|
+
const cancel = args?.cancel ?? [];
|
|
438
|
+
const parts: string[] = [];
|
|
439
|
+
if (cancel.length > 0) {
|
|
440
|
+
parts.push(cancel.length === 1 ? `cancel ${cancel[0]}` : `cancel ${cancel.length} jobs`);
|
|
441
|
+
}
|
|
442
|
+
if (poll.length > 0) {
|
|
443
|
+
parts.push(poll.length === 1 ? `poll ${poll[0]}` : `poll ${poll.length} jobs`);
|
|
444
|
+
}
|
|
445
|
+
if (parts.length === 0) return "all running jobs";
|
|
446
|
+
return parts.join(", ");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Pending-call frame for job ops (wait/cancel/jobs). */
|
|
450
|
+
export function jobsRenderCall(args: HubRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
451
|
+
const text = renderStatusLine({ icon: "pending", title: describeTarget(toJobRenderArgs(args)) || "Job" }, uiTheme);
|
|
452
|
+
return new Text(text, 0, 0);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Result frame for job snapshots (wait/cancel/jobs and the agents roster). */
|
|
456
|
+
export function jobsRenderResult(
|
|
457
|
+
result: { content: Array<{ type: string; text?: string }>; details?: CoordinationDetails; isError?: boolean },
|
|
458
|
+
options: RenderResultOptions,
|
|
459
|
+
uiTheme: Theme,
|
|
460
|
+
hubArgs?: HubRenderArgs,
|
|
461
|
+
): Component {
|
|
462
|
+
const args = toJobRenderArgs(hubArgs);
|
|
463
|
+
let jobs = result.details?.jobs ?? [];
|
|
464
|
+
const agents = result.details?.agents ?? [];
|
|
465
|
+
|
|
466
|
+
if (jobs.length === 0 && agents.length === 0) {
|
|
467
|
+
const fallback = result.content?.find(c => c.type === "text")?.text || "No jobs to process";
|
|
468
|
+
const header = renderStatusLine({ icon: "warning", title: describeTarget(args) || "Job" }, uiTheme);
|
|
469
|
+
return new Text([header, formatEmptyMessage(fallback, uiTheme)].join("\n"), 0, 0);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const isPollCall = args ? !args.list && (!args.cancel || args.cancel.length === 0 || args.poll !== undefined) : true;
|
|
473
|
+
|
|
474
|
+
// Agent-carrying results (jobs snapshot / empty-wait roster) are real
|
|
475
|
+
// snapshots, not displaceable waiting frames — only agentless waits
|
|
476
|
+
// collapse their still-running rows once sealed.
|
|
477
|
+
if (!options.isPartial && isPollCall && agents.length === 0) {
|
|
478
|
+
jobs = jobs.filter(job => job.status !== "running");
|
|
479
|
+
if (jobs.length === 0) {
|
|
480
|
+
return new Text("", 0, 0);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const counts = { completed: 0, failed: 0, cancelled: 0, running: 0 };
|
|
485
|
+
for (const job of jobs) counts[job.status]++;
|
|
486
|
+
|
|
487
|
+
// The title already carries the running count, so meta lists only the
|
|
488
|
+
// settled categories — "waiting on 19 of 19 · 19 running" read awkward.
|
|
489
|
+
const meta: string[] = [];
|
|
490
|
+
if (counts.completed > 0) meta.push(uiTheme.fg("success", `${counts.completed} done`));
|
|
491
|
+
if (counts.failed > 0) meta.push(uiTheme.fg("error", `${counts.failed} failed`));
|
|
492
|
+
if (counts.cancelled > 0) meta.push(uiTheme.fg("warning", `${counts.cancelled} cancelled`));
|
|
493
|
+
if (agents.length > 0 && jobs.length > 0) {
|
|
494
|
+
meta.push(uiTheme.fg("accent", `${agents.length} agent${agents.length === 1 ? "" : "s"}`));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const headerIcon: ToolUIStatus =
|
|
498
|
+
counts.failed > 0 ? "warning" : counts.running > 0 || agents.length > 0 ? "info" : "success";
|
|
499
|
+
const jobsNoun = jobs.length === 1 ? "job" : "jobs";
|
|
500
|
+
const description =
|
|
501
|
+
jobs.length === 0
|
|
502
|
+
? `${agents.length} running agent${agents.length === 1 ? "" : "s"} — no jobs`
|
|
503
|
+
: counts.running > 0
|
|
504
|
+
? counts.running === jobs.length
|
|
505
|
+
? `waiting on ${jobs.length} ${jobsNoun}`
|
|
506
|
+
: `waiting on ${counts.running} of ${jobs.length} ${jobsNoun}`
|
|
507
|
+
: `${jobs.length} ${jobsNoun} settled`;
|
|
508
|
+
|
|
509
|
+
const header = renderStatusLine(
|
|
510
|
+
{
|
|
511
|
+
icon: headerIcon,
|
|
512
|
+
spinnerFrame: counts.running > 0 || agents.length > 0 ? options.spinnerFrame : undefined,
|
|
513
|
+
title: description,
|
|
514
|
+
meta,
|
|
515
|
+
},
|
|
516
|
+
uiTheme,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
const outputMeta = result.details?.meta;
|
|
520
|
+
// Historical snapshots promoted a job failure to the root. Render it there
|
|
521
|
+
// only when no row identifies that failure; new report capture errors are distinct.
|
|
522
|
+
const aggregateArtifactError =
|
|
523
|
+
outputMeta?.artifactError &&
|
|
524
|
+
(outputMeta.source?.type === "report" ||
|
|
525
|
+
!jobs.some(job => (job.meta?.artifactError ?? job.artifactError) === outputMeta.artifactError))
|
|
526
|
+
? outputMeta.artifactError
|
|
527
|
+
: undefined;
|
|
528
|
+
|
|
529
|
+
// Sort: running first (so user sees what's still pending), then failed, then completed/cancelled.
|
|
530
|
+
const statusOrder: Record<JobSnapshot["status"], number> = {
|
|
531
|
+
running: 0,
|
|
532
|
+
failed: 1,
|
|
533
|
+
cancelled: 2,
|
|
534
|
+
completed: 3,
|
|
535
|
+
};
|
|
536
|
+
const sortedJobs = [...jobs].sort((a, b) => {
|
|
537
|
+
const diff = statusOrder[a.status] - statusOrder[b.status];
|
|
538
|
+
if (diff !== 0) return diff;
|
|
539
|
+
return b.durationMs - a.durationMs;
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
let cached: RenderCache | undefined;
|
|
543
|
+
return {
|
|
544
|
+
render(width: number): readonly string[] {
|
|
545
|
+
const expanded = options.expanded;
|
|
546
|
+
const spinnerFrame = options.spinnerFrame ?? 0;
|
|
547
|
+
// Running-job labels shimmer while the wait block is live; the band
|
|
548
|
+
// phase is Date.now()-sampled at render time, so serving cached bytes
|
|
549
|
+
// would pin it to the ~12.5fps spinner-glyph cadence instead of the
|
|
550
|
+
// 30fps redraw. Bypass the cache while any row animates, and key on
|
|
551
|
+
// the animation state so a sealed block never hits stale shimmered
|
|
552
|
+
// bytes (spinnerFrame falls back to 0 on both sides of the seal).
|
|
553
|
+
const shimmerActive = counts.running > 0 && options.spinnerFrame !== undefined && shimmerEnabled();
|
|
554
|
+
const showModelBadge = isFeedModelBadgeEnabled();
|
|
555
|
+
const key = new Hasher()
|
|
556
|
+
.bool(expanded)
|
|
557
|
+
.u32(width)
|
|
558
|
+
.u32(spinnerFrame)
|
|
559
|
+
.bool(shimmerActive)
|
|
560
|
+
.bool(showModelBadge)
|
|
561
|
+
.digest();
|
|
562
|
+
if (!shimmerActive && cached?.key === key) return cached.lines;
|
|
563
|
+
|
|
564
|
+
const itemLines = renderTreeList<JobSnapshot>(
|
|
565
|
+
{
|
|
566
|
+
items: sortedJobs,
|
|
567
|
+
expanded,
|
|
568
|
+
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
569
|
+
itemType: "job",
|
|
570
|
+
renderItem: (job, context) => {
|
|
571
|
+
const rowWidth = Math.max(0, width - (context.prefixWidth ?? 0));
|
|
572
|
+
const lines: string[] = [];
|
|
573
|
+
const icon = formatStatusIcon(
|
|
574
|
+
statusToIcon(job.status),
|
|
575
|
+
uiTheme,
|
|
576
|
+
job.status === "running" ? options.spinnerFrame : undefined,
|
|
577
|
+
);
|
|
578
|
+
const typeBadge = formatBadge(job.type, statusToColor(job.status), uiTheme);
|
|
579
|
+
const durationSuffix = `${uiTheme.sep.dot}${uiTheme.fg("dim", formatDuration(job.durationMs))}`;
|
|
580
|
+
const displayId = truncateToWidth(
|
|
581
|
+
replaceTabs(job.id).replace(/\s+/g, " "),
|
|
582
|
+
Math.max(0, rowWidth - visibleWidth(`${icon} ${typeBadge} ${durationSuffix}`)),
|
|
583
|
+
Ellipsis.Unicode,
|
|
584
|
+
);
|
|
585
|
+
const rawLabelLines = (job.label || "(no label)").split(/\r?\n/);
|
|
586
|
+
const maxLabelLines = expanded ? LABEL_LINES_EXPANDED : LABEL_LINES_COLLAPSED;
|
|
587
|
+
const visibleLabelLines = rawLabelLines
|
|
588
|
+
.slice(0, maxLabelLines)
|
|
589
|
+
.map(l => truncateToWidth(replaceTabs(l), LABEL_MAX_WIDTH, Ellipsis.Unicode));
|
|
590
|
+
if (rawLabelLines.length > maxLabelLines && visibleLabelLines.length > 0) {
|
|
591
|
+
const last = visibleLabelLines[visibleLabelLines.length - 1]!;
|
|
592
|
+
visibleLabelLines[visibleLabelLines.length - 1] = `${last} …`;
|
|
593
|
+
}
|
|
594
|
+
const rowPrefix = `${icon} ${typeBadge} `;
|
|
595
|
+
const modelIdentity = job.resolvedModelIdentity ?? job.resolvedModel;
|
|
596
|
+
const modelBadge =
|
|
597
|
+
job.type === "task" && showModelBadge && typeof modelIdentity === "string"
|
|
598
|
+
? formatFeedModelBadge(
|
|
599
|
+
modelIdentity,
|
|
600
|
+
job.resolvedThinkingLevel,
|
|
601
|
+
job.advisor === true,
|
|
602
|
+
uiTheme,
|
|
603
|
+
Math.min(
|
|
604
|
+
FEED_MODEL_BADGE_WIDTH,
|
|
605
|
+
Math.max(0, rowWidth - visibleWidth(`${rowPrefix}${displayId}${durationSuffix}`) - 1),
|
|
606
|
+
),
|
|
607
|
+
)
|
|
608
|
+
: "";
|
|
609
|
+
const modelLead = modelBadge ? `${modelBadge} ` : "";
|
|
610
|
+
const headRaw = displayId;
|
|
611
|
+
// Running rows in a live block shimmer their label; once the block
|
|
612
|
+
// stops animating (sealed, or a settled snapshot — spinnerFrame
|
|
613
|
+
// cleared) they render static so scrollback never keeps a mid-sweep
|
|
614
|
+
// shimmer band.
|
|
615
|
+
const live = job.status === "running" && options.spinnerFrame !== undefined;
|
|
616
|
+
const headLabel = live
|
|
617
|
+
? shimmerEnabled()
|
|
618
|
+
? shimmerText(headRaw, uiTheme)
|
|
619
|
+
: uiTheme.fg("accent", headRaw)
|
|
620
|
+
: uiTheme.fg("toolOutput", headRaw);
|
|
621
|
+
let row = `${rowPrefix}${modelLead}${headLabel}`;
|
|
622
|
+
const distinctLabel = job.label.trim() !== job.id;
|
|
623
|
+
const label = visibleLabelLines[0] ?? "";
|
|
624
|
+
const inlineLabel = distinctLabel && visibleWidth(`${row} ${label}${durationSuffix}`) <= rowWidth;
|
|
625
|
+
if (inlineLabel) row += ` ${uiTheme.fg("toolOutput", label)}`;
|
|
626
|
+
row += durationSuffix;
|
|
627
|
+
lines.push(truncateToWidth(row, rowWidth, ""));
|
|
628
|
+
const continuationWidth = Math.max(0, rowWidth - visibleWidth(" "));
|
|
629
|
+
for (let i = distinctLabel && !inlineLabel ? 0 : 1; i < visibleLabelLines.length; i++) {
|
|
630
|
+
lines.push(
|
|
631
|
+
` ${uiTheme.fg("toolOutput", truncateToWidth(visibleLabelLines[i]!, continuationWidth))}`,
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const artifactError = job.meta?.artifactError ?? job.artifactError;
|
|
635
|
+
if (artifactError) {
|
|
636
|
+
lines.push(
|
|
637
|
+
uiTheme.fg("warning", truncateToWidth(formatArtifactErrorNotice(artifactError), rowWidth)),
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Legacy rows did not retain full metadata. Strip the known warning
|
|
642
|
+
// footer so the dedicated row/root warning remains the only copy.
|
|
643
|
+
const previewError =
|
|
644
|
+
artifactError ?? (outputMeta?.source?.type !== "report" ? outputMeta?.artifactError : undefined);
|
|
645
|
+
const previewMeta = job.meta ?? (previewError ? { artifactError: previewError } : undefined);
|
|
646
|
+
|
|
647
|
+
const preview = flattenStructuredPreview(
|
|
648
|
+
stripTaskResultEnvelope(
|
|
649
|
+
stripOutputNotice(job.errorText?.trim() || job.resultText?.trim() || "", previewMeta).trim(),
|
|
650
|
+
),
|
|
651
|
+
);
|
|
652
|
+
if (preview) {
|
|
653
|
+
const maxLines = expanded ? PREVIEW_LINES_EXPANDED : PREVIEW_LINES_COLLAPSED;
|
|
654
|
+
const previewLines = getPreviewLines(
|
|
655
|
+
preview,
|
|
656
|
+
maxLines,
|
|
657
|
+
Math.min(PREVIEW_LINE_WIDTH, continuationWidth),
|
|
658
|
+
Ellipsis.Unicode,
|
|
659
|
+
);
|
|
660
|
+
const tone = job.errorText ? "error" : "dim";
|
|
661
|
+
for (const pl of previewLines) {
|
|
662
|
+
lines.push(` ${uiTheme.fg(tone, pl)}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return lines;
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
uiTheme,
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
// Agents run outside job control; render them as their own tree so
|
|
672
|
+
// they never skew the job counts or the "waiting on N jobs" title.
|
|
673
|
+
const agentLines =
|
|
674
|
+
agents.length === 0
|
|
675
|
+
? []
|
|
676
|
+
: renderTreeList<AgentActivitySnapshot>(
|
|
677
|
+
{
|
|
678
|
+
items: agents,
|
|
679
|
+
expanded,
|
|
680
|
+
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
681
|
+
itemType: "agent",
|
|
682
|
+
renderItem: (agent, context) => {
|
|
683
|
+
const rowWidth = Math.max(0, width - (context.prefixWidth ?? 0));
|
|
684
|
+
const icon = agent.live
|
|
685
|
+
? formatStatusIcon("running", uiTheme, options.spinnerFrame)
|
|
686
|
+
: formatStatusIcon("warning", uiTheme);
|
|
687
|
+
const badge = agent.live
|
|
688
|
+
? formatBadge("agent", "accent", uiTheme)
|
|
689
|
+
: formatBadge("agent · no turn", "warning", uiTheme);
|
|
690
|
+
const id = truncateToWidth(
|
|
691
|
+
replaceTabs(agent.id).replace(/\s+/g, " "),
|
|
692
|
+
Math.max(0, rowWidth - visibleWidth(`${icon} ${badge}`)),
|
|
693
|
+
Ellipsis.Unicode,
|
|
694
|
+
);
|
|
695
|
+
const gist = agent.activity
|
|
696
|
+
? ` ${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(agent.activity), LABEL_MAX_WIDTH, Ellipsis.Unicode))}`
|
|
697
|
+
: "";
|
|
698
|
+
const parent = agent.parentId ? uiTheme.fg("dim", ` ← ${agent.parentId}`) : "";
|
|
699
|
+
const age = uiTheme.fg("dim", formatDuration(agent.ageMs));
|
|
700
|
+
return [
|
|
701
|
+
truncateToWidth(
|
|
702
|
+
`${icon} ${uiTheme.fg("muted", id)} ${badge}${gist} ${age}${parent}`,
|
|
703
|
+
rowWidth,
|
|
704
|
+
"",
|
|
705
|
+
),
|
|
706
|
+
];
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
uiTheme,
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
const all = [header];
|
|
713
|
+
if (aggregateArtifactError) {
|
|
714
|
+
all.push(uiTheme.fg("warning", formatArtifactErrorNotice(aggregateArtifactError)));
|
|
715
|
+
}
|
|
716
|
+
all.push(...itemLines, ...agentLines);
|
|
717
|
+
for (let i = 0; i < all.length; i++) all[i] = truncateToWidth(all[i]!, width, Ellipsis.Unicode);
|
|
718
|
+
cached = { key, lines: all };
|
|
719
|
+
return all;
|
|
720
|
+
},
|
|
721
|
+
invalidate() {
|
|
722
|
+
cached = undefined;
|
|
723
|
+
},
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// =============================================================================
|
|
728
|
+
// TUI Renderer (launch half)
|
|
729
|
+
// =============================================================================
|
|
730
|
+
|
|
731
|
+
/** Args shape visible to the renderer, possibly mid-stream (every field optional). */
|
|
732
|
+
export type LaunchRenderArgs = Partial<Omit<LaunchParams, "op">> & { op?: string };
|
|
733
|
+
|
|
734
|
+
function stateColor(state: DaemonState): ThemeColor {
|
|
735
|
+
switch (state) {
|
|
736
|
+
case "running":
|
|
737
|
+
case "ready":
|
|
738
|
+
return "success";
|
|
739
|
+
case "failed":
|
|
740
|
+
return "error";
|
|
741
|
+
case "exited":
|
|
742
|
+
return "muted";
|
|
743
|
+
default:
|
|
744
|
+
return "warning";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Compact `state · pid · uptime` fragments for the status-line meta slot. */
|
|
749
|
+
function daemonMeta(daemon: DaemonSnapshot, theme: Theme): string[] {
|
|
750
|
+
const meta = [theme.fg(stateColor(daemon.state), daemon.state)];
|
|
751
|
+
if (daemon.readyPending?.length) meta.push(theme.fg("warning", `waiting on ${daemon.readyPending.join("+")}`));
|
|
752
|
+
if (daemon.exitCode !== undefined) {
|
|
753
|
+
meta.push(theme.fg(daemon.exitCode === 0 ? "muted" : "error", `exit ${daemon.exitCode}`));
|
|
754
|
+
} else if (daemon.pid !== undefined) {
|
|
755
|
+
meta.push(`pid ${daemon.pid}`);
|
|
756
|
+
}
|
|
757
|
+
const lifespan = formatDuration((daemon.exitedAt ?? Date.now()) - daemon.startedAt);
|
|
758
|
+
meta.push(daemon.exitedAt === undefined ? `up ${lifespan}` : `ran ${lifespan}`);
|
|
759
|
+
if (daemon.restartCount > 0) meta.push(`restarts ${daemon.restartCount}`);
|
|
760
|
+
if (daemon.detached) meta.push("detached");
|
|
761
|
+
else if (daemon.persist) meta.push("persistent");
|
|
762
|
+
return meta;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/** Op-specific call context (command line, log filters, wait condition, send payload). */
|
|
766
|
+
function launchCallMeta(args: LaunchRenderArgs): string[] {
|
|
767
|
+
const meta: string[] = [];
|
|
768
|
+
switch (args.op) {
|
|
769
|
+
case "start":
|
|
770
|
+
if (args.application) meta.push([args.application, ...(args.args ?? [])].join(" "));
|
|
771
|
+
break;
|
|
772
|
+
case "logs":
|
|
773
|
+
if (args.follow) meta.push("follow");
|
|
774
|
+
if (args.grep) meta.push(`grep /${args.grep}/`);
|
|
775
|
+
break;
|
|
776
|
+
case "wait":
|
|
777
|
+
meta.push(args.pattern ? `for /${args.pattern}/` : `for ${args.for ?? "exit"}`);
|
|
778
|
+
break;
|
|
779
|
+
case "send":
|
|
780
|
+
if (args.signal) meta.push(args.signal);
|
|
781
|
+
else if (args.text) meta.push(args.text);
|
|
782
|
+
if (args.keys?.length) meta.push(args.keys.join(" "));
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
return meta.map(entry => previewLine(replaceTabs(entry), TRUNCATE_LENGTHS.SHORT));
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** Pending-call frame for launch ops; consumes the spinner while the broker call is live. */
|
|
789
|
+
export function launchRenderCall(args: LaunchRenderArgs, options: RenderResultOptions, theme: Theme): Component {
|
|
790
|
+
const target = args.name ?? args.application;
|
|
791
|
+
const header = renderStatusLine(
|
|
792
|
+
{
|
|
793
|
+
icon: options.spinnerFrame !== undefined ? "running" : "pending",
|
|
794
|
+
spinnerFrame: options.spinnerFrame,
|
|
795
|
+
title: `Launch ${args.op ?? "…"}`,
|
|
796
|
+
description: target ? replaceTabs(target) : undefined,
|
|
797
|
+
meta: launchCallMeta(args),
|
|
798
|
+
},
|
|
799
|
+
theme,
|
|
800
|
+
);
|
|
801
|
+
return new Text(header, 0, 0);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/** Result frame: one status header per op, meta from structured details, capped body lines. */
|
|
805
|
+
export function launchRenderResult(
|
|
806
|
+
result: { content: Array<{ type: string; text?: string }>; details?: LaunchToolDetails; isError?: boolean },
|
|
807
|
+
options: RenderResultOptions,
|
|
808
|
+
theme: Theme,
|
|
809
|
+
args?: LaunchRenderArgs,
|
|
810
|
+
): Component {
|
|
811
|
+
const details = result.details;
|
|
812
|
+
const params = args ?? {};
|
|
813
|
+
const op = details?.op ?? params.op;
|
|
814
|
+
const isError = result.isError === true;
|
|
815
|
+
const daemon = details?.daemon;
|
|
816
|
+
const failed = isError || daemon?.state === "failed";
|
|
817
|
+
const text =
|
|
818
|
+
result.content
|
|
819
|
+
?.filter(item => item.type === "text")
|
|
820
|
+
.map(item => item.text ?? "")
|
|
821
|
+
.join("\n") ?? "";
|
|
822
|
+
|
|
823
|
+
const meta: string[] = [];
|
|
824
|
+
const body: string[] = [];
|
|
825
|
+
let description = params.name ?? daemon?.name;
|
|
826
|
+
|
|
827
|
+
if (isError) {
|
|
828
|
+
for (const line of replaceTabs(text.trimEnd()).split("\n")) body.push(theme.fg("error", line));
|
|
829
|
+
} else {
|
|
830
|
+
switch (op) {
|
|
831
|
+
case "start": {
|
|
832
|
+
meta.push(...launchCallMeta(params));
|
|
833
|
+
if (daemon) meta.push(...daemonMeta(daemon, theme));
|
|
834
|
+
if (daemon?.readyMatch) body.push(theme.fg("dim", `log matched: ${replaceTabs(daemon.readyMatch)}`));
|
|
835
|
+
if (daemon?.state === "failed" && daemon.exitReason)
|
|
836
|
+
body.push(theme.fg("error", replaceTabs(daemon.exitReason)));
|
|
837
|
+
if (details?.timedOut) {
|
|
838
|
+
const pending = daemon ? readyPendingSummary(daemon, params.ready) : [];
|
|
839
|
+
body.push(
|
|
840
|
+
theme.fg(
|
|
841
|
+
"warning",
|
|
842
|
+
pending.length > 0
|
|
843
|
+
? `Not ready — ${pending.join("; ")}. Still running.`
|
|
844
|
+
: "Readiness timed out; the process is still running.",
|
|
845
|
+
),
|
|
846
|
+
);
|
|
847
|
+
} else if (params.ready && daemon && daemon.readyAt === undefined && TERMINAL_STATES[daemon.state]) {
|
|
848
|
+
body.push(theme.fg("warning", "Process exited before readiness was observed."));
|
|
849
|
+
}
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
case "send":
|
|
853
|
+
meta.push(...launchCallMeta(params));
|
|
854
|
+
if (daemon) meta.push(...daemonMeta(daemon, theme));
|
|
855
|
+
break;
|
|
856
|
+
case "stop":
|
|
857
|
+
case "restart":
|
|
858
|
+
if (daemon) meta.push(...daemonMeta(daemon, theme));
|
|
859
|
+
break;
|
|
860
|
+
case "wait": {
|
|
861
|
+
meta.push(...launchCallMeta(params));
|
|
862
|
+
if (daemon) meta.push(...daemonMeta(daemon, theme));
|
|
863
|
+
if (details?.matched) body.push(theme.fg("dim", `matched: ${replaceTabs(details.matched)}`));
|
|
864
|
+
if (details?.timedOut) {
|
|
865
|
+
body.push(
|
|
866
|
+
theme.fg(
|
|
867
|
+
"warning",
|
|
868
|
+
daemon
|
|
869
|
+
? `Wait timed out — still waiting on ${waitPendingSummary(daemon, params).join("; ")}.`
|
|
870
|
+
: "Wait timed out.",
|
|
871
|
+
),
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
case "list": {
|
|
877
|
+
const daemons = details?.daemons ?? [];
|
|
878
|
+
description = `${daemons.length || "no"} ${pluralize("process", daemons.length)}`;
|
|
879
|
+
for (const item of daemons) {
|
|
880
|
+
body.push(
|
|
881
|
+
`${theme.fg("accent", replaceTabs(item.name))} ${theme.fg("dim", daemonMeta(item, theme).join(theme.sep.dot))}`,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
case "logs": {
|
|
887
|
+
if (details?.state) meta.push(theme.fg(stateColor(details.state), details.state));
|
|
888
|
+
if (details?.cursor !== undefined) meta.push(`cursor ${details.cursor}`);
|
|
889
|
+
if (details?.timedOut) meta.push(theme.fg("warning", "follow timed out"));
|
|
890
|
+
// Strip the trailing `[name: state; cursor=N]` status suffix `toolContent` appends.
|
|
891
|
+
const logText = text.replace(/\n?\[[^\n]*\]$/, "").trimEnd();
|
|
892
|
+
const terminalRows = details?.terminalRows;
|
|
893
|
+
if (terminalRows) {
|
|
894
|
+
for (const row of terminalRows) body.push(styleTerminalRow(row, theme.getFgAnsi("toolOutput")));
|
|
895
|
+
} else if (logText) {
|
|
896
|
+
for (const line of logText.split("\n")) body.push(theme.fg("toolOutput", replaceTabs(line)));
|
|
897
|
+
}
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
case "describe": {
|
|
901
|
+
if (daemon) meta.push(...daemonMeta(daemon, theme));
|
|
902
|
+
const spec = details?.spec;
|
|
903
|
+
if (spec) {
|
|
904
|
+
body.push(theme.fg("toolOutput", replaceTabs([spec.application, ...spec.args].join(" "))));
|
|
905
|
+
body.push(theme.fg("dim", `cwd ${shortenPath(spec.cwd)}`));
|
|
906
|
+
const flags = [`pty ${spec.pty}`, `restart ${spec.restart}`];
|
|
907
|
+
if (spec.detached) flags.push("detached");
|
|
908
|
+
else if (spec.persist) flags.push("persistent");
|
|
909
|
+
body.push(theme.fg("dim", flags.join(theme.sep.dot)));
|
|
910
|
+
}
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
default:
|
|
914
|
+
if (text.trim()) {
|
|
915
|
+
for (const line of replaceTabs(text.trimEnd()).split("\n")) body.push(theme.fg("toolOutput", line));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const header = renderStatusLine(
|
|
921
|
+
{
|
|
922
|
+
...(failed
|
|
923
|
+
? { icon: "error" as const }
|
|
924
|
+
: options.isPartial
|
|
925
|
+
? { icon: "pending" as const }
|
|
926
|
+
: { iconOverride: theme.styledSymbol("tool.launch", "accent") }),
|
|
927
|
+
title: `Launch ${op ?? ""}`.trimEnd(),
|
|
928
|
+
description: description ? replaceTabs(description) : undefined,
|
|
929
|
+
meta,
|
|
930
|
+
},
|
|
931
|
+
theme,
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
if (op === "logs") {
|
|
935
|
+
return framedToolCard(theme, ({ contentWidth }) => {
|
|
936
|
+
const rows = body.map(line => truncateToWidth(line, contentWidth));
|
|
937
|
+
return {
|
|
938
|
+
header,
|
|
939
|
+
phase: options.isPartial ? "partial" : failed ? "error" : "success",
|
|
940
|
+
sections: [
|
|
941
|
+
{
|
|
942
|
+
label: theme.fg("toolTitle", "Output"),
|
|
943
|
+
content: capPreviewLines(rows, theme, {
|
|
944
|
+
expanded: options.expanded,
|
|
945
|
+
max: DEFAULT_TERMINAL_PREVIEW_LINES,
|
|
946
|
+
}),
|
|
947
|
+
},
|
|
948
|
+
],
|
|
949
|
+
};
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
return createCachedComponent(
|
|
954
|
+
() => options.expanded,
|
|
955
|
+
(width, expanded) => {
|
|
956
|
+
let visible = body;
|
|
957
|
+
if (!expanded && op === "list" && body.length > PREVIEW_LIMITS.COLLAPSED_ITEMS) {
|
|
958
|
+
const remaining = body.length - PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
959
|
+
visible = [
|
|
960
|
+
...body.slice(0, PREVIEW_LIMITS.COLLAPSED_ITEMS),
|
|
961
|
+
theme.fg("dim", `${formatMoreItems(remaining, "process")} ${formatExpandHint(theme, false, true)}`),
|
|
962
|
+
];
|
|
963
|
+
}
|
|
964
|
+
return [header, ...visible].map(line => truncateToWidth(line, width));
|
|
965
|
+
},
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// =============================================================================
|
|
970
|
+
// TUI Renderer (messaging half)
|
|
971
|
+
// =============================================================================
|
|
972
|
+
|
|
973
|
+
const BODY_LINES_COLLAPSED = 2;
|
|
974
|
+
const BODY_LINES_EXPANDED = 12;
|
|
975
|
+
const BODY_LINE_WIDTH = 100;
|
|
976
|
+
|
|
977
|
+
function ircGlyph(theme: Theme): string {
|
|
978
|
+
return theme.styledSymbol("tool.irc", "accent");
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function outcomeColor(outcome: IrcDeliveryReceipt["outcome"]): ToolUIColor {
|
|
982
|
+
switch (outcome) {
|
|
983
|
+
case "woken":
|
|
984
|
+
return "success";
|
|
985
|
+
case "revived":
|
|
986
|
+
return "warning";
|
|
987
|
+
case "injected":
|
|
988
|
+
return "accent";
|
|
989
|
+
case "failed":
|
|
990
|
+
return "error";
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/** Glyph + status word, matching the agent-hub status conventions. */
|
|
995
|
+
function peerStatusBadge(status: string, theme: Theme): string {
|
|
996
|
+
switch (status) {
|
|
997
|
+
case "running":
|
|
998
|
+
return theme.fg("accent", `${theme.status.running} running`);
|
|
999
|
+
case "idle":
|
|
1000
|
+
return theme.fg("success", `${theme.status.enabled} idle`);
|
|
1001
|
+
case "parked":
|
|
1002
|
+
return theme.fg("muted", `${theme.status.shadowed} parked`);
|
|
1003
|
+
default:
|
|
1004
|
+
return theme.fg("error", `${theme.status.aborted} ${status}`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function messageAge(ts: number | undefined): string {
|
|
1009
|
+
if (!ts) return "";
|
|
1010
|
+
return formatAge(Math.max(1, Math.round((Date.now() - ts) / 1000)));
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function firstTextBlock(result: { content: Array<{ type: string; text?: string }> }): string {
|
|
1014
|
+
return result.content.find(part => part.type === "text")?.text?.trim() ?? "";
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Quote-bordered message body preview. `tone` separates outbound text (dim)
|
|
1019
|
+
* from received text (toolOutput); a trailing dim counter marks elided lines.
|
|
1020
|
+
*/
|
|
1021
|
+
function bodyLines(
|
|
1022
|
+
body: string,
|
|
1023
|
+
expanded: boolean,
|
|
1024
|
+
theme: Theme,
|
|
1025
|
+
options: { indent?: string; tone?: "dim" | "toolOutput"; collapsedLines?: number } = {},
|
|
1026
|
+
): string[] {
|
|
1027
|
+
const indent = options.indent ?? "";
|
|
1028
|
+
const tone = options.tone ?? "toolOutput";
|
|
1029
|
+
const max = expanded ? BODY_LINES_EXPANDED : (options.collapsedLines ?? BODY_LINES_COLLAPSED);
|
|
1030
|
+
const preview = cappedHeadLines(
|
|
1031
|
+
body.split("\n").filter(line => line.trim()),
|
|
1032
|
+
max,
|
|
1033
|
+
);
|
|
1034
|
+
const quote = theme.fg("dim", theme.md.quoteBorder);
|
|
1035
|
+
const lines = preview.lines.map(
|
|
1036
|
+
line =>
|
|
1037
|
+
`${indent}${quote} ${theme.fg(tone, replaceTabs(truncateToWidth(line.trim(), BODY_LINE_WIDTH, Ellipsis.Unicode)))}`,
|
|
1038
|
+
);
|
|
1039
|
+
const hidden = preview.hidden;
|
|
1040
|
+
if (hidden > 0) {
|
|
1041
|
+
lines.push(`${indent}${quote} ${theme.fg("dim", `… +${hidden} more ${hidden === 1 ? "line" : "lines"}`)}`);
|
|
1042
|
+
}
|
|
1043
|
+
return lines;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** Header title carrying the op direction: `IRC ➤ peer` out, `IRC ⟵ peer` in. */
|
|
1047
|
+
function callTitle(args: HubRenderArgs | undefined, theme: Theme): string {
|
|
1048
|
+
switch (args?.op) {
|
|
1049
|
+
case "send":
|
|
1050
|
+
return `IRC ${theme.nav.selected} ${args.to?.trim() || "…"}`;
|
|
1051
|
+
case "wait":
|
|
1052
|
+
return `IRC ${theme.nav.back} ${args.from?.trim() || "anyone"}`;
|
|
1053
|
+
case "inbox":
|
|
1054
|
+
return "IRC inbox";
|
|
1055
|
+
case "list":
|
|
1056
|
+
return "IRC peers";
|
|
1057
|
+
default:
|
|
1058
|
+
return "Hub";
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function messagingCallMeta(args: HubRenderArgs | undefined): string[] {
|
|
1063
|
+
const meta: string[] = [];
|
|
1064
|
+
if (args?.op === "send") {
|
|
1065
|
+
if (args.to === "all") meta.push("broadcast");
|
|
1066
|
+
if (args.await) meta.push("await reply");
|
|
1067
|
+
if (args.replyTo) meta.push("reply");
|
|
1068
|
+
}
|
|
1069
|
+
if (args?.op === "inbox" && args.peek) meta.push("peek");
|
|
1070
|
+
return meta;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
function renderErrorResult(
|
|
1074
|
+
result: { content: Array<{ type: string; text?: string }> },
|
|
1075
|
+
args: HubRenderArgs | undefined,
|
|
1076
|
+
theme: Theme,
|
|
1077
|
+
): string[] {
|
|
1078
|
+
const text = firstTextBlock(result) || "IRC call failed.";
|
|
1079
|
+
return [
|
|
1080
|
+
renderStatusLine({ icon: "error", title: callTitle(args, theme), meta: messagingCallMeta(args) }, theme),
|
|
1081
|
+
formatErrorDetail(text, theme),
|
|
1082
|
+
];
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Display-only transcript card for live IRC traffic: `irc:incoming` DMs
|
|
1087
|
+
* delivered to this session, `irc:autoreply` side-channel replies sent on
|
|
1088
|
+
* this session's behalf, and `irc:relay` observations of agent↔agent
|
|
1089
|
+
* traffic. Shares the tool renderer's glyph + quote-border conventions so
|
|
1090
|
+
* cards and hub messaging output look identical in the transcript.
|
|
1091
|
+
*/
|
|
1092
|
+
export function createIrcMessageCard(
|
|
1093
|
+
card: {
|
|
1094
|
+
kind: "incoming" | "autoreply" | "relay" | "workpool";
|
|
1095
|
+
from?: string;
|
|
1096
|
+
to?: string;
|
|
1097
|
+
body?: string;
|
|
1098
|
+
replyTo?: string;
|
|
1099
|
+
timestamp?: number;
|
|
1100
|
+
pool?: string;
|
|
1101
|
+
mode?: string;
|
|
1102
|
+
},
|
|
1103
|
+
getExpanded: () => boolean,
|
|
1104
|
+
uiTheme: Theme,
|
|
1105
|
+
): Component {
|
|
1106
|
+
const from = card.from?.trim() || "?";
|
|
1107
|
+
const title =
|
|
1108
|
+
card.kind === "incoming"
|
|
1109
|
+
? `IRC ${uiTheme.nav.back} ${from}`
|
|
1110
|
+
: card.kind === "autoreply"
|
|
1111
|
+
? `IRC ${uiTheme.nav.selected} ${card.to?.trim() || "?"}`
|
|
1112
|
+
: card.kind === "workpool"
|
|
1113
|
+
? `Pool ${card.pool?.trim() || "?"} ${uiTheme.nav.selected} ${card.to?.trim() || "?"}`
|
|
1114
|
+
: `IRC ${from} ${uiTheme.nav.selected} ${card.to?.trim() || "?"}`;
|
|
1115
|
+
const body = card.body ?? "";
|
|
1116
|
+
const meta: string[] = [];
|
|
1117
|
+
if (card.kind === "autoreply") meta.push("auto");
|
|
1118
|
+
if (card.kind === "workpool" && card.mode) meta.push(card.mode);
|
|
1119
|
+
if (card.replyTo) meta.push("reply");
|
|
1120
|
+
const age = messageAge(card.timestamp);
|
|
1121
|
+
if (age) meta.push(age);
|
|
1122
|
+
return createCachedComponent(
|
|
1123
|
+
getExpanded,
|
|
1124
|
+
(width, expanded) => {
|
|
1125
|
+
const lines = [renderStatusLine({ iconOverride: ircGlyph(uiTheme), title, meta }, uiTheme)];
|
|
1126
|
+
if (body.trim()) {
|
|
1127
|
+
lines.push(...bodyLines(body, expanded, uiTheme, { indent: " ", collapsedLines: 3 }));
|
|
1128
|
+
}
|
|
1129
|
+
return lines.map(line => truncateToWidth(line, width, Ellipsis.Unicode));
|
|
1130
|
+
},
|
|
1131
|
+
{ paddingX: 1 },
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function renderSendResult(
|
|
1136
|
+
result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
|
|
1137
|
+
details: Partial<CoordinationDetails>,
|
|
1138
|
+
args: HubRenderArgs | undefined,
|
|
1139
|
+
expanded: boolean,
|
|
1140
|
+
theme: Theme,
|
|
1141
|
+
): string[] {
|
|
1142
|
+
const receipts = details.receipts ?? [];
|
|
1143
|
+
const to = details.to ?? args?.to?.trim() ?? "?";
|
|
1144
|
+
const title = `IRC ${theme.nav.selected} ${to}`;
|
|
1145
|
+
|
|
1146
|
+
// Pre-delivery failures (validation) and empty broadcasts carry no receipts.
|
|
1147
|
+
if (receipts.length === 0) {
|
|
1148
|
+
const text = firstTextBlock(result) || (result.isError ? "Send failed." : "Nothing to deliver.");
|
|
1149
|
+
return [
|
|
1150
|
+
renderStatusLine({ icon: result.isError ? "error" : "warning", title }, theme),
|
|
1151
|
+
result.isError ? formatErrorDetail(text, theme) : ` ${theme.fg("muted", replaceTabs(text))}`,
|
|
1152
|
+
];
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const delivered = receipts.filter(receipt => receipt.outcome !== "failed");
|
|
1156
|
+
const failedCount = receipts.length - delivered.length;
|
|
1157
|
+
const waited = details.waited;
|
|
1158
|
+
const timedOut = waited === null;
|
|
1159
|
+
|
|
1160
|
+
const meta: string[] = [];
|
|
1161
|
+
if (to === "all") meta.push("broadcast");
|
|
1162
|
+
if (receipts.length === 1) {
|
|
1163
|
+
const receipt = receipts[0]!;
|
|
1164
|
+
meta.push(theme.fg(outcomeColor(receipt.outcome), receipt.outcome));
|
|
1165
|
+
} else {
|
|
1166
|
+
if (delivered.length > 0) meta.push(theme.fg("success", `${delivered.length} delivered`));
|
|
1167
|
+
if (failedCount > 0) meta.push(theme.fg("error", `${failedCount} failed`));
|
|
1168
|
+
}
|
|
1169
|
+
if (timedOut) meta.push(theme.fg("warning", "no reply"));
|
|
1170
|
+
|
|
1171
|
+
const icon = result.isError
|
|
1172
|
+
? { icon: "error" as const }
|
|
1173
|
+
: timedOut
|
|
1174
|
+
? { icon: "warning" as const }
|
|
1175
|
+
: { iconOverride: ircGlyph(theme) };
|
|
1176
|
+
const lines = [renderStatusLine({ ...icon, title, meta }, theme)];
|
|
1177
|
+
|
|
1178
|
+
const sent = args?.message?.trim();
|
|
1179
|
+
if (sent) lines.push(...bodyLines(sent, expanded, theme, { indent: " ", tone: "dim" }));
|
|
1180
|
+
|
|
1181
|
+
if (receipts.length > 1 || failedCount > 0) {
|
|
1182
|
+
lines.push(
|
|
1183
|
+
...renderTreeList<IrcDeliveryReceipt>(
|
|
1184
|
+
{
|
|
1185
|
+
items: receipts,
|
|
1186
|
+
expanded,
|
|
1187
|
+
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
1188
|
+
itemType: "recipient",
|
|
1189
|
+
renderItem: receipt => {
|
|
1190
|
+
const badge = formatBadge(receipt.outcome, outcomeColor(receipt.outcome), theme);
|
|
1191
|
+
const error =
|
|
1192
|
+
receipt.outcome === "failed" && receipt.error
|
|
1193
|
+
? ` ${theme.fg("error", `${theme.format.dash} ${receipt.error}`)}`
|
|
1194
|
+
: "";
|
|
1195
|
+
return `${theme.fg("toolOutput", receipt.to)} ${badge}${error}`;
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
theme,
|
|
1199
|
+
),
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (waited) {
|
|
1204
|
+
const age = messageAge(waited.ts);
|
|
1205
|
+
lines.push(
|
|
1206
|
+
` ${theme.fg("dim", theme.nav.back)} ${theme.fg("accent", waited.from)}${age ? ` ${theme.fg("dim", age)}` : ""}`,
|
|
1207
|
+
);
|
|
1208
|
+
lines.push(...bodyLines(waited.body, expanded, theme, { indent: " " }));
|
|
1209
|
+
} else if (timedOut) {
|
|
1210
|
+
lines.push(` ${theme.fg("warning", "No reply yet — they may answer later; check inbox or wait again.")}`);
|
|
1211
|
+
}
|
|
1212
|
+
return lines;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function renderWaitResult(
|
|
1216
|
+
result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
|
|
1217
|
+
details: Partial<CoordinationDetails>,
|
|
1218
|
+
args: HubRenderArgs | undefined,
|
|
1219
|
+
expanded: boolean,
|
|
1220
|
+
theme: Theme,
|
|
1221
|
+
): string[] {
|
|
1222
|
+
const waited = details.waited;
|
|
1223
|
+
if (!waited) {
|
|
1224
|
+
const text = firstTextBlock(result) || "No message arrived.";
|
|
1225
|
+
return [
|
|
1226
|
+
renderStatusLine(
|
|
1227
|
+
{ icon: "warning", title: `IRC ${theme.nav.back} ${args?.from?.trim() || "anyone"}`, meta: ["timed out"] },
|
|
1228
|
+
theme,
|
|
1229
|
+
),
|
|
1230
|
+
` ${theme.fg("muted", replaceTabs(text))}`,
|
|
1231
|
+
];
|
|
1232
|
+
}
|
|
1233
|
+
const meta = [messageAge(waited.ts)];
|
|
1234
|
+
if (waited.replyTo) meta.push("reply");
|
|
1235
|
+
return [
|
|
1236
|
+
renderStatusLine({ iconOverride: ircGlyph(theme), title: `IRC ${theme.nav.back} ${waited.from}`, meta }, theme),
|
|
1237
|
+
...bodyLines(waited.body, expanded, theme, { indent: " " }),
|
|
1238
|
+
];
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function renderInboxResult(
|
|
1242
|
+
details: Partial<CoordinationDetails>,
|
|
1243
|
+
args: HubRenderArgs | undefined,
|
|
1244
|
+
expanded: boolean,
|
|
1245
|
+
theme: Theme,
|
|
1246
|
+
): string[] {
|
|
1247
|
+
const messages = details.inbox ?? [];
|
|
1248
|
+
if (messages.length === 0) {
|
|
1249
|
+
return [renderStatusLine({ iconOverride: ircGlyph(theme), title: "IRC inbox", meta: ["empty"] }, theme)];
|
|
1250
|
+
}
|
|
1251
|
+
const meta = [`${messages.length} ${messages.length === 1 ? "message" : "messages"}`];
|
|
1252
|
+
if (args?.peek) meta.push("peek");
|
|
1253
|
+
const header = renderStatusLine({ iconOverride: ircGlyph(theme), title: "IRC inbox", meta }, theme);
|
|
1254
|
+
const items = renderTreeList<IrcMessage>(
|
|
1255
|
+
{
|
|
1256
|
+
items: messages,
|
|
1257
|
+
expanded,
|
|
1258
|
+
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
1259
|
+
itemType: "message",
|
|
1260
|
+
renderItem: msg => {
|
|
1261
|
+
const age = messageAge(msg.ts);
|
|
1262
|
+
const replyBadge = msg.replyTo ? ` ${formatBadge("reply", "muted", theme)}` : "";
|
|
1263
|
+
const head = `${theme.fg("accent", msg.from)}${age ? ` ${theme.fg("dim", age)}` : ""}${replyBadge}`;
|
|
1264
|
+
return [head, ...bodyLines(msg.body, expanded, theme, { collapsedLines: 1 })];
|
|
1265
|
+
},
|
|
1266
|
+
},
|
|
1267
|
+
theme,
|
|
1268
|
+
);
|
|
1269
|
+
return [header, ...items];
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function renderListResult(details: Partial<CoordinationDetails>, expanded: boolean, theme: Theme): string[] {
|
|
1273
|
+
const peers = [...(details.peers ?? [])].sort(
|
|
1274
|
+
(a, b) =>
|
|
1275
|
+
(LIST_STATUS_ORDER[a.status] ?? 9) - (LIST_STATUS_ORDER[b.status] ?? 9) || b.lastActivity - a.lastActivity,
|
|
1276
|
+
);
|
|
1277
|
+
const rosterCounts = details.counts;
|
|
1278
|
+
if (peers.length === 0) {
|
|
1279
|
+
const meta =
|
|
1280
|
+
rosterCounts && rosterCounts.running + rosterCounts.idle + rosterCounts.parked > 0
|
|
1281
|
+
? [
|
|
1282
|
+
`${rosterCounts.running} running`,
|
|
1283
|
+
`${rosterCounts.idle} idle`,
|
|
1284
|
+
`${rosterCounts.parked} parked`,
|
|
1285
|
+
...(rosterCounts.truncated > 0 ? [`${rosterCounts.truncated} truncated`] : []),
|
|
1286
|
+
]
|
|
1287
|
+
: ["no other agents"];
|
|
1288
|
+
return [renderStatusLine({ icon: "info", title: "IRC peers", meta }, theme)];
|
|
1289
|
+
}
|
|
1290
|
+
const counts = new Map<string, number>();
|
|
1291
|
+
for (const peer of peers) counts.set(peer.status, (counts.get(peer.status) ?? 0) + 1);
|
|
1292
|
+
const meta = rosterCounts
|
|
1293
|
+
? [
|
|
1294
|
+
`${rosterCounts.running} running`,
|
|
1295
|
+
`${rosterCounts.idle} idle`,
|
|
1296
|
+
`${rosterCounts.parked} parked`,
|
|
1297
|
+
...(rosterCounts.truncated > 0 ? [`${rosterCounts.truncated} truncated`] : []),
|
|
1298
|
+
]
|
|
1299
|
+
: [...counts].map(([status, count]) => `${count} ${status}`);
|
|
1300
|
+
const unreadTotal = peers.reduce((sum, peer) => sum + peer.unread, 0);
|
|
1301
|
+
if (unreadTotal > 0) meta.push(theme.fg("warning", `${unreadTotal} unread`));
|
|
1302
|
+
const header = renderStatusLine({ iconOverride: ircGlyph(theme), title: "IRC peers", meta }, theme);
|
|
1303
|
+
const items = renderTreeList(
|
|
1304
|
+
{
|
|
1305
|
+
items: peers,
|
|
1306
|
+
expanded,
|
|
1307
|
+
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
1308
|
+
itemType: "peer",
|
|
1309
|
+
renderItem: peer => {
|
|
1310
|
+
const kindText = peer.parentId ? `${peer.kind}${theme.sep.dot}of ${peer.parentId}` : peer.kind;
|
|
1311
|
+
const unread = peer.unread > 0 ? ` ${formatBadge(`${peer.unread} unread`, "warning", theme)}` : "";
|
|
1312
|
+
const age = messageAge(peer.lastActivity);
|
|
1313
|
+
const activity = peer.activity ? ` ${theme.fg("dim", replaceTabs(peer.activity))}` : "";
|
|
1314
|
+
const name = theme.fg("dim", replaceTabs(peer.displayName));
|
|
1315
|
+
return `${peerStatusBadge(peer.status, theme)} ${theme.bold(replaceTabs(peer.id))} ${name} ${theme.fg("dim", kindText)}${activity}${unread}${age ? ` ${theme.fg("dim", age)}` : ""}`;
|
|
1316
|
+
},
|
|
1317
|
+
},
|
|
1318
|
+
theme,
|
|
1319
|
+
);
|
|
1320
|
+
return [header, ...items];
|
|
1321
|
+
}
|
|
1322
|
+
function buildResultLines(
|
|
1323
|
+
result: { content: Array<{ type: string; text?: string }>; isError?: boolean },
|
|
1324
|
+
details: Partial<CoordinationDetails>,
|
|
1325
|
+
args: HubRenderArgs | undefined,
|
|
1326
|
+
expanded: boolean,
|
|
1327
|
+
theme: Theme,
|
|
1328
|
+
): string[] {
|
|
1329
|
+
switch (details.op ?? args?.op) {
|
|
1330
|
+
case "send":
|
|
1331
|
+
return renderSendResult(result, details, args, expanded, theme);
|
|
1332
|
+
case "wait":
|
|
1333
|
+
return renderWaitResult(result, details, args, expanded, theme);
|
|
1334
|
+
case "inbox":
|
|
1335
|
+
return result.isError
|
|
1336
|
+
? renderErrorResult(result, args, theme)
|
|
1337
|
+
: renderInboxResult(details, args, expanded, theme);
|
|
1338
|
+
case "list":
|
|
1339
|
+
return result.isError ? renderErrorResult(result, args, theme) : renderListResult(details, expanded, theme);
|
|
1340
|
+
default: {
|
|
1341
|
+
const text = firstTextBlock(result) || (result.isError ? "Hub call failed." : "Done.");
|
|
1342
|
+
return [
|
|
1343
|
+
renderStatusLine({ icon: result.isError ? "error" : "success", title: callTitle(args, theme) }, theme),
|
|
1344
|
+
result.isError ? formatErrorDetail(text, theme) : ` ${theme.fg("muted", replaceTabs(text))}`,
|
|
1345
|
+
];
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/** Pending-call frame for messaging ops (send/wait-from/inbox/list). */
|
|
1351
|
+
export function messagingRenderCall(args: HubRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
1352
|
+
const lines = [
|
|
1353
|
+
renderStatusLine({ icon: "pending", title: callTitle(args, uiTheme), meta: messagingCallMeta(args) }, uiTheme),
|
|
1354
|
+
];
|
|
1355
|
+
if (args?.op === "send" && args.message?.trim()) {
|
|
1356
|
+
lines.push(...bodyLines(args.message, false, uiTheme, { indent: " ", tone: "dim", collapsedLines: 1 }));
|
|
1357
|
+
}
|
|
1358
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/** Result frame for messaging ops and message-carrying `wait` results. */
|
|
1362
|
+
export function messagingRenderResult(
|
|
1363
|
+
result: { content: Array<{ type: string; text?: string }>; details?: CoordinationDetails; isError?: boolean },
|
|
1364
|
+
options: RenderResultOptions,
|
|
1365
|
+
uiTheme: Theme,
|
|
1366
|
+
args?: HubRenderArgs,
|
|
1367
|
+
): Component {
|
|
1368
|
+
const details: Partial<CoordinationDetails> = result.details ?? {};
|
|
1369
|
+
return createCachedComponent(
|
|
1370
|
+
() => options.expanded,
|
|
1371
|
+
(width, expanded) =>
|
|
1372
|
+
buildResultLines(result, details, args, expanded, uiTheme).map(line =>
|
|
1373
|
+
truncateToWidth(line, width, Ellipsis.Unicode),
|
|
1374
|
+
),
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// =============================================================================
|
|
1379
|
+
// TUI Renderer — dispatches to the preserved messaging/job/launch renderings.
|
|
1380
|
+
// =============================================================================
|
|
1381
|
+
|
|
1382
|
+
const LAUNCH_OPS: Record<string, true> = {
|
|
1383
|
+
start: true,
|
|
1384
|
+
ps: true,
|
|
1385
|
+
logs: true,
|
|
1386
|
+
stop: true,
|
|
1387
|
+
restart: true,
|
|
1388
|
+
describe: true,
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
/** Launch-style call: an explicit process op, or `send`/`wait` targeting a process `name`. */
|
|
1392
|
+
function isLaunchStyleArgs(args: HubRenderArgs | undefined): boolean {
|
|
1393
|
+
if (!args?.op) return false;
|
|
1394
|
+
if (LAUNCH_OPS[args.op]) return true;
|
|
1395
|
+
return (args.op === "send" || args.op === "wait") && !!args.name && !args.to && !args.from;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/** Job-style call: job ops, or a `wait` that does not target a peer or process. */
|
|
1399
|
+
function isJobStyleArgs(args: HubRenderArgs | undefined): boolean {
|
|
1400
|
+
switch (args?.op) {
|
|
1401
|
+
case "jobs":
|
|
1402
|
+
case "cancel":
|
|
1403
|
+
return true;
|
|
1404
|
+
case "wait":
|
|
1405
|
+
return !!args.ids?.length || (!args.from && !args.name);
|
|
1406
|
+
default:
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/** Launch details carry process/broker state; coordination details never define these keys. */
|
|
1412
|
+
function isLaunchDetails(details: HubDetails): details is LaunchToolDetails {
|
|
1413
|
+
// `state`/`cursor` cover logs results, which may carry neither a daemon
|
|
1414
|
+
// snapshot nor terminal rows; coordination details never define these keys.
|
|
1415
|
+
return (
|
|
1416
|
+
"daemon" in details ||
|
|
1417
|
+
"daemons" in details ||
|
|
1418
|
+
"terminalRows" in details ||
|
|
1419
|
+
"spec" in details ||
|
|
1420
|
+
"state" in details ||
|
|
1421
|
+
"cursor" in details
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/** Hub args → launch renderer args: `ps` is the broker's `list`; everything else is verbatim. */
|
|
1426
|
+
function toLaunchArgs(args: HubRenderArgs | undefined): LaunchRenderArgs {
|
|
1427
|
+
if (!args) return {};
|
|
1428
|
+
const { op, ...rest } = args;
|
|
1429
|
+
return { ...rest, op: op === "ps" ? "list" : op };
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/** Renders hub messaging, jobs, and supervised processes. */
|
|
1433
|
+
export const hubToolRenderer = {
|
|
1434
|
+
inline: true,
|
|
1435
|
+
mergeCallAndResult: true,
|
|
1436
|
+
/** Compact one-line activity: op plus its peer, process, or job target. */
|
|
1437
|
+
activitySummary(args: unknown): ToolActivitySummary {
|
|
1438
|
+
const hubArgs = (args ?? {}) as HubRenderArgs;
|
|
1439
|
+
const op = hubArgs.op;
|
|
1440
|
+
if (!op) return { label: "Hub" };
|
|
1441
|
+
let detail = op;
|
|
1442
|
+
if (op === "send" && (hubArgs.to || hubArgs.name)) detail = `send → ${hubArgs.to ?? hubArgs.name}`;
|
|
1443
|
+
else if (op === "wait" && (hubArgs.from || hubArgs.name)) detail = `wait ${hubArgs.from ?? hubArgs.name}`;
|
|
1444
|
+
else if ((op === "wait" || op === "cancel") && hubArgs.ids?.length) {
|
|
1445
|
+
detail = `${op} ${hubArgs.ids.length} job${hubArgs.ids.length === 1 ? "" : "s"}`;
|
|
1446
|
+
} else if (hubArgs.name) detail = `${op} ${hubArgs.name}`;
|
|
1447
|
+
return { label: "Hub", detail };
|
|
1448
|
+
},
|
|
1449
|
+
// Only launch pending frames consume the spinner (broker RPC in flight);
|
|
1450
|
+
// messaging/job pending frames are static, exactly as before the merge.
|
|
1451
|
+
animatedPendingPreview: (args: unknown): boolean => isLaunchStyleArgs(args as HubRenderArgs | undefined),
|
|
1452
|
+
|
|
1453
|
+
renderCall(args: HubRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
1454
|
+
if (isLaunchStyleArgs(args)) return launchRenderCall(toLaunchArgs(args), options, uiTheme);
|
|
1455
|
+
return isJobStyleArgs(args)
|
|
1456
|
+
? jobsRenderCall(args, options, uiTheme)
|
|
1457
|
+
: messagingRenderCall(args, options, uiTheme);
|
|
1458
|
+
},
|
|
1459
|
+
|
|
1460
|
+
renderResult(
|
|
1461
|
+
result: { content: Array<{ type: string; text?: string }>; details?: HubDetails; isError?: boolean },
|
|
1462
|
+
options: RenderResultOptions,
|
|
1463
|
+
uiTheme: Theme,
|
|
1464
|
+
args?: HubRenderArgs,
|
|
1465
|
+
): Component {
|
|
1466
|
+
// Results dispatch on what actually happened, falling back to the call
|
|
1467
|
+
// shape when details are absent (framework-generated errors).
|
|
1468
|
+
const details = result.details;
|
|
1469
|
+
if (details && isLaunchDetails(details)) {
|
|
1470
|
+
return launchRenderResult({ ...result, details }, options, uiTheme, toLaunchArgs(args));
|
|
1471
|
+
}
|
|
1472
|
+
const coordination = details;
|
|
1473
|
+
if (coordination && (Array.isArray(coordination.jobs) || Array.isArray(coordination.agents))) {
|
|
1474
|
+
return jobsRenderResult({ ...result, details: coordination }, options, uiTheme, args);
|
|
1475
|
+
}
|
|
1476
|
+
if (
|
|
1477
|
+
coordination &&
|
|
1478
|
+
("receipts" in coordination || "waited" in coordination || "inbox" in coordination || "peers" in coordination)
|
|
1479
|
+
) {
|
|
1480
|
+
return messagingRenderResult({ ...result, details: coordination }, options, uiTheme, args);
|
|
1481
|
+
}
|
|
1482
|
+
// Detail-less or op-only results (validation errors, disabled gates).
|
|
1483
|
+
if (isLaunchStyleArgs(args))
|
|
1484
|
+
return launchRenderResult({ ...result, details: undefined }, options, uiTheme, toLaunchArgs(args));
|
|
1485
|
+
if (isJobStyleArgs(args)) return jobsRenderResult({ ...result, details: coordination }, options, uiTheme, args);
|
|
1486
|
+
return messagingRenderResult({ ...result, details: coordination }, options, uiTheme, args);
|
|
1487
|
+
},
|
|
1488
|
+
} satisfies ToolRenderer<HubRenderArgs, HubDetails>;
|