@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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message-level helpers for send-message logic.
|
|
3
|
+
* These functions operate on ChatMessage objects, not raw blocks.
|
|
4
|
+
* For block-level operations, import from message-block-helpers.ts or block-operations.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { has } from 'lodash'
|
|
8
|
+
|
|
9
|
+
import { AI_MESSAGE_ID_PREFIX, generateAiMessageId } from './ai-message-id'
|
|
10
|
+
import { markRunningAgentsAsCancelled } from './block-operations'
|
|
11
|
+
import { shouldHideAgent } from './constants'
|
|
12
|
+
import { formatTimestamp } from './helpers'
|
|
13
|
+
import {
|
|
14
|
+
appendInterruptionNotice,
|
|
15
|
+
autoCollapseBlocks,
|
|
16
|
+
createAgentBlock,
|
|
17
|
+
stripHiddenAgentBlocks,
|
|
18
|
+
} from './message-block-helpers'
|
|
19
|
+
|
|
20
|
+
import type { AgentMode } from './constants'
|
|
21
|
+
import type { ChatMessage, ContentBlock } from '../types/chat'
|
|
22
|
+
|
|
23
|
+
// -----------------------------------------------------------------------------
|
|
24
|
+
// Message Creation Helpers
|
|
25
|
+
// -----------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export const createModeDividerMessage = (
|
|
28
|
+
agentMode: AgentMode,
|
|
29
|
+
): ChatMessage => ({
|
|
30
|
+
id: `divider-${Date.now()}`,
|
|
31
|
+
variant: 'ai',
|
|
32
|
+
content: '',
|
|
33
|
+
blocks: [
|
|
34
|
+
{
|
|
35
|
+
type: 'mode-divider',
|
|
36
|
+
mode: agentMode,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
timestamp: formatTimestamp(),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const createAiMessageShell = (messageId: string): ChatMessage => ({
|
|
43
|
+
id: messageId,
|
|
44
|
+
variant: 'ai',
|
|
45
|
+
content: '',
|
|
46
|
+
blocks: [],
|
|
47
|
+
timestamp: formatTimestamp(),
|
|
48
|
+
metadata: { allowInlineAds: true },
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const createErrorMessage = (content: string): ChatMessage => ({
|
|
52
|
+
id: `error-${Date.now()}`,
|
|
53
|
+
variant: 'error',
|
|
54
|
+
content,
|
|
55
|
+
timestamp: formatTimestamp(),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Re-exported (imported above) from a dependency-free leaf module so lightweight
|
|
59
|
+
// consumers can import the id prefix without dragging in this helper graph.
|
|
60
|
+
// Shared with sanitizeRestoredMessages so the two can't silently drift apart.
|
|
61
|
+
export { AI_MESSAGE_ID_PREFIX, generateAiMessageId }
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A restored chat may contain an AI response that was still streaming when the
|
|
65
|
+
* process died (checkpoint saves persist in-flight turns). Mark it complete
|
|
66
|
+
* with an interruption notice so it doesn't render as still in progress.
|
|
67
|
+
* Only touches streamed response shells (ids from generateAiMessageId) —
|
|
68
|
+
* other 'ai'-variant messages (mode dividers, system notices, bash results)
|
|
69
|
+
* are never marked complete by design.
|
|
70
|
+
*
|
|
71
|
+
* Also drops hidden agent blocks (e.g. context-pruner) that were persisted
|
|
72
|
+
* before live streams filtered them.
|
|
73
|
+
*/
|
|
74
|
+
export const sanitizeRestoredMessages = (
|
|
75
|
+
messages: ChatMessage[],
|
|
76
|
+
): ChatMessage[] =>
|
|
77
|
+
messages.map((message) => {
|
|
78
|
+
let restoredMessage = message
|
|
79
|
+
if (message.metadata?.allowInlineAds) {
|
|
80
|
+
const { allowInlineAds: _, ...metadata } = message.metadata
|
|
81
|
+
restoredMessage = { ...message, metadata }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (restoredMessage.blocks) {
|
|
85
|
+
const blocks = stripHiddenAgentBlocks(restoredMessage.blocks)
|
|
86
|
+
if (blocks !== restoredMessage.blocks) {
|
|
87
|
+
restoredMessage = { ...restoredMessage, blocks }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
restoredMessage.variant !== 'ai' ||
|
|
93
|
+
!restoredMessage.id.startsWith(AI_MESSAGE_ID_PREFIX) ||
|
|
94
|
+
restoredMessage.isComplete
|
|
95
|
+
) {
|
|
96
|
+
return restoredMessage
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return {
|
|
100
|
+
...restoredMessage,
|
|
101
|
+
isComplete: true,
|
|
102
|
+
blocks: appendInterruptionNotice(
|
|
103
|
+
markRunningAgentsAsCancelled(restoredMessage.blocks ?? []),
|
|
104
|
+
),
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Corrupted persisted blocks (e.g. null entries) must not prevent the
|
|
108
|
+
// chat from restoring; keep the message as-is.
|
|
109
|
+
return { ...restoredMessage, isComplete: true }
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// -----------------------------------------------------------------------------
|
|
114
|
+
// Auto-Collapse Logic
|
|
115
|
+
// -----------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
export const autoCollapsePreviousMessages = (
|
|
118
|
+
messages: ChatMessage[],
|
|
119
|
+
currentAiMessageId: string,
|
|
120
|
+
): ChatMessage[] =>
|
|
121
|
+
messages.map((message) => {
|
|
122
|
+
if (message.id === currentAiMessageId) {
|
|
123
|
+
return message
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (message.variant === 'agent') {
|
|
127
|
+
const userOpened = message.metadata?.userOpened ?? false
|
|
128
|
+
return userOpened
|
|
129
|
+
? message
|
|
130
|
+
: {
|
|
131
|
+
...message,
|
|
132
|
+
metadata: {
|
|
133
|
+
...message.metadata,
|
|
134
|
+
isCollapsed: true,
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!message.blocks) {
|
|
140
|
+
return message
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...message,
|
|
145
|
+
blocks: autoCollapseBlocks(message.blocks),
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// -----------------------------------------------------------------------------
|
|
150
|
+
// Spawn Agents Helpers
|
|
151
|
+
// -----------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
export const createSpawnAgentBlocks = (
|
|
154
|
+
toolCallId: string,
|
|
155
|
+
agents: Array<{ agent_type?: string; prompt?: string }>,
|
|
156
|
+
): ContentBlock[] =>
|
|
157
|
+
agents
|
|
158
|
+
.map((agent, index) => ({ agent, index }))
|
|
159
|
+
.filter(({ agent }) => !shouldHideAgent(agent.agent_type || ''))
|
|
160
|
+
.map(({ agent, index }) =>
|
|
161
|
+
createAgentBlock({
|
|
162
|
+
agentId: `${toolCallId}-${index}`,
|
|
163
|
+
agentType: agent.agent_type || '',
|
|
164
|
+
prompt: agent.prompt,
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
export const isSpawnAgentsResult = (outputValue: unknown): boolean =>
|
|
169
|
+
Array.isArray(outputValue) &&
|
|
170
|
+
outputValue.some((v: unknown) => {
|
|
171
|
+
if (typeof v !== 'object' || v === null) return false
|
|
172
|
+
return has(v, 'agentName') || has(v, 'agentType')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// -----------------------------------------------------------------------------
|
|
176
|
+
// Message Completion Helpers
|
|
177
|
+
// -----------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
export const markMessageComplete = (
|
|
180
|
+
message: ChatMessage,
|
|
181
|
+
options?: {
|
|
182
|
+
completionTime?: string
|
|
183
|
+
credits?: number
|
|
184
|
+
runState?: unknown
|
|
185
|
+
},
|
|
186
|
+
): ChatMessage => {
|
|
187
|
+
const metadata = {
|
|
188
|
+
...(message.metadata ?? {}),
|
|
189
|
+
...(options?.runState ? { runState: options.runState } : {}),
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
...message,
|
|
193
|
+
isComplete: true,
|
|
194
|
+
...(options?.completionTime
|
|
195
|
+
? { completionTime: options.completionTime }
|
|
196
|
+
: {}),
|
|
197
|
+
...(options?.credits !== undefined ? { credits: options.credits } : {}),
|
|
198
|
+
metadata,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const setMessageError = (
|
|
203
|
+
message: ChatMessage,
|
|
204
|
+
errorContent: string,
|
|
205
|
+
): ChatMessage => ({
|
|
206
|
+
...message,
|
|
207
|
+
content: errorContent,
|
|
208
|
+
blocks: undefined,
|
|
209
|
+
isComplete: true,
|
|
210
|
+
})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Lightweight timer wrapper so UI can react to start/stop events.
|
|
2
|
+
import type { ElapsedTimeTracker } from '../hooks/use-elapsed-time'
|
|
3
|
+
|
|
4
|
+
export type SendMessageTimerEvent =
|
|
5
|
+
| {
|
|
6
|
+
type: 'start'
|
|
7
|
+
startedAt: number
|
|
8
|
+
messageId: string
|
|
9
|
+
agentId?: string
|
|
10
|
+
}
|
|
11
|
+
| {
|
|
12
|
+
type: 'stop'
|
|
13
|
+
startedAt: number
|
|
14
|
+
finishedAt: number
|
|
15
|
+
elapsedMs: number
|
|
16
|
+
messageId: string
|
|
17
|
+
agentId?: string
|
|
18
|
+
outcome: 'success' | 'error' | 'aborted'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SendMessageTimerOutcome = 'success' | 'error' | 'aborted'
|
|
22
|
+
|
|
23
|
+
export interface SendMessageTimerController {
|
|
24
|
+
start: (messageId: string) => void
|
|
25
|
+
stop: (outcome: SendMessageTimerOutcome) => {
|
|
26
|
+
finishedAt: number
|
|
27
|
+
elapsedMs: number
|
|
28
|
+
} | null
|
|
29
|
+
pause: () => void
|
|
30
|
+
resume: () => void
|
|
31
|
+
isActive: () => boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SendMessageTimerControllerOptions {
|
|
35
|
+
mainAgentTimer: ElapsedTimeTracker
|
|
36
|
+
onTimerEvent: (event: SendMessageTimerEvent) => void
|
|
37
|
+
agentId?: string
|
|
38
|
+
now?: () => number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const createSendMessageTimerController = (
|
|
42
|
+
options: SendMessageTimerControllerOptions,
|
|
43
|
+
): SendMessageTimerController => {
|
|
44
|
+
const {
|
|
45
|
+
mainAgentTimer,
|
|
46
|
+
onTimerEvent,
|
|
47
|
+
agentId,
|
|
48
|
+
now = () => Date.now(),
|
|
49
|
+
} = options
|
|
50
|
+
|
|
51
|
+
let timerStartedAt: number | null = null
|
|
52
|
+
let timerMessageId: string | null = null
|
|
53
|
+
let timerActive = false
|
|
54
|
+
|
|
55
|
+
const start = (messageId: string) => {
|
|
56
|
+
if (timerActive) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
timerActive = true
|
|
60
|
+
timerMessageId = messageId
|
|
61
|
+
timerStartedAt = now()
|
|
62
|
+
mainAgentTimer.start()
|
|
63
|
+
const startEvent: SendMessageTimerEvent = {
|
|
64
|
+
type: 'start',
|
|
65
|
+
startedAt: timerStartedAt,
|
|
66
|
+
messageId,
|
|
67
|
+
}
|
|
68
|
+
if (agentId) {
|
|
69
|
+
startEvent.agentId = agentId
|
|
70
|
+
}
|
|
71
|
+
onTimerEvent(startEvent)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const stop = (outcome: SendMessageTimerOutcome) => {
|
|
75
|
+
if (!timerActive || timerStartedAt == null || !timerMessageId) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
timerActive = false
|
|
79
|
+
mainAgentTimer.stop()
|
|
80
|
+
const finishedAt = now()
|
|
81
|
+
const elapsedMs = Math.max(0, finishedAt - timerStartedAt)
|
|
82
|
+
const stopEvent: SendMessageTimerEvent = {
|
|
83
|
+
type: 'stop',
|
|
84
|
+
startedAt: timerStartedAt,
|
|
85
|
+
finishedAt,
|
|
86
|
+
elapsedMs,
|
|
87
|
+
messageId: timerMessageId,
|
|
88
|
+
outcome,
|
|
89
|
+
}
|
|
90
|
+
if (agentId) {
|
|
91
|
+
stopEvent.agentId = agentId
|
|
92
|
+
}
|
|
93
|
+
onTimerEvent(stopEvent)
|
|
94
|
+
timerStartedAt = null
|
|
95
|
+
timerMessageId = null
|
|
96
|
+
return { finishedAt, elapsedMs }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const pause = () => {
|
|
100
|
+
mainAgentTimer.pause()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const resume = () => {
|
|
104
|
+
mainAgentTimer.resume()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const isActive = () => timerActive
|
|
108
|
+
|
|
109
|
+
return { start, stop, pause, resume, isActive }
|
|
110
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FREEBUFF_MODELS,
|
|
6
|
+
isFreebuffModelId,
|
|
7
|
+
migrateSupersededFreebuffModelPreference,
|
|
8
|
+
} from '@anvil/common/constants/freebuff-models'
|
|
9
|
+
|
|
10
|
+
import { getConfigDir } from './auth'
|
|
11
|
+
import { AGENT_MODES } from './constants'
|
|
12
|
+
import { logger } from './logger'
|
|
13
|
+
|
|
14
|
+
import type { AgentMode } from './constants'
|
|
15
|
+
|
|
16
|
+
const DEFAULT_SETTINGS: Settings = {
|
|
17
|
+
mode: 'DEFAULT' as const,
|
|
18
|
+
adsEnabled: true,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Note: The old FREE mode has been renamed back to LITE; migrate on load.
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Settings schema - add new settings here as the product evolves
|
|
25
|
+
*/
|
|
26
|
+
export interface Settings {
|
|
27
|
+
mode?: AgentMode
|
|
28
|
+
adsEnabled?: boolean
|
|
29
|
+
/** Last model the user picked in the freebuff model selector. Restored on
|
|
30
|
+
* next freebuff launch so users land in the queue for their preferred
|
|
31
|
+
* model without re-picking. Persisted as the canonical model id. */
|
|
32
|
+
freebuffModel?: string
|
|
33
|
+
/** @deprecated Use server-side fallbackToALaCarte setting instead */
|
|
34
|
+
alwaysUseALaCarte?: boolean
|
|
35
|
+
/** @deprecated Use server-side fallbackToALaCarte setting instead */
|
|
36
|
+
fallbackToALaCarte?: boolean
|
|
37
|
+
/** Set once the user has submitted their first prompt. Used to gate the
|
|
38
|
+
* first-time onboarding suggested prompts so they only show to brand-new
|
|
39
|
+
* users and quietly retire afterwards. */
|
|
40
|
+
hasSubmittedFirstPrompt?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the settings file path
|
|
45
|
+
*/
|
|
46
|
+
export const getSettingsPath = (): string => {
|
|
47
|
+
return path.join(getConfigDir(), 'settings.json')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ensure the config directory exists, creating it if necessary
|
|
52
|
+
*/
|
|
53
|
+
const ensureConfigDirExists = (): void => {
|
|
54
|
+
const configDir = getConfigDir()
|
|
55
|
+
if (!fs.existsSync(configDir)) {
|
|
56
|
+
fs.mkdirSync(configDir, { recursive: true })
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load all settings from file system
|
|
62
|
+
* @returns The saved settings object, with defaults for missing values
|
|
63
|
+
*/
|
|
64
|
+
export const loadSettings = (): Settings => {
|
|
65
|
+
const settingsPath = getSettingsPath()
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(settingsPath)) {
|
|
68
|
+
ensureConfigDirExists()
|
|
69
|
+
// Create default settings file
|
|
70
|
+
fs.writeFileSync(settingsPath, JSON.stringify(DEFAULT_SETTINGS, null, 2))
|
|
71
|
+
return DEFAULT_SETTINGS
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const settingsFile = fs.readFileSync(settingsPath, 'utf8')
|
|
76
|
+
const parsed = JSON.parse(settingsFile)
|
|
77
|
+
return validateSettings(parsed)
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.debug(
|
|
80
|
+
{
|
|
81
|
+
error: error instanceof Error ? error.message : String(error),
|
|
82
|
+
},
|
|
83
|
+
'Error reading settings',
|
|
84
|
+
)
|
|
85
|
+
return {}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Validate and sanitize settings from file
|
|
91
|
+
*/
|
|
92
|
+
const validateSettings = (parsed: unknown): Settings => {
|
|
93
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
94
|
+
return {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const settings: Settings = {}
|
|
98
|
+
const obj = parsed as Record<string, unknown>
|
|
99
|
+
|
|
100
|
+
// Validate mode; migrate the previously-saved 'FREE' value to 'LITE'.
|
|
101
|
+
if (typeof obj.mode === 'string') {
|
|
102
|
+
const normalized = obj.mode === 'FREE' ? 'LITE' : obj.mode
|
|
103
|
+
if (AGENT_MODES.includes(normalized as AgentMode)) {
|
|
104
|
+
settings.mode = normalized as AgentMode
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate adsEnabled
|
|
109
|
+
if (typeof obj.adsEnabled === 'boolean') {
|
|
110
|
+
settings.adsEnabled = obj.adsEnabled
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Validate freebuffModel against the current picker catalog. Server support
|
|
114
|
+
// may intentionally outlive client visibility during a staged model
|
|
115
|
+
// retirement, but an updated client must not restore a retired selection.
|
|
116
|
+
if (
|
|
117
|
+
typeof obj.freebuffModel === 'string' &&
|
|
118
|
+
isFreebuffModelId(obj.freebuffModel)
|
|
119
|
+
) {
|
|
120
|
+
settings.freebuffModel = obj.freebuffModel
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Steer off a model that has since been superseded (MiniMax M3, MiMo 2.5 →
|
|
124
|
+
// V4 Flash) on EVERY load, so each new freebuff session starts
|
|
125
|
+
// on the better model instead of a pick made before it existed. Picking a
|
|
126
|
+
// superseded model still works for the session you are in; it just stops
|
|
127
|
+
// being what the next launch opens on.
|
|
128
|
+
const replacement = migrateSupersededFreebuffModelPreference(
|
|
129
|
+
settings.freebuffModel,
|
|
130
|
+
FREEBUFF_MODELS.map((model) => model.id),
|
|
131
|
+
)
|
|
132
|
+
if (replacement) settings.freebuffModel = replacement
|
|
133
|
+
|
|
134
|
+
// Validate alwaysUseALaCarte (legacy)
|
|
135
|
+
if (typeof obj.alwaysUseALaCarte === 'boolean') {
|
|
136
|
+
settings.alwaysUseALaCarte = obj.alwaysUseALaCarte
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Validate fallbackToALaCarte (legacy)
|
|
140
|
+
if (typeof obj.fallbackToALaCarte === 'boolean') {
|
|
141
|
+
settings.fallbackToALaCarte = obj.fallbackToALaCarte
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Validate hasSubmittedFirstPrompt
|
|
145
|
+
if (typeof obj.hasSubmittedFirstPrompt === 'boolean') {
|
|
146
|
+
settings.hasSubmittedFirstPrompt = obj.hasSubmittedFirstPrompt
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return settings
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Save settings to file system (merges with existing settings)
|
|
154
|
+
*/
|
|
155
|
+
export const saveSettings = (newSettings: Partial<Settings>): void => {
|
|
156
|
+
const settingsPath = getSettingsPath()
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
ensureConfigDirExists()
|
|
160
|
+
|
|
161
|
+
// Load existing settings and merge
|
|
162
|
+
const existingSettings = loadSettings()
|
|
163
|
+
const mergedSettings = { ...existingSettings, ...newSettings }
|
|
164
|
+
|
|
165
|
+
fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2))
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logger.debug(
|
|
168
|
+
{
|
|
169
|
+
error: error instanceof Error ? error.message : String(error),
|
|
170
|
+
},
|
|
171
|
+
'Error saving settings',
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Load the saved agent mode preference
|
|
178
|
+
* @returns The saved mode, or 'DEFAULT' if not found or invalid
|
|
179
|
+
*/
|
|
180
|
+
export const loadModePreference = (): AgentMode => {
|
|
181
|
+
const settings = loadSettings()
|
|
182
|
+
return settings.mode ?? 'DEFAULT'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Save the agent mode preference
|
|
187
|
+
*/
|
|
188
|
+
export const saveModePreference = (mode: AgentMode): void => {
|
|
189
|
+
saveSettings({ mode })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Load the saved freebuff model preference. Returns undefined if none is
|
|
194
|
+
* saved yet — callers should fall back to DEFAULT_FREEBUFF_MODEL_ID.
|
|
195
|
+
*/
|
|
196
|
+
export const loadFreebuffModelPreference = (): string | undefined => {
|
|
197
|
+
return loadSettings().freebuffModel
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Save an ordinary freebuff picker preference so the next launch defaults to
|
|
202
|
+
* it. Referral-only and retired session models are deliberately not
|
|
203
|
+
* rememberable: they may be valid for the current session without being
|
|
204
|
+
* selectable on the next landing screen.
|
|
205
|
+
*/
|
|
206
|
+
export const saveFreebuffModelPreference = (model: string): void => {
|
|
207
|
+
if (!isFreebuffModelId(model)) return
|
|
208
|
+
saveSettings({ freebuffModel: model })
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Whether the user has ever submitted a prompt. False only for brand-new
|
|
213
|
+
* users, who get the onboarding suggested prompts on an empty chat.
|
|
214
|
+
*/
|
|
215
|
+
export const hasSubmittedFirstPrompt = (): boolean => {
|
|
216
|
+
return loadSettings().hasSubmittedFirstPrompt === true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Mark that the user has submitted their first prompt, retiring the onboarding
|
|
221
|
+
* suggested prompts on future launches. Idempotent.
|
|
222
|
+
*/
|
|
223
|
+
export const markFirstPromptSubmitted = (): void => {
|
|
224
|
+
if (loadSettings().hasSubmittedFirstPrompt === true) return
|
|
225
|
+
saveSettings({ hasSubmittedFirstPrompt: true })
|
|
226
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { loadSkills as sdkLoadSkills } from '@anvil/sdk'
|
|
2
|
+
|
|
3
|
+
import { getProjectRoot } from '../project-files'
|
|
4
|
+
import { logger } from './logger'
|
|
5
|
+
|
|
6
|
+
import type { SkillDefinition, SkillsMap } from '@anvil/common/types/skill'
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Skills cache (loaded via SDK at startup)
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
let skillsCache: SkillsMap = {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the skill registry by loading skills via the SDK.
|
|
16
|
+
* This must be called at CLI startup.
|
|
17
|
+
*
|
|
18
|
+
* Skills are loaded from:
|
|
19
|
+
* - ~/.agents/skills/ (global)
|
|
20
|
+
* - {projectRoot}/.agents/skills/ (project, overrides global)
|
|
21
|
+
*/
|
|
22
|
+
export async function initializeSkillRegistry(): Promise<void> {
|
|
23
|
+
const cwd = getProjectRoot() || process.cwd()
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Load skills from both global (~/.agents/skills) and project directories
|
|
27
|
+
// The SDK handles merging, with project skills overriding global ones
|
|
28
|
+
skillsCache = await sdkLoadSkills({
|
|
29
|
+
cwd,
|
|
30
|
+
verbose: false,
|
|
31
|
+
})
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.warn({ error }, 'Failed to load skills')
|
|
34
|
+
skillsCache = {}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Skills access
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get all loaded skills.
|
|
44
|
+
*/
|
|
45
|
+
export function getLoadedSkills(): SkillsMap {
|
|
46
|
+
return skillsCache
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get a skill by name.
|
|
51
|
+
*/
|
|
52
|
+
export function getSkillByName(name: string): SkillDefinition | undefined {
|
|
53
|
+
return skillsCache[name]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the number of loaded skills.
|
|
58
|
+
*/
|
|
59
|
+
export function getSkillCount(): number {
|
|
60
|
+
return Object.keys(skillsCache).length
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// UI/Display utilities
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get a message describing loaded skills for display.
|
|
69
|
+
*/
|
|
70
|
+
export function getLoadedSkillsMessage(): string | null {
|
|
71
|
+
const skills = Object.values(skillsCache)
|
|
72
|
+
|
|
73
|
+
if (skills.length === 0) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const header = `Loaded ${skills.length} skill${skills.length === 1 ? '' : 's'}`
|
|
78
|
+
const skillList = skills
|
|
79
|
+
.map((skill) => ` - ${skill.name}: ${skill.description.slice(0, 60)}${skill.description.length > 60 ? '...' : ''}`)
|
|
80
|
+
.join('\n')
|
|
81
|
+
|
|
82
|
+
return `${header}\n${skillList}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Testing utilities
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clear cached skills. Intended for test scenarios.
|
|
91
|
+
*/
|
|
92
|
+
export function __resetSkillRegistryForTests(): void {
|
|
93
|
+
skillsCache = {}
|
|
94
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getAgentBaseName, moveSpawnAgentBlock } from './message-block-helpers'
|
|
2
|
+
|
|
3
|
+
import type { SpawnAgentInfo } from '../hooks/stream-state'
|
|
4
|
+
import type { ContentBlock } from '../types/chat'
|
|
5
|
+
|
|
6
|
+
export interface SpawnAgentMatch {
|
|
7
|
+
tempId: string
|
|
8
|
+
info: SpawnAgentInfo
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const findMatchingSpawnAgent = (
|
|
12
|
+
spawnAgentsMap: Map<string, SpawnAgentInfo>,
|
|
13
|
+
eventAgentType: string,
|
|
14
|
+
): SpawnAgentMatch | null => {
|
|
15
|
+
const eventBaseName = getAgentBaseName(eventAgentType || '')
|
|
16
|
+
|
|
17
|
+
for (const [tempId, info] of spawnAgentsMap.entries()) {
|
|
18
|
+
const storedBaseName = getAgentBaseName(info.agentType || '')
|
|
19
|
+
if (eventBaseName === storedBaseName) {
|
|
20
|
+
return { tempId, info }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const resolveSpawnAgentToReal = (options: {
|
|
28
|
+
blocks: ContentBlock[]
|
|
29
|
+
match: SpawnAgentMatch
|
|
30
|
+
realAgentId: string
|
|
31
|
+
realAgentType?: string
|
|
32
|
+
parentAgentId?: string
|
|
33
|
+
params?: Record<string, unknown>
|
|
34
|
+
prompt?: string
|
|
35
|
+
}): ContentBlock[] => {
|
|
36
|
+
const {
|
|
37
|
+
blocks,
|
|
38
|
+
match,
|
|
39
|
+
realAgentId,
|
|
40
|
+
realAgentType,
|
|
41
|
+
parentAgentId,
|
|
42
|
+
params: agentParams,
|
|
43
|
+
prompt,
|
|
44
|
+
} = options
|
|
45
|
+
|
|
46
|
+
return moveSpawnAgentBlock(
|
|
47
|
+
blocks,
|
|
48
|
+
match.tempId,
|
|
49
|
+
realAgentId,
|
|
50
|
+
parentAgentId,
|
|
51
|
+
agentParams,
|
|
52
|
+
prompt,
|
|
53
|
+
realAgentType,
|
|
54
|
+
)
|
|
55
|
+
}
|