@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,74 @@
|
|
|
1
|
+
import { isDirectDeepSeekMode } from '@anvil/common/constants/deepseek-direct'
|
|
2
|
+
import { validateAgents } from '@anvil/sdk'
|
|
3
|
+
import { useCallback, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { loadAgentDefinitions } from '../utils/local-agent-registry'
|
|
6
|
+
import { logger } from '../utils/logger'
|
|
7
|
+
import { filterNetworkErrors } from '../utils/validation-error-helpers'
|
|
8
|
+
|
|
9
|
+
export type ValidationError = {
|
|
10
|
+
id: string
|
|
11
|
+
message: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ValidationCheckResult = {
|
|
15
|
+
success: boolean
|
|
16
|
+
errors: ValidationError[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type UseAgentValidationResult = {
|
|
20
|
+
validationErrors: ValidationError[]
|
|
21
|
+
isValidating: boolean
|
|
22
|
+
validate: () => Promise<ValidationCheckResult>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook that provides agent validation functionality.
|
|
27
|
+
* Call validate() manually to trigger validation (e.g., on message send).
|
|
28
|
+
*/
|
|
29
|
+
export const useAgentValidation = (): UseAgentValidationResult => {
|
|
30
|
+
const [validationErrors, setValidationErrors] = useState<ValidationError[]>(
|
|
31
|
+
[],
|
|
32
|
+
)
|
|
33
|
+
const [isValidating, setIsValidating] = useState(false)
|
|
34
|
+
|
|
35
|
+
// Validate agents and update state
|
|
36
|
+
// Returns validation result with success status and any errors
|
|
37
|
+
const validate = useCallback(async (): Promise<ValidationCheckResult> => {
|
|
38
|
+
setIsValidating(true)
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const agentDefinitions = loadAgentDefinitions()
|
|
42
|
+
|
|
43
|
+
const validationResult = await validateAgents(agentDefinitions, {
|
|
44
|
+
// Remote validation hits the Anvil backend; skip it in
|
|
45
|
+
// direct-to-DeepSeek mode so sending isn't blocked by a dead endpoint.
|
|
46
|
+
remote: !isDirectDeepSeekMode(),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (validationResult.success) {
|
|
50
|
+
setValidationErrors([])
|
|
51
|
+
return { success: true, errors: [] }
|
|
52
|
+
} else {
|
|
53
|
+
const filteredValidationErrors = filterNetworkErrors(
|
|
54
|
+
validationResult.validationErrors,
|
|
55
|
+
)
|
|
56
|
+
setValidationErrors(filteredValidationErrors)
|
|
57
|
+
return { success: false, errors: filteredValidationErrors }
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.error({ error }, 'Agent validation failed with exception')
|
|
61
|
+
// Don't update validation errors on exception - keep previous state
|
|
62
|
+
// Return failure to block message sending on validation errors
|
|
63
|
+
return { success: false, errors: [] }
|
|
64
|
+
} finally {
|
|
65
|
+
setIsValidating(false)
|
|
66
|
+
}
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
validationErrors,
|
|
71
|
+
isValidating,
|
|
72
|
+
validate,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AskUserBridge } from '@anvil/common/utils/ask-user-bridge'
|
|
2
|
+
import { useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
import { useChatStore } from '../state/chat-store'
|
|
5
|
+
|
|
6
|
+
import type { AskUserQuestion } from '../types/store'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Patterns that indicate a "custom" or "other" catch-all option.
|
|
10
|
+
* These are redundant since the UI automatically provides a Custom text input.
|
|
11
|
+
*/
|
|
12
|
+
const REDUNDANT_OPTION_PATTERNS = [
|
|
13
|
+
/^custom$/i,
|
|
14
|
+
/^other$/i,
|
|
15
|
+
/^none\s*(of\s*the\s*above)?$/i,
|
|
16
|
+
/^something\s*else$/i,
|
|
17
|
+
/^enter\s*(my\s*)?own$/i,
|
|
18
|
+
/^type\s*(my\s*)?own$/i,
|
|
19
|
+
/^write\s*(my\s*)?own$/i,
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gets the label from an option, handling both string and object formats.
|
|
24
|
+
*/
|
|
25
|
+
function getOptionLabel(option: string | { label: string; description?: string }): string {
|
|
26
|
+
return typeof option === 'string' ? option : option.label
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks if an option label matches any of the redundant "custom/other" patterns.
|
|
31
|
+
*/
|
|
32
|
+
function isRedundantOption(option: string | { label: string; description?: string }): boolean {
|
|
33
|
+
const label = getOptionLabel(option).trim()
|
|
34
|
+
return REDUNDANT_OPTION_PATTERNS.some((pattern) => pattern.test(label))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Filters out redundant "Custom"/"Other" options from questions.
|
|
39
|
+
* The UI already provides a Custom text input, so these are unnecessary and confusing.
|
|
40
|
+
*/
|
|
41
|
+
function filterRedundantOptions(questions: AskUserQuestion[]): AskUserQuestion[] {
|
|
42
|
+
return questions.map((question) => {
|
|
43
|
+
const filteredOptions = question.options.filter((option) => !isRedundantOption(option))
|
|
44
|
+
return {
|
|
45
|
+
...question,
|
|
46
|
+
// Preserve the original array type (string[] or object[])
|
|
47
|
+
options: filteredOptions as typeof question.options,
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useAskUserBridge() {
|
|
53
|
+
const setAskUserState = useChatStore((state) => state.setAskUserState)
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const unsubscribe = AskUserBridge.subscribe((request) => {
|
|
57
|
+
if (request) {
|
|
58
|
+
// Filter out redundant "Custom"/"Other" options since UI provides its own
|
|
59
|
+
const filteredQuestions = filterRedundantOptions(request.questions)
|
|
60
|
+
setAskUserState({
|
|
61
|
+
toolCallId: request.toolCallId,
|
|
62
|
+
questions: filteredQuestions,
|
|
63
|
+
// Initialize based on question type: multi-select → [], single-select → -1
|
|
64
|
+
selectedAnswers: filteredQuestions.map((q) => (q.multiSelect ? [] : -1)),
|
|
65
|
+
otherTexts: new Array(filteredQuestions.length).fill(''),
|
|
66
|
+
})
|
|
67
|
+
} else {
|
|
68
|
+
setAskUserState(null)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
return unsubscribe
|
|
72
|
+
}, [setAskUserState])
|
|
73
|
+
|
|
74
|
+
const submitAnswers = (
|
|
75
|
+
answers: Array<{
|
|
76
|
+
questionIndex: number
|
|
77
|
+
selectedOption?: string
|
|
78
|
+
selectedOptions?: string[]
|
|
79
|
+
otherText?: string
|
|
80
|
+
}>
|
|
81
|
+
) => {
|
|
82
|
+
// Don't clear input value - preserve user's input from before the questionnaire
|
|
83
|
+
AskUserBridge.submit({ answers })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const skip = () => {
|
|
87
|
+
// Don't clear input value - preserve user's input from before the questionnaire
|
|
88
|
+
AskUserBridge.submit({ skipped: true })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { submitAnswers, skip }
|
|
92
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { createHash } from 'crypto'
|
|
2
|
+
|
|
3
|
+
import { getDirectDeepSeekApiKey } from '@anvil/common/constants/deepseek-direct'
|
|
4
|
+
import { getCiEnv } from '@anvil/common/env-ci'
|
|
5
|
+
import {
|
|
6
|
+
getUserInfoFromApiKey as defaultGetUserInfoFromApiKey,
|
|
7
|
+
isRetryableStatusCode,
|
|
8
|
+
getErrorStatusCode,
|
|
9
|
+
createAuthError,
|
|
10
|
+
createServerError,
|
|
11
|
+
MAX_RETRIES_PER_MESSAGE,
|
|
12
|
+
RETRY_BACKOFF_BASE_DELAY_MS,
|
|
13
|
+
} from '@anvil/sdk'
|
|
14
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
getUserCredentials as defaultGetUserCredentials,
|
|
18
|
+
saveUserCredentials as defaultSaveUserCredentials,
|
|
19
|
+
logoutUser as logoutUserUtil,
|
|
20
|
+
type User,
|
|
21
|
+
} from '../utils/auth'
|
|
22
|
+
import { resetAnvilClient } from '../utils/anvil-client'
|
|
23
|
+
import { logger as defaultLogger, loggerContext } from '../utils/logger'
|
|
24
|
+
|
|
25
|
+
import type { GetUserInfoFromApiKeyFn } from '@anvil/common/types/contracts/database'
|
|
26
|
+
import type { Logger } from '@anvil/common/types/contracts/logger'
|
|
27
|
+
|
|
28
|
+
const getApiKeyHash = (apiKey: string): string => {
|
|
29
|
+
return createHash('sha256').update(apiKey).digest('hex')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Query keys for type-safe cache management
|
|
33
|
+
export const authQueryKeys = {
|
|
34
|
+
all: ['auth'] as const,
|
|
35
|
+
user: () => [...authQueryKeys.all, 'user'] as const,
|
|
36
|
+
validation: (apiKey: string) =>
|
|
37
|
+
[...authQueryKeys.all, 'validation', getApiKeyHash(apiKey)] as const,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ValidateAuthParams {
|
|
41
|
+
apiKey: string
|
|
42
|
+
getUserInfoFromApiKey?: GetUserInfoFromApiKeyFn
|
|
43
|
+
logger?: Logger
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type ValidatedUserInfo = {
|
|
47
|
+
id: string
|
|
48
|
+
email: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if an error is an authentication error (401, 403)
|
|
53
|
+
*/
|
|
54
|
+
function isAuthenticationError(error: unknown): boolean {
|
|
55
|
+
const statusCode = getErrorStatusCode(error)
|
|
56
|
+
return statusCode === 401 || statusCode === 403
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validates an API key by calling the backend
|
|
61
|
+
*
|
|
62
|
+
* CHANGE: Exported for testing purposes and accepts optional dependencies
|
|
63
|
+
* Previously this was not exported, making it impossible to test in isolation
|
|
64
|
+
*/
|
|
65
|
+
export async function validateApiKey({
|
|
66
|
+
apiKey,
|
|
67
|
+
getUserInfoFromApiKey = defaultGetUserInfoFromApiKey,
|
|
68
|
+
logger = defaultLogger,
|
|
69
|
+
}: ValidateAuthParams): Promise<ValidatedUserInfo> {
|
|
70
|
+
const requestedFields = ['id', 'email'] as const
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const authResult = await getUserInfoFromApiKey({
|
|
74
|
+
apiKey,
|
|
75
|
+
fields: requestedFields,
|
|
76
|
+
logger,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (!authResult) {
|
|
80
|
+
logger.error('❌ API key validation failed - invalid credentials')
|
|
81
|
+
throw createAuthError('Invalid API key')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return authResult
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const statusCode = getErrorStatusCode(error)
|
|
87
|
+
|
|
88
|
+
if (isAuthenticationError(error)) {
|
|
89
|
+
logger.error('❌ API key validation failed - authentication error')
|
|
90
|
+
// Rethrow the original error to preserve statusCode for higher layers
|
|
91
|
+
throw error
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (statusCode !== undefined && isRetryableStatusCode(statusCode)) {
|
|
95
|
+
logger.error(
|
|
96
|
+
{
|
|
97
|
+
error: error instanceof Error ? error.message : String(error),
|
|
98
|
+
statusCode,
|
|
99
|
+
},
|
|
100
|
+
'❌ API key validation failed - network error',
|
|
101
|
+
)
|
|
102
|
+
// Rethrow the original error to preserve statusCode for higher layers
|
|
103
|
+
throw error
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Unknown error - wrap with statusCode for consistency
|
|
107
|
+
logger.error(
|
|
108
|
+
{
|
|
109
|
+
error: error instanceof Error ? error.message : String(error),
|
|
110
|
+
},
|
|
111
|
+
'❌ API key validation failed - unknown error',
|
|
112
|
+
)
|
|
113
|
+
throw createServerError('Authentication failed')
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface UseAuthQueryDeps {
|
|
118
|
+
getUserCredentials?: () => User | null
|
|
119
|
+
getUserInfoFromApiKey?: GetUserInfoFromApiKeyFn
|
|
120
|
+
logger?: Logger
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Hook to validate authentication status
|
|
125
|
+
* Uses stored credentials if available, otherwise checks environment variable
|
|
126
|
+
*
|
|
127
|
+
* CHANGE: Now accepts optional dependencies for testing via dependency injection
|
|
128
|
+
*/
|
|
129
|
+
export function useAuthQuery(deps: UseAuthQueryDeps = {}) {
|
|
130
|
+
const {
|
|
131
|
+
getUserCredentials = defaultGetUserCredentials,
|
|
132
|
+
getUserInfoFromApiKey = defaultGetUserInfoFromApiKey,
|
|
133
|
+
logger = defaultLogger,
|
|
134
|
+
} = deps
|
|
135
|
+
|
|
136
|
+
const userCredentials = getUserCredentials()
|
|
137
|
+
const apiKey =
|
|
138
|
+
userCredentials?.authToken ||
|
|
139
|
+
getCiEnv().ANVIL_API_KEY ||
|
|
140
|
+
getDirectDeepSeekApiKey() ||
|
|
141
|
+
''
|
|
142
|
+
|
|
143
|
+
return useQuery({
|
|
144
|
+
queryKey: authQueryKeys.validation(apiKey),
|
|
145
|
+
queryFn: () => validateApiKey({ apiKey, getUserInfoFromApiKey, logger }),
|
|
146
|
+
enabled: !!apiKey,
|
|
147
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
148
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
149
|
+
// Retry only for retryable network errors (5xx, timeouts, etc.)
|
|
150
|
+
// Don't retry authentication errors (invalid credentials)
|
|
151
|
+
retry: (failureCount, error) => {
|
|
152
|
+
const statusCode = getErrorStatusCode(error)
|
|
153
|
+
// Don't retry authentication errors - user needs to update credentials
|
|
154
|
+
if (isAuthenticationError(error)) {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
// Retry network errors if they're retryable and we haven't exceeded max retries
|
|
158
|
+
if (statusCode !== undefined && isRetryableStatusCode(statusCode)) {
|
|
159
|
+
return failureCount < MAX_RETRIES_PER_MESSAGE
|
|
160
|
+
}
|
|
161
|
+
// Don't retry other errors
|
|
162
|
+
return false
|
|
163
|
+
},
|
|
164
|
+
retryDelay: (attemptIndex) => {
|
|
165
|
+
// Exponential backoff: 1s, 2s, 4s
|
|
166
|
+
return Math.min(
|
|
167
|
+
RETRY_BACKOFF_BASE_DELAY_MS * Math.pow(2, attemptIndex),
|
|
168
|
+
8000, // Cap at 8 seconds
|
|
169
|
+
)
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface UseLoginMutationDeps {
|
|
175
|
+
saveUserCredentials?: (user: User) => void
|
|
176
|
+
getUserInfoFromApiKey?: GetUserInfoFromApiKeyFn
|
|
177
|
+
logger?: Logger
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Hook for login mutation
|
|
182
|
+
*
|
|
183
|
+
* CHANGE: Now accepts optional dependencies for testing via dependency injection
|
|
184
|
+
*/
|
|
185
|
+
export function useLoginMutation(deps: UseLoginMutationDeps = {}) {
|
|
186
|
+
const queryClient = useQueryClient()
|
|
187
|
+
const {
|
|
188
|
+
saveUserCredentials = defaultSaveUserCredentials,
|
|
189
|
+
getUserInfoFromApiKey = defaultGetUserInfoFromApiKey,
|
|
190
|
+
logger = defaultLogger,
|
|
191
|
+
} = deps
|
|
192
|
+
|
|
193
|
+
return useMutation({
|
|
194
|
+
mutationFn: async (user: User) => {
|
|
195
|
+
// Save credentials to file system
|
|
196
|
+
saveUserCredentials(user)
|
|
197
|
+
|
|
198
|
+
// Validate the new credentials
|
|
199
|
+
const authResult = await validateApiKey({
|
|
200
|
+
apiKey: user.authToken,
|
|
201
|
+
getUserInfoFromApiKey,
|
|
202
|
+
logger,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const mergedUser = { ...user, ...authResult }
|
|
206
|
+
return mergedUser
|
|
207
|
+
},
|
|
208
|
+
onSuccess: () => {
|
|
209
|
+
// Invalidate auth queries to trigger refetch with new credentials
|
|
210
|
+
queryClient.invalidateQueries({ queryKey: authQueryKeys.all })
|
|
211
|
+
},
|
|
212
|
+
onError: (error) => {
|
|
213
|
+
logger.error(
|
|
214
|
+
{
|
|
215
|
+
error: error instanceof Error ? error.message : String(error),
|
|
216
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
217
|
+
},
|
|
218
|
+
'❌ Login mutation failed',
|
|
219
|
+
)
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface UseLogoutMutationDeps {
|
|
225
|
+
logoutUser?: () => Promise<boolean>
|
|
226
|
+
logger?: Logger
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Hook for logout mutation
|
|
231
|
+
*
|
|
232
|
+
* CHANGE: Now accepts optional dependencies for testing via dependency injection
|
|
233
|
+
*/
|
|
234
|
+
export function useLogoutMutation(deps: UseLogoutMutationDeps = {}) {
|
|
235
|
+
const queryClient = useQueryClient()
|
|
236
|
+
const { logoutUser = logoutUserUtil, logger = defaultLogger } = deps
|
|
237
|
+
|
|
238
|
+
return useMutation({
|
|
239
|
+
mutationFn: logoutUser,
|
|
240
|
+
onSuccess: () => {
|
|
241
|
+
// Reset the SDK client after logout
|
|
242
|
+
resetAnvilClient()
|
|
243
|
+
// Clear all auth-related cache
|
|
244
|
+
queryClient.removeQueries({ queryKey: authQueryKeys.all })
|
|
245
|
+
// Clear logger context
|
|
246
|
+
delete loggerContext.userId
|
|
247
|
+
delete loggerContext.userEmail
|
|
248
|
+
},
|
|
249
|
+
onError: (error) => {
|
|
250
|
+
logger.error(error, 'Logout failed')
|
|
251
|
+
},
|
|
252
|
+
})
|
|
253
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { useAuthQuery, useLogoutMutation } from './use-auth-query'
|
|
5
|
+
import { useLoginStore } from '../state/login-store'
|
|
6
|
+
import { identifyUser, trackEvent } from '../utils/analytics'
|
|
7
|
+
import { getUserCredentials } from '../utils/auth'
|
|
8
|
+
import { resetAnvilClient } from '../utils/anvil-client'
|
|
9
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
10
|
+
import { loggerContext } from '../utils/logger'
|
|
11
|
+
|
|
12
|
+
import type { MultilineInputHandle } from '../components/multiline-input'
|
|
13
|
+
import type { User } from '../utils/auth'
|
|
14
|
+
|
|
15
|
+
const setAuthLoggerContext = (params: { userId: string; email: string }) => {
|
|
16
|
+
loggerContext.userId = params.userId
|
|
17
|
+
loggerContext.userEmail = params.email
|
|
18
|
+
identifyUser(params.userId, { email: params.email, freebuff: IS_FREEBUFF })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const clearAuthLoggerContext = () => {
|
|
22
|
+
delete loggerContext.userId
|
|
23
|
+
delete loggerContext.userEmail
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface UseAuthStateOptions {
|
|
27
|
+
requireAuth: boolean | null
|
|
28
|
+
inputRef: React.MutableRefObject<MultilineInputHandle | null>
|
|
29
|
+
setInputFocused: (focused: boolean) => void
|
|
30
|
+
resetChatStore: () => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const useAuthState = ({
|
|
34
|
+
requireAuth,
|
|
35
|
+
inputRef,
|
|
36
|
+
setInputFocused,
|
|
37
|
+
resetChatStore,
|
|
38
|
+
}: UseAuthStateOptions) => {
|
|
39
|
+
const authQuery = useAuthQuery()
|
|
40
|
+
const logoutMutation = useLogoutMutation()
|
|
41
|
+
const { resetLoginState } = useLoginStore()
|
|
42
|
+
|
|
43
|
+
const initialAuthState = requireAuth === null ? null : !requireAuth
|
|
44
|
+
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(
|
|
45
|
+
initialAuthState,
|
|
46
|
+
)
|
|
47
|
+
const [user, setUser] = useState<User | null>(null)
|
|
48
|
+
|
|
49
|
+
// Update authentication state when requireAuth changes
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (requireAuth === null) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
setIsAuthenticated(!requireAuth)
|
|
55
|
+
}, [requireAuth])
|
|
56
|
+
|
|
57
|
+
// Update authentication state based on query results
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (authQuery.isSuccess && authQuery.data) {
|
|
60
|
+
setIsAuthenticated(true)
|
|
61
|
+
if (!user) {
|
|
62
|
+
const userCredentials = getUserCredentials()
|
|
63
|
+
const userData: User = {
|
|
64
|
+
id: authQuery.data.id,
|
|
65
|
+
name: userCredentials?.name || '',
|
|
66
|
+
email: authQuery.data.email || '',
|
|
67
|
+
authToken: userCredentials?.authToken || '',
|
|
68
|
+
}
|
|
69
|
+
setUser(userData)
|
|
70
|
+
// Identify first so the login event is attributed to the real user id
|
|
71
|
+
// and the pre-login anonymous history is aliased in.
|
|
72
|
+
setAuthLoggerContext({
|
|
73
|
+
userId: authQuery.data.id,
|
|
74
|
+
email: authQuery.data.email || '',
|
|
75
|
+
})
|
|
76
|
+
// Returning users restore an existing session instead of going through
|
|
77
|
+
// the login modal, so `cli.login` would otherwise never fire for them —
|
|
78
|
+
// making them look like a login-step drop-off in the funnel even though
|
|
79
|
+
// they're authenticated. Emit it here too, tagged by `via`.
|
|
80
|
+
trackEvent(AnalyticsEvent.LOGIN, {
|
|
81
|
+
userId: authQuery.data.id,
|
|
82
|
+
via: 'session_restore',
|
|
83
|
+
hasEmail: Boolean(authQuery.data.email),
|
|
84
|
+
hasName: Boolean(userCredentials?.name),
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
} else if (authQuery.isError) {
|
|
88
|
+
setIsAuthenticated(false)
|
|
89
|
+
setUser(null)
|
|
90
|
+
clearAuthLoggerContext()
|
|
91
|
+
}
|
|
92
|
+
}, [authQuery.isSuccess, authQuery.isError, authQuery.data, user])
|
|
93
|
+
|
|
94
|
+
// Handle successful login
|
|
95
|
+
const handleLoginSuccess = useCallback(
|
|
96
|
+
(loggedInUser: User) => {
|
|
97
|
+
// Identify first (aliases the pre-login anonymous history to the real
|
|
98
|
+
// user id) so the login event below is attributed to the user.
|
|
99
|
+
if (loggedInUser.id && loggedInUser.email) {
|
|
100
|
+
setAuthLoggerContext({
|
|
101
|
+
userId: loggedInUser.id,
|
|
102
|
+
email: loggedInUser.email,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Track successful login
|
|
107
|
+
trackEvent(AnalyticsEvent.LOGIN, {
|
|
108
|
+
userId: loggedInUser.id,
|
|
109
|
+
via: 'modal',
|
|
110
|
+
hasEmail: Boolean(loggedInUser.email),
|
|
111
|
+
hasName: Boolean(loggedInUser.name),
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Reset the SDK client to pick up new credentials
|
|
115
|
+
resetAnvilClient()
|
|
116
|
+
resetChatStore()
|
|
117
|
+
resetLoginState()
|
|
118
|
+
setInputFocused(true)
|
|
119
|
+
setUser(loggedInUser)
|
|
120
|
+
setIsAuthenticated(true)
|
|
121
|
+
},
|
|
122
|
+
[resetChatStore, resetLoginState, setInputFocused],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
// Auto-focus input after authentication
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (isAuthenticated !== true) return
|
|
128
|
+
|
|
129
|
+
setInputFocused(true)
|
|
130
|
+
|
|
131
|
+
const focusNow = () => {
|
|
132
|
+
const handle = inputRef.current
|
|
133
|
+
if (handle && typeof handle.focus === 'function') {
|
|
134
|
+
handle.focus()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
focusNow()
|
|
139
|
+
const timeoutId = setTimeout(focusNow, 0)
|
|
140
|
+
|
|
141
|
+
return () => clearTimeout(timeoutId)
|
|
142
|
+
}, [isAuthenticated, setInputFocused, inputRef])
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
isAuthenticated,
|
|
146
|
+
setIsAuthenticated,
|
|
147
|
+
user,
|
|
148
|
+
setUser,
|
|
149
|
+
handleLoginSuccess,
|
|
150
|
+
logoutMutation,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react'
|
|
2
|
+
import stringWidth from 'string-width'
|
|
3
|
+
|
|
4
|
+
import { useChatStore } from '../state/chat-store'
|
|
5
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
6
|
+
|
|
7
|
+
import type { InputValue } from '../types/store'
|
|
8
|
+
import type { AgentMode } from '../utils/constants'
|
|
9
|
+
|
|
10
|
+
interface UseChatInputOptions {
|
|
11
|
+
setInputValue: (value: InputValue) => void
|
|
12
|
+
agentMode: AgentMode
|
|
13
|
+
setAgentMode: (mode: AgentMode) => void
|
|
14
|
+
separatorWidth: number
|
|
15
|
+
consumeInitialPrompt: () => string | null
|
|
16
|
+
onSubmitPrompt: (content: string, mode: AgentMode) => void | Promise<unknown>
|
|
17
|
+
isCompactHeight: boolean
|
|
18
|
+
isNarrowWidth: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const BUILD_IT_TEXT = 'Build it!'
|
|
22
|
+
|
|
23
|
+
export const useChatInput = ({
|
|
24
|
+
setInputValue,
|
|
25
|
+
agentMode,
|
|
26
|
+
setAgentMode,
|
|
27
|
+
separatorWidth,
|
|
28
|
+
consumeInitialPrompt,
|
|
29
|
+
onSubmitPrompt,
|
|
30
|
+
isCompactHeight,
|
|
31
|
+
isNarrowWidth,
|
|
32
|
+
}: UseChatInputOptions) => {
|
|
33
|
+
const inputMode = useChatStore((state) => state.inputMode)
|
|
34
|
+
|
|
35
|
+
// Estimate the collapsed toggle width as rendered by AgentModeToggle.
|
|
36
|
+
// In Freebuff, the toggle is always hidden, so never reserve width for it.
|
|
37
|
+
// In non-Freebuff: hide in bash mode, compact height, or narrow width.
|
|
38
|
+
const estimatedToggleWidth =
|
|
39
|
+
IS_FREEBUFF || inputMode !== 'default' || isCompactHeight || isNarrowWidth
|
|
40
|
+
? 0
|
|
41
|
+
: stringWidth(`< ${agentMode}`) + 6 // 2 padding + 2 borders + 2 gap
|
|
42
|
+
|
|
43
|
+
// The content box that wraps the input row has paddingLeft/paddingRight = 1
|
|
44
|
+
// (see cli/src/chat.tsx). Subtract those columns so our MultilineInput width
|
|
45
|
+
// matches the true drawable area between the borders.
|
|
46
|
+
const contentPadding = 2 // 1 left + 1 right padding
|
|
47
|
+
const availableContentWidth = Math.max(1, separatorWidth - contentPadding)
|
|
48
|
+
const inputWidth = Math.max(1, availableContentWidth - estimatedToggleWidth)
|
|
49
|
+
|
|
50
|
+
const handleBuildFast = useCallback(() => {
|
|
51
|
+
setAgentMode('DEFAULT')
|
|
52
|
+
setInputValue({
|
|
53
|
+
text: BUILD_IT_TEXT,
|
|
54
|
+
cursorPosition: BUILD_IT_TEXT.length,
|
|
55
|
+
lastEditDueToNav: true,
|
|
56
|
+
})
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
onSubmitPrompt(BUILD_IT_TEXT, 'DEFAULT')
|
|
59
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
60
|
+
}, 0)
|
|
61
|
+
}, [setAgentMode, setInputValue, onSubmitPrompt])
|
|
62
|
+
|
|
63
|
+
const handleBuildMax = useCallback(() => {
|
|
64
|
+
setAgentMode('MAX')
|
|
65
|
+
setInputValue({
|
|
66
|
+
text: BUILD_IT_TEXT,
|
|
67
|
+
cursorPosition: BUILD_IT_TEXT.length,
|
|
68
|
+
lastEditDueToNav: true,
|
|
69
|
+
})
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
onSubmitPrompt('Build it!', 'MAX')
|
|
72
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
73
|
+
}, 0)
|
|
74
|
+
}, [setAgentMode, setInputValue, onSubmitPrompt])
|
|
75
|
+
|
|
76
|
+
const handleBuildLite = useCallback(() => {
|
|
77
|
+
setAgentMode('LITE')
|
|
78
|
+
setInputValue({
|
|
79
|
+
text: BUILD_IT_TEXT,
|
|
80
|
+
cursorPosition: BUILD_IT_TEXT.length,
|
|
81
|
+
lastEditDueToNav: true,
|
|
82
|
+
})
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
onSubmitPrompt(BUILD_IT_TEXT, 'LITE')
|
|
85
|
+
setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
86
|
+
}, 0)
|
|
87
|
+
}, [setAgentMode, setInputValue, onSubmitPrompt])
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const initialPrompt = consumeInitialPrompt()
|
|
91
|
+
if (initialPrompt) {
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
onSubmitPrompt(initialPrompt, agentMode)
|
|
94
|
+
}, 100)
|
|
95
|
+
}
|
|
96
|
+
return undefined
|
|
97
|
+
}, [consumeInitialPrompt, agentMode, onSubmitPrompt])
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
inputWidth,
|
|
101
|
+
handleBuildFast,
|
|
102
|
+
handleBuildMax,
|
|
103
|
+
handleBuildLite,
|
|
104
|
+
}
|
|
105
|
+
}
|