@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,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sacrificial watchdog process that resets the terminal if the CLI dies
|
|
3
|
+
* without running its own cleanup (SIGKILL, native crash, group kill).
|
|
4
|
+
*
|
|
5
|
+
* The in-process handlers (renderer-cleanup.ts) cover catchable exits, and
|
|
6
|
+
* the npm wrapper resets when it outlives the binary — but neither survives
|
|
7
|
+
* `pkill -9 node`-style sweeps that take out the wrapper and binary together,
|
|
8
|
+
* and dev/direct-binary runs have no wrapper at all. This covers those.
|
|
9
|
+
*
|
|
10
|
+
* POSIX:
|
|
11
|
+
* - We spawn a detached `/bin/sh` whose stdin is a pipe from this process.
|
|
12
|
+
* `sh` isn't named node/bun/anvil/freebuff, so process-name kill sweeps
|
|
13
|
+
* miss it, and `detached` puts it in its own session so process-group kills
|
|
14
|
+
* miss it too.
|
|
15
|
+
* - The watchdog blocks on `cat` until the pipe hits EOF — which only happens
|
|
16
|
+
* when this process is gone, however it died — then writes the reset
|
|
17
|
+
* sequences to its stdout, which is a dup of our stdout (the terminal).
|
|
18
|
+
* It must NOT open /dev/tty: being in its own session it has no controlling
|
|
19
|
+
* terminal, so that open fails with ENXIO. Writing to an inherited tty fd
|
|
20
|
+
* needs no controlling terminal.
|
|
21
|
+
* - On clean shutdown the CLI first writes reset bytes synchronously to the
|
|
22
|
+
* controlling terminal, then SIGKILLs the watchdog. If that direct write
|
|
23
|
+
* fails, the watchdog remains armed and repairs the terminal after exit.
|
|
24
|
+
*
|
|
25
|
+
* Windows (closes the anvil#843 after-exit gap, where the hosting
|
|
26
|
+
* terminal keeps sending mouse/focus VT input that the shell echoes as
|
|
27
|
+
* `^[[<35;12;7M` gibberish):
|
|
28
|
+
* - Bun/libuv put direct children in a kill-on-job-close job object, so a
|
|
29
|
+
* plain child — detached or not — is terminated the moment we die
|
|
30
|
+
* (oven-sh/bun#31603) and can never fire. Grandchildren of job members are
|
|
31
|
+
* NOT added to the job (silent-breakaway semantics), so we launch a
|
|
32
|
+
* short-lived PowerShell bootstrap (in the job; its death doesn't matter)
|
|
33
|
+
* that uses Start-Process -NoNewWindow to spawn the real watchdog outside
|
|
34
|
+
* the job, attached to our console.
|
|
35
|
+
* - The pipe/EOF trick can't cross the bootstrap hop, so the watchdog
|
|
36
|
+
* detects our death with `Wait-Process -Id <our pid>` instead, then writes
|
|
37
|
+
* the reset sequences to its console stdout (ConPTY forwards the disable
|
|
38
|
+
* sequences to the hosting terminal).
|
|
39
|
+
* - We hold no handle to the grandchild, so clean shutdown can't kill it.
|
|
40
|
+
* After a confirmed synchronous reset, stopTerminalWatchdog() drops a disarm
|
|
41
|
+
* file; the watchdog checks it after Wait-Process and exits silently.
|
|
42
|
+
* - Windows PowerShell 5.1 always exists and is invoked by absolute path.
|
|
43
|
+
* Scripts are passed as plain -Command text so the command lines stay
|
|
44
|
+
* human-readable in process listings (encoded PowerShell spawned by a CLI
|
|
45
|
+
* is a classic EDR/AV malware heuristic). They are deliberately built
|
|
46
|
+
* without double quotes, which makes that quoting-safe — see
|
|
47
|
+
* spawnWindowsWatchdog.
|
|
48
|
+
* - Arming takes a few hundred ms (PowerShell boot); deaths inside that
|
|
49
|
+
* window fall back to the pre-existing behavior (npm wrapper or nothing).
|
|
50
|
+
*/
|
|
51
|
+
import { spawn } from 'child_process'
|
|
52
|
+
import { closeSync, existsSync, openSync, rmSync, writeFileSync } from 'fs'
|
|
53
|
+
import os from 'os'
|
|
54
|
+
import path from 'path'
|
|
55
|
+
|
|
56
|
+
import { AnalyticsEvent } from '@anvil/common/constants/analytics-events'
|
|
57
|
+
|
|
58
|
+
import { TERMINAL_RESET_SEQUENCES } from './terminal-reset-sequences'
|
|
59
|
+
import { getCliEnv } from './env'
|
|
60
|
+
import { reportWindowsTerminalFailure } from './windows-terminal-health'
|
|
61
|
+
|
|
62
|
+
import type { ChildProcess } from 'child_process'
|
|
63
|
+
|
|
64
|
+
let watchdog: ChildProcess | null = null
|
|
65
|
+
let disarmFilePath: string | null = null
|
|
66
|
+
let armedFilePath: string | null = null
|
|
67
|
+
let armMonitor: ReturnType<typeof setTimeout> | null = null
|
|
68
|
+
|
|
69
|
+
const WINDOWS_ARM_TIMEOUT_MS = 10_000
|
|
70
|
+
|
|
71
|
+
export type TerminalWatchdogFailure = {
|
|
72
|
+
stage: 'spawn' | 'bootstrap' | 'arming'
|
|
73
|
+
failureCode:
|
|
74
|
+
| 'enoent'
|
|
75
|
+
| 'eacces'
|
|
76
|
+
| 'eperm'
|
|
77
|
+
| 'exit_nonzero'
|
|
78
|
+
| 'terminated'
|
|
79
|
+
| 'timeout'
|
|
80
|
+
| 'unknown'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function classifyTerminalWatchdogSpawnFailure(
|
|
84
|
+
error: unknown,
|
|
85
|
+
): TerminalWatchdogFailure['failureCode'] {
|
|
86
|
+
const code =
|
|
87
|
+
error && typeof error === 'object' && 'code' in error
|
|
88
|
+
? String((error as NodeJS.ErrnoException).code ?? '').toUpperCase()
|
|
89
|
+
: ''
|
|
90
|
+
if (code === 'ENOENT') return 'enoent'
|
|
91
|
+
if (code === 'EACCES') return 'eacces'
|
|
92
|
+
if (code === 'EPERM') return 'eperm'
|
|
93
|
+
return 'unknown'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function reportTerminalWatchdogFailure(failure: TerminalWatchdogFailure): void {
|
|
97
|
+
reportWindowsTerminalFailure(AnalyticsEvent.TERMINAL_WATCHDOG_FAILED, failure)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function clearArmMonitor(): void {
|
|
101
|
+
if (armMonitor) clearTimeout(armMonitor)
|
|
102
|
+
armMonitor = null
|
|
103
|
+
if (armedFilePath) {
|
|
104
|
+
try {
|
|
105
|
+
rmSync(armedFilePath, { force: true })
|
|
106
|
+
} catch {
|
|
107
|
+
// The external watchdog also removes this marker when it exits.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
armedFilePath = null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Read-only watchdog state for local process diagnostics. */
|
|
114
|
+
export function getTerminalWatchdogDiagnostics() {
|
|
115
|
+
const external = disarmFilePath !== null
|
|
116
|
+
const childIsRunning = Boolean(
|
|
117
|
+
watchdog?.pid && watchdog.exitCode === null && watchdog.signalCode === null,
|
|
118
|
+
)
|
|
119
|
+
return {
|
|
120
|
+
armed: childIsRunning || external,
|
|
121
|
+
external,
|
|
122
|
+
pid: !external && childIsRunning ? watchdog?.pid : undefined,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Reset payload with ESC as printf-compatible octal escapes. */
|
|
127
|
+
function printfPayload(): string {
|
|
128
|
+
return TERMINAL_RESET_SEQUENCES.replace(/\x1b/g, '\\033')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function spawnPosixWatchdog(overrideFd: number | null): ChildProcess {
|
|
132
|
+
// `cat` holds until our death closes the pipe; the reset then goes to the
|
|
133
|
+
// watchdog's stdout (see stdio below). The payload contains no quotes, so
|
|
134
|
+
// embedding it in single quotes is safe.
|
|
135
|
+
const script = `cat >/dev/null 2>&1; printf '${printfPayload()}'`
|
|
136
|
+
return spawn('/bin/sh', ['-c', script, 'terminal-reset-watchdog'], {
|
|
137
|
+
detached: true,
|
|
138
|
+
stdio: ['pipe', overrideFd ?? 'inherit', 'ignore'],
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Single-quote a string for PowerShell (only ' needs escaping, by doubling). */
|
|
143
|
+
function psQuote(value: string): string {
|
|
144
|
+
return `'${value.replace(/'/g, "''")}'`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function spawnWindowsWatchdog(options: {
|
|
148
|
+
ttyPath?: string
|
|
149
|
+
disarmPath: string
|
|
150
|
+
armedPath: string
|
|
151
|
+
powershellPath?: string
|
|
152
|
+
}): ChildProcess {
|
|
153
|
+
// The payload rides as a numeric byte array, which keeps the script free of
|
|
154
|
+
// double quotes and string interpolation (the no-`"` invariant the quoting
|
|
155
|
+
// below relies on) and avoids any Console.Out encoding translation, so the
|
|
156
|
+
// reset payload arrives byte-exact.
|
|
157
|
+
const payloadBytes = Array.from(
|
|
158
|
+
Buffer.from(TERMINAL_RESET_SEQUENCES, 'ascii'),
|
|
159
|
+
).join(',')
|
|
160
|
+
// Tests observe a file instead of the console; production writes to the
|
|
161
|
+
// watchdog's stdout, which is the console (Start-Process -NoNewWindow
|
|
162
|
+
// without redirection leaves the grandchild on our console's handles).
|
|
163
|
+
const writeResets = options.ttyPath
|
|
164
|
+
? `[System.IO.File]::WriteAllBytes(${psQuote(options.ttyPath)}, $b)`
|
|
165
|
+
: '$s=[Console]::OpenStandardOutput(); $s.Write($b, 0, $b.Length); $s.Flush()'
|
|
166
|
+
// The marker lets tests wait out the bootstrap hop and lets production
|
|
167
|
+
// report a bounded arming timeout without inspecting PowerShell output.
|
|
168
|
+
const armedMarker = `[System.IO.File]::WriteAllText(${psQuote(options.armedPath)}, 'armed'); `
|
|
169
|
+
const watchdogScript =
|
|
170
|
+
armedMarker +
|
|
171
|
+
`try { Wait-Process -Id ${process.pid} -ErrorAction Stop } catch {}; ` +
|
|
172
|
+
`if (Test-Path -LiteralPath ${psQuote(options.disarmPath)}) { ` +
|
|
173
|
+
`Remove-Item -LiteralPath ${psQuote(options.disarmPath)} -Force -ErrorAction SilentlyContinue ` +
|
|
174
|
+
`} else { ` +
|
|
175
|
+
`$b=[byte[]](${payloadBytes}); ` +
|
|
176
|
+
`${writeResets} }; ` +
|
|
177
|
+
`Remove-Item -LiteralPath ${psQuote(options.armedPath)} -Force -ErrorAction SilentlyContinue`
|
|
178
|
+
|
|
179
|
+
// Windows PowerShell 5.1 ships with every supported Windows; use the
|
|
180
|
+
// absolute path so a broken PATH can't take out the safety net.
|
|
181
|
+
const powershell =
|
|
182
|
+
options.powershellPath ??
|
|
183
|
+
path.join(
|
|
184
|
+
getCliEnv().SystemRoot ?? 'C:\\Windows',
|
|
185
|
+
'System32',
|
|
186
|
+
'WindowsPowerShell',
|
|
187
|
+
'v1.0',
|
|
188
|
+
'powershell.exe',
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// Plain -Command (not -EncodedCommand) so the command lines are auditable
|
|
192
|
+
// in process listings — encoded PowerShell trips EDR/AV heuristics. This is
|
|
193
|
+
// quoting-safe because the watchdog script contains no `"` (paths are
|
|
194
|
+
// single-quoted, the payload is numeric): it survives as the one
|
|
195
|
+
// double-quoted region of the grandchild's argument string, which
|
|
196
|
+
// Start-Process passes verbatim (single pre-built -ArgumentList string). On
|
|
197
|
+
// the bootstrap's own command line those `"` are escaped as \" by spawn,
|
|
198
|
+
// which powershell.exe's argv tokenizer unescapes.
|
|
199
|
+
const watchdogArgs = `-NoProfile -NonInteractive -Command "${watchdogScript}"`
|
|
200
|
+
const bootstrapScript =
|
|
201
|
+
`Start-Process -FilePath ${psQuote(powershell)} ` +
|
|
202
|
+
`-ArgumentList ${psQuote(watchdogArgs)} -NoNewWindow`
|
|
203
|
+
|
|
204
|
+
return spawn(
|
|
205
|
+
powershell,
|
|
206
|
+
['-NoProfile', '-NonInteractive', '-Command', bootstrapScript],
|
|
207
|
+
{
|
|
208
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
209
|
+
},
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const isTruthy = (value: string | undefined): boolean =>
|
|
214
|
+
value === '1' || value?.toLowerCase() === 'true'
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Start the watchdog. Call once, before the TUI renderer starts enabling
|
|
218
|
+
* terminal modes. No-op when stdout isn't a TTY (unless an explicit ttyPath
|
|
219
|
+
* is injected, e.g. in tests), or if already started.
|
|
220
|
+
*
|
|
221
|
+
* Also a no-op when ANVIL_NO_TERMINAL_WATCHDOG is set. This remains an
|
|
222
|
+
* explicit escape hatch for Windows endpoint-security policies that reject the
|
|
223
|
+
* out-of-job PowerShell grandchild used by the recovery path.
|
|
224
|
+
*
|
|
225
|
+
* @param options.ttyPath - Override the reset target (POSIX: the watchdog's
|
|
226
|
+
* stdout is pointed at this file; Windows: the watchdog writes the payload
|
|
227
|
+
* to this file and drops a `<ttyPath>.armed` marker once running). Tests
|
|
228
|
+
* inject a regular file here to observe what gets written.
|
|
229
|
+
*/
|
|
230
|
+
export function startTerminalWatchdog(options?: {
|
|
231
|
+
ttyPath?: string
|
|
232
|
+
reportFailure?: (failure: TerminalWatchdogFailure) => void
|
|
233
|
+
/** Test-only override for exercising Windows spawn failures. */
|
|
234
|
+
windowsPowerShellPath?: string
|
|
235
|
+
}): void {
|
|
236
|
+
if (watchdog) return
|
|
237
|
+
const env = getCliEnv()
|
|
238
|
+
if (isTruthy(env.ANVIL_NO_TERMINAL_WATCHDOG)) return
|
|
239
|
+
if (!options?.ttyPath && !process.stdout.isTTY) return
|
|
240
|
+
|
|
241
|
+
const reportFailure = options?.reportFailure ?? reportTerminalWatchdogFailure
|
|
242
|
+
let overrideFd: number | null = null
|
|
243
|
+
try {
|
|
244
|
+
let child: ChildProcess
|
|
245
|
+
if (process.platform === 'win32') {
|
|
246
|
+
const disarmPath = path.join(
|
|
247
|
+
os.tmpdir(),
|
|
248
|
+
`anvil-watchdog-disarm-${process.pid}-${Math.random().toString(36).slice(2)}`,
|
|
249
|
+
)
|
|
250
|
+
const armedPath = options?.ttyPath
|
|
251
|
+
? `${options.ttyPath}.armed`
|
|
252
|
+
: `${disarmPath}.armed`
|
|
253
|
+
child = spawnWindowsWatchdog({
|
|
254
|
+
ttyPath: options?.ttyPath,
|
|
255
|
+
disarmPath,
|
|
256
|
+
armedPath,
|
|
257
|
+
powershellPath: options?.windowsPowerShellPath,
|
|
258
|
+
})
|
|
259
|
+
disarmFilePath = disarmPath
|
|
260
|
+
if (!options?.ttyPath) {
|
|
261
|
+
armedFilePath = armedPath
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
if (options?.ttyPath) {
|
|
265
|
+
overrideFd = openSync(options.ttyPath, 'w')
|
|
266
|
+
}
|
|
267
|
+
child = spawnPosixWatchdog(overrideFd)
|
|
268
|
+
}
|
|
269
|
+
let failureReported = false
|
|
270
|
+
const reportOnce = (failure: TerminalWatchdogFailure) => {
|
|
271
|
+
if (failureReported) return
|
|
272
|
+
failureReported = true
|
|
273
|
+
reportFailure(failure)
|
|
274
|
+
}
|
|
275
|
+
const fail = (failure: TerminalWatchdogFailure) => {
|
|
276
|
+
if (failureReported || watchdog !== child) return
|
|
277
|
+
watchdog = null
|
|
278
|
+
disarmFilePath = null
|
|
279
|
+
clearArmMonitor()
|
|
280
|
+
reportOnce(failure)
|
|
281
|
+
}
|
|
282
|
+
child.on('error', (error) => {
|
|
283
|
+
fail({
|
|
284
|
+
stage: 'spawn',
|
|
285
|
+
failureCode: classifyTerminalWatchdogSpawnFailure(error),
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
if (process.platform === 'win32') {
|
|
289
|
+
child.on('exit', (code, signal) => {
|
|
290
|
+
if (code === 0 || watchdog !== child) return
|
|
291
|
+
fail({
|
|
292
|
+
stage: 'bootstrap',
|
|
293
|
+
failureCode: signal ? 'terminated' : 'exit_nonzero',
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
// Don't let the watchdog (or our write end of its pipe) hold the event
|
|
298
|
+
// loop open — the CLI must still be able to exit naturally. stdin is a
|
|
299
|
+
// Socket at runtime; its unref isn't in the Writable type.
|
|
300
|
+
child.unref()
|
|
301
|
+
child.stdin?.on('error', () => {})
|
|
302
|
+
;(child.stdin as { unref?: () => void } | null)?.unref?.()
|
|
303
|
+
watchdog = child
|
|
304
|
+
if (armedFilePath) {
|
|
305
|
+
const expectedMarker = armedFilePath
|
|
306
|
+
armMonitor = setTimeout(() => {
|
|
307
|
+
if (watchdog !== child) return
|
|
308
|
+
const armed = existsSync(expectedMarker)
|
|
309
|
+
clearArmMonitor()
|
|
310
|
+
if (!armed) {
|
|
311
|
+
reportOnce({ stage: 'arming', failureCode: 'timeout' })
|
|
312
|
+
}
|
|
313
|
+
}, WINDOWS_ARM_TIMEOUT_MS)
|
|
314
|
+
;(armMonitor as { unref?: () => void }).unref?.()
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
disarmFilePath = null
|
|
318
|
+
clearArmMonitor()
|
|
319
|
+
if (process.platform === 'win32') {
|
|
320
|
+
reportFailure({
|
|
321
|
+
stage: 'spawn',
|
|
322
|
+
failureCode: classifyTerminalWatchdogSpawnFailure(error),
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
// Best-effort: no watchdog is the pre-existing behavior.
|
|
326
|
+
} finally {
|
|
327
|
+
if (overrideFd !== null) {
|
|
328
|
+
try {
|
|
329
|
+
closeSync(overrideFd) // the child holds its own dup
|
|
330
|
+
} catch {
|
|
331
|
+
// Ignore close errors
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Disarm the watchdog after the clean-shutdown path has synchronously restored
|
|
339
|
+
* the terminal. Safe to call multiple times and synchronous, so it also works
|
|
340
|
+
* inside a process 'exit' handler.
|
|
341
|
+
*/
|
|
342
|
+
export function stopTerminalWatchdog(): void {
|
|
343
|
+
const child = watchdog
|
|
344
|
+
const disarm = disarmFilePath
|
|
345
|
+
if (!child && !disarm) return
|
|
346
|
+
watchdog = null
|
|
347
|
+
disarmFilePath = null
|
|
348
|
+
clearArmMonitor()
|
|
349
|
+
if (disarm) {
|
|
350
|
+
// Windows: the real watchdog is a grandchild we hold no handle to; it
|
|
351
|
+
// checks for this file after our death and stays silent when present.
|
|
352
|
+
try {
|
|
353
|
+
writeFileSync(disarm, '')
|
|
354
|
+
} catch {
|
|
355
|
+
// Best-effort; worst case the watchdog writes resets on a clean exit,
|
|
356
|
+
// which the terminal treats as no-ops.
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (child) {
|
|
360
|
+
try {
|
|
361
|
+
child.kill('SIGKILL')
|
|
362
|
+
} catch {
|
|
363
|
+
// Already dead — nothing to stop.
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import stringWidth from 'string-width'
|
|
2
|
+
|
|
3
|
+
// Keep measurement logic centralised so components can share consistent wrap behavior.
|
|
4
|
+
function measureLines(text: string, cols: number): number {
|
|
5
|
+
if (text.length === 0) return 1
|
|
6
|
+
|
|
7
|
+
let lines = 1
|
|
8
|
+
let current = 0
|
|
9
|
+
const tokens = text.split(/(\s+)/)
|
|
10
|
+
|
|
11
|
+
const emitHardWrap = () => {
|
|
12
|
+
lines += 1
|
|
13
|
+
current = 0
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const appendSegment = (
|
|
17
|
+
segment: string,
|
|
18
|
+
{ flushBeforeOversize = true }: { flushBeforeOversize?: boolean } = {},
|
|
19
|
+
) => {
|
|
20
|
+
if (!segment) return
|
|
21
|
+
|
|
22
|
+
const segmentWidth = stringWidth(segment)
|
|
23
|
+
if (segmentWidth > cols) {
|
|
24
|
+
if (flushBeforeOversize && current > 0) emitHardWrap()
|
|
25
|
+
let acc = 0
|
|
26
|
+
for (const ch of Array.from(segment)) {
|
|
27
|
+
const w = stringWidth(ch)
|
|
28
|
+
if (acc + w > cols) {
|
|
29
|
+
emitHardWrap()
|
|
30
|
+
acc = 0
|
|
31
|
+
}
|
|
32
|
+
acc += w
|
|
33
|
+
}
|
|
34
|
+
current = acc
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (current + segmentWidth > cols) emitHardWrap()
|
|
39
|
+
current += segmentWidth
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const token of tokens) {
|
|
43
|
+
if (!token) continue
|
|
44
|
+
|
|
45
|
+
if (token.includes('\n')) {
|
|
46
|
+
const parts = token.split('\n')
|
|
47
|
+
for (let i = 0; i < parts.length; i++) {
|
|
48
|
+
appendSegment(parts[i], { flushBeforeOversize: false })
|
|
49
|
+
if (i < parts.length - 1) emitHardWrap()
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
appendSegment(token)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return lines
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getLastNVisualLines(text: string, cols: number, n: number): { lines: string[]; hasMore: boolean } {
|
|
61
|
+
if (n <= 0 || cols <= 0) return { lines: [], hasMore: false }
|
|
62
|
+
const lines: string[] = []
|
|
63
|
+
if (!text) return { lines, hasMore: false }
|
|
64
|
+
|
|
65
|
+
const tokens = text.split(/(\s+)/)
|
|
66
|
+
let current = ''
|
|
67
|
+
let currentWidth = 0
|
|
68
|
+
|
|
69
|
+
const pushLine = () => {
|
|
70
|
+
lines.push(current)
|
|
71
|
+
current = ''
|
|
72
|
+
currentWidth = 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const appendSegment = (segment: string) => {
|
|
76
|
+
if (!segment) return
|
|
77
|
+
const segWidth = stringWidth(segment)
|
|
78
|
+
|
|
79
|
+
if (segWidth > cols) {
|
|
80
|
+
for (const ch of Array.from(segment)) {
|
|
81
|
+
const w = stringWidth(ch)
|
|
82
|
+
if (currentWidth + w > cols) pushLine()
|
|
83
|
+
current += ch
|
|
84
|
+
currentWidth += w
|
|
85
|
+
}
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (currentWidth + segWidth > cols) pushLine()
|
|
90
|
+
current += segment
|
|
91
|
+
currentWidth += segWidth
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const token of tokens) {
|
|
95
|
+
if (!token) continue
|
|
96
|
+
if (token.includes('\n')) {
|
|
97
|
+
const parts = token.split('\n')
|
|
98
|
+
for (let i = 0; i < parts.length; i++) {
|
|
99
|
+
appendSegment(parts[i])
|
|
100
|
+
if (i < parts.length - 1) pushLine()
|
|
101
|
+
}
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
appendSegment(token)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (current.length > 0 || lines.length === 0) pushLine()
|
|
108
|
+
const hasMore = lines.length > n
|
|
109
|
+
const lastLines = lines.slice(-n)
|
|
110
|
+
return { lines: lastLines, hasMore }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function computeInputLayoutMetrics({
|
|
114
|
+
layoutContent,
|
|
115
|
+
cursorProbe,
|
|
116
|
+
cols,
|
|
117
|
+
maxHeight,
|
|
118
|
+
minHeight = 1,
|
|
119
|
+
}: {
|
|
120
|
+
layoutContent: string
|
|
121
|
+
cursorProbe: string
|
|
122
|
+
cols: number
|
|
123
|
+
maxHeight: number
|
|
124
|
+
minHeight?: number
|
|
125
|
+
}): { heightLines: number; gutterEnabled: boolean } {
|
|
126
|
+
const safeMaxHeight = Math.max(1, maxHeight)
|
|
127
|
+
const effectiveMinHeight = Math.max(
|
|
128
|
+
1,
|
|
129
|
+
Math.min(minHeight ?? 1, safeMaxHeight),
|
|
130
|
+
)
|
|
131
|
+
const totalLines = measureLines(layoutContent, cols)
|
|
132
|
+
const cursorLines = measureLines(cursorProbe, cols)
|
|
133
|
+
|
|
134
|
+
// Add bottom gutter when cursor is on line 2 of exactly 2 lines
|
|
135
|
+
const gutterEnabled =
|
|
136
|
+
totalLines === 2 && cursorLines === 2 && totalLines + 1 <= safeMaxHeight
|
|
137
|
+
|
|
138
|
+
const rawHeight = Math.min(
|
|
139
|
+
totalLines + (gutterEnabled ? 1 : 0),
|
|
140
|
+
safeMaxHeight,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const heightLines = Math.max(effectiveMinHeight, rawHeight)
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
heightLines,
|
|
147
|
+
gutterEnabled,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Configuration System
|
|
3
|
+
*
|
|
4
|
+
* Provides plugin system and customization support for themes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ChatTheme } from '../types/theme-system'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plugin interface for extending theme system
|
|
11
|
+
* Plugins can modify themes at runtime
|
|
12
|
+
*/
|
|
13
|
+
export interface ThemePlugin {
|
|
14
|
+
/** Unique plugin name */
|
|
15
|
+
name: string
|
|
16
|
+
/**
|
|
17
|
+
* Apply plugin modifications to a theme
|
|
18
|
+
* @param theme - The base theme
|
|
19
|
+
* @param mode - The detected light/dark mode
|
|
20
|
+
* @returns Partial theme to merge
|
|
21
|
+
*/
|
|
22
|
+
apply: (
|
|
23
|
+
theme: ChatTheme,
|
|
24
|
+
mode: 'dark' | 'light',
|
|
25
|
+
) => Partial<ChatTheme>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Main theme configuration interface
|
|
30
|
+
*/
|
|
31
|
+
export interface ThemeConfig {
|
|
32
|
+
/** Global color overrides applied to themes */
|
|
33
|
+
customColors?: Partial<ChatTheme>
|
|
34
|
+
/** Registered plugins for theme extensions */
|
|
35
|
+
plugins?: ThemePlugin[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default theme configuration
|
|
40
|
+
*/
|
|
41
|
+
export const defaultThemeConfig: ThemeConfig = {
|
|
42
|
+
customColors: {},
|
|
43
|
+
plugins: [],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Active theme configuration
|
|
48
|
+
* Can be modified at runtime for customization
|
|
49
|
+
*/
|
|
50
|
+
export let themeConfig: ThemeConfig = defaultThemeConfig
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update the active theme configuration
|
|
54
|
+
* @param config - New configuration (will be merged with defaults)
|
|
55
|
+
*/
|
|
56
|
+
export const setThemeConfig = (config: Partial<ThemeConfig>): void => {
|
|
57
|
+
themeConfig = {
|
|
58
|
+
...defaultThemeConfig,
|
|
59
|
+
...config,
|
|
60
|
+
plugins: [...(defaultThemeConfig.plugins ?? []), ...(config.plugins ?? [])],
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register a theme plugin
|
|
66
|
+
* @param plugin - Plugin to register
|
|
67
|
+
*/
|
|
68
|
+
export const registerThemePlugin = (plugin: ThemePlugin): void => {
|
|
69
|
+
if (!themeConfig.plugins) {
|
|
70
|
+
themeConfig.plugins = []
|
|
71
|
+
}
|
|
72
|
+
// Check if plugin already registered
|
|
73
|
+
if (themeConfig.plugins.some((p) => p.name === plugin.name)) {
|
|
74
|
+
console.warn(`Theme plugin "${plugin.name}" is already registered`)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
themeConfig.plugins.push(plugin)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve 'default' color values to fallback colors
|
|
82
|
+
* Components should never see 'default' - it's resolved during theme building
|
|
83
|
+
*/
|
|
84
|
+
const resolveThemeColors = (theme: ChatTheme, mode: 'dark' | 'light'): void => {
|
|
85
|
+
const defaultFallback = mode === 'dark' ? '#ffffff' : '#000000'
|
|
86
|
+
|
|
87
|
+
const resolve = (value: string, fallback: string = defaultFallback): string => {
|
|
88
|
+
if (typeof value === 'string') {
|
|
89
|
+
const normalized = value.trim().toLowerCase()
|
|
90
|
+
if (normalized === 'default' || normalized.length === 0) {
|
|
91
|
+
return fallback
|
|
92
|
+
}
|
|
93
|
+
return value
|
|
94
|
+
}
|
|
95
|
+
return fallback
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Resolve all ThemeColor properties to actual colors
|
|
99
|
+
theme.foreground = resolve(theme.foreground)
|
|
100
|
+
theme.muted = resolve(theme.muted)
|
|
101
|
+
theme.inputFg = resolve(theme.inputFg)
|
|
102
|
+
theme.inputFocusedFg = resolve(theme.inputFocusedFg)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build a complete theme by applying custom colors and plugins
|
|
107
|
+
* All 'default' color values are resolved to actual colors
|
|
108
|
+
* @param baseTheme - The base theme to start from
|
|
109
|
+
* @param mode - Current theme mode (dark or light)
|
|
110
|
+
* @param customColors - Optional custom color overrides
|
|
111
|
+
* @param plugins - Optional theme plugins to apply
|
|
112
|
+
* @returns Complete theme with all customizations applied
|
|
113
|
+
*/
|
|
114
|
+
export const buildTheme = (
|
|
115
|
+
baseTheme: ChatTheme,
|
|
116
|
+
mode: 'dark' | 'light',
|
|
117
|
+
customColors?: Partial<ChatTheme>,
|
|
118
|
+
plugins?: ThemePlugin[],
|
|
119
|
+
): ChatTheme => {
|
|
120
|
+
// Start with cloned base theme (cloning handled by caller)
|
|
121
|
+
const theme = { ...baseTheme }
|
|
122
|
+
|
|
123
|
+
// Layer 1: Apply global custom colors
|
|
124
|
+
if (customColors) {
|
|
125
|
+
Object.assign(theme, customColors)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Layer 2: Apply plugins
|
|
129
|
+
if (plugins) {
|
|
130
|
+
for (const plugin of plugins) {
|
|
131
|
+
const pluginOverrides = plugin.apply(theme, mode)
|
|
132
|
+
Object.assign(theme, pluginOverrides)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Final step: Resolve all 'default' values to actual colors
|
|
137
|
+
resolveThemeColors(theme, mode)
|
|
138
|
+
theme.name = mode
|
|
139
|
+
|
|
140
|
+
return theme
|
|
141
|
+
}
|