@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,126 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { formatValidationError } from '../validation-error-formatting'
|
|
4
|
+
|
|
5
|
+
describe('formatValidationError', () => {
|
|
6
|
+
test('parses JSON array payloads and extracts field/message', () => {
|
|
7
|
+
const raw = `[
|
|
8
|
+
{
|
|
9
|
+
"code": "custom",
|
|
10
|
+
"path": [
|
|
11
|
+
"toolNames"
|
|
12
|
+
],
|
|
13
|
+
"message": "Non-empty spawnableAgents array requires the 'spawn_agents' tool. Add 'spawn_agents' to toolNames or remove spawnableAgents."
|
|
14
|
+
}
|
|
15
|
+
]`
|
|
16
|
+
|
|
17
|
+
const result = formatValidationError(raw)
|
|
18
|
+
|
|
19
|
+
expect(result.fieldName).toBe('toolNames')
|
|
20
|
+
expect(result.message).toBe(
|
|
21
|
+
"Non-empty spawnableAgents array requires the 'spawn_agents' tool. Add 'spawn_agents' to toolNames or remove spawnableAgents.",
|
|
22
|
+
)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('strips agent name prefix', () => {
|
|
26
|
+
const result = formatValidationError(
|
|
27
|
+
'Agent "demo" (demo.ts): Invalid input: expected string, received number',
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(result.fieldName).toBeUndefined()
|
|
31
|
+
expect(result.message).toBe(
|
|
32
|
+
'Invalid input: expected string, received number',
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('extracts field:message pattern', () => {
|
|
37
|
+
const result = formatValidationError(
|
|
38
|
+
'instructions: Required field is missing',
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
expect(result.fieldName).toBe('instructions')
|
|
42
|
+
expect(result.message).toBe('Required field is missing')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('handles messages without field patterns', () => {
|
|
46
|
+
const result = formatValidationError(
|
|
47
|
+
'Schema validation failed: Generic error',
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
expect(result.fieldName).toBeUndefined()
|
|
51
|
+
expect(result.message).toBe('Generic error')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('handles nested path from JSON error', () => {
|
|
55
|
+
const raw = `[
|
|
56
|
+
{
|
|
57
|
+
"path": ["outputSchema", "properties", "summary"],
|
|
58
|
+
"message": "Required"
|
|
59
|
+
}
|
|
60
|
+
]`
|
|
61
|
+
|
|
62
|
+
const result = formatValidationError(raw)
|
|
63
|
+
|
|
64
|
+
expect(result.fieldName).toBe('outputSchema.properties.summary')
|
|
65
|
+
expect(result.message).toBe('Required')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('handles empty string input', () => {
|
|
69
|
+
const result = formatValidationError('')
|
|
70
|
+
|
|
71
|
+
expect(result.fieldName).toBeUndefined()
|
|
72
|
+
expect(result.message).toBe('Unknown validation error')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('handles JSON object (not array) with error', () => {
|
|
76
|
+
const raw = `{"path": ["field"], "message": "Invalid value"}`
|
|
77
|
+
|
|
78
|
+
const result = formatValidationError(raw)
|
|
79
|
+
|
|
80
|
+
expect(result.fieldName).toBe('field')
|
|
81
|
+
expect(result.message).toBe('Invalid value')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('handles JSON with empty path array', () => {
|
|
85
|
+
const raw = `[{"path": [], "message": "Root level error"}]`
|
|
86
|
+
|
|
87
|
+
const result = formatValidationError(raw)
|
|
88
|
+
|
|
89
|
+
expect(result.fieldName).toBeUndefined()
|
|
90
|
+
expect(result.message).toBe('Root level error')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('handles malformed JSON gracefully', () => {
|
|
94
|
+
const raw = '[{"path": ["field"], invalid json'
|
|
95
|
+
|
|
96
|
+
const result = formatValidationError(raw)
|
|
97
|
+
|
|
98
|
+
expect(result.fieldName).toBeUndefined()
|
|
99
|
+
expect(result.message).toBe('[{"path": ["field"], invalid json')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('handles colon in message part correctly', () => {
|
|
103
|
+
const result = formatValidationError(
|
|
104
|
+
'fieldName: Error: something went wrong',
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
expect(result.fieldName).toBe('fieldName')
|
|
108
|
+
expect(result.message).toBe('Error: something went wrong')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('handles field pattern with no message after colon', () => {
|
|
112
|
+
const result = formatValidationError('fieldName:')
|
|
113
|
+
|
|
114
|
+
expect(result.fieldName).toBeUndefined()
|
|
115
|
+
expect(result.message).toBe('fieldName:')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('handles JSON with missing message property', () => {
|
|
119
|
+
const raw = `[{"path": ["field"], "code": "custom"}]`
|
|
120
|
+
|
|
121
|
+
const result = formatValidationError(raw)
|
|
122
|
+
|
|
123
|
+
expect(result.fieldName).toBe('field')
|
|
124
|
+
expect(result.message).toBe(raw)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
|
|
2
|
+
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
initAnalytics,
|
|
6
|
+
resetAnalyticsState,
|
|
7
|
+
trackEvent,
|
|
8
|
+
type AnalyticsDeps,
|
|
9
|
+
} from '../analytics'
|
|
10
|
+
import { deliverWindowsTerminalFailure } from '../windows-terminal-health'
|
|
11
|
+
|
|
12
|
+
import type { LogRecordInput } from '@anvil/common/schemas/logs'
|
|
13
|
+
|
|
14
|
+
const FAILED_ANALYTICS_DEPS: AnalyticsDeps = {
|
|
15
|
+
env: {},
|
|
16
|
+
isProd: true,
|
|
17
|
+
createClient: mock(() => {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'PostHog client should not be created without configuration',
|
|
20
|
+
)
|
|
21
|
+
}),
|
|
22
|
+
generateAnonymousId: () => 'analytics-anonymous-id',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
resetAnalyticsState()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('Windows terminal-health delivery', () => {
|
|
30
|
+
test('queues and drains the bounded Axiom event when analytics initialization fails', async () => {
|
|
31
|
+
const analyticsRecords: LogRecordInput[] = []
|
|
32
|
+
resetAnalyticsState({
|
|
33
|
+
...FAILED_ANALYTICS_DEPS,
|
|
34
|
+
enqueueClientLog: (record) => analyticsRecords.push(record),
|
|
35
|
+
})
|
|
36
|
+
expect(() => initAnalytics()).toThrow(
|
|
37
|
+
'NEXT_PUBLIC_POSTHOG_API_KEY or NEXT_PUBLIC_POSTHOG_HOST_URL is not set',
|
|
38
|
+
)
|
|
39
|
+
const fallbackRecords: LogRecordInput[] = []
|
|
40
|
+
const drainClientLogs = mock(async () => {})
|
|
41
|
+
const properties = {
|
|
42
|
+
version: '0.0.142',
|
|
43
|
+
platform: 'win32' as const,
|
|
44
|
+
stage: 'arming' as const,
|
|
45
|
+
failureCode: 'timeout' as const,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await deliverWindowsTerminalFailure(
|
|
49
|
+
AnalyticsEvent.TERMINAL_WATCHDOG_FAILED,
|
|
50
|
+
properties,
|
|
51
|
+
{
|
|
52
|
+
trackEvent,
|
|
53
|
+
getAnonymousId: () => 'anonymous-install-id',
|
|
54
|
+
enqueueClientLog: (record) => fallbackRecords.push(record),
|
|
55
|
+
drainClientLogs,
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
expect(analyticsRecords).toEqual([
|
|
60
|
+
{
|
|
61
|
+
level: 'info',
|
|
62
|
+
event: AnalyticsEvent.TERMINAL_WATCHDOG_FAILED,
|
|
63
|
+
message: AnalyticsEvent.TERMINAL_WATCHDOG_FAILED,
|
|
64
|
+
client_session_id: 'analytics-anonymous-id',
|
|
65
|
+
data: properties,
|
|
66
|
+
},
|
|
67
|
+
])
|
|
68
|
+
expect(fallbackRecords).toEqual([])
|
|
69
|
+
expect(drainClientLogs).toHaveBeenCalledTimes(1)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('preserves the analytics mirror without queuing a duplicate', async () => {
|
|
73
|
+
const tracked = mock(() => {})
|
|
74
|
+
const enqueueClientLog = mock((_record: LogRecordInput) => {})
|
|
75
|
+
const drainClientLogs = mock(async () => {})
|
|
76
|
+
const properties = {
|
|
77
|
+
version: '0.0.142',
|
|
78
|
+
platform: 'win32' as const,
|
|
79
|
+
stage: 'spawn' as const,
|
|
80
|
+
failureCode: 'enoent' as const,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await deliverWindowsTerminalFailure(
|
|
84
|
+
AnalyticsEvent.TERMINAL_BROKER_SPAWN_FAILED,
|
|
85
|
+
properties,
|
|
86
|
+
{
|
|
87
|
+
trackEvent: tracked,
|
|
88
|
+
getAnonymousId: () => 'unused',
|
|
89
|
+
enqueueClientLog,
|
|
90
|
+
drainClientLogs,
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
expect(tracked).toHaveBeenCalledWith(
|
|
95
|
+
AnalyticsEvent.TERMINAL_BROKER_SPAWN_FAILED,
|
|
96
|
+
properties,
|
|
97
|
+
)
|
|
98
|
+
expect(enqueueClientLog).not.toHaveBeenCalled()
|
|
99
|
+
expect(drainClientLogs).toHaveBeenCalledTimes(1)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import * as os from 'os'
|
|
4
|
+
import * as path from 'path'
|
|
5
|
+
|
|
6
|
+
import { writeFileAtomic, writeFileAtomicAsync } from '../write-file-atomic'
|
|
7
|
+
|
|
8
|
+
let tempDir = ''
|
|
9
|
+
|
|
10
|
+
describe('writeFileAtomic', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'anvil-atomic-'))
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('writes a new file', () => {
|
|
20
|
+
const target = path.join(tempDir, 'out.json')
|
|
21
|
+
|
|
22
|
+
writeFileAtomic(target, '{"a":1}')
|
|
23
|
+
|
|
24
|
+
expect(fs.readFileSync(target, 'utf8')).toBe('{"a":1}')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('replaces an existing file', () => {
|
|
28
|
+
const target = path.join(tempDir, 'out.json')
|
|
29
|
+
fs.writeFileSync(target, 'old content')
|
|
30
|
+
|
|
31
|
+
writeFileAtomic(target, 'new content')
|
|
32
|
+
|
|
33
|
+
expect(fs.readFileSync(target, 'utf8')).toBe('new content')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('leaves no temp file behind on success', () => {
|
|
37
|
+
const target = path.join(tempDir, 'out.json')
|
|
38
|
+
|
|
39
|
+
writeFileAtomic(target, 'data')
|
|
40
|
+
|
|
41
|
+
expect(fs.readdirSync(tempDir)).toEqual(['out.json'])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('cleans up the temp file and rethrows on failure', () => {
|
|
45
|
+
// Renaming a file over an existing directory fails on all platforms
|
|
46
|
+
const target = path.join(tempDir, 'target-dir')
|
|
47
|
+
fs.mkdirSync(target)
|
|
48
|
+
|
|
49
|
+
expect(() => writeFileAtomic(target, 'data')).toThrow()
|
|
50
|
+
|
|
51
|
+
expect(fs.readdirSync(tempDir)).toEqual(['target-dir'])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('uses a unique temp name per write (no collision between calls)', () => {
|
|
55
|
+
// Two writes to different targets must not share a temp path, otherwise
|
|
56
|
+
// concurrent sync + async writes would tear each other. The temp name
|
|
57
|
+
// includes a random component, so back-to-back writes never collide.
|
|
58
|
+
const a = path.join(tempDir, 'a.json')
|
|
59
|
+
const b = path.join(tempDir, 'b.json')
|
|
60
|
+
|
|
61
|
+
writeFileAtomic(a, 'a')
|
|
62
|
+
writeFileAtomic(b, 'b')
|
|
63
|
+
|
|
64
|
+
expect(fs.readdirSync(tempDir).sort()).toEqual(['a.json', 'b.json'])
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('writeFileAtomicAsync', () => {
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'anvil-atomic-async-'))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('writes a new file', async () => {
|
|
78
|
+
const target = path.join(tempDir, 'out.json')
|
|
79
|
+
|
|
80
|
+
await writeFileAtomicAsync(target, '{"a":1}')
|
|
81
|
+
|
|
82
|
+
expect(fs.readFileSync(target, 'utf8')).toBe('{"a":1}')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('replaces an existing file', async () => {
|
|
86
|
+
const target = path.join(tempDir, 'out.json')
|
|
87
|
+
fs.writeFileSync(target, 'old content')
|
|
88
|
+
|
|
89
|
+
await writeFileAtomicAsync(target, 'new content')
|
|
90
|
+
|
|
91
|
+
expect(fs.readFileSync(target, 'utf8')).toBe('new content')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('leaves no temp file behind on success', async () => {
|
|
95
|
+
const target = path.join(tempDir, 'out.json')
|
|
96
|
+
|
|
97
|
+
await writeFileAtomicAsync(target, 'data')
|
|
98
|
+
|
|
99
|
+
expect(fs.readdirSync(tempDir)).toEqual(['out.json'])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('cleans up the temp file and rejects on failure', async () => {
|
|
103
|
+
const target = path.join(tempDir, 'target-dir')
|
|
104
|
+
fs.mkdirSync(target)
|
|
105
|
+
|
|
106
|
+
await expect(writeFileAtomicAsync(target, 'data')).rejects.toThrow()
|
|
107
|
+
|
|
108
|
+
expect(fs.readdirSync(tempDir)).toEqual(['target-dir'])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('concurrent writes to the same file do not tear (last write wins)', async () => {
|
|
112
|
+
const target = path.join(tempDir, 'out.json')
|
|
113
|
+
|
|
114
|
+
await Promise.all([
|
|
115
|
+
writeFileAtomicAsync(target, 'first'),
|
|
116
|
+
writeFileAtomicAsync(target, 'second'),
|
|
117
|
+
writeFileAtomicAsync(target, 'third'),
|
|
118
|
+
])
|
|
119
|
+
|
|
120
|
+
// Whichever rename lands last wins, but the file is always one intact
|
|
121
|
+
// value — never a torn mix — and no temp files remain.
|
|
122
|
+
expect(['first', 'second', 'third']).toContain(
|
|
123
|
+
fs.readFileSync(target, 'utf8'),
|
|
124
|
+
)
|
|
125
|
+
expect(fs.readdirSync(tempDir)).toEqual(['out.json'])
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/** Why the active agent run is being stopped. */
|
|
2
|
+
export type ActiveRunStopReason =
|
|
3
|
+
| 'user-interrupt'
|
|
4
|
+
| 'logout'
|
|
5
|
+
| 'new-chat'
|
|
6
|
+
| 'history-resume'
|
|
7
|
+
| 'session-transition'
|
|
8
|
+
| 'process-exit'
|
|
9
|
+
|
|
10
|
+
type ActiveRunQueuePolicy =
|
|
11
|
+
| 'pause-if-pending'
|
|
12
|
+
| 'clear-and-block'
|
|
13
|
+
| 'preserve-and-block'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Every reason aborts through the same run owner, which marks the message
|
|
17
|
+
* interrupted and checkpoints it. Process-exit callers additionally flush
|
|
18
|
+
* that checkpoint synchronously. Queue behavior is the only policy that
|
|
19
|
+
* varies here: context changes discard old prompts, a manual interrupt pauses
|
|
20
|
+
* them, and exit preserves them while blocking any new dequeue.
|
|
21
|
+
*/
|
|
22
|
+
export const ACTIVE_RUN_QUEUE_POLICIES = {
|
|
23
|
+
'user-interrupt': 'pause-if-pending',
|
|
24
|
+
logout: 'clear-and-block',
|
|
25
|
+
'new-chat': 'clear-and-block',
|
|
26
|
+
'history-resume': 'clear-and-block',
|
|
27
|
+
'session-transition': 'clear-and-block',
|
|
28
|
+
'process-exit': 'preserve-and-block',
|
|
29
|
+
} satisfies Record<ActiveRunStopReason, ActiveRunQueuePolicy>
|
|
30
|
+
|
|
31
|
+
export type ActiveRunQueueControls = {
|
|
32
|
+
pauseQueueIfPending: () => void
|
|
33
|
+
discardQueue: () => void
|
|
34
|
+
setCanProcessQueue: (canProcess: boolean) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Apply the reason matrix to the persistent runtime's latest queue state. */
|
|
38
|
+
export function applyActiveRunQueuePolicy(
|
|
39
|
+
reason: ActiveRunStopReason,
|
|
40
|
+
controls: ActiveRunQueueControls,
|
|
41
|
+
): void {
|
|
42
|
+
const policy = ACTIVE_RUN_QUEUE_POLICIES[reason]
|
|
43
|
+
if (policy === 'pause-if-pending') {
|
|
44
|
+
controls.pauseQueueIfPending()
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
if (policy === 'clear-and-block') {
|
|
48
|
+
controls.discardQueue()
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
if (policy === 'preserve-and-block') {
|
|
52
|
+
// Freebuff exit can spend up to a second releasing the session seat. Keep
|
|
53
|
+
// queued prompts in memory, but do not let abort cleanup dequeue a new run
|
|
54
|
+
// during that window.
|
|
55
|
+
controls.setCanProcessQueue(false)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type ActiveRun = {
|
|
60
|
+
ownerId: string
|
|
61
|
+
stop: (reason: ActiveRunStopReason) => void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let activeRun: ActiveRun | null = null
|
|
65
|
+
let runtimeStopHandler: ((reason: ActiveRunStopReason) => void) | null = null
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Install cleanup owned by the persistent ChatRuntime. It runs even while no
|
|
69
|
+
* SDK run exists, which is important for clearing a paused queue on /logout,
|
|
70
|
+
* /new, or a session reset.
|
|
71
|
+
*/
|
|
72
|
+
export function registerActiveRunStopHandler(
|
|
73
|
+
handler: (reason: ActiveRunStopReason) => void,
|
|
74
|
+
): () => void {
|
|
75
|
+
runtimeStopHandler = handler
|
|
76
|
+
return () => {
|
|
77
|
+
if (runtimeStopHandler === handler) runtimeStopHandler = null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Register the sole run that may currently mutate the active chat. */
|
|
82
|
+
export function registerActiveRun(
|
|
83
|
+
ownerId: string,
|
|
84
|
+
stop: (reason: ActiveRunStopReason) => void,
|
|
85
|
+
): void {
|
|
86
|
+
const previousRun = activeRun
|
|
87
|
+
activeRun = { ownerId, stop }
|
|
88
|
+
if (previousRun && previousRun.ownerId !== ownerId) {
|
|
89
|
+
// This should only happen if an unexpected path bypassed the chain lock.
|
|
90
|
+
// Stop the displaced owner after installing the new one so its late
|
|
91
|
+
// owner-guarded cleanup cannot unregister the replacement.
|
|
92
|
+
try {
|
|
93
|
+
previousRun.stop('user-interrupt')
|
|
94
|
+
} catch {
|
|
95
|
+
// The new owner is already installed; never orphan it because cleanup
|
|
96
|
+
// for the displaced run failed.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Owner-guarded so a stale run settling cannot release a newer run. */
|
|
102
|
+
export function clearActiveRun(ownerId: string): void {
|
|
103
|
+
if (activeRun?.ownerId === ownerId) {
|
|
104
|
+
activeRun = null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Stop the in-flight run, if any. Ownership is released before invoking the
|
|
110
|
+
* callback: repeated entry points are idempotent and the stopped run cannot
|
|
111
|
+
* consume a later reason intended for a newer run.
|
|
112
|
+
*/
|
|
113
|
+
export function stopActiveRun(reason: ActiveRunStopReason): boolean {
|
|
114
|
+
const run = activeRun
|
|
115
|
+
if (run) activeRun = null
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
run?.stop(reason)
|
|
119
|
+
} catch {
|
|
120
|
+
// Cancellation is a best-effort boundary used during fatal process
|
|
121
|
+
// cleanup too; one broken run callback must not block queue/terminal
|
|
122
|
+
// cleanup.
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
runtimeStopHandler?.(reason)
|
|
126
|
+
} catch {
|
|
127
|
+
// The run is already detached. Keep cancellation idempotent and allow the
|
|
128
|
+
// initiating transition or process cleanup to continue.
|
|
129
|
+
}
|
|
130
|
+
return run !== null
|
|
131
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Tracker Module
|
|
3
|
+
*
|
|
4
|
+
* A singleton module that tracks user activity in the terminal.
|
|
5
|
+
* Used by the ads feature and activity-aware query hooks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type ActivityListener = (timestamp: number) => void
|
|
9
|
+
|
|
10
|
+
const listeners = new Set<ActivityListener>()
|
|
11
|
+
let lastActivityTime = Date.now()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Report that the user performed an activity.
|
|
15
|
+
* Call this when:
|
|
16
|
+
* - User types in the input field
|
|
17
|
+
* - User moves the mouse
|
|
18
|
+
* - User presses keyboard shortcuts
|
|
19
|
+
*/
|
|
20
|
+
export function reportActivity(): void {
|
|
21
|
+
lastActivityTime = Date.now()
|
|
22
|
+
for (const listener of listeners) {
|
|
23
|
+
listener(lastActivityTime)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the timestamp of the last user activity.
|
|
29
|
+
*/
|
|
30
|
+
export function getLastActivityTime(): number {
|
|
31
|
+
return lastActivityTime
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if the user is currently active (within the idle threshold).
|
|
36
|
+
* @param idleThresholdMs - Time in ms to consider user idle (default: 30 seconds)
|
|
37
|
+
*/
|
|
38
|
+
export function isUserActive(idleThresholdMs: number = 30_000): boolean {
|
|
39
|
+
return Date.now() - lastActivityTime < idleThresholdMs
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get the time in ms since the last activity.
|
|
44
|
+
*/
|
|
45
|
+
export function getIdleTime(): number {
|
|
46
|
+
return Date.now() - lastActivityTime
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to activity events.
|
|
51
|
+
* @returns Unsubscribe function
|
|
52
|
+
*/
|
|
53
|
+
export function subscribeToActivity(callback: ActivityListener): () => void {
|
|
54
|
+
listeners.add(callback)
|
|
55
|
+
return () => {
|
|
56
|
+
listeners.delete(callback)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Reset the activity tracker (mainly for testing).
|
|
62
|
+
*/
|
|
63
|
+
export function resetActivityTracker(): void {
|
|
64
|
+
lastActivityTime = Date.now()
|
|
65
|
+
listeners.clear()
|
|
66
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getAgentBaseName } from './message-block-helpers'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AgentContentBlock,
|
|
5
|
+
TextContentBlock,
|
|
6
|
+
ToolContentBlock,
|
|
7
|
+
} from '../types/chat'
|
|
8
|
+
|
|
9
|
+
const DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH = 120
|
|
10
|
+
const PREVIEW_ELLIPSIS = '...'
|
|
11
|
+
|
|
12
|
+
export function truncateToSingleLinePreview(
|
|
13
|
+
text: string,
|
|
14
|
+
maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
|
|
15
|
+
): string | undefined {
|
|
16
|
+
const singleLine = text.replace(/\s+/g, ' ').trim()
|
|
17
|
+
if (!singleLine) {
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (singleLine.length <= maxLength) {
|
|
22
|
+
return singleLine
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const previewLength = Math.max(0, maxLength - PREVIEW_ELLIPSIS.length)
|
|
26
|
+
return `${singleLine.slice(0, previewLength).trimEnd()}${PREVIEW_ELLIPSIS}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getAgentDisplayPrompt(
|
|
30
|
+
agentBlock: AgentContentBlock,
|
|
31
|
+
): string | undefined {
|
|
32
|
+
const initialPrompt = agentBlock.initialPrompt?.trim()
|
|
33
|
+
if (initialPrompt) {
|
|
34
|
+
return initialPrompt
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (getAgentBaseName(agentBlock.agentType) !== 'basher') {
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const whatToSummarize = agentBlock.params?.what_to_summarize
|
|
42
|
+
return typeof whatToSummarize === 'string' && whatToSummarize.trim()
|
|
43
|
+
? whatToSummarize.trim()
|
|
44
|
+
: undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getBasherFinishedOutputPreview(
|
|
48
|
+
agentBlock: AgentContentBlock,
|
|
49
|
+
maxLength = DEFAULT_BASHER_OUTPUT_PREVIEW_MAX_LENGTH,
|
|
50
|
+
): string | undefined {
|
|
51
|
+
if (
|
|
52
|
+
getAgentBaseName(agentBlock.agentType) !== 'basher' ||
|
|
53
|
+
agentBlock.status === 'running'
|
|
54
|
+
) {
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const blocks = agentBlock.blocks ?? []
|
|
59
|
+
return (
|
|
60
|
+
truncateToSingleLinePreview(getTextOutput(blocks), maxLength) ??
|
|
61
|
+
truncateToSingleLinePreview(getCommandOutput(blocks), maxLength)
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getTextOutput(
|
|
66
|
+
blocks: NonNullable<AgentContentBlock['blocks']>,
|
|
67
|
+
): string {
|
|
68
|
+
return blocks
|
|
69
|
+
.filter(
|
|
70
|
+
(block): block is TextContentBlock =>
|
|
71
|
+
block.type === 'text' && block.textType !== 'reasoning',
|
|
72
|
+
)
|
|
73
|
+
.map((block) => block.content)
|
|
74
|
+
.join('\n')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getCommandOutput(
|
|
78
|
+
blocks: NonNullable<AgentContentBlock['blocks']>,
|
|
79
|
+
): string {
|
|
80
|
+
return blocks
|
|
81
|
+
.filter(
|
|
82
|
+
(block): block is ToolContentBlock =>
|
|
83
|
+
block.type === 'tool' && block.toolName === 'run_terminal_command',
|
|
84
|
+
)
|
|
85
|
+
.map((block) => block.output ?? '')
|
|
86
|
+
.join('\n')
|
|
87
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AgentContentBlock } from '../types/chat'
|
|
2
|
+
|
|
3
|
+
export interface StatusInfo {
|
|
4
|
+
indicator: string
|
|
5
|
+
label: string
|
|
6
|
+
color: string
|
|
7
|
+
text: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Get status indicator, label, color, and formatted text based on agent status */
|
|
11
|
+
export function getAgentStatusInfo(
|
|
12
|
+
status: AgentContentBlock['status'],
|
|
13
|
+
theme: { primary: string; foreground: string; muted: string },
|
|
14
|
+
): StatusInfo {
|
|
15
|
+
switch (status) {
|
|
16
|
+
case 'running':
|
|
17
|
+
return { indicator: '●', label: 'running', color: theme.primary, text: '● running' }
|
|
18
|
+
case 'failed':
|
|
19
|
+
return { indicator: '✗', label: 'failed', color: 'red', text: '✗ failed' }
|
|
20
|
+
case 'complete':
|
|
21
|
+
return { indicator: '✓', label: 'completed', color: theme.foreground, text: 'completed ✓' }
|
|
22
|
+
case 'cancelled':
|
|
23
|
+
return { indicator: '⊘', label: 'cancelled', color: 'red', text: '⊘ cancelled' }
|
|
24
|
+
default:
|
|
25
|
+
return { indicator: '○', label: 'waiting', color: theme.muted, text: '○ waiting' }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for parsing and normalizing agent identifiers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the simple agent ID from a potentially qualified ID.
|
|
7
|
+
* Handles formats like:
|
|
8
|
+
* - "my-agent" -> "my-agent"
|
|
9
|
+
* - "publisher/my-agent" -> "my-agent"
|
|
10
|
+
* - "publisher/my-agent@1.0.0" -> "my-agent"
|
|
11
|
+
*/
|
|
12
|
+
export function getSimpleAgentId(qualifiedId: string): string {
|
|
13
|
+
return qualifiedId.split('/').pop()?.split('@')[0] ?? qualifiedId
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity of streamed AI-response messages, kept in a dependency-free leaf
|
|
3
|
+
* module so lightweight consumers (e.g. the ads hook) can discriminate real LLM
|
|
4
|
+
* answers from other 'ai'-variant messages without pulling in the full
|
|
5
|
+
* send-message helper graph.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Id prefix identifying streamed AI response shells. */
|
|
9
|
+
export const AI_MESSAGE_ID_PREFIX = 'ai-'
|
|
10
|
+
|
|
11
|
+
export const generateAiMessageId = (): string =>
|
|
12
|
+
`${AI_MESSAGE_ID_PREFIX}${Date.now()}-${Math.random().toString(16).slice(2)}`
|