@alumbwe/anvil 1.0.0
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 +84 -0
- package/bin/anvil +2 -0
- package/bin/tree-sitter.wasm +0 -0
- package/package.json +67 -0
- package/src/__tests__/README.md +256 -0
- package/src/__tests__/bash-mode.test.ts +442 -0
- package/src/__tests__/cli-args.test.ts +181 -0
- package/src/__tests__/cli-harness.test.ts +52 -0
- package/src/__tests__/e2e/first-time-login.test.ts +173 -0
- package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
- package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
- package/src/__tests__/e2e-cli.test.ts +183 -0
- package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
- package/src/__tests__/helpers/mock-api-client.ts +64 -0
- package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
- package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
- package/src/__tests__/home-directory-detection.test.ts +213 -0
- package/src/__tests__/integration/api-integration.test.ts +288 -0
- package/src/__tests__/integration/credentials-storage.test.ts +486 -0
- package/src/__tests__/integration/local-agents.test.ts +1187 -0
- package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
- package/src/__tests__/integration/login-polling-working.test.ts +285 -0
- package/src/__tests__/integration/queue-editing.test.tsx +261 -0
- package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
- package/src/__tests__/integration-tmux.test.ts +188 -0
- package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
- package/src/__tests__/launcher-disconnect.test.ts +104 -0
- package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
- package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
- package/src/__tests__/path-completion.test.ts +243 -0
- package/src/__tests__/project-files-chat-id.test.ts +34 -0
- package/src/__tests__/release/proxy-http-get.test.ts +667 -0
- package/src/__tests__/release/wrapper-safety.test.ts +412 -0
- package/src/__tests__/renderer-cleanup.test.ts +174 -0
- package/src/__tests__/rerender-perf.integration.test.ts +283 -0
- package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
- package/src/__tests__/terminal-watchdog.test.ts +388 -0
- package/src/__tests__/test-utils.ts +271 -0
- package/src/__tests__/tmux-poc.ts +150 -0
- package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
- package/src/__tests__/unit/copy-button.test.ts +157 -0
- package/src/__tests__/unit/create-run-config.test.ts +66 -0
- package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
- package/src/__tests__/unit/segmented-control.test.ts +152 -0
- package/src/__tests__/utils/env.test.ts +169 -0
- package/src/__tests__/utils/project-picker.test.ts +39 -0
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3692 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/bash-command.test.ts +445 -0
- package/src/commands/__tests__/command-args.test.ts +349 -0
- package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
- package/src/commands/__tests__/copy-conversation.test.ts +226 -0
- package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
- package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
- package/src/commands/__tests__/image.test.ts +99 -0
- package/src/commands/__tests__/init.test.ts +456 -0
- package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
- package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
- package/src/commands/__tests__/prompt-builders.test.ts +28 -0
- package/src/commands/__tests__/router-input.test.ts +298 -0
- package/src/commands/ads.ts +44 -0
- package/src/commands/command-registry.ts +726 -0
- package/src/commands/copy-conversation.ts +392 -0
- package/src/commands/help.ts +13 -0
- package/src/commands/image.ts +20 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/process-diagnostics.ts +120 -0
- package/src/commands/prompt-builders.ts +100 -0
- package/src/commands/publish.ts +203 -0
- package/src/commands/router-utils.ts +80 -0
- package/src/commands/router.ts +487 -0
- package/src/commands/usage.ts +26 -0
- package/src/components/__tests__/ad-banner.test.tsx +83 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
- package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
- package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
- package/src/components/__tests__/grid-layout.test.tsx +1051 -0
- package/src/components/__tests__/message-block.completion.test.tsx +104 -0
- package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
- package/src/components/__tests__/message-with-agents.test.tsx +579 -0
- package/src/components/__tests__/multiline-input.test.tsx +1132 -0
- package/src/components/__tests__/queue-panel.test.tsx +398 -0
- package/src/components/__tests__/selectable-list.test.ts +232 -0
- package/src/components/__tests__/status-bar.test.tsx +44 -0
- package/src/components/__tests__/status-indicator.test.tsx +204 -0
- package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
- package/src/components/__tests__/user-error-banner.test.tsx +102 -0
- package/src/components/ad-banner.tsx +323 -0
- package/src/components/agent-checklist.tsx +350 -0
- package/src/components/agent-mode-toggle.tsx +241 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
- package/src/components/ask-user/__tests__/validation.test.ts +213 -0
- package/src/components/ask-user/components/accordion-question.tsx +153 -0
- package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
- package/src/components/ask-user/components/options-list.tsx +133 -0
- package/src/components/ask-user/components/question-header.tsx +80 -0
- package/src/components/ask-user/components/question-option.tsx +89 -0
- package/src/components/ask-user/constants.ts +38 -0
- package/src/components/ask-user/index.tsx +631 -0
- package/src/components/ask-user/utils/validation.ts +85 -0
- package/src/components/attachment-card.tsx +65 -0
- package/src/components/blocks/agent-block-grid.tsx +58 -0
- package/src/components/blocks/agent-branch-item.tsx +311 -0
- package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
- package/src/components/blocks/agent-list-branch.tsx +77 -0
- package/src/components/blocks/ask-user-branch.tsx +81 -0
- package/src/components/blocks/block-helpers.ts +11 -0
- package/src/components/blocks/blocks-renderer.tsx +289 -0
- package/src/components/blocks/content-with-markdown.tsx +33 -0
- package/src/components/blocks/image-block.tsx +128 -0
- package/src/components/blocks/implementor-row.tsx +429 -0
- package/src/components/blocks/single-block.tsx +201 -0
- package/src/components/blocks/thinking-block.tsx +67 -0
- package/src/components/blocks/tool-block-group.tsx +57 -0
- package/src/components/blocks/tool-branch.tsx +206 -0
- package/src/components/blocks/user-content-copy.tsx +157 -0
- package/src/components/bottom-banner.tsx +136 -0
- package/src/components/build-mode-buttons.tsx +110 -0
- package/src/components/button.tsx +66 -0
- package/src/components/chat-header.tsx +77 -0
- package/src/components/chat-history-screen.tsx +413 -0
- package/src/components/chat-input-bar.tsx +523 -0
- package/src/components/clickable-title-box.tsx +66 -0
- package/src/components/clickable.tsx +119 -0
- package/src/components/collapse-button.tsx +34 -0
- package/src/components/copy-button.tsx +203 -0
- package/src/components/elapsed-timer.tsx +55 -0
- package/src/components/error-boundary.tsx +55 -0
- package/src/components/feedback-container.tsx +181 -0
- package/src/components/feedback-icon-button.tsx +89 -0
- package/src/components/feedback-input-mode.tsx +344 -0
- package/src/components/file-attachment-card.tsx +98 -0
- package/src/components/freebuff-active-session-summary.tsx +63 -0
- package/src/components/freebuff-landing-screen.tsx +865 -0
- package/src/components/freebuff-model-selector.tsx +1087 -0
- package/src/components/freebuff-referral-banner.tsx +604 -0
- package/src/components/freebuff-superseded-screen.tsx +62 -0
- package/src/components/grid-layout.tsx +80 -0
- package/src/components/help-banner.tsx +127 -0
- package/src/components/highlighted-text.tsx +52 -0
- package/src/components/image-card.tsx +167 -0
- package/src/components/image-thumbnail.tsx +114 -0
- package/src/components/input-cursor.tsx +77 -0
- package/src/components/input-mode-banner.tsx +57 -0
- package/src/components/load-previous-button.tsx +48 -0
- package/src/components/login-modal.tsx +475 -0
- package/src/components/message-block.tsx +346 -0
- package/src/components/message-footer.tsx +258 -0
- package/src/components/message-with-agents.tsx +506 -0
- package/src/components/mode-divider.tsx +43 -0
- package/src/components/multiline-input.tsx +1241 -0
- package/src/components/out-of-credits-banner.tsx +160 -0
- package/src/components/pending-attachments-banner.tsx +116 -0
- package/src/components/pending-bash-message.tsx +59 -0
- package/src/components/progress-bar.tsx +81 -0
- package/src/components/project-picker-screen.tsx +485 -0
- package/src/components/publish-confirmation.tsx +493 -0
- package/src/components/publish-container.tsx +687 -0
- package/src/components/queue-panel.tsx +310 -0
- package/src/components/raised-pill.tsx +95 -0
- package/src/components/renderers/plan-box.tsx +59 -0
- package/src/components/review-screen.tsx +112 -0
- package/src/components/scroll-to-bottom-button.tsx +34 -0
- package/src/components/segmented-control.tsx +187 -0
- package/src/components/selectable-list.tsx +248 -0
- package/src/components/selected-chips.tsx +80 -0
- package/src/components/separator.tsx +40 -0
- package/src/components/session-ended-banner.tsx +226 -0
- package/src/components/shimmer-text.tsx +248 -0
- package/src/components/status-bar.tsx +313 -0
- package/src/components/subscription-limit-banner.tsx +206 -0
- package/src/components/suggested-prompts.tsx +184 -0
- package/src/components/suggestion-menu.tsx +215 -0
- package/src/components/terminal-command-display.tsx +151 -0
- package/src/components/terminal-link.tsx +97 -0
- package/src/components/terminal-thinking-orb.tsx +95 -0
- package/src/components/text-attachment-card.tsx +77 -0
- package/src/components/thinking.tsx +109 -0
- package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
- package/src/components/tools/__tests__/code-search.test.tsx +45 -0
- package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
- package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
- package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
- package/src/components/tools/apply-patch.tsx +95 -0
- package/src/components/tools/code-search.tsx +45 -0
- package/src/components/tools/composio.tsx +152 -0
- package/src/components/tools/diff-viewer.tsx +76 -0
- package/src/components/tools/glob.tsx +61 -0
- package/src/components/tools/gravity-index.tsx +77 -0
- package/src/components/tools/list-directory.tsx +61 -0
- package/src/components/tools/read-docs.tsx +37 -0
- package/src/components/tools/read-files.tsx +91 -0
- package/src/components/tools/read-subtree.tsx +44 -0
- package/src/components/tools/read-url.tsx +33 -0
- package/src/components/tools/registry.ts +126 -0
- package/src/components/tools/render-ui.tsx +122 -0
- package/src/components/tools/run-terminal-command.tsx +79 -0
- package/src/components/tools/skill.tsx +29 -0
- package/src/components/tools/str-replace.tsx +77 -0
- package/src/components/tools/suggest-followups.tsx +369 -0
- package/src/components/tools/task-completed.tsx +16 -0
- package/src/components/tools/tool-call-item.tsx +273 -0
- package/src/components/tools/types.ts +55 -0
- package/src/components/tools/web-search.tsx +33 -0
- package/src/components/tools/write-file.tsx +16 -0
- package/src/components/tools/write-todos.tsx +95 -0
- package/src/components/top-banner.tsx +178 -0
- package/src/components/usage-banner.tsx +244 -0
- package/src/components/user-error-banner.tsx +56 -0
- package/src/components/validation-error-popover.tsx +192 -0
- package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
- package/src/contexts/chat-runtime-context.tsx +240 -0
- package/src/data/slash-commands.test.ts +24 -0
- package/src/data/slash-commands.ts +249 -0
- package/src/entry.ts +12 -0
- package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
- package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
- package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
- package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
- package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
- package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
- package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
- package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
- package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
- package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
- package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
- package/src/hooks/__tests__/use-input-history.test.ts +699 -0
- package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
- package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
- package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
- package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
- package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
- package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
- package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
- package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
- package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
- package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
- package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
- package/src/hooks/__tests__/use-timeout.test.ts +330 -0
- package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
- package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
- package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
- package/src/hooks/helpers/send-message.ts +654 -0
- package/src/hooks/stream-state.ts +83 -0
- package/src/hooks/use-activity-query.ts +668 -0
- package/src/hooks/use-agent-validation.ts +74 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +253 -0
- package/src/hooks/use-auth-state.ts +152 -0
- package/src/hooks/use-chat-input.ts +105 -0
- package/src/hooks/use-chat-keyboard.ts +321 -0
- package/src/hooks/use-chat-messages.ts +254 -0
- package/src/hooks/use-chat-state.ts +165 -0
- package/src/hooks/use-chat-streaming.ts +180 -0
- package/src/hooks/use-chat-ui.ts +131 -0
- package/src/hooks/use-clipboard.ts +151 -0
- package/src/hooks/use-connection-status.ts +142 -0
- package/src/hooks/use-directory-browser.ts +88 -0
- package/src/hooks/use-elapsed-time.ts +116 -0
- package/src/hooks/use-event.ts +34 -0
- package/src/hooks/use-exit-handler.ts +66 -0
- package/src/hooks/use-fetch-login-url.ts +65 -0
- package/src/hooks/use-fingerprint.ts +61 -0
- package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-freebuff-session-progress.ts +34 -0
- package/src/hooks/use-freebuff-session.ts +806 -0
- package/src/hooks/use-freebuff-streak-query.ts +61 -0
- package/src/hooks/use-gravity-ad.ts +656 -0
- package/src/hooks/use-grid-layout.ts +73 -0
- package/src/hooks/use-input-history.ts +173 -0
- package/src/hooks/use-login-keyboard-handlers.ts +75 -0
- package/src/hooks/use-login-polling.ts +114 -0
- package/src/hooks/use-logo.tsx +161 -0
- package/src/hooks/use-message-queue.ts +387 -0
- package/src/hooks/use-now.ts +20 -0
- package/src/hooks/use-path-tab-completion.ts +88 -0
- package/src/hooks/use-publish-mutation.ts +60 -0
- package/src/hooks/use-queue-controls.ts +68 -0
- package/src/hooks/use-queue-ui.ts +74 -0
- package/src/hooks/use-scroll-management.ts +184 -0
- package/src/hooks/use-searchable-list.ts +98 -0
- package/src/hooks/use-send-message.ts +773 -0
- package/src/hooks/use-sheen-animation.tsx +88 -0
- package/src/hooks/use-subscription-query.ts +71 -0
- package/src/hooks/use-suggestion-engine.ts +783 -0
- package/src/hooks/use-terminal-breakpoints.ts +77 -0
- package/src/hooks/use-terminal-dimensions.ts +46 -0
- package/src/hooks/use-terminal-focus.ts +29 -0
- package/src/hooks/use-terminal-layout.ts +189 -0
- package/src/hooks/use-theme.tsx +140 -0
- package/src/hooks/use-timeout.ts +85 -0
- package/src/hooks/use-update-preference.ts +66 -0
- package/src/hooks/use-usage-monitor.ts +55 -0
- package/src/hooks/use-usage-query.ts +124 -0
- package/src/hooks/use-user-details-query.ts +93 -0
- package/src/hooks/use-why-did-you-update.ts +177 -0
- package/src/index.tsx +441 -0
- package/src/init/__tests__/init-direnv.test.ts +526 -0
- package/src/init/init-app.ts +34 -0
- package/src/init/init-direnv.ts +133 -0
- package/src/login/constants.ts +60 -0
- package/src/login/login-flow.ts +204 -0
- package/src/login/plain-login.ts +108 -0
- package/src/login/utils.ts +183 -0
- package/src/native/ripgrep.ts +80 -0
- package/src/polyfills/bun-strip-ansi.ts +12 -0
- package/src/pre-init/tree-sitter-wasm.ts +96 -0
- package/src/project-files.ts +102 -0
- package/src/smoke/terminal-command-broker.ts +439 -0
- package/src/state/__tests__/chat-store-focus.test.ts +22 -0
- package/src/state/__tests__/feedback-store.test.ts +257 -0
- package/src/state/chat-history-store.ts +30 -0
- package/src/state/chat-store.ts +544 -0
- package/src/state/feedback-store.ts +160 -0
- package/src/state/freebuff-model-store.ts +42 -0
- package/src/state/freebuff-session-store.ts +53 -0
- package/src/state/login-store.ts +150 -0
- package/src/state/message-block-store.ts +149 -0
- package/src/state/publish-store.ts +146 -0
- package/src/state/queue-panel-store.ts +26 -0
- package/src/state/review-store.ts +24 -0
- package/src/testing/env.ts +46 -0
- package/src/types/chat-state.ts +15 -0
- package/src/types/chat.ts +241 -0
- package/src/types/contracts/send-message.ts +12 -0
- package/src/types/env.ts +98 -0
- package/src/types/freebuff-session.ts +17 -0
- package/src/types/function-params.ts +28 -0
- package/src/types/react19-compat.d.ts +19 -0
- package/src/types/store.ts +111 -0
- package/src/types/theme-system.ts +144 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/__tests__/active-run.test.ts +140 -0
- package/src/utils/__tests__/activity-tracker.test.ts +440 -0
- package/src/utils/__tests__/agent-display.test.ts +139 -0
- package/src/utils/__tests__/analytics-client.test.ts +262 -0
- package/src/utils/__tests__/anonymous-id.test.ts +74 -0
- package/src/utils/__tests__/anvil-api.test.ts +578 -0
- package/src/utils/__tests__/arrays.test.ts +264 -0
- package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
- package/src/utils/__tests__/block-processor.test.ts +810 -0
- package/src/utils/__tests__/chat-history.test.ts +203 -0
- package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
- package/src/utils/__tests__/chat-meta.test.ts +130 -0
- package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
- package/src/utils/__tests__/clipboard.test.ts +910 -0
- package/src/utils/__tests__/code-search-summary.test.ts +84 -0
- package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
- package/src/utils/__tests__/error-handling.test.ts +658 -0
- package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
- package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
- package/src/utils/__tests__/feedback-submission.test.ts +26 -0
- package/src/utils/__tests__/fetch-usage.test.ts +355 -0
- package/src/utils/__tests__/fingerprint.test.ts +144 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
- package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
- package/src/utils/__tests__/format-timeout.test.ts +87 -0
- package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
- package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
- package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
- package/src/utils/__tests__/image-dimensions.test.ts +249 -0
- package/src/utils/__tests__/image-processor.test.ts +209 -0
- package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
- package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
- package/src/utils/__tests__/layout-helpers.test.ts +36 -0
- package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
- package/src/utils/__tests__/log-shipper.test.ts +53 -0
- package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
- package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
- package/src/utils/__tests__/message-updater.test.ts +645 -0
- package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
- package/src/utils/__tests__/pending-attachments.test.ts +277 -0
- package/src/utils/__tests__/polling-backoff.test.ts +51 -0
- package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
- package/src/utils/__tests__/run-state-storage.test.ts +919 -0
- package/src/utils/__tests__/safe-json.test.ts +77 -0
- package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
- package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
- package/src/utils/__tests__/send-message-timer.test.ts +64 -0
- package/src/utils/__tests__/settings.test.ts +99 -0
- package/src/utils/__tests__/strings.test.ts +192 -0
- package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
- package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
- package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
- package/src/utils/__tests__/terminal-io.test.ts +137 -0
- package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
- package/src/utils/__tests__/text-layout.test.ts +82 -0
- package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
- package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
- package/src/utils/__tests__/trace-writer.test.ts +223 -0
- package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
- package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
- package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
- package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
- package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
- package/src/utils/active-run.ts +131 -0
- package/src/utils/activity-tracker.ts +66 -0
- package/src/utils/agent-display.ts +87 -0
- package/src/utils/agent-helpers.ts +27 -0
- package/src/utils/agent-id-utils.ts +14 -0
- package/src/utils/ai-message-id.ts +12 -0
- package/src/utils/analytics.ts +361 -0
- package/src/utils/anonymous-id.ts +87 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/arrays.ts +117 -0
- package/src/utils/auth.ts +258 -0
- package/src/utils/bash-context-processor.ts +47 -0
- package/src/utils/bash-messages.ts +108 -0
- package/src/utils/block-margins.ts +35 -0
- package/src/utils/block-operations.ts +523 -0
- package/src/utils/block-processor.ts +213 -0
- package/src/utils/chat-history.ts +253 -0
- package/src/utils/chat-input-key-intercept.ts +52 -0
- package/src/utils/chat-meta.ts +90 -0
- package/src/utils/chat-scroll-accel.ts +151 -0
- package/src/utils/clipboard-image.ts +588 -0
- package/src/utils/clipboard.ts +397 -0
- package/src/utils/code-search-summary.ts +70 -0
- package/src/utils/collapse-helpers.ts +256 -0
- package/src/utils/config-dir.ts +24 -0
- package/src/utils/constants.ts +190 -0
- package/src/utils/create-event-handler-state.ts +73 -0
- package/src/utils/create-run-config.ts +121 -0
- package/src/utils/detect-shell.ts +116 -0
- package/src/utils/directory-browser.ts +72 -0
- package/src/utils/engagement.ts +55 -0
- package/src/utils/env.ts +92 -0
- package/src/utils/error-handling.ts +255 -0
- package/src/utils/error-messages.ts +62 -0
- package/src/utils/exit-cleanly.ts +86 -0
- package/src/utils/feedback-helpers.ts +103 -0
- package/src/utils/feedback-submission.ts +22 -0
- package/src/utils/fetch-usage.ts +76 -0
- package/src/utils/fingerprint.ts +245 -0
- package/src/utils/format-elapsed-time.ts +33 -0
- package/src/utils/format-session-units.ts +6 -0
- package/src/utils/format-timeout.ts +28 -0
- package/src/utils/format-validation-errors-for-message.ts +84 -0
- package/src/utils/freebuff-agent-selection.ts +33 -0
- package/src/utils/freebuff-instance-owner.ts +66 -0
- package/src/utils/freebuff-model-navigation.ts +50 -0
- package/src/utils/freebuff-premium-reset.ts +55 -0
- package/src/utils/freebuff-referral-cache.ts +51 -0
- package/src/utils/freebuff-session-api.ts +243 -0
- package/src/utils/freebuff-session-display.ts +21 -0
- package/src/utils/freebuff-streak-line.ts +93 -0
- package/src/utils/git.ts +17 -0
- package/src/utils/helpers.ts +47 -0
- package/src/utils/image-display.ts +67 -0
- package/src/utils/image-handler.ts +325 -0
- package/src/utils/image-processor.ts +133 -0
- package/src/utils/image-thumbnail.ts +78 -0
- package/src/utils/implementor-helpers.ts +787 -0
- package/src/utils/input-modes.ts +174 -0
- package/src/utils/keyboard-actions.ts +392 -0
- package/src/utils/keypad-keys.ts +47 -0
- package/src/utils/layout-helpers.ts +33 -0
- package/src/utils/lazy-response-ads.ts +1 -0
- package/src/utils/local-agent-registry.ts +474 -0
- package/src/utils/log-shipper.ts +126 -0
- package/src/utils/logger.ts +327 -0
- package/src/utils/markdown-renderer.tsx +1090 -0
- package/src/utils/math.ts +3 -0
- package/src/utils/message-block-helpers.ts +684 -0
- package/src/utils/message-history.ts +143 -0
- package/src/utils/message-tree-utils.ts +67 -0
- package/src/utils/message-updater.ts +279 -0
- package/src/utils/open-file.ts +146 -0
- package/src/utils/open-url.ts +63 -0
- package/src/utils/path-completion.ts +111 -0
- package/src/utils/path-helpers.ts +38 -0
- package/src/utils/pending-attachments.ts +360 -0
- package/src/utils/polling-backoff.ts +59 -0
- package/src/utils/project-picker.ts +12 -0
- package/src/utils/queue-panel-actions.ts +70 -0
- package/src/utils/recent-projects.ts +156 -0
- package/src/utils/renderer-cleanup.ts +233 -0
- package/src/utils/response-ad-positions.ts +1 -0
- package/src/utils/run-state-storage.ts +603 -0
- package/src/utils/safe-json.ts +153 -0
- package/src/utils/sdk-event-handlers.ts +571 -0
- package/src/utils/send-message-helpers.ts +210 -0
- package/src/utils/send-message-timer.ts +110 -0
- package/src/utils/settings.ts +226 -0
- package/src/utils/skill-registry.ts +94 -0
- package/src/utils/spawn-agent-matcher.ts +55 -0
- package/src/utils/status-indicator-state.ts +107 -0
- package/src/utils/stream-chunk-processor.ts +57 -0
- package/src/utils/strings.ts +271 -0
- package/src/utils/subscription.ts +31 -0
- package/src/utils/syntax-highlighter.tsx +19 -0
- package/src/utils/terminal-color-detection.ts +474 -0
- package/src/utils/terminal-command-broker.ts +467 -0
- package/src/utils/terminal-enter-detection.ts +63 -0
- package/src/utils/terminal-images.ts +229 -0
- package/src/utils/terminal-io.ts +85 -0
- package/src/utils/terminal-protocol-controller.ts +173 -0
- package/src/utils/terminal-reset-sequences.ts +33 -0
- package/src/utils/terminal-title.ts +84 -0
- package/src/utils/terminal-watchdog.ts +366 -0
- package/src/utils/text-layout.ts +149 -0
- package/src/utils/theme-config.ts +141 -0
- package/src/utils/theme-system.ts +1206 -0
- package/src/utils/think-tag-parser.ts +95 -0
- package/src/utils/time-format.ts +21 -0
- package/src/utils/trace-writer.ts +162 -0
- package/src/utils/ui-constants.ts +69 -0
- package/src/utils/update-checker.ts +110 -0
- package/src/utils/usage-banner-state.ts +146 -0
- package/src/utils/validation-error-formatting.ts +86 -0
- package/src/utils/validation-error-helpers.ts +19 -0
- package/src/utils/windows-terminal-health.ts +124 -0
- package/src/utils/word-wrap-utils.ts +53 -0
- package/src/utils/write-file-atomic.ts +54 -0
- package/src/utils/yield-to-event-loop.ts +9 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
|
|
2
|
+
import { shouldCollapseByDefault } from './constants'
|
|
3
|
+
import {
|
|
4
|
+
isImplementorAgent,
|
|
5
|
+
groupConsecutiveImplementors,
|
|
6
|
+
groupConsecutiveNonImplementorAgents,
|
|
7
|
+
groupConsecutiveToolBlocks,
|
|
8
|
+
} from './implementor-helpers'
|
|
9
|
+
import { isImageBlock } from '../types/chat'
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ContentBlock,
|
|
13
|
+
AgentContentBlock,
|
|
14
|
+
ToolContentBlock,
|
|
15
|
+
TextContentBlock,
|
|
16
|
+
ImageContentBlock,
|
|
17
|
+
} from '../types/chat'
|
|
18
|
+
import type { ReactNode } from 'react'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Type guard for reasoning text blocks (thinking blocks)
|
|
22
|
+
*/
|
|
23
|
+
export function isReasoningTextBlock(
|
|
24
|
+
block: ContentBlock,
|
|
25
|
+
): block is Extract<ContentBlock, { type: 'text' }> {
|
|
26
|
+
return block.type === 'text' && block.textType === 'reasoning'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handler callbacks for processing different block types.
|
|
31
|
+
* Each handler receives the block(s) and relevant indices, and returns a ReactNode.
|
|
32
|
+
*/
|
|
33
|
+
export interface BlockProcessorHandlers {
|
|
34
|
+
/** Handle a group of consecutive reasoning text blocks */
|
|
35
|
+
onReasoningGroup: (
|
|
36
|
+
blocks: TextContentBlock[],
|
|
37
|
+
startIndex: number,
|
|
38
|
+
) => ReactNode
|
|
39
|
+
|
|
40
|
+
/** Handle an image block (optional - if not provided, images are skipped) */
|
|
41
|
+
onImageBlock?: (block: ImageContentBlock, index: number) => ReactNode
|
|
42
|
+
|
|
43
|
+
/** Handle a group of consecutive tool blocks */
|
|
44
|
+
onToolGroup: (
|
|
45
|
+
blocks: ToolContentBlock[],
|
|
46
|
+
startIndex: number,
|
|
47
|
+
nextIndex: number,
|
|
48
|
+
) => ReactNode
|
|
49
|
+
|
|
50
|
+
/** Handle a group of consecutive implementor agent blocks */
|
|
51
|
+
onImplementorGroup: (
|
|
52
|
+
blocks: AgentContentBlock[],
|
|
53
|
+
startIndex: number,
|
|
54
|
+
nextIndex: number,
|
|
55
|
+
) => ReactNode
|
|
56
|
+
|
|
57
|
+
/** Handle a group of consecutive non-implementor agent blocks */
|
|
58
|
+
onAgentGroup: (
|
|
59
|
+
blocks: AgentContentBlock[],
|
|
60
|
+
startIndex: number,
|
|
61
|
+
nextIndex: number,
|
|
62
|
+
) => ReactNode
|
|
63
|
+
|
|
64
|
+
/** Handle a single block that doesn't fit into any group category */
|
|
65
|
+
onSingleBlock: (block: ContentBlock, index: number) => ReactNode
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Split an array of items into sub-groups based on agent size.
|
|
70
|
+
* Consecutive "small" agents (collapsed by default) are grouped together
|
|
71
|
+
* so they can share a grid row. Each "large" agent gets its own sub-group
|
|
72
|
+
* so it renders at full width.
|
|
73
|
+
*/
|
|
74
|
+
export function splitByAgentSize<T>(
|
|
75
|
+
items: T[],
|
|
76
|
+
getAgentType: (item: T) => string,
|
|
77
|
+
): T[][] {
|
|
78
|
+
if (items.length <= 1) return [items]
|
|
79
|
+
|
|
80
|
+
const subGroups: T[][] = []
|
|
81
|
+
let currentSmallGroup: T[] = []
|
|
82
|
+
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
if (shouldCollapseByDefault(getAgentType(item))) {
|
|
85
|
+
currentSmallGroup.push(item)
|
|
86
|
+
} else {
|
|
87
|
+
if (currentSmallGroup.length > 0) {
|
|
88
|
+
subGroups.push(currentSmallGroup)
|
|
89
|
+
currentSmallGroup = []
|
|
90
|
+
}
|
|
91
|
+
subGroups.push([item])
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (currentSmallGroup.length > 0) {
|
|
96
|
+
subGroups.push(currentSmallGroup)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return subGroups
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Convenience wrapper for splitting AgentContentBlock arrays by size. */
|
|
103
|
+
export function splitAgentsBySize(
|
|
104
|
+
agents: AgentContentBlock[],
|
|
105
|
+
): AgentContentBlock[][] {
|
|
106
|
+
return splitByAgentSize(agents, (a) => a.agentType)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Process a list of content blocks, grouping consecutive blocks of the same type
|
|
111
|
+
* and calling the appropriate handler for each group or single block.
|
|
112
|
+
*
|
|
113
|
+
* This utility abstracts the common iteration pattern used by BlocksRenderer and AgentBody.
|
|
114
|
+
*
|
|
115
|
+
* @param blocks - The array of content blocks to process
|
|
116
|
+
* @param handlers - Callback handlers for each block type
|
|
117
|
+
* @returns An array of ReactNode elements
|
|
118
|
+
*/
|
|
119
|
+
export function processBlocks(
|
|
120
|
+
blocks: ContentBlock[],
|
|
121
|
+
handlers: BlockProcessorHandlers,
|
|
122
|
+
): ReactNode[] {
|
|
123
|
+
const nodes: ReactNode[] = []
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < blocks.length; ) {
|
|
126
|
+
const block = blocks[i]
|
|
127
|
+
|
|
128
|
+
// Handle reasoning text blocks (thinking)
|
|
129
|
+
if (isReasoningTextBlock(block)) {
|
|
130
|
+
const start = i
|
|
131
|
+
const reasoningBlocks: TextContentBlock[] = []
|
|
132
|
+
while (i < blocks.length) {
|
|
133
|
+
const currentBlock = blocks[i]
|
|
134
|
+
if (!isReasoningTextBlock(currentBlock)) break
|
|
135
|
+
reasoningBlocks.push(currentBlock)
|
|
136
|
+
i++
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const node = handlers.onReasoningGroup(reasoningBlocks, start)
|
|
140
|
+
if (node !== null) {
|
|
141
|
+
nodes.push(node)
|
|
142
|
+
}
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Handle image blocks
|
|
147
|
+
if (isImageBlock(block)) {
|
|
148
|
+
if (handlers.onImageBlock) {
|
|
149
|
+
const node = handlers.onImageBlock(block, i)
|
|
150
|
+
if (node !== null) {
|
|
151
|
+
nodes.push(node)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
i++
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Handle tool blocks
|
|
159
|
+
if (block.type === 'tool') {
|
|
160
|
+
const start = i
|
|
161
|
+
const { group: toolBlocks, nextIndex } = groupConsecutiveToolBlocks(
|
|
162
|
+
blocks,
|
|
163
|
+
i,
|
|
164
|
+
)
|
|
165
|
+
i = nextIndex
|
|
166
|
+
|
|
167
|
+
const node = handlers.onToolGroup(toolBlocks, start, nextIndex)
|
|
168
|
+
if (node !== null) {
|
|
169
|
+
nodes.push(node)
|
|
170
|
+
}
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Handle agent blocks
|
|
175
|
+
if (block.type === 'agent') {
|
|
176
|
+
if (isImplementorAgent(block)) {
|
|
177
|
+
// Implementor agents
|
|
178
|
+
const start = i
|
|
179
|
+
const { group: implementors, nextIndex } = groupConsecutiveImplementors(
|
|
180
|
+
blocks,
|
|
181
|
+
i,
|
|
182
|
+
)
|
|
183
|
+
i = nextIndex
|
|
184
|
+
|
|
185
|
+
const node = handlers.onImplementorGroup(implementors, start, nextIndex)
|
|
186
|
+
if (node !== null) {
|
|
187
|
+
nodes.push(node)
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
// Non-implementor agents
|
|
191
|
+
const start = i
|
|
192
|
+
const { group: agentBlocks, nextIndex } =
|
|
193
|
+
groupConsecutiveNonImplementorAgents(blocks, i)
|
|
194
|
+
i = nextIndex
|
|
195
|
+
|
|
196
|
+
const node = handlers.onAgentGroup(agentBlocks, start, nextIndex)
|
|
197
|
+
if (node !== null) {
|
|
198
|
+
nodes.push(node)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Handle all other block types (text, html, etc.)
|
|
205
|
+
const node = handlers.onSingleBlock(block, i)
|
|
206
|
+
if (node !== null) {
|
|
207
|
+
nodes.push(node)
|
|
208
|
+
}
|
|
209
|
+
i++
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return nodes
|
|
213
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CHAT_MESSAGES_FILENAME,
|
|
6
|
+
getFirstUserPrompt,
|
|
7
|
+
readChatMeta,
|
|
8
|
+
} from './chat-meta'
|
|
9
|
+
import { CHAT_LOG_FILENAME, logger } from './logger'
|
|
10
|
+
import { getProjectDataDir } from '../project-files'
|
|
11
|
+
|
|
12
|
+
import type { ChatMessage } from '../types/chat'
|
|
13
|
+
|
|
14
|
+
export interface ChatHistoryEntry {
|
|
15
|
+
chatId: string
|
|
16
|
+
lastPrompt: string
|
|
17
|
+
timestamp: Date
|
|
18
|
+
messageCount: number
|
|
19
|
+
/** True when chat-messages.json exists but can't be parsed (e.g. truncated
|
|
20
|
+
* by a crash mid-write). Shown in /history so the chat doesn't silently
|
|
21
|
+
* vanish; can be deleted but not resumed. */
|
|
22
|
+
unreadable?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getChatsDir(dataDir: string = getProjectDataDir()): string {
|
|
26
|
+
return path.join(dataDir, 'chats')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ChatDirInfo {
|
|
30
|
+
chatId: string
|
|
31
|
+
chatPath: string
|
|
32
|
+
messagesPath: string
|
|
33
|
+
mtime: Date
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List all available chats sorted by most recent first
|
|
38
|
+
* @param maxChats - Maximum number of chats to load (default: 500)
|
|
39
|
+
*/
|
|
40
|
+
export function getAllChats(
|
|
41
|
+
maxChats: number = 500,
|
|
42
|
+
dataDir?: string,
|
|
43
|
+
): ChatHistoryEntry[] {
|
|
44
|
+
try {
|
|
45
|
+
const chatsDir = getChatsDir(dataDir)
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(chatsDir)) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const chatDirs = fs.readdirSync(chatsDir)
|
|
52
|
+
|
|
53
|
+
// First pass: get mtime for all chat directories (fast, no file reading)
|
|
54
|
+
const chatDirInfos: ChatDirInfo[] = []
|
|
55
|
+
for (const chatId of chatDirs) {
|
|
56
|
+
const chatPath = path.join(chatsDir, chatId)
|
|
57
|
+
try {
|
|
58
|
+
const stat = fs.statSync(chatPath)
|
|
59
|
+
if (!stat.isDirectory()) continue
|
|
60
|
+
|
|
61
|
+
chatDirInfos.push({
|
|
62
|
+
chatId,
|
|
63
|
+
chatPath,
|
|
64
|
+
messagesPath: path.join(chatPath, CHAT_MESSAGES_FILENAME),
|
|
65
|
+
mtime: stat.mtime,
|
|
66
|
+
})
|
|
67
|
+
} catch {
|
|
68
|
+
// Skip directories we can't stat
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Sort by mtime first (most recent first)
|
|
73
|
+
chatDirInfos.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
|
|
74
|
+
|
|
75
|
+
// Second pass: only read message content for the top N chats
|
|
76
|
+
const chats: ChatHistoryEntry[] = []
|
|
77
|
+
const chatsToLoad = chatDirInfos.slice(0, maxChats)
|
|
78
|
+
|
|
79
|
+
for (const info of chatsToLoad) {
|
|
80
|
+
try {
|
|
81
|
+
let messageCount = 0
|
|
82
|
+
let lastPrompt = '(empty chat)'
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync(info.messagesPath)) {
|
|
85
|
+
// Prefer the sidecar summary: transcripts are unbounded, so parsing
|
|
86
|
+
// every full chat-messages.json here can make /history slow.
|
|
87
|
+
const meta = readChatMeta(info.chatPath)
|
|
88
|
+
if (meta) {
|
|
89
|
+
messageCount = meta.messageCount
|
|
90
|
+
lastPrompt = meta.firstPrompt
|
|
91
|
+
} else {
|
|
92
|
+
// Pre-sidecar chats, or a sidecar that no longer matches the
|
|
93
|
+
// messages file (rewritten by an older CLI, crash between the
|
|
94
|
+
// two writes): parse the full file.
|
|
95
|
+
const content = fs.readFileSync(info.messagesPath, 'utf8')
|
|
96
|
+
const messages = JSON.parse(content) as ChatMessage[]
|
|
97
|
+
if (!Array.isArray(messages)) {
|
|
98
|
+
throw new Error('chat-messages.json is not an array')
|
|
99
|
+
}
|
|
100
|
+
messageCount = messages.length
|
|
101
|
+
lastPrompt = getFirstUserPrompt(messages)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Skip empty chats (no messages)
|
|
106
|
+
if (messageCount > 0) {
|
|
107
|
+
chats.push({
|
|
108
|
+
chatId: info.chatId,
|
|
109
|
+
lastPrompt,
|
|
110
|
+
timestamp: info.mtime,
|
|
111
|
+
messageCount,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.debug(
|
|
116
|
+
{
|
|
117
|
+
chatId: info.chatId,
|
|
118
|
+
error: error instanceof Error ? error.message : String(error),
|
|
119
|
+
},
|
|
120
|
+
'Failed to read chat messages',
|
|
121
|
+
)
|
|
122
|
+
// Don't silently hide the chat: list it as unreadable so the user
|
|
123
|
+
// knows it exists (and can delete it) instead of thinking it was lost
|
|
124
|
+
chats.push({
|
|
125
|
+
chatId: info.chatId,
|
|
126
|
+
lastPrompt: '(unreadable chat)',
|
|
127
|
+
timestamp: info.mtime,
|
|
128
|
+
messageCount: 0,
|
|
129
|
+
unreadable: true,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return chats
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.error(
|
|
137
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
138
|
+
'Failed to list chats',
|
|
139
|
+
)
|
|
140
|
+
return []
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Older CLI versions logged the full conversation (including attachments) to
|
|
145
|
+
// log.jsonl on every step, leaving multi-GB files in chat directories. Delete
|
|
146
|
+
// any log file over this cap; with summary-only logging, healthy logs stay
|
|
147
|
+
// far below it.
|
|
148
|
+
const MAX_LOG_FILE_BYTES = 10 * 1024 * 1024
|
|
149
|
+
// Only delete logs from chats untouched for this long, so debug logs for
|
|
150
|
+
// recent chats stay available.
|
|
151
|
+
const MIN_LOG_AGE_MS = 14 * 24 * 60 * 60 * 1000
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Delete oversized log.jsonl files from chat directories that haven't been
|
|
155
|
+
* touched in 14+ days. Only debug logs are removed — chat history files are
|
|
156
|
+
* untouched.
|
|
157
|
+
*/
|
|
158
|
+
export function trimOversizedChatLogs(dataDir?: string): void {
|
|
159
|
+
let chatsDir: string
|
|
160
|
+
let chatIds: string[]
|
|
161
|
+
try {
|
|
162
|
+
chatsDir = getChatsDir(dataDir)
|
|
163
|
+
chatIds = fs.readdirSync(chatsDir)
|
|
164
|
+
} catch {
|
|
165
|
+
return // No project root set or no chats directory yet
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const deleteBefore = Date.now() - MIN_LOG_AGE_MS
|
|
169
|
+
for (const chatId of chatIds) {
|
|
170
|
+
const logFile = path.join(chatsDir, chatId, CHAT_LOG_FILENAME)
|
|
171
|
+
try {
|
|
172
|
+
const stats = fs.statSync(logFile, { throwIfNoEntry: false })
|
|
173
|
+
if (
|
|
174
|
+
stats &&
|
|
175
|
+
stats.size > MAX_LOG_FILE_BYTES &&
|
|
176
|
+
stats.mtimeMs < deleteBefore
|
|
177
|
+
) {
|
|
178
|
+
fs.unlinkSync(logFile)
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// Ignore errors for individual files
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Delete a saved chat session from local history.
|
|
188
|
+
*/
|
|
189
|
+
export function deleteChatSession(chatId: string, dataDir?: string): boolean {
|
|
190
|
+
try {
|
|
191
|
+
const safeChatId = chatId.trim()
|
|
192
|
+
if (
|
|
193
|
+
!safeChatId ||
|
|
194
|
+
safeChatId === '.' ||
|
|
195
|
+
safeChatId === '..' ||
|
|
196
|
+
path.basename(safeChatId) !== safeChatId
|
|
197
|
+
) {
|
|
198
|
+
logger.warn({ chatId }, 'Refusing to delete invalid chat id')
|
|
199
|
+
return false
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const chatsDir = getChatsDir(dataDir)
|
|
203
|
+
const chatPath = path.join(chatsDir, safeChatId)
|
|
204
|
+
|
|
205
|
+
if (!fs.existsSync(chatPath)) {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const stat = fs.statSync(chatPath)
|
|
210
|
+
if (!stat.isDirectory()) {
|
|
211
|
+
logger.warn(
|
|
212
|
+
{ chatId, chatPath },
|
|
213
|
+
'Refusing to delete non-directory chat path',
|
|
214
|
+
)
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fs.rmSync(chatPath, { recursive: true, force: false })
|
|
219
|
+
return true
|
|
220
|
+
} catch (error) {
|
|
221
|
+
logger.error(
|
|
222
|
+
{ chatId, error: error instanceof Error ? error.message : String(error) },
|
|
223
|
+
'Failed to delete chat session',
|
|
224
|
+
)
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Format a timestamp relative to now (e.g., "2 hours ago", "yesterday")
|
|
231
|
+
*/
|
|
232
|
+
export function formatRelativeTime(date: Date): string {
|
|
233
|
+
const now = new Date()
|
|
234
|
+
const diffMs = now.getTime() - date.getTime()
|
|
235
|
+
const diffSecs = Math.floor(diffMs / 1000)
|
|
236
|
+
const diffMins = Math.floor(diffSecs / 60)
|
|
237
|
+
const diffHours = Math.floor(diffMins / 60)
|
|
238
|
+
const diffDays = Math.floor(diffHours / 24)
|
|
239
|
+
|
|
240
|
+
if (diffSecs < 60) {
|
|
241
|
+
return 'just now'
|
|
242
|
+
} else if (diffMins < 60) {
|
|
243
|
+
return `${diffMins}m ago`
|
|
244
|
+
} else if (diffHours < 24) {
|
|
245
|
+
return `${diffHours}h ago`
|
|
246
|
+
} else if (diffDays === 1) {
|
|
247
|
+
return 'yesterday'
|
|
248
|
+
} else if (diffDays < 7) {
|
|
249
|
+
return `${diffDays}d ago`
|
|
250
|
+
} else {
|
|
251
|
+
return date.toLocaleDateString()
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isPlainEnterKey } from './terminal-enter-detection'
|
|
2
|
+
|
|
3
|
+
type ChatInputKey = {
|
|
4
|
+
name?: string
|
|
5
|
+
sequence?: string
|
|
6
|
+
shift?: boolean
|
|
7
|
+
ctrl?: boolean
|
|
8
|
+
meta?: boolean
|
|
9
|
+
option?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type ChatInputKeyInterceptState = {
|
|
13
|
+
hasSlashSuggestions: boolean
|
|
14
|
+
hasMentionSuggestions: boolean
|
|
15
|
+
lastEditDueToNav: boolean
|
|
16
|
+
cursorPosition: number
|
|
17
|
+
inputLength: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function shouldInterceptChatInputKey(
|
|
21
|
+
key: ChatInputKey,
|
|
22
|
+
state: ChatInputKeyInterceptState,
|
|
23
|
+
): boolean {
|
|
24
|
+
const isPlainEnter = isPlainEnterKey(key)
|
|
25
|
+
const isTab = key.name === 'tab' && !key.ctrl && !key.meta && !key.option
|
|
26
|
+
const isUp = key.name === 'up' && !key.ctrl && !key.meta && !key.option
|
|
27
|
+
const isDown = key.name === 'down' && !key.ctrl && !key.meta && !key.option
|
|
28
|
+
const isUpDown = isUp || isDown
|
|
29
|
+
|
|
30
|
+
const hasSuggestions =
|
|
31
|
+
state.hasSlashSuggestions || state.hasMentionSuggestions
|
|
32
|
+
if (hasSuggestions) {
|
|
33
|
+
if (isUpDown && state.lastEditDueToNav) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
if (isPlainEnter || isTab || isUpDown) {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const historyUpEnabled = state.lastEditDueToNav || state.cursorPosition === 0
|
|
42
|
+
const historyDownEnabled =
|
|
43
|
+
state.lastEditDueToNav || state.cursorPosition === state.inputLength
|
|
44
|
+
if (isUp && historyUpEnabled) {
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
if (isDown && historyDownEnabled) {
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
import { writeFileAtomic } from './write-file-atomic'
|
|
7
|
+
|
|
8
|
+
import type { ChatMessage } from '../types/chat'
|
|
9
|
+
|
|
10
|
+
export const CHAT_MESSAGES_FILENAME = 'chat-messages.json'
|
|
11
|
+
export const CHAT_META_FILENAME = 'chat-meta.json'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Small sidecar summary of a chat written alongside chat-messages.json.
|
|
15
|
+
* Transcripts are unbounded and can grow to many MB, so /history reads this
|
|
16
|
+
* instead of parsing every full chat-messages.json.
|
|
17
|
+
*
|
|
18
|
+
* messagesSize/messagesMtimeMs bind the sidecar to the exact messages file it
|
|
19
|
+
* summarizes: if the transcript is later rewritten by anything that doesn't
|
|
20
|
+
* refresh the sidecar (an older CLI version, a crash between the two writes),
|
|
21
|
+
* readChatMeta rejects the stale sidecar and callers fall back to the full
|
|
22
|
+
* parse instead of showing outdated data or hiding corruption.
|
|
23
|
+
*/
|
|
24
|
+
const chatMetaSchema = z.object({
|
|
25
|
+
messageCount: z.number(),
|
|
26
|
+
firstPrompt: z.string(),
|
|
27
|
+
messagesSize: z.number(),
|
|
28
|
+
messagesMtimeMs: z.number(),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export type ChatMeta = z.infer<typeof chatMetaSchema>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the first user message from a list of chat messages
|
|
35
|
+
*/
|
|
36
|
+
export function getFirstUserPrompt(messages: ChatMessage[]): string {
|
|
37
|
+
for (const msg of messages) {
|
|
38
|
+
if (msg?.variant === 'user' && msg.content) {
|
|
39
|
+
// Truncate long prompts
|
|
40
|
+
const content = msg.content.trim()
|
|
41
|
+
if (content.length > 100) {
|
|
42
|
+
return content.slice(0, 97) + '...'
|
|
43
|
+
}
|
|
44
|
+
return content
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return '(empty chat)'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Write the sidecar for the given chat directory. The chat-messages.json in
|
|
52
|
+
* that directory must already contain exactly `messages` (its stats are
|
|
53
|
+
* recorded to bind the sidecar to it).
|
|
54
|
+
*/
|
|
55
|
+
export function writeChatMeta(chatDir: string, messages: ChatMessage[]): void {
|
|
56
|
+
const stats = fs.statSync(path.join(chatDir, CHAT_MESSAGES_FILENAME))
|
|
57
|
+
const meta: ChatMeta = {
|
|
58
|
+
messageCount: messages.length,
|
|
59
|
+
firstPrompt: getFirstUserPrompt(messages),
|
|
60
|
+
messagesSize: stats.size,
|
|
61
|
+
messagesMtimeMs: stats.mtimeMs,
|
|
62
|
+
}
|
|
63
|
+
writeFileAtomic(path.join(chatDir, CHAT_META_FILENAME), JSON.stringify(meta))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Read the sidecar for a chat directory. Returns null when it is missing,
|
|
68
|
+
* unparsable, or stale (chat-messages.json no longer matches the recorded
|
|
69
|
+
* size/mtime) — callers should fall back to parsing chat-messages.json.
|
|
70
|
+
*/
|
|
71
|
+
export function readChatMeta(chatDir: string): ChatMeta | null {
|
|
72
|
+
try {
|
|
73
|
+
const raw = fs.readFileSync(path.join(chatDir, CHAT_META_FILENAME), 'utf8')
|
|
74
|
+
const parsed = chatMetaSchema.safeParse(JSON.parse(raw))
|
|
75
|
+
if (!parsed.success) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
const meta = parsed.data
|
|
79
|
+
const stats = fs.statSync(path.join(chatDir, CHAT_MESSAGES_FILENAME))
|
|
80
|
+
if (
|
|
81
|
+
stats.size !== meta.messagesSize ||
|
|
82
|
+
stats.mtimeMs !== meta.messagesMtimeMs
|
|
83
|
+
) {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
return meta
|
|
87
|
+
} catch {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
}
|