@bubblebrain-ai/bubble 0.0.51 → 0.0.53
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/README.md +4 -12
- package/dist/agent/routing-catalog.js +3 -2
- package/dist/agent/subagent/router.js +5 -0
- package/dist/agent/subagent/runtime.d.ts +1 -0
- package/dist/agent/subagent/runtime.js +13 -1
- package/dist/agent/subagent-control.d.ts +4 -0
- package/dist/agent/subagent-control.js +1 -0
- package/dist/agent/subagent-store.js +2 -0
- package/dist/agent/workflow/control.d.ts +2 -0
- package/dist/agent/workflow/runs.d.ts +1 -0
- package/dist/agent/workflow/runs.js +3 -0
- package/dist/agent.d.ts +11 -1
- package/dist/agent.js +158 -57
- package/dist/bin.js +2 -44
- package/dist/config.d.ts +14 -1
- package/dist/config.js +45 -4
- package/dist/context/budget.d.ts +7 -1
- package/dist/context/budget.js +25 -2
- package/dist/context/compact-llm.d.ts +1 -1
- package/dist/context/compact-llm.js +5 -2
- package/dist/context/overflow.js +2 -0
- package/dist/context/projector.d.ts +8 -0
- package/dist/context/projector.js +55 -6
- package/dist/context/tool-output-truncate.d.ts +17 -1
- package/dist/context/tool-output-truncate.js +171 -49
- package/dist/feishu/agent-host/run-driver.js +3 -0
- package/dist/feishu/serve.js +2 -0
- package/dist/goal/command.d.ts +1 -1
- package/dist/goal/command.js +7 -2
- package/dist/main.d.ts +1 -1
- package/dist/main.js +38 -8
- package/dist/memory/db.js +0 -7
- package/dist/model-catalog.js +25 -6
- package/dist/model-config.js +3 -2
- package/dist/model-pricing.d.ts +24 -5
- package/dist/model-pricing.js +0 -0
- package/dist/model-selection.js +13 -4
- package/dist/network/chatgpt-transport.js +0 -34
- package/dist/network/errors.d.ts +19 -0
- package/dist/network/errors.js +24 -0
- package/dist/network/provider-transport.d.ts +0 -4
- package/dist/network/provider-transport.js +3 -35
- package/dist/network/retry.d.ts +6 -1
- package/dist/network/retry.js +9 -0
- package/dist/provider-model-policy.d.ts +16 -0
- package/dist/provider-model-policy.js +33 -0
- package/dist/provider-registry.d.ts +1 -1
- package/dist/provider-registry.js +80 -11
- package/dist/provider-transform.js +32 -4
- package/dist/provider.d.ts +6 -2
- package/dist/provider.js +100 -11
- package/dist/question/controller.js +5 -4
- package/dist/question/normalize.d.ts +6 -0
- package/dist/question/normalize.js +11 -0
- package/dist/rewind.d.ts +20 -0
- package/dist/rewind.js +29 -0
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +11 -3
- package/dist/session.d.ts +7 -0
- package/dist/session.js +20 -0
- package/dist/skills/discovery.js +18 -0
- package/dist/skills/registry.d.ts +12 -2
- package/dist/skills/registry.js +40 -5
- package/dist/skills/types.d.ts +2 -0
- package/dist/slash-commands/commands.js +120 -81
- package/dist/slash-commands/registry.d.ts +2 -1
- package/dist/slash-commands/registry.js +3 -0
- package/dist/slash-commands/types.d.ts +22 -4
- package/dist/stats/usage.d.ts +3 -1
- package/dist/stats/usage.js +22 -13
- package/dist/tasks/manager.d.ts +4 -2
- package/dist/tasks/manager.js +34 -9
- package/dist/tasks/promotion.d.ts +2 -2
- package/dist/tasks/promotion.js +2 -2
- package/dist/tools/agent-lifecycle.js +4 -0
- package/dist/tools/bash.d.ts +3 -1
- package/dist/tools/bash.js +13 -2
- package/dist/tools/grep.d.ts +5 -1
- package/dist/tools/grep.js +318 -69
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/question.js +5 -4
- package/dist/tools/read.js +40 -21
- package/dist/tui/animation-clock.d.ts +14 -0
- package/dist/tui/animation-clock.js +34 -0
- package/dist/tui/app.d.ts +176 -0
- package/dist/tui/app.js +1647 -0
- package/dist/tui/components/approval-dialog.d.ts +43 -0
- package/dist/tui/components/approval-dialog.js +247 -0
- package/dist/tui/components/bottom-sheet.d.ts +9 -0
- package/dist/tui/components/bottom-sheet.js +32 -0
- package/dist/tui/components/context-info.d.ts +54 -0
- package/dist/tui/components/context-info.js +388 -0
- package/dist/tui/components/feedback-dialog.d.ts +56 -0
- package/dist/tui/components/feedback-dialog.js +262 -0
- package/dist/tui/components/feishu-setup-dialog.d.ts +65 -0
- package/dist/tui/components/feishu-setup-dialog.js +332 -0
- package/dist/tui/components/image-preview.d.ts +32 -0
- package/dist/tui/components/image-preview.js +130 -0
- package/dist/tui/components/plan-dialog.d.ts +34 -0
- package/dist/tui/components/plan-dialog.js +196 -0
- package/dist/tui/components/provider-key-input.d.ts +18 -0
- package/dist/tui/components/provider-key-input.js +49 -0
- package/dist/tui/components/question-dialog.d.ts +25 -0
- package/dist/tui/components/question-dialog.js +263 -0
- package/dist/tui/components/responsive-transcript.d.ts +31 -0
- package/dist/tui/components/responsive-transcript.js +77 -0
- package/dist/tui/components/rewind-picker.d.ts +62 -0
- package/dist/tui/components/rewind-picker.js +317 -0
- package/dist/tui/components/session-picker.d.ts +49 -0
- package/dist/tui/components/session-picker.js +303 -0
- package/dist/tui/components/skills-panel.d.ts +59 -0
- package/dist/tui/components/skills-panel.js +568 -0
- package/dist/tui/components/stats-panel.d.ts +40 -0
- package/dist/tui/components/stats-panel.js +301 -0
- package/dist/tui/components/streaming-message.d.ts +69 -0
- package/dist/tui/components/streaming-message.js +393 -0
- package/dist/tui/components/subagent-inspector.d.ts +28 -0
- package/dist/tui/components/subagent-inspector.js +127 -0
- package/dist/tui/components/task-inspector.d.ts +26 -0
- package/dist/tui/components/task-inspector.js +70 -0
- package/dist/tui/components/tasks-pane.d.ts +88 -0
- package/dist/tui/components/tasks-pane.js +416 -0
- package/dist/tui/components/transcript.d.ts +96 -0
- package/dist/tui/components/transcript.js +771 -0
- package/dist/tui/components/welcome.d.ts +20 -0
- package/dist/tui/components/welcome.js +133 -0
- package/dist/tui/components/workflow-inspector.d.ts +30 -0
- package/dist/tui/components/workflow-inspector.js +136 -0
- package/dist/tui/composer-autocomplete.d.ts +61 -0
- package/dist/tui/composer-autocomplete.js +383 -0
- package/dist/tui/composer-style.d.ts +7 -0
- package/dist/tui/composer-style.js +29 -0
- package/dist/tui/controller/agent-event-reducer.d.ts +75 -0
- package/dist/tui/controller/agent-event-reducer.js +238 -0
- package/dist/tui/controller/composer-controller.d.ts +95 -0
- package/dist/tui/controller/composer-controller.js +502 -0
- package/dist/tui/controller/controller.d.ts +194 -0
- package/dist/tui/controller/controller.js +1035 -0
- package/dist/tui/controller/effects.d.ts +71 -0
- package/dist/tui/controller/effects.js +1 -0
- package/dist/tui/controller/goal-runtime-controller.d.ts +47 -0
- package/dist/tui/controller/goal-runtime-controller.js +202 -0
- package/dist/tui/controller/input-queue-machine.d.ts +71 -0
- package/dist/tui/controller/input-queue-machine.js +121 -0
- package/dist/tui/controller/intents.d.ts +56 -0
- package/dist/tui/controller/intents.js +6 -0
- package/dist/tui/controller/overlay-controller.d.ts +64 -0
- package/dist/tui/controller/overlay-controller.js +129 -0
- package/dist/tui/controller/ports.d.ts +57 -0
- package/dist/tui/controller/ports.js +9 -0
- package/dist/tui/controller/session-transition.d.ts +54 -0
- package/dist/tui/controller/session-transition.js +68 -0
- package/dist/tui/controller/state.d.ts +14 -0
- package/dist/tui/controller/state.js +36 -0
- package/dist/tui/controller/task-runtime-controller.d.ts +66 -0
- package/dist/tui/controller/task-runtime-controller.js +158 -0
- package/dist/tui/footer.d.ts +37 -0
- package/dist/tui/footer.js +80 -0
- package/dist/{tui-ink → tui/formatting}/edit-diff.d.ts +1 -1
- package/dist/{tui-ink → tui/formatting}/edit-diff.js +1 -1
- package/dist/{tui-ink/app-helpers.d.ts → tui/formatting/summary.d.ts} +4 -4
- package/dist/{tui-ink/app-helpers.js → tui/formatting/summary.js} +2 -2
- package/dist/tui/fullscreen.d.ts +48 -0
- package/dist/tui/fullscreen.js +255 -0
- package/dist/tui/image-display.d.ts +4 -0
- package/dist/tui/image-display.js +30 -12
- package/dist/tui/markdown-style.d.ts +5 -0
- package/dist/tui/markdown-style.js +23 -0
- package/dist/{tui-ink → tui/model}/composer-buffer.js +1 -1
- package/dist/tui/model/composer-types.d.ts +17 -0
- package/dist/tui/model/composer-types.js +1 -0
- package/dist/{tui-ink → tui/model}/display-history.d.ts +7 -1
- package/dist/{tui-ink → tui/model}/display-reconstruct.d.ts +1 -1
- package/dist/{tui-ink → tui/model}/display-reconstruct.js +54 -6
- package/dist/{tui-ink → tui/model}/file-mentions.d.ts +2 -0
- package/dist/{tui-ink → tui/model}/file-mentions.js +4 -0
- package/dist/tui/model/image-attachment.d.ts +15 -0
- package/dist/tui/model/image-attachment.js +1 -0
- package/dist/{tui-ink → tui/model}/image-paste.d.ts +7 -1
- package/dist/{tui-ink → tui/model}/image-paste.js +46 -0
- package/dist/tui/model/input-history.d.ts +1 -0
- package/dist/tui/model/input-history.js +1 -0
- package/dist/{tui-ink → tui/model}/input-queue.d.ts +1 -1
- package/dist/{tui-ink → tui/model}/recent-activity.js +1 -1
- package/dist/{tui-ink → tui/model}/subagent-view.d.ts +17 -2
- package/dist/{tui-ink → tui/model}/subagent-view.js +125 -4
- package/dist/{tui-ink → tui/model}/submit-dedupe.d.ts +1 -1
- package/dist/tui/model/theme-style.d.ts +10 -0
- package/dist/tui/model/theme-style.js +79 -0
- package/dist/{tui-ink → tui/model}/theme.d.ts +11 -23
- package/dist/{tui-ink → tui/model}/theme.js +23 -38
- package/dist/{tui-ink → tui/model}/trace-groups.d.ts +2 -0
- package/dist/{tui-ink → tui/model}/trace-groups.js +91 -30
- package/dist/tui/model/trace-interaction.d.ts +63 -0
- package/dist/tui/model/trace-interaction.js +87 -0
- package/dist/tui/model-picker-data.d.ts +1 -2
- package/dist/tui/model-picker-data.js +8 -1
- package/dist/tui/model-switch.js +2 -0
- package/dist/{tui-ink → tui}/run-session-picker.d.ts +2 -3
- package/dist/tui/run-session-picker.js +64 -0
- package/dist/tui/run.d.ts +72 -0
- package/dist/tui/run.js +139 -0
- package/dist/tui/terminal/multiplexer.d.ts +5 -0
- package/dist/tui/terminal/multiplexer.js +14 -0
- package/dist/tui/testing/fake-agent.d.ts +24 -0
- package/dist/tui/testing/fake-agent.js +22 -0
- package/dist/tui/testing/spy-host.d.ts +21 -0
- package/dist/tui/testing/spy-host.js +55 -0
- package/dist/tui/trace-groups.js +27 -6
- package/dist/types.d.ts +7 -0
- package/node_modules/@bubblebrain-ai/pi-tui/LICENSE +21 -0
- package/node_modules/@bubblebrain-ai/pi-tui/README.md +854 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts +26 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js +121 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts +86 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js +649 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts +13 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js +36 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts +22 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js +99 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts +22 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js +36 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts +330 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js +2344 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts +7 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js +43 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts +28 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js +82 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts +48 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js +390 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts +31 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js +67 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts +103 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js +797 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts +56 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js +179 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts +53 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js +175 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts +50 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js +174 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts +12 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js +22 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts +32 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js +109 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts +19 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js +81 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts +13 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js +48 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts +8 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js +26 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts +39 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts +16 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js +110 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts +31 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js +42 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts +273 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js +231 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts +184 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js +1174 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts +28 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js +46 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts +10 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js +1268 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts +40 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js +6 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts +42 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js +335 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts +3 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js +60 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts +56 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js +363 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts +10 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js +59 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts +116 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js +518 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts +142 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js +544 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts +11 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js +11 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +140 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +1334 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts +39 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js +536 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +338 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +1016 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts +17 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js +27 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts +96 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js +1193 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts +25 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js +96 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts +73 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js +188 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js.map +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/README.md +20 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/build.sh +62 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/src/darwin-modifiers.c +70 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/win32/README.md +22 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/win32/build.mjs +229 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/node_modules/@bubblebrain-ai/pi-tui/native/win32/src/win32-console-mode.c +119 -0
- package/node_modules/@bubblebrain-ai/pi-tui/package.json +66 -0
- package/node_modules/get-east-asian-width/index.d.ts +60 -0
- package/node_modules/get-east-asian-width/index.js +30 -0
- package/node_modules/get-east-asian-width/license +9 -0
- package/node_modules/get-east-asian-width/lookup-data.js +21 -0
- package/node_modules/get-east-asian-width/lookup.js +138 -0
- package/node_modules/get-east-asian-width/package.json +71 -0
- package/node_modules/get-east-asian-width/readme.md +65 -0
- package/node_modules/get-east-asian-width/utilities.js +24 -0
- package/package.json +23 -10
- package/packages/pi-tui/node_modules/marked/LICENSE +44 -0
- package/packages/pi-tui/node_modules/marked/README.md +115 -0
- package/packages/pi-tui/node_modules/marked/bin/main.js +284 -0
- package/packages/pi-tui/node_modules/marked/bin/marked.js +16 -0
- package/packages/pi-tui/node_modules/marked/lib/marked.d.ts +760 -0
- package/packages/pi-tui/node_modules/marked/lib/marked.esm.js +77 -0
- package/packages/pi-tui/node_modules/marked/lib/marked.esm.js.map +7 -0
- package/packages/pi-tui/node_modules/marked/lib/marked.umd.js +79 -0
- package/packages/pi-tui/node_modules/marked/lib/marked.umd.js.map +7 -0
- package/packages/pi-tui/node_modules/marked/man/marked.1 +113 -0
- package/packages/pi-tui/node_modules/marked/man/marked.1.md +93 -0
- package/packages/pi-tui/node_modules/marked/package.json +103 -0
- package/dist/tui-ink/app.d.ts +0 -76
- package/dist/tui-ink/app.js +0 -2377
- package/dist/tui-ink/approval/approval-dialog.d.ts +0 -13
- package/dist/tui-ink/approval/approval-dialog.js +0 -184
- package/dist/tui-ink/approval/diff-view.d.ts +0 -7
- package/dist/tui-ink/approval/diff-view.js +0 -44
- package/dist/tui-ink/approval/select.d.ts +0 -35
- package/dist/tui-ink/approval/select.js +0 -98
- package/dist/tui-ink/code-highlight.d.ts +0 -8
- package/dist/tui-ink/code-highlight.js +0 -121
- package/dist/tui-ink/command-palette.d.ts +0 -32
- package/dist/tui-ink/command-palette.js +0 -158
- package/dist/tui-ink/compaction-progress.d.ts +0 -19
- package/dist/tui-ink/compaction-progress.js +0 -74
- package/dist/tui-ink/detect-theme.d.ts +0 -1
- package/dist/tui-ink/detect-theme.js +0 -1
- package/dist/tui-ink/feedback-dialog.d.ts +0 -19
- package/dist/tui-ink/feedback-dialog.js +0 -133
- package/dist/tui-ink/feishu-setup-picker.d.ts +0 -5
- package/dist/tui-ink/feishu-setup-picker.js +0 -271
- package/dist/tui-ink/footer.d.ts +0 -26
- package/dist/tui-ink/footer.js +0 -51
- package/dist/tui-ink/input-box.d.ts +0 -189
- package/dist/tui-ink/input-box.js +0 -1279
- package/dist/tui-ink/input-history.d.ts +0 -1
- package/dist/tui-ink/input-history.js +0 -1
- package/dist/tui-ink/markdown.d.ts +0 -103
- package/dist/tui-ink/markdown.js +0 -747
- package/dist/tui-ink/message-list.d.ts +0 -49
- package/dist/tui-ink/message-list.js +0 -841
- package/dist/tui-ink/model-picker.d.ts +0 -143
- package/dist/tui-ink/model-picker.js +0 -966
- package/dist/tui-ink/plan-confirm.d.ts +0 -7
- package/dist/tui-ink/plan-confirm.js +0 -115
- package/dist/tui-ink/question-dialog.d.ts +0 -8
- package/dist/tui-ink/question-dialog.js +0 -135
- package/dist/tui-ink/rewind-picker.d.ts +0 -8
- package/dist/tui-ink/rewind-picker.js +0 -102
- package/dist/tui-ink/run-session-picker.js +0 -22
- package/dist/tui-ink/run.d.ts +0 -62
- package/dist/tui-ink/run.js +0 -103
- package/dist/tui-ink/session-picker.d.ts +0 -10
- package/dist/tui-ink/session-picker.js +0 -113
- package/dist/tui-ink/stats-panel.d.ts +0 -11
- package/dist/tui-ink/stats-panel.js +0 -65
- package/dist/tui-ink/subagent-inspector.d.ts +0 -21
- package/dist/tui-ink/subagent-inspector.js +0 -205
- package/dist/tui-ink/use-terminal-size.d.ts +0 -4
- package/dist/tui-ink/use-terminal-size.js +0 -48
- package/dist/tui-ink/waiting-indicator.d.ts +0 -12
- package/dist/tui-ink/waiting-indicator.js +0 -129
- package/dist/tui-ink/welcome.d.ts +0 -25
- package/dist/tui-ink/welcome.js +0 -88
- package/dist/tui-ink/wordmark.d.ts +0 -8
- package/dist/tui-ink/wordmark.js +0 -103
- /package/dist/{tui-ink → tui/model}/composer-buffer.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/display-history.js +0 -0
- /package/dist/{tui-ink → tui/model}/input-queue.js +0 -0
- /package/dist/{tui-ink → tui/model}/key-events.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/key-events.js +0 -0
- /package/dist/{tui-ink → tui/model}/paste-operation-tracker.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/paste-operation-tracker.js +0 -0
- /package/dist/{tui-ink → tui/model}/recent-activity.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/submit-dedupe.js +0 -0
- /package/dist/{tui-ink → tui/model}/sync-output.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/sync-output.js +0 -0
- /package/dist/{tui-ink → tui/model}/terminal-env.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/terminal-env.js +0 -0
- /package/dist/{tui-ink → tui/model}/terminal-mouse.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/terminal-mouse.js +0 -0
- /package/dist/{tui-ink → tui/model}/width.d.ts +0 -0
- /package/dist/{tui-ink → tui/model}/width.js +0 -0
|
@@ -1,841 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { Box, Static, Text, measureElement } from "ink";
|
|
4
|
-
import { useTheme } from "./theme.js";
|
|
5
|
-
import { highlightCode, inferLang } from "./code-highlight.js";
|
|
6
|
-
import { MarkdownContent, StreamingMarkdown } from "./markdown.js";
|
|
7
|
-
import { visualWidth, graphemeWidth, ambiguousIsWide } from "./width.js";
|
|
8
|
-
import { userInputStatusBadgeLabel, } from "./display-history.js";
|
|
9
|
-
import { buildTraceGroups, executeCommandBlock, formatTracePath, shouldInlineExecuteCommand, traceGroupLabel, } from "./trace-groups.js";
|
|
10
|
-
import { EDIT_COLLAPSED_DIFF_LINES, formatEditSuccessSummary, getEditDiffDetails } from "./edit-diff.js";
|
|
11
|
-
import { latestSubagentNote, sortSubagents, subagentDescriptor, subagentLabel, subagentStatusColor, subagentSummary, } from "./subagent-view.js";
|
|
12
|
-
import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
|
|
13
|
-
import { stripFileBlocks } from "../context/compaction-files.js";
|
|
14
|
-
import { splitImageDisplayContent } from "../tui/image-display.js";
|
|
15
|
-
const EXECUTE_COMMAND_BLOCK_MAX_LINES = 4;
|
|
16
|
-
/**
|
|
17
|
-
* Every transcript row — each committed <Static> item and the DynamicClamp
|
|
18
|
-
* live region — wraps its content in paddingX={1}: one column of inset on
|
|
19
|
-
* EACH side. Any wrap budget derived from terminalColumns inside a row must
|
|
20
|
-
* net out these two columns. Forgetting them hands the pre-wrapper a budget
|
|
21
|
-
* two columns wider than the real room, and a dense CJK line that packs flush
|
|
22
|
-
* against that budget renders past the terminal edge — the terminal then
|
|
23
|
-
* hard-wraps the last glyph onto a stray row at column 0.
|
|
24
|
-
*/
|
|
25
|
-
const TRANSCRIPT_ROW_PADDING = 2;
|
|
26
|
-
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration = 0, paddingX = 1, maxStreamRows, }) {
|
|
27
|
-
const theme = useTheme();
|
|
28
|
-
const hasStreaming = !!(streamingContent ||
|
|
29
|
-
streamingReasoning ||
|
|
30
|
-
streamingTools.length > 0 ||
|
|
31
|
-
streamingParts.length > 0);
|
|
32
|
-
const regularMessages = messages.filter((message) => !message.inputStatus);
|
|
33
|
-
const pendingSteerMessages = messages.filter((message) => message.inputStatus === "pending_steer");
|
|
34
|
-
const queuedInputMessages = messages.filter((message) => message.inputStatus === "queued");
|
|
35
|
-
// While the transcript has no settled rows, the banner stays OUT of
|
|
36
|
-
// <Static> and renders in the live region instead, so it repaints on every
|
|
37
|
-
// commit — a /model switch on the fresh launch screen updates the banner in
|
|
38
|
-
// place. It is committed to scrollback together with the first settled row
|
|
39
|
-
// (same render commit, so the hand-off is seamless) and frozen from then on.
|
|
40
|
-
const bannerCommitted = regularMessages.length > 0;
|
|
41
|
-
const staticItems = [];
|
|
42
|
-
if (welcomeBanner && bannerCommitted) {
|
|
43
|
-
staticItems.push({ kind: "welcome", key: "welcome" });
|
|
44
|
-
}
|
|
45
|
-
const lastMessageIndex = regularMessages.length - 1;
|
|
46
|
-
for (let i = 0; i < regularMessages.length; i++) {
|
|
47
|
-
const msg = regularMessages[i];
|
|
48
|
-
staticItems.push({
|
|
49
|
-
kind: "message",
|
|
50
|
-
key: msg.key ?? `message-${i}`,
|
|
51
|
-
message: msg,
|
|
52
|
-
showExpandHint: !hasStreaming && i === lastMessageIndex,
|
|
53
|
-
separateFromPrevious: msg.role === "user" && regularMessages[i - 1]?.role === "user",
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
const hasDynamic = hasStreaming || pendingSteerMessages.length > 0 || queuedInputMessages.length > 0;
|
|
57
|
-
// The live region must never grow taller than the viewport: a frame taller
|
|
58
|
-
// than the pane breaks Ink's in-place redraw under tmux (the cursor-up clear
|
|
59
|
-
// can't reach scrolled-off rows), leaving large blank gaps + stray glyphs.
|
|
60
|
-
// Clip it to maxStreamRows and pin to the bottom so the user sees the latest
|
|
61
|
-
// output (tail view); the full turn lands in <Static> scrollback on commit.
|
|
62
|
-
const clampDynamic = typeof maxStreamRows === "number" && maxStreamRows > 0;
|
|
63
|
-
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(Static, { items: staticItems, children: (item) => {
|
|
64
|
-
if (item.kind === "welcome") {
|
|
65
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, backgroundColor: theme.background, children: welcomeBanner }, item.key));
|
|
66
|
-
}
|
|
67
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, backgroundColor: theme.background, children: _jsx(MessageItem, { message: item.message, terminalColumns: terminalColumns, showThinking: showThinking, verboseTrace: verboseTrace, showExpandHint: item.showExpandHint, separateFromPrevious: item.separateFromPrevious }) }, item.key));
|
|
68
|
-
} }, `transcript-${staticGeneration}`), welcomeBanner && !bannerCommitted && (_jsx(Box, { flexDirection: "column", paddingX: paddingX, backgroundColor: theme.background, children: welcomeBanner })), hasDynamic && (_jsxs(DynamicClamp, { maxRows: clampDynamic ? maxStreamRows : undefined, paddingX: paddingX, backgroundColor: theme.background, children: [hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, showThinking: showThinking, verboseTrace: verboseTrace, pendingApproval: pendingApproval, nowTick: nowTick })), pendingSteerMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: pendingSteerMessages, terminalColumns: terminalColumns, title: "Messages to steer at next model call", hint: "applies before the next provider request", bulletColor: theme.warning })), queuedInputMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: queuedInputMessages, terminalColumns: terminalColumns, title: "Messages queued for next turn", hint: "runs after the current answer", bulletColor: theme.muted }))] }))] }));
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Bounds the live (in-progress turn) region to at most `maxRows` rows, pinned
|
|
72
|
-
* to the bottom so the user always sees the latest output (tail view). A live
|
|
73
|
-
* frame taller than the terminal pane breaks Ink's in-place redraw under tmux
|
|
74
|
-
* (the cursor-up clear can't reach rows that scrolled off), leaving large blank
|
|
75
|
-
* gaps and stray glyphs. We measure the natural content height and only clip
|
|
76
|
-
* when it actually exceeds `maxRows`, so short turns keep their natural height
|
|
77
|
-
* (no reserved-space gap). The full turn still lands in <Static> scrollback the
|
|
78
|
-
* moment it commits, so nothing is lost — only the live preview is windowed.
|
|
79
|
-
*/
|
|
80
|
-
function DynamicClamp({ maxRows, paddingX, backgroundColor, children, }) {
|
|
81
|
-
const innerRef = React.useRef(null);
|
|
82
|
-
const [offset, setOffset] = React.useState(0);
|
|
83
|
-
const [clipHeight, setClipHeight] = React.useState(undefined);
|
|
84
|
-
// Re-measure after every commit (streaming changes height with each token).
|
|
85
|
-
// useLayoutEffect runs before Ink flushes the frame to the terminal, so the
|
|
86
|
-
// clamp is applied in the same paint — avoiding a one-frame overflow flash
|
|
87
|
-
// under tmux when a turn first grows past the pane. setState bails out via
|
|
88
|
-
// Object.is when nothing moved, so the steady state does not loop.
|
|
89
|
-
React.useLayoutEffect(() => {
|
|
90
|
-
if (!maxRows || !innerRef.current) {
|
|
91
|
-
if (offset !== 0)
|
|
92
|
-
setOffset(0);
|
|
93
|
-
if (clipHeight !== undefined)
|
|
94
|
-
setClipHeight(undefined);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const height = measureElement(innerRef.current).height;
|
|
98
|
-
if (height > maxRows) {
|
|
99
|
-
const nextOffset = -(height - maxRows);
|
|
100
|
-
if (nextOffset !== offset)
|
|
101
|
-
setOffset(nextOffset);
|
|
102
|
-
if (clipHeight !== maxRows)
|
|
103
|
-
setClipHeight(maxRows);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
if (offset !== 0)
|
|
107
|
-
setOffset(0);
|
|
108
|
-
if (clipHeight !== undefined)
|
|
109
|
-
setClipHeight(undefined);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: paddingX, backgroundColor: backgroundColor, ...(clipHeight !== undefined ? { height: clipHeight, overflowY: "hidden" } : {}), children: _jsx(Box, { ref: innerRef, flexDirection: "column", flexShrink: 0, marginTop: offset, children: children }) }));
|
|
113
|
-
}
|
|
114
|
-
// Memoized: with no <Static> region, every transcript row re-renders on each
|
|
115
|
-
// state change unless its props are referentially stable. Message objects are
|
|
116
|
-
// append-only (compaction reuses already-compacted instances), keys are
|
|
117
|
-
// stable, and nowTick is only threaded to the last row, so memo hits for all
|
|
118
|
-
// settled history rows.
|
|
119
|
-
const MessageItem = React.memo(function MessageItem({ message, terminalColumns, showThinking, verboseTrace, showExpandHint, separateFromPrevious, nowTick, }) {
|
|
120
|
-
const theme = useTheme();
|
|
121
|
-
if (message.role === "user") {
|
|
122
|
-
return (_jsx(UserMessageBlock, { content: message.content, terminalColumns: terminalColumns, inputStatus: message.inputStatus, separateFromPrevious: separateFromPrevious }));
|
|
123
|
-
}
|
|
124
|
-
if (message.role === "error") {
|
|
125
|
-
return (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsxs(Text, { color: theme.error, children: ["Error: ", message.content] }) }));
|
|
126
|
-
}
|
|
127
|
-
if (message.syntheticKind === "ui_compact_summary") {
|
|
128
|
-
return _jsx(CompactionSummaryBlock, { message: message });
|
|
129
|
-
}
|
|
130
|
-
if (message.syntheticKind === "ui_interrupt") {
|
|
131
|
-
return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.error, children: "\u23F9 " }), _jsx(Text, { color: theme.muted, dimColor: true, children: message.content || "Interrupted by user" })] }));
|
|
132
|
-
}
|
|
133
|
-
// One-line status notices (model switch etc.): accent-colored so they read
|
|
134
|
-
// as UI feedback, not assistant prose.
|
|
135
|
-
if (message.syntheticKind === "ui_notice") {
|
|
136
|
-
return (_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsxs(Text, { color: theme.accent, children: ["\u2726 ", message.content] }) }));
|
|
137
|
-
}
|
|
138
|
-
const visibleReasoning = sanitizeInternalReminderBlocks(message.reasoning ?? "").trim();
|
|
139
|
-
// Same defense as reasoning: strip any internal reminder markup the model
|
|
140
|
-
// echoed back into its visible answer so it never reaches the transcript.
|
|
141
|
-
const visibleContent = sanitizeInternalReminderBlocks(message.content ?? "");
|
|
142
|
-
// Decide visibility by what will ACTUALLY render below, not by raw array
|
|
143
|
-
// lengths. A turn whose only text part is an echoed <bubble_internal_*>
|
|
144
|
-
// reminder sanitizes to empty (TimelineText returns null), but the wrapper's
|
|
145
|
-
// marginTop/marginBottom would still emit a blank band — and long sessions
|
|
146
|
-
// inject more reminders, so consecutive empty turns stack into a large gap
|
|
147
|
-
// after the tool rows. Mirror the render: parts path → MessageParts; non-parts
|
|
148
|
-
// path → toolCalls/visibleContent; verbose adds a TurnDigest from toolCalls.
|
|
149
|
-
const hasParts = (message.parts?.length ?? 0) > 0;
|
|
150
|
-
const hasVisibleParts = hasParts &&
|
|
151
|
-
(message.parts ?? []).some((part) => part.type === "tools"
|
|
152
|
-
? part.toolCalls.length > 0
|
|
153
|
-
: sanitizeInternalReminderBlocks(part.content).trim() !== "");
|
|
154
|
-
const toolCallCount = message.toolCalls?.length ?? 0;
|
|
155
|
-
const hasVisibleAssistantContent = (hasParts ? hasVisibleParts : (!!visibleContent.trim() || toolCallCount > 0)) ||
|
|
156
|
-
// Reasoning now always renders (collapsed by default), so its presence
|
|
157
|
-
// alone makes a turn visible.
|
|
158
|
-
!!visibleReasoning ||
|
|
159
|
-
// A finalized turn carries taskElapsedMs and renders a TaskDurationLine even
|
|
160
|
-
// when its text/parts are empty — mirror that so the duration isn't dropped.
|
|
161
|
-
// (The verbose TurnDigest needs no term: whenever it renders, the same
|
|
162
|
-
// toolCalls already make the turn visible via the parts/non-parts branch.)
|
|
163
|
-
message.taskElapsedMs !== undefined;
|
|
164
|
-
if (!hasVisibleAssistantContent)
|
|
165
|
-
return null;
|
|
166
|
-
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (_jsx(ReasoningTraceBlock, { reasoning: visibleReasoning, expanded: showThinking || verboseTrace })), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), visibleContent.trim() && (
|
|
167
|
-
// Thread the real terminal width down (minus the row's paddingX
|
|
168
|
-
// inset); without it, width-aware blocks like tables fall back to
|
|
169
|
-
// a stale/default useTerminalSize and mis-budget their columns.
|
|
170
|
-
_jsx(MarkdownContent, { content: visibleContent, maxWidth: Math.max(20, terminalColumns - 4) }))] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
|
|
171
|
-
});
|
|
172
|
-
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, showThinking, verboseTrace, pendingApproval, nowTick, }) {
|
|
173
|
-
const deferredContent = React.useDeferredValue(content);
|
|
174
|
-
const deferredReasoning = React.useDeferredValue(reasoning);
|
|
175
|
-
const deferredParts = React.useDeferredValue(parts);
|
|
176
|
-
const visibleReasoning = sanitizeInternalReminderBlocks(deferredReasoning).trim();
|
|
177
|
-
const visibleParts = deferredParts.length > 0
|
|
178
|
-
? deferredParts
|
|
179
|
-
: fallbackStreamingParts(deferredContent, tools);
|
|
180
|
-
return (_jsxs(Box, { flexDirection: "column", children: [visibleReasoning && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning, expanded: showThinking || verboseTrace, streaming: true }) })), visibleParts.length > 0 && (
|
|
181
|
-
// marginTop=1 matches the committed MessageItem layout exactly, so the
|
|
182
|
-
// gap under the user message is identical while streaming and after the
|
|
183
|
-
// turn commits — no spacing jump at finalize time. (The old marginTop=0
|
|
184
|
-
// was a flicker mitigation for the main-screen <Static> renderer; the
|
|
185
|
-
// alt-screen viewport repaints frames atomically, so it's obsolete.)
|
|
186
|
-
_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
|
|
187
|
-
}
|
|
188
|
-
function MessageParts({ parts, terminalColumns, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
|
|
189
|
-
const lastToolsPartIndex = findLastToolsPartIndex(parts);
|
|
190
|
-
const lastTextPartIndex = findLastTextPartIndex(parts);
|
|
191
|
-
return (_jsx(Box, { flexDirection: "column", children: parts.map((part, idx) => {
|
|
192
|
-
if (part.type === "text") {
|
|
193
|
-
return (_jsx(TimelineText, { content: part.content, compactTop: idx === 0, terminalColumns: terminalColumns, streaming: streaming && idx === lastTextPartIndex }, `text-${idx}`));
|
|
194
|
-
}
|
|
195
|
-
return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: showExpandHint && idx === lastToolsPartIndex, compactTop: idx === 0, nowTick: nowTick, showActivity: showActivity && idx === lastToolsPartIndex }, `tools-${idx}`));
|
|
196
|
-
}) }));
|
|
197
|
-
}
|
|
198
|
-
function findLastTextPartIndex(parts) {
|
|
199
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
200
|
-
if (parts[i]?.type === "text")
|
|
201
|
-
return i;
|
|
202
|
-
}
|
|
203
|
-
return -1;
|
|
204
|
-
}
|
|
205
|
-
function TimelineText({ content, compactTop, terminalColumns, streaming = false, }) {
|
|
206
|
-
const theme = useTheme();
|
|
207
|
-
// Strip any internal reminder/context markup the model echoed back into its
|
|
208
|
-
// visible text — the reasoning path already does this. Without it, a model
|
|
209
|
-
// that parrots a <bubble_internal_*> block (e.g. an injected system reminder)
|
|
210
|
-
// leaks it straight into the transcript. The streaming sanitizer also holds a
|
|
211
|
-
// half-typed block until it closes, so partial markup never flashes.
|
|
212
|
-
const visible = sanitizeInternalReminderBlocks(content);
|
|
213
|
-
if (!visible.trim())
|
|
214
|
-
return null;
|
|
215
|
-
// Timeline gutter = marginLeft (2) + "● " marker. The ● (U+25CF) is itself
|
|
216
|
-
// an ambiguous-width glyph, so the marker is 3 cells on a narrow terminal but
|
|
217
|
-
// 4 on an ambiguous-wide one — Ink lays it out as 3 either way (it measures
|
|
218
|
-
// ●=1), so on a wide terminal the first line of every message gets shoved 1
|
|
219
|
-
// cell right and would overflow. Reserve that extra cell up front so the
|
|
220
|
-
// pre-wrap never packs a line the terminal then hard-wraps.
|
|
221
|
-
const gutter = ambiguousIsWide() ? 6 : 5;
|
|
222
|
-
const available = terminalColumns
|
|
223
|
-
? Math.max(20, terminalColumns - gutter - TRANSCRIPT_ROW_PADDING)
|
|
224
|
-
: undefined;
|
|
225
|
-
const trimmed = visible.trim();
|
|
226
|
-
return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\u25CF " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: streaming ? (_jsx(StreamingMarkdown, { content: trimmed, maxWidth: available, flushTop: true })) : (_jsx(MarkdownContent, { content: trimmed, maxWidth: available, flushTop: true })) })] }));
|
|
227
|
-
}
|
|
228
|
-
function ToolsPart({ toolCalls, terminalColumns, verboseTrace, pendingApproval, showExpandHint, compactTop = false, nowTick, showActivity = false, }) {
|
|
229
|
-
if (toolCalls.length === 0)
|
|
230
|
-
return null;
|
|
231
|
-
if (!verboseTrace) {
|
|
232
|
-
return (_jsx(TraceGroupList, { toolCalls: toolCalls, terminalColumns: terminalColumns, pendingApproval: pendingApproval, nowTick: nowTick, compactTop: compactTop, showActivity: showActivity }));
|
|
233
|
-
}
|
|
234
|
-
const lastIdx = toolCalls.length - 1;
|
|
235
|
-
return (_jsx(Box, { flexDirection: "column", children: toolCalls.map((tc, idx) => {
|
|
236
|
-
const isWaitingApproval = isToolPending(tc) && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
|
|
237
|
-
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose: verboseTrace, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
|
|
238
|
-
}) }));
|
|
239
|
-
}
|
|
240
|
-
function fallbackStreamingParts(content, tools) {
|
|
241
|
-
const parts = [];
|
|
242
|
-
if (tools.length > 0)
|
|
243
|
-
parts.push({ type: "tools", toolCalls: tools });
|
|
244
|
-
if (content)
|
|
245
|
-
parts.push({ type: "text", content });
|
|
246
|
-
return parts;
|
|
247
|
-
}
|
|
248
|
-
function findLastToolsPartIndex(parts) {
|
|
249
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
250
|
-
if (parts[i]?.type === "tools")
|
|
251
|
-
return i;
|
|
252
|
-
}
|
|
253
|
-
return -1;
|
|
254
|
-
}
|
|
255
|
-
function TraceGroupList({ toolCalls, terminalColumns, pendingApproval, nowTick, compactTop = false, showActivity = false, }) {
|
|
256
|
-
const groups = React.useMemo(() => buildTraceGroups(toolCalls), [toolCalls]);
|
|
257
|
-
const activeGroup = showActivity ? findActiveTraceGroup(groups, pendingApproval) : undefined;
|
|
258
|
-
if (groups.length === 0)
|
|
259
|
-
return null;
|
|
260
|
-
return (_jsxs(Box, { flexDirection: "column", children: [activeGroup && (_jsx(TraceActivityLine, { group: activeGroup, pendingApproval: pendingApproval, nowTick: nowTick, terminalColumns: terminalColumns })), groups.map((group, idx) => (_jsx(TraceGroupBlock, { group: group, terminalColumns: terminalColumns, pendingApproval: pendingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, group.raw.map((tool) => tool.id).join(":"))))] }));
|
|
261
|
-
}
|
|
262
|
-
function TraceActivityLine({ group, pendingApproval, nowTick, terminalColumns, }) {
|
|
263
|
-
const theme = useTheme();
|
|
264
|
-
const waiting = isTraceGroupWaitingForApproval(group, pendingApproval);
|
|
265
|
-
void nowTick;
|
|
266
|
-
const labelWidth = Math.max(20, terminalColumns - 26);
|
|
267
|
-
const label = truncateVisual(traceGroupLabel(group), labelWidth);
|
|
268
|
-
return (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: waiting ? theme.warning : theme.tracePending, children: "\u25CF " }), _jsxs(Text, { color: theme.traceDetail, children: [waiting ? "Waiting for approval" : "Working on", " "] }), _jsx(Text, { color: theme.traceAction, children: label })] }));
|
|
269
|
-
}
|
|
270
|
-
function TraceGroupBlock({ group, terminalColumns, pendingApproval, compactTop, nowTick, }) {
|
|
271
|
-
const theme = useTheme();
|
|
272
|
-
const waiting = isTraceGroupWaitingForApproval(group, pendingApproval);
|
|
273
|
-
const status = traceGroupStatus(group, waiting, theme, nowTick);
|
|
274
|
-
const editTool = group.kind === "edit" && group.raw.length === 1 ? group.raw[0] : undefined;
|
|
275
|
-
const editDetails = editTool && !group.pending && !group.hasError ? getEditDiffDetails(editTool) : null;
|
|
276
|
-
if (editTool && editDetails) {
|
|
277
|
-
return (_jsx(EditTraceBlock, { tool: editTool, details: editDetails, terminalColumns: terminalColumns, compactTop: compactTop, status: status }));
|
|
278
|
-
}
|
|
279
|
-
const allErrored = group.hasError && group.errorCount >= group.raw.length && !group.pending;
|
|
280
|
-
const titleColor = allErrored ? theme.error : theme.traceAction;
|
|
281
|
-
const detailColor = allErrored ? theme.error : theme.traceDetail;
|
|
282
|
-
const commandWidth = Math.max(14, terminalColumns - group.title.length - 20);
|
|
283
|
-
const detailWidth = Math.max(20, terminalColumns - 8);
|
|
284
|
-
const detailLines = group.previewLines.length > 0 ? group.previewLines : group.items;
|
|
285
|
-
// Finished, error-free Execute rows collapse to a one-line summary: the
|
|
286
|
-
// multi-line command body and stdout preview only earn space while running
|
|
287
|
-
// (you want to see what's executing) or on failure (you need the error).
|
|
288
|
-
// Ctrl+O (verbose trace) shows everything.
|
|
289
|
-
const collapsedExecute = group.kind === "execute" && !group.pending && !waiting && !group.hasError;
|
|
290
|
-
// A model-provided bash description owns the header slot; the command then
|
|
291
|
-
// always renders as a block below. Without one, single-line commands that
|
|
292
|
-
// fit stay inline; anything longer becomes a wrapped block preserving the
|
|
293
|
-
// command's own line structure — commands are never clipped mid-line.
|
|
294
|
-
// Collapsed rows always inline the (truncated) command instead of a block.
|
|
295
|
-
const showDescription = group.kind === "execute" && !!group.description;
|
|
296
|
-
const inlineCommand = !showDescription && group.command
|
|
297
|
-
? (group.kind === "execute"
|
|
298
|
-
? (collapsedExecute || shouldInlineExecuteCommand(group, commandWidth)
|
|
299
|
-
? truncateVisual(group.command.replace(/\s+/g, " "), commandWidth)
|
|
300
|
-
: undefined)
|
|
301
|
-
: (visualWidth(group.command) <= commandWidth ? group.command : undefined))
|
|
302
|
-
: undefined;
|
|
303
|
-
const commandBlock = group.command && !inlineCommand && !collapsedExecute
|
|
304
|
-
? (group.kind === "execute"
|
|
305
|
-
? executeCommandBlock(group, EXECUTE_COMMAND_BLOCK_MAX_LINES)
|
|
306
|
-
: { lines: [group.command], omitted: 0 })
|
|
307
|
-
: null;
|
|
308
|
-
const collapsedOutputLines = group.previewLines.length + group.omitted;
|
|
309
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: titleColor, children: group.title }), showDescription ? (_jsxs(Text, { color: theme.traceDetail, children: [" ", truncateVisual(group.description, commandWidth)] })) : inlineCommand ? (_jsxs(Text, { color: theme.traceCommand, children: [" ", inlineCommand] })) : !group.command && group.count !== undefined && group.noun ? (_jsxs(Text, { color: theme.traceCount, children: [" ", group.count, " ", group.noun] })) : null, status && _jsxs(Text, { color: status.color, children: [" ", status.text] })] }), commandBlock && commandBlock.lines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [commandBlock.lines.flatMap((line, idx) => wrapByVisualWidth(line || " ", Math.max(10, detailWidth - 2)).map((seg, segIdx) => (_jsx(Text, { color: theme.traceCommand, children: seg }, `cmd-${idx}-${segIdx}`)))), commandBlock.omitted > 0 && (_jsxs(Text, { color: theme.traceDetail, children: ["\u2026 ", commandBlock.omitted, " more line", commandBlock.omitted === 1 ? "" : "s"] }))] })), collapsedExecute ? (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.traceDetail, children: ["\u23BF ", collapsedOutputLines > 0
|
|
310
|
-
? `${collapsedOutputLines} line${collapsedOutputLines === 1 ? "" : "s"} output · Ctrl+O to view`
|
|
311
|
-
: "no output"] }) })) : detailLines.length > 0 && (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: detailLines.map((line, index) => (_jsxs(Box, { marginLeft: index === 0 ? 0 : 2, children: [index === 0 && _jsx(Text, { color: theme.traceDetail, children: "\u21B3 " }), _jsx(Text, { color: detailColor, children: truncateVisual(line, detailWidth - (index === 0 ? 2 : 0)) })] }, index))) })), group.errorLines.length > 0 && (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: group.errorLines.map((line, index) => (_jsxs(Box, { marginLeft: index === 0 ? 0 : 2, children: [index === 0 && _jsx(Text, { color: theme.traceDetail, children: "\u21B3 " }), _jsx(Text, { color: theme.error, children: truncateVisual(line, detailWidth - (index === 0 ? 2 : 0)) })] }, `error-${index}`))) })), group.omitted > 0 && !collapsedExecute && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.traceDetail, children: ["... ", group.omitted, " more, Ctrl+O to view"] }) }))] }));
|
|
312
|
-
}
|
|
313
|
-
function EditTraceBlock({ tool, details, terminalColumns, compactTop, status, }) {
|
|
314
|
-
const theme = useTheme();
|
|
315
|
-
const path = formatTracePath(details.path ?? tool.args.path ?? "");
|
|
316
|
-
const pathWidth = Math.max(14, terminalColumns - 12);
|
|
317
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: theme.traceAction, children: "Edit" }), path && _jsxs(Text, { color: theme.traceCommand, children: [" ", truncateVisual(path, pathWidth)] }), status && _jsxs(Text, { color: status.color, children: [" ", status.text] })] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: theme.traceDetail, children: "\u23BF " }), _jsx(Text, { color: theme.success, children: formatEditSuccessSummary(details) })] }), _jsx(DiffBlock, { diff: details.diff, terminalColumns: terminalColumns, maxLines: EDIT_COLLAPSED_DIFF_LINES, verbose: false, showExpandHint: true })] }));
|
|
318
|
-
}
|
|
319
|
-
function traceGroupStatus(group, waitingApproval, theme, nowTick) {
|
|
320
|
-
if (waitingApproval)
|
|
321
|
-
return { text: "waiting for approval", color: theme.warning };
|
|
322
|
-
if (group.pending) {
|
|
323
|
-
void nowTick;
|
|
324
|
-
return { text: "running", color: theme.tracePending };
|
|
325
|
-
}
|
|
326
|
-
if (group.hasError) {
|
|
327
|
-
const count = group.errorCount || 1;
|
|
328
|
-
return { text: count === 1 ? "1 error" : `${count} errors`, color: theme.error };
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
function findActiveTraceGroup(groups, pendingApproval) {
|
|
333
|
-
for (let i = groups.length - 1; i >= 0; i--) {
|
|
334
|
-
const group = groups[i];
|
|
335
|
-
if (isTraceGroupWaitingForApproval(group, pendingApproval) || group.pending) {
|
|
336
|
-
return group;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return undefined;
|
|
340
|
-
}
|
|
341
|
-
function isTraceGroupWaitingForApproval(group, pendingApproval) {
|
|
342
|
-
return !!pendingApproval && group.raw.some((tool) => isToolPending(tool) && approvalMatchesTool(pendingApproval, tool));
|
|
343
|
-
}
|
|
344
|
-
function approvalMatchesTool(hint, tc) {
|
|
345
|
-
if (hint.toolName !== tc.name)
|
|
346
|
-
return false;
|
|
347
|
-
if (hint.toolName === "bash") {
|
|
348
|
-
return !hint.command || hint.command === tc.args.command;
|
|
349
|
-
}
|
|
350
|
-
return !hint.path || hint.path === tc.args.path;
|
|
351
|
-
}
|
|
352
|
-
function isToolPending(tool) {
|
|
353
|
-
return tool.result === undefined;
|
|
354
|
-
}
|
|
355
|
-
/** Live rolling window size — enough to read the model's train of thought. */
|
|
356
|
-
const REASONING_STREAM_WINDOW = 5;
|
|
357
|
-
/** Lines kept when a finished turn's reasoning is collapsed. */
|
|
358
|
-
const REASONING_COLLAPSED_LINES = 2;
|
|
359
|
-
/**
|
|
360
|
-
* Reasoning trace with three states, because a reasoning model can spend
|
|
361
|
-
* minutes here (qwen3.8-max at high effort drafts entire files in thought):
|
|
362
|
-
* hiding it looks like a hang, printing all of it buries the transcript.
|
|
363
|
-
*
|
|
364
|
-
* streaming + compact rolling last N lines, so there is always motion
|
|
365
|
-
* settled + compact opening lines + "(ctrl+o to expand)"
|
|
366
|
-
* expanded everything (Ctrl+T thinking, or Ctrl+O verbose)
|
|
367
|
-
*/
|
|
368
|
-
function ReasoningTraceBlock({ reasoning, expanded = false, streaming = false, }) {
|
|
369
|
-
const theme = useTheme();
|
|
370
|
-
const lines = React.useMemo(() => reasoning.split("\n").filter((l) => l.trim() !== ""), [reasoning]);
|
|
371
|
-
if (lines.length === 0)
|
|
372
|
-
return null;
|
|
373
|
-
const visible = expanded
|
|
374
|
-
? lines
|
|
375
|
-
: streaming
|
|
376
|
-
? lines.slice(-REASONING_STREAM_WINDOW)
|
|
377
|
-
: lines.slice(0, REASONING_COLLAPSED_LINES);
|
|
378
|
-
const hidden = lines.length - visible.length;
|
|
379
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: theme.thinkingDim, dimColor: true, children: ["\u273B ", streaming && !expanded ? "Thinking…" : "Thinking"] }), visible.map((line, i) => (_jsx(Text, { color: theme.thinkingDim, dimColor: true, italic: true, children: line }, i))), hidden > 0 && (_jsx(Text, { color: theme.thinkingDim, dimColor: true, children: streaming ? "…" : "… (ctrl+o to expand)" }))] }));
|
|
380
|
-
}
|
|
381
|
-
function CompactionSummaryBlock({ message }) {
|
|
382
|
-
const theme = useTheme();
|
|
383
|
-
const rawStatus = message.content.replace(/^✓\s*/, "").trim();
|
|
384
|
-
const status = rawStatus.replace(/^Compaction complete\s*(?:·\s*)?/i, "").trim() || "Session compacted";
|
|
385
|
-
// Same defense as every other visible-text path: strip any internal reminder
|
|
386
|
-
// markup before rendering, so a summary that echoed it never reaches the
|
|
387
|
-
// transcript. Belt-and-suspenders — the summarizer is fed sanitized history,
|
|
388
|
-
// but the summary is model-generated and also re-injected as context.
|
|
389
|
-
// File-tracking blocks (<read-files>/<modified-files>) are model-facing
|
|
390
|
-
// bookkeeping welded onto the summary — never render them raw either.
|
|
391
|
-
const summary = stripFileBlocks(sanitizeInternalReminderBlocks(message.compactionSummary ?? "")).trim() || undefined;
|
|
392
|
-
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, paddingX: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.borderActive, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.success, bold: true, children: "\u2713 " }), _jsx(Text, { color: theme.accent, bold: true, children: "Compaction checkpoint" }), _jsxs(Text, { color: theme.muted, children: [" \u00B7 ", status] })] }), summary && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.muted, dimColor: true, children: "Preserved context summary" }), _jsx(Box, { paddingLeft: 2, flexDirection: "column", children: _jsx(MarkdownContent, { content: summary }) })] }))] }));
|
|
393
|
-
}
|
|
394
|
-
function UserMessageBlock({ content, terminalColumns, inputStatus, separateFromPrevious = false, }) {
|
|
395
|
-
const theme = useTheme();
|
|
396
|
-
const badge = userInputStatusBadgeLabel(inputStatus);
|
|
397
|
-
// Defense in depth: pure internal-block rows are dropped upstream
|
|
398
|
-
// (isInternalBlockOnlyContent), but a block with trailing text — or a
|
|
399
|
-
// truncated block — would slip that anchored filter. Never render markup.
|
|
400
|
-
const visibleContent = sanitizeInternalReminderBlocks(content);
|
|
401
|
-
// Preserve the message's existing text inset after removing the decorative
|
|
402
|
-
// rail: the former two rail columns become ordinary background padding.
|
|
403
|
-
const leftPaddingWidth = 3;
|
|
404
|
-
const rightPaddingWidth = 1;
|
|
405
|
-
const horizontalRoom = Math.max(20, terminalColumns - 2);
|
|
406
|
-
const bubbleTextWidth = Math.max(1, horizontalRoom - leftPaddingWidth - rightPaddingWidth);
|
|
407
|
-
const { bodyLines, referenceLines } = splitImageDisplayContent(visibleContent);
|
|
408
|
-
const wrappedLines = bodyLines
|
|
409
|
-
.flatMap((line) => wrapByVisualWidth(line, bubbleTextWidth));
|
|
410
|
-
const attachmentReferenceIndent = " ".repeat(leftPaddingWidth);
|
|
411
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: separateFromPrevious ? 1 : 0, children: [badge && (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: inputStatus === "pending_steer" ? theme.warning : theme.muted, children: ` ${badge} ` }), _jsx(Text, { color: theme.dim, children: inputStatus === "pending_steer" ? "applies at the next model call" : "runs after this turn" })] })), wrappedLines.map((line, index) => (_jsx(Box, { children: _jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userMessageText, children: `${" ".repeat(leftPaddingWidth)}${padVisual(line || " ", bubbleTextWidth)}${" ".repeat(rightPaddingWidth)}` }) }, index))), referenceLines.map((line, index) => (_jsx(Box, { children: _jsx(Text, { color: theme.muted, children: `${attachmentReferenceIndent}${line}` }) }, `attachment-${index}`)))] }));
|
|
412
|
-
}
|
|
413
|
-
function PendingInputMessagesBlock({ messages, terminalColumns, title, hint, bulletColor, }) {
|
|
414
|
-
const theme = useTheme();
|
|
415
|
-
// Inset per row: marginLeft (2) + "↳ " (2) + the transcript row padding.
|
|
416
|
-
const contentWidth = Math.max(20, terminalColumns - 4 - TRANSCRIPT_ROW_PADDING);
|
|
417
|
-
return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: bulletColor, children: "\u2022 " }), _jsxs(Text, { bold: true, color: theme.inputText, children: [title, " "] }), _jsxs(Text, { color: theme.dim, children: ["(", hint, ")"] })] }), messages.flatMap((message, messageIndex) => {
|
|
418
|
-
const { bodyLines, referenceLines } = splitImageDisplayContent(message.content || " ");
|
|
419
|
-
const wrappedBody = bodyLines.flatMap((line) => wrapByVisualWidth(line || " ", contentWidth));
|
|
420
|
-
const bodyRows = wrappedBody.map((line, lineIndex) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: theme.dim, children: lineIndex === 0 ? "↳ " : " " }), _jsx(Text, { color: theme.inputText, children: line })] }, `body-${message.key ?? messageIndex}-${lineIndex}`)));
|
|
421
|
-
const attachmentRows = referenceLines.map((line, lineIndex) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.dim, children: [" ", line] }) }, `attachment-${message.key ?? messageIndex}-${lineIndex}`)));
|
|
422
|
-
return [...bodyRows, ...attachmentRows];
|
|
423
|
-
})] }));
|
|
424
|
-
}
|
|
425
|
-
const TOOL_DISPLAY_NAMES = {
|
|
426
|
-
read: "Read",
|
|
427
|
-
write: "Write",
|
|
428
|
-
edit: "Edit",
|
|
429
|
-
bash: "Bash",
|
|
430
|
-
grep: "Grep",
|
|
431
|
-
glob: "Glob",
|
|
432
|
-
web_fetch: "WebFetch",
|
|
433
|
-
web_search: "WebSearch",
|
|
434
|
-
};
|
|
435
|
-
const TOOL_GLYPHS = {
|
|
436
|
-
read: "⏺",
|
|
437
|
-
write: "✎",
|
|
438
|
-
edit: "✎",
|
|
439
|
-
bash: "▶",
|
|
440
|
-
grep: "⌕",
|
|
441
|
-
glob: "⌕",
|
|
442
|
-
web_fetch: "⇲",
|
|
443
|
-
web_search: "⌕",
|
|
444
|
-
task: "↳",
|
|
445
|
-
skill: "★",
|
|
446
|
-
};
|
|
447
|
-
function displayToolName(name) {
|
|
448
|
-
if (TOOL_DISPLAY_NAMES[name])
|
|
449
|
-
return TOOL_DISPLAY_NAMES[name];
|
|
450
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
451
|
-
}
|
|
452
|
-
function toolGlyph(name) {
|
|
453
|
-
return TOOL_GLYPHS[name] ?? "●";
|
|
454
|
-
}
|
|
455
|
-
function getToolHeader(toolCall) {
|
|
456
|
-
const args = toolCall.args || {};
|
|
457
|
-
const trunc = (s, n = 50) => (s.length > n ? s.slice(0, n) + "..." : s);
|
|
458
|
-
switch (toolCall.name) {
|
|
459
|
-
case "read":
|
|
460
|
-
case "write":
|
|
461
|
-
case "edit":
|
|
462
|
-
return args.path ? trunc(String(args.path), 60) : undefined;
|
|
463
|
-
case "bash":
|
|
464
|
-
return args.command ? trunc(String(args.command).replace(/\n/g, " "), 60) : undefined;
|
|
465
|
-
case "grep":
|
|
466
|
-
return args.pattern ? trunc(String(args.pattern), 60) : undefined;
|
|
467
|
-
case "glob":
|
|
468
|
-
return args.pattern ? trunc(String(args.pattern), 60) : undefined;
|
|
469
|
-
case "web_fetch":
|
|
470
|
-
return args.url ? trunc(String(args.url), 60) : undefined;
|
|
471
|
-
case "web_search":
|
|
472
|
-
return args.query ? trunc(String(args.query), 60) : undefined;
|
|
473
|
-
default:
|
|
474
|
-
return undefined;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
function summarizeToolResult(tc) {
|
|
478
|
-
if (tc.result === undefined)
|
|
479
|
-
return "pending";
|
|
480
|
-
const raw = tc.result.replace(/\r\n/g, "\n");
|
|
481
|
-
if (tc.isError) {
|
|
482
|
-
const firstLine = raw.split("\n").find((l) => l.trim() !== "") || "Error";
|
|
483
|
-
return firstLine.length > 80 ? firstLine.slice(0, 80) + "..." : firstLine;
|
|
484
|
-
}
|
|
485
|
-
const nonEmpty = raw.split("\n").filter((l) => l.trim() !== "");
|
|
486
|
-
const lineCount = nonEmpty.length;
|
|
487
|
-
const p = (n, singular, plural) => `${n} ${n === 1 ? singular : plural}`;
|
|
488
|
-
switch (tc.name) {
|
|
489
|
-
case "read":
|
|
490
|
-
return p(lineCount, "line", "lines");
|
|
491
|
-
case "write": {
|
|
492
|
-
const firstLine = raw.split("\n")[0] || "";
|
|
493
|
-
if (firstLine.startsWith("Wrote ") || firstLine.startsWith("Updated ")) {
|
|
494
|
-
return firstLine;
|
|
495
|
-
}
|
|
496
|
-
return "Wrote file";
|
|
497
|
-
}
|
|
498
|
-
case "edit": {
|
|
499
|
-
return formatEditSuccessSummary(getEditDiffDetails(tc));
|
|
500
|
-
}
|
|
501
|
-
case "bash":
|
|
502
|
-
return lineCount > 0 ? `${p(lineCount, "line", "lines")} output` : "Done";
|
|
503
|
-
case "grep":
|
|
504
|
-
return `Found ${p(lineCount, "match", "matches")}`;
|
|
505
|
-
case "glob":
|
|
506
|
-
return `Found ${p(lineCount, "file", "files")}`;
|
|
507
|
-
case "web_search":
|
|
508
|
-
return `${p(lineCount, "result", "results")}`;
|
|
509
|
-
case "web_fetch":
|
|
510
|
-
return p(lineCount, "line", "lines");
|
|
511
|
-
default:
|
|
512
|
-
return lineCount > 0 ? p(lineCount, "line", "lines") : "Done";
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
function subagentsFrom(toolCall) {
|
|
516
|
-
const raw = toolCall.metadata?.subagents;
|
|
517
|
-
if (!Array.isArray(raw))
|
|
518
|
-
return [];
|
|
519
|
-
return raw.filter((item) => typeof item === "object" && item !== null);
|
|
520
|
-
}
|
|
521
|
-
const COLLAPSED_PREVIEW_LINES = 10;
|
|
522
|
-
const EXPANDED_PREVIEW_LINES = 50;
|
|
523
|
-
function ToolCallDisplay({ toolCall, isStreaming, verbose, terminalColumns, showExpandHint = false, waitingApproval = false, compactTop = false, nowTick, }) {
|
|
524
|
-
const theme = useTheme();
|
|
525
|
-
if (toolCall.metadata?.kind === "subagent") {
|
|
526
|
-
return (_jsx(SubagentToolDisplay, { toolCall: toolCall, verbose: verbose, terminalColumns: terminalColumns, compactTop: compactTop }));
|
|
527
|
-
}
|
|
528
|
-
// Show raw output immediately, then upgrade to highlighted ANSI when shiki
|
|
529
|
-
// resolves. Avoids a noticeable "flash" where the line jumps from empty/raw
|
|
530
|
-
// to colorized after a tick.
|
|
531
|
-
const initialPreview = React.useMemo(() => {
|
|
532
|
-
if (toolCall.result === undefined || toolCall.isError)
|
|
533
|
-
return null;
|
|
534
|
-
return toolCall.result.replace(/\r\n/g, "\n");
|
|
535
|
-
}, [toolCall.result, toolCall.isError]);
|
|
536
|
-
const [highlighted, setHighlighted] = React.useState(initialPreview);
|
|
537
|
-
const header = getToolHeader(toolCall);
|
|
538
|
-
const maxLines = verbose ? EXPANDED_PREVIEW_LINES : COLLAPSED_PREVIEW_LINES;
|
|
539
|
-
React.useEffect(() => {
|
|
540
|
-
let cancelled = false;
|
|
541
|
-
if (toolCall.result === undefined || toolCall.isError) {
|
|
542
|
-
setHighlighted(null);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
const raw = toolCall.result.replace(/\r\n/g, "\n");
|
|
546
|
-
let lang = "text";
|
|
547
|
-
if (toolCall.name === "read")
|
|
548
|
-
lang = inferLang(toolCall.args.path);
|
|
549
|
-
else if (toolCall.name === "bash")
|
|
550
|
-
lang = "shell";
|
|
551
|
-
// Always seed with raw so the user sees content immediately.
|
|
552
|
-
setHighlighted(raw);
|
|
553
|
-
if (lang === "text") {
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
highlightCode(raw, lang)
|
|
557
|
-
.then((out) => {
|
|
558
|
-
if (!cancelled)
|
|
559
|
-
setHighlighted(out);
|
|
560
|
-
})
|
|
561
|
-
.catch(() => {
|
|
562
|
-
if (!cancelled)
|
|
563
|
-
setHighlighted(raw);
|
|
564
|
-
});
|
|
565
|
-
return () => {
|
|
566
|
-
cancelled = true;
|
|
567
|
-
};
|
|
568
|
-
}, [toolCall.result, toolCall.name, toolCall.args.path, toolCall.isError]);
|
|
569
|
-
const glyph = toolGlyph(toolCall.name);
|
|
570
|
-
const bulletColor = toolCall.isError
|
|
571
|
-
? theme.error
|
|
572
|
-
: waitingApproval
|
|
573
|
-
? theme.warning
|
|
574
|
-
: isStreaming
|
|
575
|
-
? theme.toolPending
|
|
576
|
-
: theme.user;
|
|
577
|
-
const name = displayToolName(toolCall.name);
|
|
578
|
-
// Compose summary: pending tools stay compact; waiting-for-approval gets an
|
|
579
|
-
// explicit badge so the trail survives the dialog closing.
|
|
580
|
-
let summary;
|
|
581
|
-
let summaryColor = theme.muted;
|
|
582
|
-
if (waitingApproval) {
|
|
583
|
-
summary = "⏸ waiting for approval";
|
|
584
|
-
summaryColor = theme.warning;
|
|
585
|
-
}
|
|
586
|
-
else if (isToolPending(toolCall) && toolCall.startedAt) {
|
|
587
|
-
void nowTick;
|
|
588
|
-
summary = "running";
|
|
589
|
-
summaryColor = theme.toolPending;
|
|
590
|
-
}
|
|
591
|
-
else {
|
|
592
|
-
summary = summarizeToolResult(toolCall);
|
|
593
|
-
if (toolCall.isError)
|
|
594
|
-
summaryColor = theme.error;
|
|
595
|
-
else if (toolCall.name === "edit" && toolCall.result !== undefined)
|
|
596
|
-
summaryColor = theme.success;
|
|
597
|
-
}
|
|
598
|
-
const editDetails = getEditDiffDetails(toolCall);
|
|
599
|
-
const isEditDiff = editDetails !== null && toolCall.result !== undefined;
|
|
600
|
-
// Only show the file preview once the tool actually executed. During the
|
|
601
|
-
// streaming-args phase, args.content is incomplete and re-rendering the
|
|
602
|
-
// entire body per delta both looks chaotic and breaks on partial escapes.
|
|
603
|
-
const isWritePreview = toolCall.name === "write" && !toolCall.isError && toolCall.result !== undefined;
|
|
604
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: bulletColor, children: [glyph, " "] }), _jsx(Text, { bold: true, color: theme.toolName, children: name }), header && _jsxs(Text, { color: theme.muted, children: ["(", header, ")"] })] }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: summaryColor, children: ["\u23BF ", summary] }) }), toolCall.isError && toolCall.result && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: toolCall.result.replace(/\r\n/g, "\n").split("\n").slice(0, 6).map((line, i) => (_jsx(Text, { color: theme.error, children: line }, i))) })), isEditDiff && (_jsx(DiffBlock, { diff: editDetails.diff, terminalColumns: terminalColumns, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), isWritePreview && (_jsx(WritePreview, { content: String(toolCall.args.content || ""), maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), !toolCall.isError && !isEditDiff && !isWritePreview && highlighted && (_jsx(OutputPreview, { text: highlighted, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint }))] }));
|
|
605
|
-
}
|
|
606
|
-
function SubagentToolDisplay({ toolCall, verbose, terminalColumns, compactTop, }) {
|
|
607
|
-
const theme = useTheme();
|
|
608
|
-
const subagents = subagentsFrom(toolCall);
|
|
609
|
-
const hasError = toolCall.isError || subagents.some((subagent) => (subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled"));
|
|
610
|
-
const bulletColor = hasError ? theme.error : isToolPending(toolCall) ? theme.toolPending : theme.user;
|
|
611
|
-
const detailWidth = Math.max(24, terminalColumns - 10);
|
|
612
|
-
const rows = verbose ? sortSubagents(subagents) : sortSubagents(subagents).slice(0, 4);
|
|
613
|
-
const omitted = Math.max(0, subagents.length - rows.length);
|
|
614
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: bulletColor, children: "\u21B3 " }), _jsx(Text, { bold: true, color: theme.toolName, children: "Subagents" }), subagents.length > 0 && _jsxs(Text, { color: theme.muted, children: [" ", subagentSummary(subagents)] })] }), rows.length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [rows.map((subagent, index) => {
|
|
615
|
-
const status = subagent.status ?? "running";
|
|
616
|
-
const label = padVisual(truncateVisual(subagentLabel(subagent), 10), 10);
|
|
617
|
-
const descriptorWidth = verbose ? 42 : 32;
|
|
618
|
-
const descriptor = padVisual(truncateVisual(subagentDescriptor(subagent), descriptorWidth), descriptorWidth);
|
|
619
|
-
const note = truncateVisual(latestSubagentNote(subagent), Math.max(12, detailWidth - 16 - descriptorWidth - 10));
|
|
620
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: subagentStatusColor(status, theme), children: label }), _jsxs(Text, { color: theme.traceAction, children: [" ", descriptor] }), _jsxs(Text, { color: subagentStatusColor(status, theme), children: [" ", padVisual(status, 9)] }), note && _jsxs(Text, { color: subagent.error ? theme.error : theme.traceDetail, children: [" ", note] })] }, subagent.subAgentId ?? `${subagentLabel(subagent)}-${index}`));
|
|
621
|
-
}), omitted > 0 && (_jsxs(Text, { color: theme.muted, children: ["... ", omitted, " more \u00B7 Ctrl+O to expand \u00B7 \u2193 then Enter to inspect traces"] }))] })), subagents.length === 0 && toolCall.result && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: hasError ? theme.error : theme.muted, children: summarizeToolResult(toolCall) }) }))] }));
|
|
622
|
-
}
|
|
623
|
-
function TruncationHint({ remaining, verbose, showExpandHint, }) {
|
|
624
|
-
const theme = useTheme();
|
|
625
|
-
if (remaining <= 0)
|
|
626
|
-
return null;
|
|
627
|
-
const noun = `line${remaining === 1 ? "" : "s"}`;
|
|
628
|
-
if (verbose) {
|
|
629
|
-
return (_jsxs(Text, { color: theme.muted, children: ["... (", remaining, " more ", noun, ")"] }));
|
|
630
|
-
}
|
|
631
|
-
return (_jsxs(Text, { color: theme.muted, children: ["\u2026 +", remaining, " ", noun, showExpandHint ? " (ctrl+o to expand)" : ""] }));
|
|
632
|
-
}
|
|
633
|
-
function OutputPreview({ text, maxLines, verbose, showExpandHint, }) {
|
|
634
|
-
const theme = useTheme();
|
|
635
|
-
const lines = text.split("\n");
|
|
636
|
-
const shown = lines.slice(0, maxLines);
|
|
637
|
-
const remaining = Math.max(0, lines.length - maxLines);
|
|
638
|
-
if (shown.length === 0 || (shown.length === 1 && shown[0] === ""))
|
|
639
|
-
return null;
|
|
640
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [shown.map((line, i) => (_jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "\u2502 " }), _jsx(Text, { children: line })] }, i))), _jsx(TruncationHint, { remaining: remaining, verbose: verbose, showExpandHint: showExpandHint })] }));
|
|
641
|
-
}
|
|
642
|
-
function WritePreview({ content, maxLines, verbose, showExpandHint, }) {
|
|
643
|
-
const theme = useTheme();
|
|
644
|
-
const lines = content.split("\n");
|
|
645
|
-
const shown = lines.slice(0, maxLines);
|
|
646
|
-
const remaining = Math.max(0, lines.length - maxLines);
|
|
647
|
-
const numWidth = Math.max(2, String(lines.length).length);
|
|
648
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [shown.map((line, i) => (_jsxs(Box, { children: [_jsxs(Text, { color: theme.muted, children: [String(i + 1).padStart(numWidth, " "), " "] }), _jsx(Text, { children: line })] }, i))), _jsx(TruncationHint, { remaining: remaining, verbose: verbose, showExpandHint: showExpandHint })] }));
|
|
649
|
-
}
|
|
650
|
-
function parseDiffLines(body) {
|
|
651
|
-
const result = [];
|
|
652
|
-
let oldNum = 0;
|
|
653
|
-
let newNum = 0;
|
|
654
|
-
const rawLines = body.split("\n");
|
|
655
|
-
if (rawLines[rawLines.length - 1] === "")
|
|
656
|
-
rawLines.pop();
|
|
657
|
-
for (const raw of rawLines) {
|
|
658
|
-
if (raw.startsWith("+++") ||
|
|
659
|
-
raw.startsWith("---") ||
|
|
660
|
-
raw.startsWith("Index:") ||
|
|
661
|
-
raw.startsWith("==="))
|
|
662
|
-
continue;
|
|
663
|
-
if (raw.startsWith("@@")) {
|
|
664
|
-
const m = raw.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
665
|
-
if (m) {
|
|
666
|
-
oldNum = parseInt(m[1], 10);
|
|
667
|
-
newNum = parseInt(m[2], 10);
|
|
668
|
-
}
|
|
669
|
-
continue;
|
|
670
|
-
}
|
|
671
|
-
if (raw.startsWith("+")) {
|
|
672
|
-
result.push({ type: "add", num: newNum, content: raw.slice(1) });
|
|
673
|
-
newNum++;
|
|
674
|
-
}
|
|
675
|
-
else if (raw.startsWith("-")) {
|
|
676
|
-
result.push({ type: "remove", num: oldNum, content: raw.slice(1) });
|
|
677
|
-
oldNum++;
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
const content = raw.startsWith(" ") ? raw.slice(1) : raw;
|
|
681
|
-
result.push({ type: "context", num: newNum, content });
|
|
682
|
-
oldNum++;
|
|
683
|
-
newNum++;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
return result;
|
|
687
|
-
}
|
|
688
|
-
function DiffBlock({ diff, terminalColumns, maxLines, verbose, showExpandHint, }) {
|
|
689
|
-
const theme = useTheme();
|
|
690
|
-
const lines = parseDiffLines(diff);
|
|
691
|
-
const shown = lines.slice(0, maxLines);
|
|
692
|
-
const remaining = Math.max(0, lines.length - maxLines);
|
|
693
|
-
const maxNum = lines.reduce((acc, l) => Math.max(acc, l.num), 0);
|
|
694
|
-
const numWidth = Math.max(2, String(maxNum).length);
|
|
695
|
-
const leftMargin = 2;
|
|
696
|
-
const prefixWidth = numWidth + 4; // " NUM ± "
|
|
697
|
-
// Reserve the full left-margin chain from terminal edge to diff content:
|
|
698
|
-
// app padding (1) + ToolCallDisplay marginLeft (2) + DiffBlock marginLeft (2)
|
|
699
|
-
// + right padding (1) + 1-col safety = 7. Without this, each row overflows
|
|
700
|
-
// by 1 column, the terminal auto-wraps, and every line renders with a blank
|
|
701
|
-
// row beneath it.
|
|
702
|
-
const bandWidth = Math.max(10, terminalColumns - 7);
|
|
703
|
-
const contentWidth = Math.max(1, bandWidth - prefixWidth);
|
|
704
|
-
const blankPrefix = " ".repeat(prefixWidth);
|
|
705
|
-
return (_jsxs(Box, { flexDirection: "column", marginLeft: leftMargin, children: [shown.flatMap((line, i) => {
|
|
706
|
-
const bg = line.type === "add"
|
|
707
|
-
? theme.diffAdd
|
|
708
|
-
: line.type === "remove"
|
|
709
|
-
? theme.diffRemove
|
|
710
|
-
: undefined;
|
|
711
|
-
const sign = line.type === "add" ? "+" : line.type === "remove" ? "-" : " ";
|
|
712
|
-
const numStr = String(line.num).padStart(numWidth, " ");
|
|
713
|
-
// Soft-wrap long lines at the terminal-derived content width so narrow
|
|
714
|
-
// splits still show the full content. Continuation rows reuse the same
|
|
715
|
-
// background but blank out the gutter (no line number, no +/-) so a
|
|
716
|
-
// reader can tell at a glance which rows belong to the same logical
|
|
717
|
-
// diff line.
|
|
718
|
-
const segments = wrapByVisualWidth(line.content, contentWidth);
|
|
719
|
-
return segments.map((segment, segIdx) => {
|
|
720
|
-
const padded = padVisual(segment, contentWidth);
|
|
721
|
-
const prefix = segIdx === 0 ? ` ${numStr} ${sign} ` : blankPrefix;
|
|
722
|
-
return (_jsx(Text, { backgroundColor: bg, color: theme.userMessageText, children: `${prefix}${padded}` }, `${i}-${segIdx}`));
|
|
723
|
-
});
|
|
724
|
-
}), _jsx(TruncationHint, { remaining: remaining, verbose: verbose, showExpandHint: showExpandHint })] }));
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* "Edited 3 files (+42 -8) — a.ts, b.ts" digest below the assistant turn.
|
|
728
|
-
* Surfaces only when there is at least one file-mutating tool call.
|
|
729
|
-
*/
|
|
730
|
-
function TurnDigest({ toolCalls }) {
|
|
731
|
-
const theme = useTheme();
|
|
732
|
-
const digest = React.useMemo(() => buildDigest(toolCalls), [toolCalls]);
|
|
733
|
-
if (!digest)
|
|
734
|
-
return null;
|
|
735
|
-
return (_jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsx(Text, { color: theme.muted, dimColor: true, children: digest }) }));
|
|
736
|
-
}
|
|
737
|
-
function TaskDurationLine({ elapsedMs }) {
|
|
738
|
-
const theme = useTheme();
|
|
739
|
-
return (_jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["Task duration: ", formatDuration(elapsedMs)] }) }));
|
|
740
|
-
}
|
|
741
|
-
function buildDigest(toolCalls) {
|
|
742
|
-
const paths = new Set();
|
|
743
|
-
let added = 0;
|
|
744
|
-
let removed = 0;
|
|
745
|
-
let writes = 0;
|
|
746
|
-
let edits = 0;
|
|
747
|
-
for (const tc of toolCalls) {
|
|
748
|
-
if (tc.isError || !tc.result)
|
|
749
|
-
continue;
|
|
750
|
-
if (tc.name === "edit") {
|
|
751
|
-
const details = getEditDiffDetails(tc);
|
|
752
|
-
if (details) {
|
|
753
|
-
added += details.added;
|
|
754
|
-
removed += details.removed;
|
|
755
|
-
}
|
|
756
|
-
if (tc.args.path)
|
|
757
|
-
paths.add(String(tc.args.path));
|
|
758
|
-
edits += 1;
|
|
759
|
-
}
|
|
760
|
-
else if (tc.name === "write") {
|
|
761
|
-
if (tc.args.path)
|
|
762
|
-
paths.add(String(tc.args.path));
|
|
763
|
-
writes += 1;
|
|
764
|
-
const content = String(tc.args.content ?? "");
|
|
765
|
-
if (content)
|
|
766
|
-
added += content.split("\n").length;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const total = edits + writes;
|
|
770
|
-
if (total === 0 || paths.size === 0)
|
|
771
|
-
return null;
|
|
772
|
-
const verb = edits > 0 && writes === 0 ? "Edited" : writes > 0 && edits === 0 ? "Wrote" : "Touched";
|
|
773
|
-
const pathList = Array.from(paths);
|
|
774
|
-
const shownPaths = pathList.slice(0, 4).map((p) => p.split("/").pop() || p);
|
|
775
|
-
const extra = pathList.length - shownPaths.length;
|
|
776
|
-
const pathDisplay = shownPaths.join(", ") + (extra > 0 ? `, +${extra} more` : "");
|
|
777
|
-
const stats = added || removed
|
|
778
|
-
? ` (${added ? `+${added}` : ""}${added && removed ? " " : ""}${removed ? `-${removed}` : ""})`
|
|
779
|
-
: "";
|
|
780
|
-
return `↳ ${verb} ${paths.size} file${paths.size === 1 ? "" : "s"}${stats} — ${pathDisplay}`;
|
|
781
|
-
}
|
|
782
|
-
function formatDuration(ms) {
|
|
783
|
-
if (!Number.isFinite(ms) || ms <= 0)
|
|
784
|
-
return "0s";
|
|
785
|
-
if (ms < 1000)
|
|
786
|
-
return `${Math.max(1, Math.round(ms))}ms`;
|
|
787
|
-
const seconds = ms / 1000;
|
|
788
|
-
if (seconds < 10)
|
|
789
|
-
return `${seconds.toFixed(1)}s`;
|
|
790
|
-
if (seconds < 60)
|
|
791
|
-
return `${Math.round(seconds)}s`;
|
|
792
|
-
let minutes = Math.floor(seconds / 60);
|
|
793
|
-
let remSec = Math.round(seconds - minutes * 60);
|
|
794
|
-
if (remSec >= 60) {
|
|
795
|
-
minutes += Math.floor(remSec / 60);
|
|
796
|
-
remSec %= 60;
|
|
797
|
-
}
|
|
798
|
-
return remSec === 0 ? `${minutes}m` : `${minutes}m ${remSec}s`;
|
|
799
|
-
}
|
|
800
|
-
function truncateVisual(str, maxWidth) {
|
|
801
|
-
if (maxWidth <= 0)
|
|
802
|
-
return "";
|
|
803
|
-
let out = "";
|
|
804
|
-
let width = 0;
|
|
805
|
-
for (const char of str) {
|
|
806
|
-
const w = graphemeWidth(char);
|
|
807
|
-
if (width + w > maxWidth)
|
|
808
|
-
break;
|
|
809
|
-
out += char;
|
|
810
|
-
width += w;
|
|
811
|
-
}
|
|
812
|
-
return out;
|
|
813
|
-
}
|
|
814
|
-
function padVisual(str, width) {
|
|
815
|
-
const currentWidth = visualWidth(str);
|
|
816
|
-
return str + " ".repeat(Math.max(0, width - currentWidth));
|
|
817
|
-
}
|
|
818
|
-
function wrapByVisualWidth(line, maxWidth) {
|
|
819
|
-
if (maxWidth <= 0)
|
|
820
|
-
return [line];
|
|
821
|
-
if (line === "")
|
|
822
|
-
return [""];
|
|
823
|
-
const result = [];
|
|
824
|
-
let current = "";
|
|
825
|
-
let currentWidth = 0;
|
|
826
|
-
for (const char of line) {
|
|
827
|
-
const w = graphemeWidth(char);
|
|
828
|
-
if (currentWidth + w > maxWidth) {
|
|
829
|
-
result.push(current);
|
|
830
|
-
current = char;
|
|
831
|
-
currentWidth = w;
|
|
832
|
-
}
|
|
833
|
-
else {
|
|
834
|
-
current += char;
|
|
835
|
-
currentWidth += w;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
if (current !== "" || result.length === 0)
|
|
839
|
-
result.push(current);
|
|
840
|
-
return result;
|
|
841
|
-
}
|