@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,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/copy` command — serialize the entire conversation (user + assistant text,
|
|
3
|
+
* reasoning, tool calls with their inputs and outputs, sub-agents) into clean
|
|
4
|
+
* Markdown and write it to the system clipboard.
|
|
5
|
+
*
|
|
6
|
+
* Over SSH the clipboard path is OSC 52, which caps the base64 payload at 32 KB
|
|
7
|
+
* (see clipboard.ts). A real back-and-forth easily exceeds that, so when we're on
|
|
8
|
+
* a remote session and the transcript is too large, we progressively drop the
|
|
9
|
+
* largest tool results (then, if still needed, large tool inputs) — replacing each
|
|
10
|
+
* with a short omission note — until it fits. Local sessions first try a native
|
|
11
|
+
* clipboard tool with the full transcript, then use the same bounded rendering
|
|
12
|
+
* as an OSC 52 fallback when no native clipboard tool is available.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { copyTextToClipboard, showClipboardMessage } from '../utils/clipboard'
|
|
16
|
+
import { useChatStore } from '../state/chat-store'
|
|
17
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
18
|
+
|
|
19
|
+
import type { RouterParams } from './command-registry'
|
|
20
|
+
import type { ChatMessage, ContentBlock } from '../types/chat'
|
|
21
|
+
|
|
22
|
+
// OSC 52 caps its base64 payload at 32 KB (clipboard.ts OSC52_MAX_PAYLOAD).
|
|
23
|
+
// base64 is ~4/3 of the raw byte count, so the raw-text ceiling is ~24 KB. Leave
|
|
24
|
+
// headroom for tmux/screen passthrough wrapping and the message framing.
|
|
25
|
+
const OSC52_TEXT_BUDGET_BYTES = 22_000
|
|
26
|
+
|
|
27
|
+
const byteLen = (text: string): number => Buffer.byteLength(text, 'utf8')
|
|
28
|
+
|
|
29
|
+
/** Human-friendly tool label, e.g. `read_files` -> `Read Files`. */
|
|
30
|
+
function toolDisplayName(toolName: string): string {
|
|
31
|
+
if (toolName === 'list_directory') return 'List Directories'
|
|
32
|
+
return toolName.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const formatBytes = (bytes: number): string => {
|
|
36
|
+
if (bytes < 1024) return `${bytes} B`
|
|
37
|
+
return `${(bytes / 1024).toFixed(1)} KB`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Return the longest suffix of `s` that fits in `maxBytes`, cut on a UTF-8
|
|
42
|
+
* codepoint boundary (and a line boundary when one is close by) so the result
|
|
43
|
+
* is valid text. Used as the last-resort fallback when dropping tool bodies
|
|
44
|
+
* still can't get the transcript under the clipboard budget.
|
|
45
|
+
*/
|
|
46
|
+
function keepTailBytes(s: string, maxBytes: number): string {
|
|
47
|
+
if (maxBytes <= 0) return ''
|
|
48
|
+
const buf = Buffer.from(s, 'utf8')
|
|
49
|
+
if (buf.length <= maxBytes) return s
|
|
50
|
+
|
|
51
|
+
let start = buf.length - maxBytes
|
|
52
|
+
// Advance off any UTF-8 continuation byte (0b10xxxxxx) to a codepoint start.
|
|
53
|
+
while (start < buf.length && (buf[start] & 0xc0) === 0x80) start++
|
|
54
|
+
// Prefer cutting at the next line boundary if it's nearby, for cleaner output.
|
|
55
|
+
const nl = buf.indexOf(0x0a, start)
|
|
56
|
+
if (nl !== -1 && nl - start < 200) start = nl + 1
|
|
57
|
+
return buf.toString('utf8', start)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A segment of the rendered transcript whose body can be dropped to save space.
|
|
62
|
+
* `full` is the normal rendering; `note` is the compact omission replacement.
|
|
63
|
+
*/
|
|
64
|
+
interface Droppable {
|
|
65
|
+
kind: 'output' | 'input'
|
|
66
|
+
full: string
|
|
67
|
+
note: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type Segment = string | Droppable
|
|
71
|
+
|
|
72
|
+
const isDroppable = (segment: Segment): segment is Droppable =>
|
|
73
|
+
typeof segment !== 'string'
|
|
74
|
+
|
|
75
|
+
function fence(content: string, lang = ''): string {
|
|
76
|
+
// Avoid breaking out of the fence if the content itself contains ```.
|
|
77
|
+
const ticks = content.includes('```') ? '````' : '```'
|
|
78
|
+
return `${ticks}${lang}\n${content}\n${ticks}`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderToolInput(input: unknown): string {
|
|
82
|
+
if (input == null) return ''
|
|
83
|
+
try {
|
|
84
|
+
return JSON.stringify(input, null, 2)
|
|
85
|
+
} catch {
|
|
86
|
+
return String(input)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Tool results are stored as strings; many are a JSON envelope (e.g. the
|
|
92
|
+
* `[{"type":"json","value":{...}}]` shape from terminal/tool calls). Pretty-print
|
|
93
|
+
* when the string parses as JSON so the transcript is readable; otherwise keep
|
|
94
|
+
* the raw text. Returns the fence language to use alongside the body.
|
|
95
|
+
*/
|
|
96
|
+
function renderToolOutput(output: string): { body: string; lang: string } {
|
|
97
|
+
const trimmed = output.trim()
|
|
98
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
99
|
+
try {
|
|
100
|
+
return {
|
|
101
|
+
body: JSON.stringify(JSON.parse(trimmed), null, 2),
|
|
102
|
+
lang: 'json',
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// Not valid JSON — fall through to raw.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { body: output, lang: '' }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function roleHeading(message: ChatMessage): string {
|
|
112
|
+
switch (message.variant) {
|
|
113
|
+
case 'user':
|
|
114
|
+
return '## User'
|
|
115
|
+
case 'error':
|
|
116
|
+
return '## Error'
|
|
117
|
+
default:
|
|
118
|
+
return '## Assistant'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Render a single content block into the segment list. Recurses for sub-agents.
|
|
124
|
+
* Pushes plain strings for fixed text and `Droppable` objects for tool bodies.
|
|
125
|
+
*/
|
|
126
|
+
function renderBlock(block: ContentBlock, out: Segment[]): void {
|
|
127
|
+
switch (block.type) {
|
|
128
|
+
case 'text': {
|
|
129
|
+
const text = block.content?.trim()
|
|
130
|
+
if (!text) return
|
|
131
|
+
if (block.textType === 'reasoning') {
|
|
132
|
+
// Reasoning as a blockquote so it reads as model thinking, not output.
|
|
133
|
+
const quoted = text
|
|
134
|
+
.split('\n')
|
|
135
|
+
.map((line) => `> ${line}`)
|
|
136
|
+
.join('\n')
|
|
137
|
+
out.push(`> _Reasoning_\n${quoted}`)
|
|
138
|
+
} else {
|
|
139
|
+
out.push(text)
|
|
140
|
+
}
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case 'tool': {
|
|
145
|
+
const name = toolDisplayName(block.toolName)
|
|
146
|
+
const inputText = renderToolInput(block.input)
|
|
147
|
+
const hasInput = inputText.trim().length > 0
|
|
148
|
+
const output = block.output ?? ''
|
|
149
|
+
const hasOutput = output.trim().length > 0
|
|
150
|
+
|
|
151
|
+
// A tool call with no input and no output (e.g. still running) gets a
|
|
152
|
+
// compact one-liner rather than a header with nothing beneath it.
|
|
153
|
+
if (!hasInput && !hasOutput) {
|
|
154
|
+
out.push(`**🛠 ${name}** _(no input or output)_`)
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
out.push(`**🛠 ${name}**`)
|
|
159
|
+
if (hasInput) {
|
|
160
|
+
out.push({
|
|
161
|
+
kind: 'input',
|
|
162
|
+
full: fence(inputText, 'json'),
|
|
163
|
+
note: `_Input omitted (${formatBytes(byteLen(inputText))}) to fit clipboard._`,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
if (hasOutput) {
|
|
167
|
+
const rendered = renderToolOutput(output)
|
|
168
|
+
out.push({
|
|
169
|
+
kind: 'output',
|
|
170
|
+
full: fence(rendered.body, rendered.lang),
|
|
171
|
+
note: `_Result omitted (${formatBytes(byteLen(rendered.body))}) to fit clipboard._`,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case 'agent': {
|
|
178
|
+
const label = block.agentName || block.agentType
|
|
179
|
+
out.push(
|
|
180
|
+
`### ⤷ Subagent: ${label}${block.agentName ? ` (${block.agentType})` : ''}`,
|
|
181
|
+
)
|
|
182
|
+
if (block.initialPrompt?.trim()) {
|
|
183
|
+
out.push(`_Prompt:_ ${block.initialPrompt.trim()}`)
|
|
184
|
+
}
|
|
185
|
+
if (block.content?.trim()) {
|
|
186
|
+
out.push(block.content.trim())
|
|
187
|
+
}
|
|
188
|
+
for (const child of block.blocks ?? []) {
|
|
189
|
+
renderBlock(child, out)
|
|
190
|
+
}
|
|
191
|
+
out.push('### ⤶ End subagent')
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'plan': {
|
|
196
|
+
if (block.content?.trim()) {
|
|
197
|
+
out.push(`**Plan**\n\n${block.content.trim()}`)
|
|
198
|
+
}
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case 'ask-user': {
|
|
203
|
+
for (const [i, q] of block.questions.entries()) {
|
|
204
|
+
const answer = block.answers?.find((a) => a.questionIndex === i)
|
|
205
|
+
const selected =
|
|
206
|
+
answer?.selectedOptions?.join(', ') ??
|
|
207
|
+
answer?.selectedOption ??
|
|
208
|
+
answer?.otherText ??
|
|
209
|
+
(block.skipped ? '(skipped)' : '(no answer)')
|
|
210
|
+
out.push(`**Question:** ${q.question}\n_Answer:_ ${selected}`)
|
|
211
|
+
}
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case 'image': {
|
|
216
|
+
out.push(`_[image: ${block.filename ?? block.mediaType}]_`)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case 'agent-list': {
|
|
221
|
+
const names = block.agents.map((a) => a.displayName).join(', ')
|
|
222
|
+
if (names) out.push(`_[available agents: ${names}]_`)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// mode-divider and html carry no transcript-worthy text.
|
|
227
|
+
default:
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function renderMessage(message: ChatMessage, out: Segment[]): void {
|
|
233
|
+
out.push(roleHeading(message))
|
|
234
|
+
|
|
235
|
+
if (message.blocks?.length) {
|
|
236
|
+
for (const block of message.blocks) {
|
|
237
|
+
renderBlock(block, out)
|
|
238
|
+
}
|
|
239
|
+
} else if (message.content?.trim()) {
|
|
240
|
+
out.push(message.content.trim())
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Attachments live on the message, not in blocks — note them for context.
|
|
244
|
+
const fileNames = message.fileAttachments?.map((f) => f.filename) ?? []
|
|
245
|
+
const imageNames = message.attachments?.map((a) => a.filename) ?? []
|
|
246
|
+
const textCount = message.textAttachments?.length ?? 0
|
|
247
|
+
if (fileNames.length) out.push(`> Attached files: ${fileNames.join(', ')}`)
|
|
248
|
+
if (imageNames.length) out.push(`> Attached images: ${imageNames.join(', ')}`)
|
|
249
|
+
if (textCount) out.push(`> Attached ${textCount} pasted text snippet(s)`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface SerializedConversation {
|
|
253
|
+
text: string
|
|
254
|
+
/** Number of droppable tool bodies omitted to fit the clipboard budget. */
|
|
255
|
+
omittedCount: number
|
|
256
|
+
/** True if the transcript was hard-truncated as a last resort. */
|
|
257
|
+
truncated: boolean
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const TRUNCATION_MARKER =
|
|
261
|
+
'_[…earlier conversation truncated to fit clipboard…]_'
|
|
262
|
+
|
|
263
|
+
function copySuccessMessage(
|
|
264
|
+
messageCount: number,
|
|
265
|
+
{ omittedCount, truncated }: SerializedConversation,
|
|
266
|
+
): string {
|
|
267
|
+
const count = `${messageCount} message${messageCount === 1 ? '' : 's'}`
|
|
268
|
+
const trimNotes: string[] = []
|
|
269
|
+
if (omittedCount > 0) {
|
|
270
|
+
trimNotes.push(
|
|
271
|
+
`${omittedCount} large tool call${omittedCount === 1 ? '' : 's'} trimmed`,
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
if (truncated) trimNotes.push('older messages truncated')
|
|
275
|
+
return trimNotes.length > 0
|
|
276
|
+
? `Copied conversation · ${count} (${trimNotes.join(', ')} to fit clipboard)`
|
|
277
|
+
: `Copied conversation · ${count}`
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function conversationClipboardContent(
|
|
281
|
+
messages: ChatMessage[],
|
|
282
|
+
maxBytes?: number,
|
|
283
|
+
): { text: string; successMessage: string } {
|
|
284
|
+
const serialized = serializeConversation(messages, { maxBytes })
|
|
285
|
+
return {
|
|
286
|
+
text: serialized.text,
|
|
287
|
+
successMessage: copySuccessMessage(messages.length, serialized),
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Serialize the conversation to Markdown. When `maxBytes` is provided and the
|
|
293
|
+
* full transcript exceeds it, the largest tool results (then large tool inputs)
|
|
294
|
+
* are replaced with a short omission note. If that still isn't enough (e.g. a
|
|
295
|
+
* single huge text block), the oldest content is hard-truncated so the copy
|
|
296
|
+
* always fits.
|
|
297
|
+
*/
|
|
298
|
+
export function serializeConversation(
|
|
299
|
+
messages: ChatMessage[],
|
|
300
|
+
options: { maxBytes?: number } = {},
|
|
301
|
+
): SerializedConversation {
|
|
302
|
+
const segments: Segment[] = []
|
|
303
|
+
for (const message of messages) {
|
|
304
|
+
renderMessage(message, segments)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const product = IS_FREEBUFF ? 'Freebuff' : 'Anvil'
|
|
308
|
+
const header = `# ${product} conversation\n_${messages.length} message${messages.length === 1 ? '' : 's'}_`
|
|
309
|
+
const prefix = `${header}\n\n---\n\n`
|
|
310
|
+
|
|
311
|
+
const assembleBody = (dropped: Set<Droppable>): string =>
|
|
312
|
+
segments
|
|
313
|
+
.map((seg) =>
|
|
314
|
+
isDroppable(seg) ? (dropped.has(seg) ? seg.note : seg.full) : seg,
|
|
315
|
+
)
|
|
316
|
+
.join('\n\n')
|
|
317
|
+
|
|
318
|
+
const dropped = new Set<Droppable>()
|
|
319
|
+
let body = assembleBody(dropped)
|
|
320
|
+
let truncated = false
|
|
321
|
+
|
|
322
|
+
const { maxBytes } = options
|
|
323
|
+
if (maxBytes) {
|
|
324
|
+
// The body must fit in maxBytes minus the prefix and the trailing newline.
|
|
325
|
+
const bodyBudget = maxBytes - byteLen(prefix) - 1
|
|
326
|
+
|
|
327
|
+
if (byteLen(body) > bodyBudget) {
|
|
328
|
+
// Tier 1: drop tool outputs first (the bulk of the noise), then inputs.
|
|
329
|
+
// Within each tier, largest first so we drop as few blocks as possible.
|
|
330
|
+
// Pre-measure savings once rather than recomputing inside the comparator.
|
|
331
|
+
const candidates = segments
|
|
332
|
+
.filter(isDroppable)
|
|
333
|
+
.map((d) => ({ d, save: byteLen(d.full) - byteLen(d.note) }))
|
|
334
|
+
.filter((c) => c.save > 0)
|
|
335
|
+
.sort((a, b) => {
|
|
336
|
+
if (a.d.kind !== b.d.kind) return a.d.kind === 'output' ? -1 : 1
|
|
337
|
+
return b.save - a.save
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const needed = byteLen(body) - bodyBudget
|
|
341
|
+
let saved = 0
|
|
342
|
+
for (const { d, save } of candidates) {
|
|
343
|
+
if (saved >= needed) break
|
|
344
|
+
dropped.add(d)
|
|
345
|
+
saved += save
|
|
346
|
+
}
|
|
347
|
+
body = assembleBody(dropped)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (byteLen(body) > bodyBudget) {
|
|
351
|
+
// Tier 2: nothing droppable left to cut (e.g. a giant text block). Keep
|
|
352
|
+
// the most recent content and mark the truncation so the copy never fails.
|
|
353
|
+
const marker = `${TRUNCATION_MARKER}\n\n`
|
|
354
|
+
body = marker + keepTailBytes(body, bodyBudget - byteLen(marker))
|
|
355
|
+
truncated = true
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { text: `${prefix}${body}\n`, omittedCount: dropped.size, truncated }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function handleCopyConversationCommand(
|
|
363
|
+
params: RouterParams,
|
|
364
|
+
): Promise<void> {
|
|
365
|
+
const messages = useChatStore.getState().messages
|
|
366
|
+
|
|
367
|
+
params.saveToHistory(params.inputValue.trim())
|
|
368
|
+
params.setInputValue({ text: '', cursorPosition: 0, lastEditDueToNav: false })
|
|
369
|
+
|
|
370
|
+
if (messages.length === 0) {
|
|
371
|
+
showClipboardMessage('Nothing to copy — the conversation is empty.', {
|
|
372
|
+
durationMs: 3000,
|
|
373
|
+
})
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const primary = conversationClipboardContent(messages)
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
await copyTextToClipboard(primary.text, {
|
|
381
|
+
successMessage: primary.successMessage,
|
|
382
|
+
durationMs: 4000,
|
|
383
|
+
getOsc52Fallback:
|
|
384
|
+
byteLen(primary.text) > OSC52_TEXT_BUDGET_BYTES
|
|
385
|
+
? () =>
|
|
386
|
+
conversationClipboardContent(messages, OSC52_TEXT_BUDGET_BYTES)
|
|
387
|
+
: undefined,
|
|
388
|
+
})
|
|
389
|
+
} catch {
|
|
390
|
+
// copyTextToClipboard already surfaces a failure/guidance message.
|
|
391
|
+
}
|
|
392
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useChatStore } from '../state/chat-store'
|
|
2
|
+
|
|
3
|
+
import type { PostUserMessageFn } from '../types/contracts/send-message'
|
|
4
|
+
|
|
5
|
+
export async function handleHelpCommand(): Promise<{
|
|
6
|
+
postUserMessage: PostUserMessageFn
|
|
7
|
+
}> {
|
|
8
|
+
// Show the help banner with keyboard shortcuts
|
|
9
|
+
useChatStore.getState().setInputMode('help')
|
|
10
|
+
|
|
11
|
+
const postUserMessage: PostUserMessageFn = (prev) => prev
|
|
12
|
+
return { postUserMessage }
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getProjectRoot } from '../project-files'
|
|
2
|
+
import { validateAndAddImage } from '../utils/pending-attachments'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handle the /image command to attach an image file.
|
|
6
|
+
* Usage: /image <path> [message]
|
|
7
|
+
* Example: /image ./screenshot.png please analyze this
|
|
8
|
+
*
|
|
9
|
+
* Returns the optional message as transformedPrompt (empty string if none).
|
|
10
|
+
* Errors are shown in the pending images banner with auto-remove.
|
|
11
|
+
*/
|
|
12
|
+
export async function handleImageCommand(args: string): Promise<string> {
|
|
13
|
+
const [imagePath, ...rest] = args.trim().split(/\s+/)
|
|
14
|
+
|
|
15
|
+
if (imagePath) {
|
|
16
|
+
await validateAndAddImage(imagePath, getProjectRoot())
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return rest.join(' ')
|
|
20
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
|
|
5
|
+
import { KNOWLEDGE_FILE_NAMES } from '@anvil/common/constants/knowledge'
|
|
6
|
+
|
|
7
|
+
// @ts-expect-error - Bun text import attribute not supported by TypeScript
|
|
8
|
+
import agentDefinitionSource from '../../../common/src/templates/initial-agents-dir/types/agent-definition' with { type: 'text' }
|
|
9
|
+
// @ts-expect-error - Bun text import attribute not supported by TypeScript
|
|
10
|
+
import toolsSource from '../../../common/src/templates/initial-agents-dir/types/tools' with { type: 'text' }
|
|
11
|
+
// @ts-expect-error - Bun text import attribute not supported by TypeScript
|
|
12
|
+
import utilTypesSource from '../../../common/src/templates/initial-agents-dir/types/util-types' with { type: 'text' }
|
|
13
|
+
import { getProjectRoot } from '../project-files'
|
|
14
|
+
import { trackEvent } from '../utils/analytics'
|
|
15
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
16
|
+
import { getSystemMessage } from '../utils/message-history'
|
|
17
|
+
|
|
18
|
+
import type { PostUserMessageFn } from '../types/contracts/send-message'
|
|
19
|
+
|
|
20
|
+
const brandName = IS_FREEBUFF ? 'Freebuff' : 'Anvil'
|
|
21
|
+
|
|
22
|
+
const INITIAL_KNOWLEDGE_FILE = `# Project knowledge
|
|
23
|
+
|
|
24
|
+
This file gives ${brandName} context about your project: goals, commands, conventions, and gotchas.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
- Setup:
|
|
28
|
+
- Dev:
|
|
29
|
+
- Test:
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
- Key directories:
|
|
33
|
+
- Data flow:
|
|
34
|
+
|
|
35
|
+
## Conventions
|
|
36
|
+
- Formatting/linting:
|
|
37
|
+
- Patterns to follow:
|
|
38
|
+
- Things to avoid:
|
|
39
|
+
`
|
|
40
|
+
|
|
41
|
+
const COMMON_TYPE_FILES = [
|
|
42
|
+
{
|
|
43
|
+
fileName: 'agent-definition.ts',
|
|
44
|
+
source: agentDefinitionSource,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
fileName: 'tools.ts',
|
|
48
|
+
source: toolsSource,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
fileName: 'util-types.ts',
|
|
52
|
+
source: utilTypesSource,
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
export function handleInitializationFlowLocally(): {
|
|
57
|
+
postUserMessage: PostUserMessageFn
|
|
58
|
+
} {
|
|
59
|
+
const projectRoot = getProjectRoot()
|
|
60
|
+
const knowledgePath = path.join(projectRoot, KNOWLEDGE_FILE_NAMES[0])
|
|
61
|
+
const messages: string[] = []
|
|
62
|
+
|
|
63
|
+
if (existsSync(knowledgePath)) {
|
|
64
|
+
messages.push(`📋 \`${KNOWLEDGE_FILE_NAMES[0]}\` already exists.`)
|
|
65
|
+
} else {
|
|
66
|
+
writeFileSync(knowledgePath, INITIAL_KNOWLEDGE_FILE)
|
|
67
|
+
messages.push(`✅ Created \`${KNOWLEDGE_FILE_NAMES[0]}\``)
|
|
68
|
+
|
|
69
|
+
// Track knowledge file creation
|
|
70
|
+
trackEvent(AnalyticsEvent.KNOWLEDGE_FILE_UPDATED, {
|
|
71
|
+
action: 'created',
|
|
72
|
+
fileName: KNOWLEDGE_FILE_NAMES[0],
|
|
73
|
+
fileSizeBytes: Buffer.byteLength(INITIAL_KNOWLEDGE_FILE, 'utf8'),
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const agentsDir = path.join(projectRoot, '.agents')
|
|
78
|
+
const agentsTypesDir = path.join(agentsDir, 'types')
|
|
79
|
+
|
|
80
|
+
if (existsSync(agentsDir)) {
|
|
81
|
+
messages.push('📋 `.agents/` already exists.')
|
|
82
|
+
} else {
|
|
83
|
+
mkdirSync(agentsDir, { recursive: true })
|
|
84
|
+
messages.push('✅ Created `.agents/`')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (existsSync(agentsTypesDir)) {
|
|
88
|
+
messages.push('📋 `.agents/types/` already exists.')
|
|
89
|
+
} else {
|
|
90
|
+
mkdirSync(agentsTypesDir, { recursive: true })
|
|
91
|
+
messages.push('✅ Created `.agents/types/`')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const { fileName, source } of COMMON_TYPE_FILES) {
|
|
95
|
+
const targetPath = path.join(agentsTypesDir, fileName)
|
|
96
|
+
if (existsSync(targetPath)) {
|
|
97
|
+
messages.push(`📋 \`.agents/types/${fileName}\` already exists.`)
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
if (!source || source.trim().length === 0) {
|
|
103
|
+
throw new Error('Source content is empty')
|
|
104
|
+
}
|
|
105
|
+
writeFileSync(targetPath, source)
|
|
106
|
+
messages.push(`✅ Copied \`.agents/types/${fileName}\``)
|
|
107
|
+
} catch (error) {
|
|
108
|
+
messages.push(
|
|
109
|
+
`⚠️ Failed to copy \`.agents/types/${fileName}\`: ${
|
|
110
|
+
error instanceof Error ? error.message : String(error ?? 'Unknown')
|
|
111
|
+
}`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const postUserMessage: PostUserMessageFn = (prev) => [
|
|
117
|
+
...prev,
|
|
118
|
+
...messages.map((message) => getSystemMessage(message)),
|
|
119
|
+
]
|
|
120
|
+
return { postUserMessage }
|
|
121
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { getActiveTerminalCommandProcesses } from '@anvil/sdk'
|
|
2
|
+
|
|
3
|
+
import { getTerminalWatchdogDiagnostics } from '../utils/terminal-watchdog'
|
|
4
|
+
import { getCliEnv } from '../utils/env'
|
|
5
|
+
import { IS_FREEBUFF } from '../utils/constants'
|
|
6
|
+
|
|
7
|
+
export type ProcessDiagnosticsSnapshot = {
|
|
8
|
+
product: string
|
|
9
|
+
version: string
|
|
10
|
+
runtime: string
|
|
11
|
+
platform: string
|
|
12
|
+
architecture: string
|
|
13
|
+
uptimeSeconds: number
|
|
14
|
+
cpuUserMicros: number
|
|
15
|
+
cpuSystemMicros: number
|
|
16
|
+
memory: NodeJS.MemoryUsage
|
|
17
|
+
parentPid: number
|
|
18
|
+
cliPid: number
|
|
19
|
+
watchdog: {
|
|
20
|
+
armed: boolean
|
|
21
|
+
external: boolean
|
|
22
|
+
pid?: number
|
|
23
|
+
}
|
|
24
|
+
activeTools: Array<{
|
|
25
|
+
pid: number
|
|
26
|
+
processGroupId?: number
|
|
27
|
+
}>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const formatBytes = (bytes: number): string => {
|
|
31
|
+
const units = ['B', 'KiB', 'MiB', 'GiB']
|
|
32
|
+
let value = bytes
|
|
33
|
+
let unit = units[0]
|
|
34
|
+
for (let i = 1; i < units.length && value >= 1024; i++) {
|
|
35
|
+
value /= 1024
|
|
36
|
+
unit = units[i]
|
|
37
|
+
}
|
|
38
|
+
return `${value.toFixed(value >= 10 || unit === 'B' ? 0 : 1)} ${unit}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const formatDuration = (seconds: number): string => {
|
|
42
|
+
const wholeSeconds = Math.max(0, Math.floor(seconds))
|
|
43
|
+
const hours = Math.floor(wholeSeconds / 3600)
|
|
44
|
+
const minutes = Math.floor((wholeSeconds % 3600) / 60)
|
|
45
|
+
const remainingSeconds = wholeSeconds % 60
|
|
46
|
+
return [hours, minutes, remainingSeconds]
|
|
47
|
+
.map((value) => value.toString().padStart(2, '0'))
|
|
48
|
+
.join(':')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const formatWatchdog = (
|
|
52
|
+
watchdog: ProcessDiagnosticsSnapshot['watchdog'],
|
|
53
|
+
): string => {
|
|
54
|
+
if (watchdog.pid) return `PID ${watchdog.pid}`
|
|
55
|
+
if (!watchdog.armed) return 'not running'
|
|
56
|
+
return watchdog.external
|
|
57
|
+
? 'armed externally (PID unavailable on Windows)'
|
|
58
|
+
: 'armed'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatProcessDiagnostics(
|
|
62
|
+
snapshot: ProcessDiagnosticsSnapshot,
|
|
63
|
+
): string {
|
|
64
|
+
const cpuSeconds =
|
|
65
|
+
(snapshot.cpuUserMicros + snapshot.cpuSystemMicros) / 1_000_000
|
|
66
|
+
const lines = [
|
|
67
|
+
`### ${snapshot.product} process diagnostics`,
|
|
68
|
+
'',
|
|
69
|
+
`- Version: ${snapshot.version}`,
|
|
70
|
+
`- Runtime: ${snapshot.runtime}`,
|
|
71
|
+
`- Platform: ${snapshot.platform} ${snapshot.architecture}`,
|
|
72
|
+
`- CLI uptime: ${formatDuration(snapshot.uptimeSeconds)}`,
|
|
73
|
+
`- CLI CPU time: ${cpuSeconds.toFixed(1)}s`,
|
|
74
|
+
`- CLI memory: ${formatBytes(snapshot.memory.rss)} RSS, ${formatBytes(snapshot.memory.heapUsed)} heap used`,
|
|
75
|
+
'',
|
|
76
|
+
'Processes:',
|
|
77
|
+
`- parent/wrapper: PID ${snapshot.parentPid}`,
|
|
78
|
+
`- CLI binary: PID ${snapshot.cliPid}`,
|
|
79
|
+
`- terminal watchdog: ${formatWatchdog(snapshot.watchdog)}`,
|
|
80
|
+
'',
|
|
81
|
+
'Active terminal tools:',
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
if (snapshot.activeTools.length === 0) {
|
|
85
|
+
lines.push('- none')
|
|
86
|
+
} else {
|
|
87
|
+
for (const child of snapshot.activeTools) {
|
|
88
|
+
lines.push(
|
|
89
|
+
`- PID ${child.pid}${child.processGroupId ? `, PGID ${child.processGroupId}` : ''}`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
lines.push(
|
|
94
|
+
'',
|
|
95
|
+
'Command lines and environment variables are omitted for safety.',
|
|
96
|
+
)
|
|
97
|
+
return lines.join('\n')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function collectProcessDiagnostics(): ProcessDiagnosticsSnapshot {
|
|
101
|
+
const cpuUsage = process.cpuUsage()
|
|
102
|
+
return {
|
|
103
|
+
product: IS_FREEBUFF ? 'Freebuff' : 'Anvil',
|
|
104
|
+
version: getCliEnv().ANVIL_CLI_VERSION ?? 'dev',
|
|
105
|
+
runtime:
|
|
106
|
+
typeof Bun !== 'undefined'
|
|
107
|
+
? `Bun ${Bun.version}`
|
|
108
|
+
: `${process.release.name} ${process.version}`,
|
|
109
|
+
platform: process.platform,
|
|
110
|
+
architecture: process.arch,
|
|
111
|
+
uptimeSeconds: process.uptime(),
|
|
112
|
+
cpuUserMicros: cpuUsage.user,
|
|
113
|
+
cpuSystemMicros: cpuUsage.system,
|
|
114
|
+
memory: process.memoryUsage(),
|
|
115
|
+
parentPid: process.ppid,
|
|
116
|
+
cliPid: process.pid,
|
|
117
|
+
watchdog: getTerminalWatchdogDiagnostics(),
|
|
118
|
+
activeTools: getActiveTerminalCommandProcesses(),
|
|
119
|
+
}
|
|
120
|
+
}
|