@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,387 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { logger } from '../utils/logger'
|
|
4
|
+
|
|
5
|
+
import type { PendingAttachment } from '../types/store'
|
|
6
|
+
|
|
7
|
+
export type StreamStatus = 'idle' | 'waiting' | 'streaming'
|
|
8
|
+
|
|
9
|
+
export type QueuedMessage = {
|
|
10
|
+
/** Stable across edits and reorders so the queue editor can address a row
|
|
11
|
+
* by identity rather than by a position that shifts underneath it. */
|
|
12
|
+
id: string
|
|
13
|
+
content: string
|
|
14
|
+
attachments: PendingAttachment[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const newQueueId = () => crypto.randomUUID()
|
|
18
|
+
|
|
19
|
+
// Watchdog timeout duration: 60 seconds
|
|
20
|
+
const QUEUE_WATCHDOG_TIMEOUT_MS = 60 * 1000
|
|
21
|
+
|
|
22
|
+
export const useMessageQueue = (
|
|
23
|
+
sendMessage: (message: QueuedMessage) => Promise<void>,
|
|
24
|
+
isChainInProgressRef: React.MutableRefObject<boolean>,
|
|
25
|
+
activeAgentStreamsRef: React.MutableRefObject<number>,
|
|
26
|
+
opts: {
|
|
27
|
+
/** External hold on dequeuing (e.g. the freebuff session ended and new
|
|
28
|
+
* requests would be rejected). Queued messages are kept, not dropped;
|
|
29
|
+
* processing resumes automatically when this flips back to false. */
|
|
30
|
+
sendBlocked?: boolean
|
|
31
|
+
} = {},
|
|
32
|
+
) => {
|
|
33
|
+
const sendBlocked = opts.sendBlocked ?? false
|
|
34
|
+
const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([])
|
|
35
|
+
const [streamStatus, setStreamStatus] = useState<StreamStatus>('idle')
|
|
36
|
+
const [canProcessQueue, setCanProcessQueue] = useState<boolean>(true)
|
|
37
|
+
// Separate state for user-initiated pause to ensure re-renders when pause status changes
|
|
38
|
+
const [queuePausedState, setQueuePausedState] = useState<boolean>(false)
|
|
39
|
+
|
|
40
|
+
// Keep a ref so clearQueue can return the current queue synchronously.
|
|
41
|
+
const queuedMessagesRef = useRef<QueuedMessage[]>([])
|
|
42
|
+
const streamTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
43
|
+
const streamIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
44
|
+
const streamMessageIdRef = useRef<string | null>(null)
|
|
45
|
+
const isProcessingQueueRef = useRef<boolean>(false)
|
|
46
|
+
const queueProcessingOwnerRef = useRef<symbol | null>(null)
|
|
47
|
+
// User-initiated pause state (separate from system-busy state)
|
|
48
|
+
const isQueuePausedRef = useRef<boolean>(false)
|
|
49
|
+
// Watchdog timer to recover from stuck queue processing lock
|
|
50
|
+
const watchdogTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
51
|
+
|
|
52
|
+
// queuePaused reflects whether the user has explicitly paused the queue
|
|
53
|
+
// (not whether the system is temporarily busy processing)
|
|
54
|
+
// Use state instead of ref to ensure components re-render when pause status changes
|
|
55
|
+
const queuePaused = queuePausedState
|
|
56
|
+
|
|
57
|
+
/** Every queue write goes through here: the ref is updated before React
|
|
58
|
+
* state so anything reading between renders (cancellation, the next
|
|
59
|
+
* dequeue) sees the same queue the user just acted on. */
|
|
60
|
+
const writeQueue = useCallback((next: QueuedMessage[]) => {
|
|
61
|
+
queuedMessagesRef.current = next
|
|
62
|
+
setQueuedMessages(next)
|
|
63
|
+
}, [])
|
|
64
|
+
|
|
65
|
+
const clearStreaming = useCallback(() => {
|
|
66
|
+
if (streamTimeoutRef.current) {
|
|
67
|
+
clearTimeout(streamTimeoutRef.current)
|
|
68
|
+
streamTimeoutRef.current = null
|
|
69
|
+
}
|
|
70
|
+
if (streamIntervalRef.current) {
|
|
71
|
+
clearInterval(streamIntervalRef.current)
|
|
72
|
+
streamIntervalRef.current = null
|
|
73
|
+
}
|
|
74
|
+
streamMessageIdRef.current = null
|
|
75
|
+
activeAgentStreamsRef.current = 0
|
|
76
|
+
setStreamStatus('idle')
|
|
77
|
+
}, [activeAgentStreamsRef])
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
return () => {
|
|
81
|
+
clearStreaming()
|
|
82
|
+
// Clean up watchdog timer on unmount
|
|
83
|
+
if (watchdogTimeoutRef.current) {
|
|
84
|
+
clearTimeout(watchdogTimeoutRef.current)
|
|
85
|
+
watchdogTimeoutRef.current = null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, [clearStreaming])
|
|
89
|
+
|
|
90
|
+
const processNextMessage = useCallback(() => {
|
|
91
|
+
const queuedList = queuedMessagesRef.current
|
|
92
|
+
const queueLength = queuedList.length
|
|
93
|
+
|
|
94
|
+
if (queueLength === 0) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check if user has explicitly paused the queue
|
|
99
|
+
if (isQueuePausedRef.current) {
|
|
100
|
+
logger.debug(
|
|
101
|
+
{ queueLength },
|
|
102
|
+
'[message-queue] Queue blocked: user paused',
|
|
103
|
+
)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// External hold: sending is currently pointless (e.g. freebuff session
|
|
108
|
+
// fully ended — requests without a live session are rejected). Leave the
|
|
109
|
+
// messages queued; the effect below re-runs when sendBlocked flips false.
|
|
110
|
+
// No log here: unlike the transient busy branches above, this state can
|
|
111
|
+
// persist for the whole hold, and this path re-runs on every render —
|
|
112
|
+
// the transition is logged once by the caller instead.
|
|
113
|
+
if (sendBlocked) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!canProcessQueue) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
if (streamStatus !== 'idle') {
|
|
121
|
+
logger.debug(
|
|
122
|
+
{ queueLength, streamStatus },
|
|
123
|
+
'[message-queue] Queue blocked: stream not idle',
|
|
124
|
+
)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
if (streamMessageIdRef.current) {
|
|
128
|
+
logger.debug(
|
|
129
|
+
{ queueLength, streamMessageId: streamMessageIdRef.current },
|
|
130
|
+
'[message-queue] Queue blocked: streamMessageId set',
|
|
131
|
+
)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
if (isChainInProgressRef.current) {
|
|
135
|
+
logger.debug(
|
|
136
|
+
{ queueLength, isChainInProgress: isChainInProgressRef.current },
|
|
137
|
+
'[message-queue] Queue blocked: chain in progress',
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (activeAgentStreamsRef.current > 0) {
|
|
142
|
+
logger.debug(
|
|
143
|
+
{ queueLength, activeAgentStreams: activeAgentStreamsRef.current },
|
|
144
|
+
'[message-queue] Queue blocked: active agent streams',
|
|
145
|
+
)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (isProcessingQueueRef.current) {
|
|
150
|
+
logger.debug(
|
|
151
|
+
{ queueLength },
|
|
152
|
+
'[message-queue] Queue blocked: already processing',
|
|
153
|
+
)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
logger.info(
|
|
158
|
+
{ queueLength },
|
|
159
|
+
'[message-queue] Processing next message from queue',
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const processingOwner = Symbol('queue-processing-owner')
|
|
163
|
+
queueProcessingOwnerRef.current = processingOwner
|
|
164
|
+
isProcessingQueueRef.current = true
|
|
165
|
+
|
|
166
|
+
// Start watchdog timer to recover from stuck processing lock
|
|
167
|
+
if (watchdogTimeoutRef.current) {
|
|
168
|
+
clearTimeout(watchdogTimeoutRef.current)
|
|
169
|
+
}
|
|
170
|
+
watchdogTimeoutRef.current = setTimeout(() => {
|
|
171
|
+
if (queueProcessingOwnerRef.current !== processingOwner) return
|
|
172
|
+
if (isProcessingQueueRef.current) {
|
|
173
|
+
logger.warn(
|
|
174
|
+
{ stuckDurationMs: QUEUE_WATCHDOG_TIMEOUT_MS },
|
|
175
|
+
'[message-queue] Watchdog: isProcessingQueueRef stuck for too long, forcing reset',
|
|
176
|
+
)
|
|
177
|
+
// Also reset canProcessQueue to allow queue to resume (unless user-paused)
|
|
178
|
+
setCanProcessQueue(!isQueuePausedRef.current)
|
|
179
|
+
}
|
|
180
|
+
queueProcessingOwnerRef.current = null
|
|
181
|
+
isProcessingQueueRef.current = false
|
|
182
|
+
watchdogTimeoutRef.current = null
|
|
183
|
+
}, QUEUE_WATCHDOG_TIMEOUT_MS)
|
|
184
|
+
|
|
185
|
+
// Read the message to process from the synchronous queue source.
|
|
186
|
+
const messageToProcess = queuedMessagesRef.current[0]
|
|
187
|
+
|
|
188
|
+
if (!messageToProcess) {
|
|
189
|
+
queueProcessingOwnerRef.current = null
|
|
190
|
+
isProcessingQueueRef.current = false
|
|
191
|
+
// Clear watchdog timer on early return
|
|
192
|
+
if (watchdogTimeoutRef.current) {
|
|
193
|
+
clearTimeout(watchdogTimeoutRef.current)
|
|
194
|
+
watchdogTimeoutRef.current = null
|
|
195
|
+
}
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Remove it from both sources synchronously. Cancellation decisions read
|
|
200
|
+
// the ref between renders, so deferring this update inside a React state
|
|
201
|
+
// updater can make an in-flight message look queued.
|
|
202
|
+
writeQueue(queuedMessagesRef.current.slice(1))
|
|
203
|
+
|
|
204
|
+
sendMessage(messageToProcess)
|
|
205
|
+
.catch((err: unknown) => {
|
|
206
|
+
logger.warn(
|
|
207
|
+
{ error: err },
|
|
208
|
+
'[message-queue] sendMessage promise rejected',
|
|
209
|
+
)
|
|
210
|
+
})
|
|
211
|
+
.finally(() => {
|
|
212
|
+
if (queueProcessingOwnerRef.current !== processingOwner) return
|
|
213
|
+
queueProcessingOwnerRef.current = null
|
|
214
|
+
isProcessingQueueRef.current = false
|
|
215
|
+
// Clear watchdog timer when processing completes normally
|
|
216
|
+
if (watchdogTimeoutRef.current) {
|
|
217
|
+
clearTimeout(watchdogTimeoutRef.current)
|
|
218
|
+
watchdogTimeoutRef.current = null
|
|
219
|
+
}
|
|
220
|
+
logger.debug('[message-queue] Processing lock released')
|
|
221
|
+
})
|
|
222
|
+
}, [
|
|
223
|
+
canProcessQueue,
|
|
224
|
+
streamStatus,
|
|
225
|
+
sendMessage,
|
|
226
|
+
sendBlocked,
|
|
227
|
+
isChainInProgressRef,
|
|
228
|
+
activeAgentStreamsRef,
|
|
229
|
+
writeQueue,
|
|
230
|
+
])
|
|
231
|
+
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
processNextMessage()
|
|
234
|
+
}, [
|
|
235
|
+
canProcessQueue,
|
|
236
|
+
streamStatus,
|
|
237
|
+
queuedMessages.length,
|
|
238
|
+
processNextMessage,
|
|
239
|
+
isChainInProgressRef,
|
|
240
|
+
])
|
|
241
|
+
|
|
242
|
+
const addToQueue = useCallback(
|
|
243
|
+
(message: string, attachments: PendingAttachment[] = []) => {
|
|
244
|
+
const queuedMessage = {
|
|
245
|
+
id: newQueueId(),
|
|
246
|
+
content: message,
|
|
247
|
+
attachments,
|
|
248
|
+
}
|
|
249
|
+
writeQueue([...queuedMessagesRef.current, queuedMessage])
|
|
250
|
+
},
|
|
251
|
+
[writeQueue],
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
/** Put a message back at the HEAD of the queue. Used when a send was
|
|
255
|
+
* aborted before it did anything (e.g. the freebuff session ended between
|
|
256
|
+
* dequeue and run start) so the message keeps its place instead of being
|
|
257
|
+
* consumed. */
|
|
258
|
+
const addToQueueFront = useCallback(
|
|
259
|
+
(message: Omit<QueuedMessage, 'id'>) => {
|
|
260
|
+
writeQueue([
|
|
261
|
+
{ ...message, id: newQueueId() },
|
|
262
|
+
...queuedMessagesRef.current,
|
|
263
|
+
])
|
|
264
|
+
},
|
|
265
|
+
[writeQueue],
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
/** Replace a queued message's text, keeping its place and attachments.
|
|
269
|
+
* Returns false when the message is no longer queued — it started running
|
|
270
|
+
* between the editor's render and this call, and rewriting a prompt the
|
|
271
|
+
* agent is already working on would be a lie either way. */
|
|
272
|
+
const editQueuedMessage = useCallback(
|
|
273
|
+
(id: string, content: string): boolean => {
|
|
274
|
+
const current = queuedMessagesRef.current
|
|
275
|
+
const index = current.findIndex((message) => message.id === id)
|
|
276
|
+
if (index === -1) return false
|
|
277
|
+
|
|
278
|
+
const next = [...current]
|
|
279
|
+
next[index] = { ...next[index]!, content }
|
|
280
|
+
writeQueue(next)
|
|
281
|
+
return true
|
|
282
|
+
},
|
|
283
|
+
[writeQueue],
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
/** Drop a single queued message. Returns false if it already left the
|
|
287
|
+
* queue, which is the caller's cue that it is running, not cancelled. */
|
|
288
|
+
const removeQueuedMessage = useCallback(
|
|
289
|
+
(id: string): boolean => {
|
|
290
|
+
const current = queuedMessagesRef.current
|
|
291
|
+
const next = current.filter((message) => message.id !== id)
|
|
292
|
+
if (next.length === current.length) return false
|
|
293
|
+
|
|
294
|
+
writeQueue(next)
|
|
295
|
+
return true
|
|
296
|
+
},
|
|
297
|
+
[writeQueue],
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
/** Move a queued message to `toIndex`, clamped to the queue's bounds so
|
|
301
|
+
* callers can pass index±1 at the edges without a guard. */
|
|
302
|
+
const moveQueuedMessage = useCallback(
|
|
303
|
+
(id: string, toIndex: number): boolean => {
|
|
304
|
+
const current = queuedMessagesRef.current
|
|
305
|
+
const from = current.findIndex((message) => message.id === id)
|
|
306
|
+
if (from === -1) return false
|
|
307
|
+
|
|
308
|
+
const to = Math.max(0, Math.min(current.length - 1, toIndex))
|
|
309
|
+
if (to === from) return false
|
|
310
|
+
|
|
311
|
+
const next = [...current]
|
|
312
|
+
const [moved] = next.splice(from, 1)
|
|
313
|
+
next.splice(to, 0, moved!)
|
|
314
|
+
writeQueue(next)
|
|
315
|
+
return true
|
|
316
|
+
},
|
|
317
|
+
[writeQueue],
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
const pauseQueue = useCallback(() => {
|
|
321
|
+
isQueuePausedRef.current = true
|
|
322
|
+
setQueuePausedState(true)
|
|
323
|
+
setCanProcessQueue(false)
|
|
324
|
+
}, [])
|
|
325
|
+
|
|
326
|
+
const pauseQueueIfPending = useCallback(() => {
|
|
327
|
+
if (queuedMessagesRef.current.length === 0) return
|
|
328
|
+
pauseQueue()
|
|
329
|
+
}, [pauseQueue])
|
|
330
|
+
|
|
331
|
+
const resumeQueue = useCallback(() => {
|
|
332
|
+
isQueuePausedRef.current = false
|
|
333
|
+
setQueuePausedState(false)
|
|
334
|
+
setCanProcessQueue(true)
|
|
335
|
+
}, [])
|
|
336
|
+
|
|
337
|
+
const clearQueue = useCallback(() => {
|
|
338
|
+
const current = queuedMessagesRef.current
|
|
339
|
+
writeQueue([])
|
|
340
|
+
return current
|
|
341
|
+
}, [writeQueue])
|
|
342
|
+
|
|
343
|
+
/** Drop queue state when leaving its chat. Unlike clearQueue (the user's
|
|
344
|
+
* Ctrl-C action), this also removes paused/processing bookkeeping so a
|
|
345
|
+
* same-provider /new cannot inherit a phantom paused queue. */
|
|
346
|
+
const discardQueue = useCallback(() => {
|
|
347
|
+
writeQueue([])
|
|
348
|
+
isQueuePausedRef.current = false
|
|
349
|
+
setQueuePausedState(false)
|
|
350
|
+
queueProcessingOwnerRef.current = null
|
|
351
|
+
isProcessingQueueRef.current = false
|
|
352
|
+
if (watchdogTimeoutRef.current) {
|
|
353
|
+
clearTimeout(watchdogTimeoutRef.current)
|
|
354
|
+
watchdogTimeoutRef.current = null
|
|
355
|
+
}
|
|
356
|
+
setCanProcessQueue(false)
|
|
357
|
+
}, [writeQueue])
|
|
358
|
+
|
|
359
|
+
const startStreaming = useCallback(() => {
|
|
360
|
+
setStreamStatus('streaming')
|
|
361
|
+
setCanProcessQueue(false)
|
|
362
|
+
}, [])
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
queuedMessages,
|
|
366
|
+
streamStatus,
|
|
367
|
+
canProcessQueue,
|
|
368
|
+
queuePaused,
|
|
369
|
+
streamMessageIdRef,
|
|
370
|
+
addToQueue,
|
|
371
|
+
addToQueueFront,
|
|
372
|
+
editQueuedMessage,
|
|
373
|
+
removeQueuedMessage,
|
|
374
|
+
moveQueuedMessage,
|
|
375
|
+
startStreaming,
|
|
376
|
+
setStreamStatus,
|
|
377
|
+
clearStreaming,
|
|
378
|
+
setCanProcessQueue,
|
|
379
|
+
pauseQueue,
|
|
380
|
+
pauseQueueIfPending,
|
|
381
|
+
resumeQueue,
|
|
382
|
+
clearQueue,
|
|
383
|
+
discardQueue,
|
|
384
|
+
isQueuePausedRef,
|
|
385
|
+
isProcessingQueueRef,
|
|
386
|
+
}
|
|
387
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns `Date.now()`, refreshed at the given interval. Pass `enabled: false`
|
|
5
|
+
* to freeze the timer (and cancel the interval). Multiple components can call
|
|
6
|
+
* this independently; setIntervals are cheap and React batches the resulting
|
|
7
|
+
* renders.
|
|
8
|
+
*
|
|
9
|
+
* Intended for short-lived UI countdowns like the freebuff session timer or
|
|
10
|
+
* elapsed-in-queue display.
|
|
11
|
+
*/
|
|
12
|
+
export function useNow(intervalMs: number, enabled = true): number {
|
|
13
|
+
const [now, setNow] = useState(() => Date.now())
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!enabled) return
|
|
16
|
+
const id = setInterval(() => setNow(Date.now()), intervalMs)
|
|
17
|
+
return () => clearInterval(id)
|
|
18
|
+
}, [intervalMs, enabled])
|
|
19
|
+
return now
|
|
20
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
import { useCallback } from 'react'
|
|
5
|
+
|
|
6
|
+
import { getPathCompletion } from '../utils/path-completion'
|
|
7
|
+
|
|
8
|
+
export interface UsePathTabCompletionOptions {
|
|
9
|
+
/** Current search query */
|
|
10
|
+
searchQuery: string
|
|
11
|
+
/** Set the search query */
|
|
12
|
+
setSearchQuery: (query: string) => void
|
|
13
|
+
/** Current directory path */
|
|
14
|
+
currentPath: string
|
|
15
|
+
/** Set the current directory path */
|
|
16
|
+
setCurrentPath: (path: string) => void
|
|
17
|
+
/** Function to expand ~ to home directory */
|
|
18
|
+
expandPath: (inputPath: string) => string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UsePathTabCompletionReturn {
|
|
22
|
+
/** Handle tab completion, returns true to indicate key was handled */
|
|
23
|
+
handleTabCompletion: () => boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Hook for path tab completion.
|
|
28
|
+
* Handles both absolute (/, ~) and relative path completion.
|
|
29
|
+
* Always navigates to completed directories when completion ends with /.
|
|
30
|
+
*/
|
|
31
|
+
export function usePathTabCompletion({
|
|
32
|
+
searchQuery,
|
|
33
|
+
setSearchQuery,
|
|
34
|
+
currentPath,
|
|
35
|
+
setCurrentPath,
|
|
36
|
+
expandPath,
|
|
37
|
+
}: UsePathTabCompletionOptions): UsePathTabCompletionReturn {
|
|
38
|
+
const handleTabCompletion = useCallback((): boolean => {
|
|
39
|
+
if (searchQuery.startsWith('/') || searchQuery.startsWith('~')) {
|
|
40
|
+
// Absolute path completion
|
|
41
|
+
const completed = getPathCompletion(searchQuery)
|
|
42
|
+
if (completed) {
|
|
43
|
+
// If completion is a full directory (ends with /), navigate there and keep the path in input
|
|
44
|
+
if (completed.endsWith('/')) {
|
|
45
|
+
const dirPath = expandPath(completed.slice(0, -1))
|
|
46
|
+
try {
|
|
47
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
|
|
48
|
+
setCurrentPath(dirPath)
|
|
49
|
+
setSearchQuery(completed)
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// Fall through to just set the query
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
setSearchQuery(completed)
|
|
57
|
+
}
|
|
58
|
+
} else if (searchQuery.length > 0) {
|
|
59
|
+
// Relative path completion - try from current directory
|
|
60
|
+
const relativePath = path.join(currentPath, searchQuery)
|
|
61
|
+
const completed = getPathCompletion(relativePath)
|
|
62
|
+
if (completed) {
|
|
63
|
+
// If completion is a full directory (ends with /), navigate there and keep the path in input
|
|
64
|
+
if (completed.endsWith('/')) {
|
|
65
|
+
try {
|
|
66
|
+
const dirPath = completed.slice(0, -1)
|
|
67
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
|
|
68
|
+
setCurrentPath(dirPath)
|
|
69
|
+
setSearchQuery(completed)
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// Fall through to just set the query
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Convert back to relative path for display
|
|
77
|
+
if (completed.startsWith(currentPath + path.sep)) {
|
|
78
|
+
setSearchQuery(completed.slice(currentPath.length + 1))
|
|
79
|
+
} else {
|
|
80
|
+
setSearchQuery(completed)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return true
|
|
85
|
+
}, [searchQuery, setSearchQuery, currentPath, setCurrentPath, expandPath])
|
|
86
|
+
|
|
87
|
+
return { handleTabCompletion }
|
|
88
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query'
|
|
2
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
handlePublish as defaultHandlePublish,
|
|
6
|
+
type PublishResult,
|
|
7
|
+
} from '../commands/publish'
|
|
8
|
+
import { usePublishStore } from '../state/publish-store'
|
|
9
|
+
|
|
10
|
+
// Query keys for type-safe cache management
|
|
11
|
+
export const publishQueryKeys = {
|
|
12
|
+
all: ['publish'] as const,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UsePublishMutationDeps {
|
|
16
|
+
handlePublish?: (agentIds: string[]) => Promise<PublishResult>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook for publishing agents to the agent store
|
|
21
|
+
* Uses TanStack Query mutation for proper state management
|
|
22
|
+
*/
|
|
23
|
+
export function usePublishMutation(deps: UsePublishMutationDeps = {}) {
|
|
24
|
+
const { handlePublish = defaultHandlePublish } = deps
|
|
25
|
+
|
|
26
|
+
const { setIsPublishing, setSuccessResult, setErrorResult } = usePublishStore(
|
|
27
|
+
useShallow((state) => ({
|
|
28
|
+
setIsPublishing: state.setIsPublishing,
|
|
29
|
+
setSuccessResult: state.setSuccessResult,
|
|
30
|
+
setErrorResult: state.setErrorResult,
|
|
31
|
+
})),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return useMutation({
|
|
35
|
+
mutationFn: async (agentIds: string[]) => {
|
|
36
|
+
setIsPublishing(true)
|
|
37
|
+
return handlePublish(agentIds)
|
|
38
|
+
},
|
|
39
|
+
onSuccess: (result) => {
|
|
40
|
+
if (result.success && result.publisherId && result.agents) {
|
|
41
|
+
setSuccessResult({
|
|
42
|
+
publisherId: result.publisherId,
|
|
43
|
+
agents: result.agents,
|
|
44
|
+
})
|
|
45
|
+
} else {
|
|
46
|
+
setErrorResult({
|
|
47
|
+
error: result.error || 'Unknown error',
|
|
48
|
+
details: result.details,
|
|
49
|
+
hint: result.hint,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
onError: (error) => {
|
|
54
|
+
setErrorResult({
|
|
55
|
+
error: 'Publish failed',
|
|
56
|
+
details: error instanceof Error ? error.message : String(error),
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { QueuedMessage } from './use-message-queue'
|
|
4
|
+
|
|
5
|
+
interface UseQueueControlsParams {
|
|
6
|
+
queuePaused: boolean
|
|
7
|
+
queuedCount: number
|
|
8
|
+
clearQueue: () => QueuedMessage[]
|
|
9
|
+
resumeQueue: () => void
|
|
10
|
+
inputHasText: boolean
|
|
11
|
+
baseHandleCtrlC: () => true
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type QueueCtrlCHandlerOptions = UseQueueControlsParams
|
|
15
|
+
|
|
16
|
+
export const createQueueCtrlCHandler = ({
|
|
17
|
+
queuePaused,
|
|
18
|
+
queuedCount,
|
|
19
|
+
clearQueue,
|
|
20
|
+
resumeQueue,
|
|
21
|
+
inputHasText,
|
|
22
|
+
baseHandleCtrlC,
|
|
23
|
+
}: QueueCtrlCHandlerOptions) => () => {
|
|
24
|
+
if (queuePaused && queuedCount > 0 && !inputHasText) {
|
|
25
|
+
clearQueue()
|
|
26
|
+
resumeQueue()
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
return baseHandleCtrlC()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const useQueueControls = ({
|
|
33
|
+
queuePaused,
|
|
34
|
+
queuedCount,
|
|
35
|
+
clearQueue,
|
|
36
|
+
resumeQueue,
|
|
37
|
+
inputHasText,
|
|
38
|
+
baseHandleCtrlC,
|
|
39
|
+
}: UseQueueControlsParams) => {
|
|
40
|
+
const handleCtrlC = useCallback(
|
|
41
|
+
createQueueCtrlCHandler({
|
|
42
|
+
queuePaused,
|
|
43
|
+
queuedCount,
|
|
44
|
+
clearQueue,
|
|
45
|
+
resumeQueue,
|
|
46
|
+
inputHasText,
|
|
47
|
+
baseHandleCtrlC,
|
|
48
|
+
}),
|
|
49
|
+
[
|
|
50
|
+
baseHandleCtrlC,
|
|
51
|
+
clearQueue,
|
|
52
|
+
inputHasText,
|
|
53
|
+
queuePaused,
|
|
54
|
+
queuedCount,
|
|
55
|
+
resumeQueue,
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const ensureQueueActiveBeforeSubmit = useCallback(() => {
|
|
60
|
+
if (queuePaused) {
|
|
61
|
+
resumeQueue()
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
return false
|
|
65
|
+
}, [queuePaused, resumeQueue])
|
|
66
|
+
|
|
67
|
+
return { handleCtrlC, ensureQueueActiveBeforeSubmit }
|
|
68
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { pluralize } from '@anvil/common/util/string'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import { formatQueuedPreview } from '../utils/helpers'
|
|
6
|
+
|
|
7
|
+
import type { QueuedMessage } from './use-message-queue'
|
|
8
|
+
|
|
9
|
+
interface UseQueueUiParams {
|
|
10
|
+
queuePaused: boolean
|
|
11
|
+
queuedMessages: QueuedMessage[]
|
|
12
|
+
separatorWidth: number
|
|
13
|
+
terminalWidth: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Below this the title has no room for a second segment, so the expand hint
|
|
17
|
+
* is omitted rather than crowding out the preview itself. */
|
|
18
|
+
const HINT_MIN_WIDTH = 80
|
|
19
|
+
|
|
20
|
+
export const useQueueUi = ({
|
|
21
|
+
queuePaused,
|
|
22
|
+
queuedMessages,
|
|
23
|
+
separatorWidth,
|
|
24
|
+
terminalWidth,
|
|
25
|
+
}: UseQueueUiParams) => {
|
|
26
|
+
const queuedCount = queuedMessages.length
|
|
27
|
+
const shouldShowQueuePreview = queuedCount > 0 && !queuePaused
|
|
28
|
+
|
|
29
|
+
/** The composer's border title: what is queued, and how to expand it.
|
|
30
|
+
* Queuing is discoverable (you just type while it runs); editing what you
|
|
31
|
+
* queued is not, so the hint rides along with the preview. */
|
|
32
|
+
const inputBoxTitle = useMemo(() => {
|
|
33
|
+
let preview: string | undefined
|
|
34
|
+
if (shouldShowQueuePreview) {
|
|
35
|
+
preview = formatQueuedPreview(
|
|
36
|
+
queuedMessages,
|
|
37
|
+
Math.max(30, separatorWidth - 20),
|
|
38
|
+
)
|
|
39
|
+
} else if (queuePaused && queuedCount > 0) {
|
|
40
|
+
preview = `⏸ ${pluralize(queuedCount, 'message')} queued — your next message sends first`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!preview) return undefined
|
|
44
|
+
if (terminalWidth < HINT_MIN_WIDTH) return ` ▸ ${preview} `
|
|
45
|
+
return ` ▸ ${preview} click to expand `
|
|
46
|
+
}, [
|
|
47
|
+
shouldShowQueuePreview,
|
|
48
|
+
queuePaused,
|
|
49
|
+
queuedCount,
|
|
50
|
+
queuedMessages,
|
|
51
|
+
separatorWidth,
|
|
52
|
+
terminalWidth,
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
const inputPlaceholder = useMemo(() => {
|
|
56
|
+
const base =
|
|
57
|
+
terminalWidth < 65
|
|
58
|
+
? 'What are we building?'
|
|
59
|
+
: 'What are we building? ( / for commands )'
|
|
60
|
+
|
|
61
|
+
if (queuePaused && queuedCount > 0) {
|
|
62
|
+
return 'Ctrl-C to cancel queued messages'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return base
|
|
66
|
+
}, [queuePaused, queuedCount, terminalWidth])
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
queuedCount,
|
|
70
|
+
shouldShowQueuePreview,
|
|
71
|
+
inputBoxTitle,
|
|
72
|
+
inputPlaceholder,
|
|
73
|
+
}
|
|
74
|
+
}
|