@amodalai/amodal 0.1.2
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/src/auth/index.d.ts +13 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +10 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/oauth2.d.ts +51 -0
- package/dist/src/auth/oauth2.d.ts.map +1 -0
- package/dist/src/auth/oauth2.js +196 -0
- package/dist/src/auth/oauth2.js.map +1 -0
- package/dist/src/auth/prompt.d.ts +21 -0
- package/dist/src/auth/prompt.d.ts.map +1 -0
- package/dist/src/auth/prompt.js +81 -0
- package/dist/src/auth/prompt.js.map +1 -0
- package/dist/src/auth/test-connection.d.ts +27 -0
- package/dist/src/auth/test-connection.d.ts.map +1 -0
- package/dist/src/auth/test-connection.js +153 -0
- package/dist/src/auth/test-connection.js.map +1 -0
- package/dist/src/auth/types.d.ts +32 -0
- package/dist/src/auth/types.d.ts.map +1 -0
- package/dist/src/auth/types.js +7 -0
- package/dist/src/auth/types.js.map +1 -0
- package/dist/src/commands/audit.d.ts +18 -0
- package/dist/src/commands/audit.d.ts.map +1 -0
- package/dist/src/commands/audit.js +86 -0
- package/dist/src/commands/audit.js.map +1 -0
- package/dist/src/commands/automations.d.ts +28 -0
- package/dist/src/commands/automations.d.ts.map +1 -0
- package/dist/src/commands/automations.js +179 -0
- package/dist/src/commands/automations.js.map +1 -0
- package/dist/src/commands/build-manifest-types.d.ts +33 -0
- package/dist/src/commands/build-manifest-types.d.ts.map +1 -0
- package/dist/src/commands/build-manifest-types.js +30 -0
- package/dist/src/commands/build-manifest-types.js.map +1 -0
- package/dist/src/commands/build-tools.d.ts +33 -0
- package/dist/src/commands/build-tools.d.ts.map +1 -0
- package/dist/src/commands/build-tools.js +237 -0
- package/dist/src/commands/build-tools.js.map +1 -0
- package/dist/src/commands/build.d.ts +23 -0
- package/dist/src/commands/build.d.ts.map +1 -0
- package/dist/src/commands/build.js +120 -0
- package/dist/src/commands/build.js.map +1 -0
- package/dist/src/commands/chat.d.ts +26 -0
- package/dist/src/commands/chat.d.ts.map +1 -0
- package/dist/src/commands/chat.js +123 -0
- package/dist/src/commands/chat.js.map +1 -0
- package/dist/src/commands/connect.d.ts +18 -0
- package/dist/src/commands/connect.d.ts.map +1 -0
- package/dist/src/commands/connect.js +198 -0
- package/dist/src/commands/connect.js.map +1 -0
- package/dist/src/commands/deploy.d.ts +20 -0
- package/dist/src/commands/deploy.d.ts.map +1 -0
- package/dist/src/commands/deploy.js +137 -0
- package/dist/src/commands/deploy.js.map +1 -0
- package/dist/src/commands/deployments.d.ts +17 -0
- package/dist/src/commands/deployments.d.ts.map +1 -0
- package/dist/src/commands/deployments.js +77 -0
- package/dist/src/commands/deployments.js.map +1 -0
- package/dist/src/commands/dev.d.ts +17 -0
- package/dist/src/commands/dev.d.ts.map +1 -0
- package/dist/src/commands/dev.js +109 -0
- package/dist/src/commands/dev.js.map +1 -0
- package/dist/src/commands/diff.d.ts +19 -0
- package/dist/src/commands/diff.d.ts.map +1 -0
- package/dist/src/commands/diff.js +120 -0
- package/dist/src/commands/diff.js.map +1 -0
- package/dist/src/commands/docker.d.ts +21 -0
- package/dist/src/commands/docker.d.ts.map +1 -0
- package/dist/src/commands/docker.js +215 -0
- package/dist/src/commands/docker.js.map +1 -0
- package/dist/src/commands/eval.d.ts +20 -0
- package/dist/src/commands/eval.d.ts.map +1 -0
- package/dist/src/commands/eval.js +236 -0
- package/dist/src/commands/eval.js.map +1 -0
- package/dist/src/commands/experiment.d.ts +21 -0
- package/dist/src/commands/experiment.d.ts.map +1 -0
- package/dist/src/commands/experiment.js +133 -0
- package/dist/src/commands/experiment.js.map +1 -0
- package/dist/src/commands/index.d.ts +10 -0
- package/dist/src/commands/index.d.ts.map +1 -0
- package/dist/src/commands/index.js +75 -0
- package/dist/src/commands/index.js.map +1 -0
- package/dist/src/commands/init.d.ts +17 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +73 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/inspect.d.ts +22 -0
- package/dist/src/commands/inspect.d.ts.map +1 -0
- package/dist/src/commands/inspect.js +131 -0
- package/dist/src/commands/inspect.js.map +1 -0
- package/dist/src/commands/install-pkg.d.ts +29 -0
- package/dist/src/commands/install-pkg.d.ts.map +1 -0
- package/dist/src/commands/install-pkg.js +202 -0
- package/dist/src/commands/install-pkg.js.map +1 -0
- package/dist/src/commands/link.d.ts +32 -0
- package/dist/src/commands/link.d.ts.map +1 -0
- package/dist/src/commands/link.js +227 -0
- package/dist/src/commands/link.js.map +1 -0
- package/dist/src/commands/list.d.ts +19 -0
- package/dist/src/commands/list.d.ts.map +1 -0
- package/dist/src/commands/list.js +78 -0
- package/dist/src/commands/list.js.map +1 -0
- package/dist/src/commands/login.d.ts +31 -0
- package/dist/src/commands/login.d.ts.map +1 -0
- package/dist/src/commands/login.js +205 -0
- package/dist/src/commands/login.js.map +1 -0
- package/dist/src/commands/promote.d.ts +16 -0
- package/dist/src/commands/promote.d.ts.map +1 -0
- package/dist/src/commands/promote.js +55 -0
- package/dist/src/commands/promote.js.map +1 -0
- package/dist/src/commands/publish.d.ts +18 -0
- package/dist/src/commands/publish.d.ts.map +1 -0
- package/dist/src/commands/publish.js +122 -0
- package/dist/src/commands/publish.js.map +1 -0
- package/dist/src/commands/rollback.d.ts +17 -0
- package/dist/src/commands/rollback.d.ts.map +1 -0
- package/dist/src/commands/rollback.js +62 -0
- package/dist/src/commands/rollback.js.map +1 -0
- package/dist/src/commands/search.d.ts +20 -0
- package/dist/src/commands/search.d.ts.map +1 -0
- package/dist/src/commands/search.js +133 -0
- package/dist/src/commands/search.js.map +1 -0
- package/dist/src/commands/secrets.d.ts +20 -0
- package/dist/src/commands/secrets.d.ts.map +1 -0
- package/dist/src/commands/secrets.js +137 -0
- package/dist/src/commands/secrets.js.map +1 -0
- package/dist/src/commands/serve.d.ts +23 -0
- package/dist/src/commands/serve.d.ts.map +1 -0
- package/dist/src/commands/serve.js +144 -0
- package/dist/src/commands/serve.js.map +1 -0
- package/dist/src/commands/status.d.ts +16 -0
- package/dist/src/commands/status.d.ts.map +1 -0
- package/dist/src/commands/status.js +83 -0
- package/dist/src/commands/status.js.map +1 -0
- package/dist/src/commands/sync.d.ts +21 -0
- package/dist/src/commands/sync.d.ts.map +1 -0
- package/dist/src/commands/sync.js +94 -0
- package/dist/src/commands/sync.js.map +1 -0
- package/dist/src/commands/test-query.d.ts +19 -0
- package/dist/src/commands/test-query.d.ts.map +1 -0
- package/dist/src/commands/test-query.js +116 -0
- package/dist/src/commands/test-query.js.map +1 -0
- package/dist/src/commands/uninstall.d.ts +19 -0
- package/dist/src/commands/uninstall.d.ts.map +1 -0
- package/dist/src/commands/uninstall.js +84 -0
- package/dist/src/commands/uninstall.js.map +1 -0
- package/dist/src/commands/update.d.ts +21 -0
- package/dist/src/commands/update.d.ts.map +1 -0
- package/dist/src/commands/update.js +145 -0
- package/dist/src/commands/update.js.map +1 -0
- package/dist/src/commands/validate.d.ts +19 -0
- package/dist/src/commands/validate.d.ts.map +1 -0
- package/dist/src/commands/validate.js +114 -0
- package/dist/src/commands/validate.js.map +1 -0
- package/dist/src/fixtures/incident-response.d.ts +91 -0
- package/dist/src/fixtures/incident-response.d.ts.map +1 -0
- package/dist/src/fixtures/incident-response.js +208 -0
- package/dist/src/fixtures/incident-response.js.map +1 -0
- package/dist/src/main.d.ts +8 -0
- package/dist/src/main.d.ts.map +1 -0
- package/dist/src/main.js +30 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/shared/platform-client.d.ts +92 -0
- package/dist/src/shared/platform-client.d.ts.map +1 -0
- package/dist/src/shared/platform-client.js +155 -0
- package/dist/src/shared/platform-client.js.map +1 -0
- package/dist/src/shared/repo-discovery.d.ts +11 -0
- package/dist/src/shared/repo-discovery.d.ts.map +1 -0
- package/dist/src/shared/repo-discovery.js +33 -0
- package/dist/src/shared/repo-discovery.js.map +1 -0
- package/dist/src/templates/compose-template.d.ts +10 -0
- package/dist/src/templates/compose-template.d.ts.map +1 -0
- package/dist/src/templates/compose-template.js +30 -0
- package/dist/src/templates/compose-template.js.map +1 -0
- package/dist/src/templates/config-template.d.ts +14 -0
- package/dist/src/templates/config-template.d.ts.map +1 -0
- package/dist/src/templates/config-template.js +35 -0
- package/dist/src/templates/config-template.js.map +1 -0
- package/dist/src/templates/dockerfile-template.d.ts +10 -0
- package/dist/src/templates/dockerfile-template.d.ts.map +1 -0
- package/dist/src/templates/dockerfile-template.js +30 -0
- package/dist/src/templates/dockerfile-template.js.map +1 -0
- package/dist/src/templates/env-template.d.ts +12 -0
- package/dist/src/templates/env-template.d.ts.map +1 -0
- package/dist/src/templates/env-template.js +24 -0
- package/dist/src/templates/env-template.js.map +1 -0
- package/dist/src/templates/knowledge-template.d.ts +10 -0
- package/dist/src/templates/knowledge-template.d.ts.map +1 -0
- package/dist/src/templates/knowledge-template.js +24 -0
- package/dist/src/templates/knowledge-template.js.map +1 -0
- package/dist/src/templates/skill-template.d.ts +10 -0
- package/dist/src/templates/skill-template.d.ts.map +1 -0
- package/dist/src/templates/skill-template.js +27 -0
- package/dist/src/templates/skill-template.js.map +1 -0
- package/dist/src/ui/AskUserPrompt.d.ts +14 -0
- package/dist/src/ui/AskUserPrompt.d.ts.map +1 -0
- package/dist/src/ui/AskUserPrompt.js +17 -0
- package/dist/src/ui/AskUserPrompt.js.map +1 -0
- package/dist/src/ui/AssistantMessage.d.ts +14 -0
- package/dist/src/ui/AssistantMessage.d.ts.map +1 -0
- package/dist/src/ui/AssistantMessage.js +8 -0
- package/dist/src/ui/AssistantMessage.js.map +1 -0
- package/dist/src/ui/ChatApp.d.ts +15 -0
- package/dist/src/ui/ChatApp.d.ts.map +1 -0
- package/dist/src/ui/ChatApp.js +144 -0
- package/dist/src/ui/ChatApp.js.map +1 -0
- package/dist/src/ui/ConfirmationPrompt.d.ts +17 -0
- package/dist/src/ui/ConfirmationPrompt.d.ts.map +1 -0
- package/dist/src/ui/ConfirmationPrompt.js +21 -0
- package/dist/src/ui/ConfirmationPrompt.js.map +1 -0
- package/dist/src/ui/DiffRenderer.d.ts +32 -0
- package/dist/src/ui/DiffRenderer.d.ts.map +1 -0
- package/dist/src/ui/DiffRenderer.js +118 -0
- package/dist/src/ui/DiffRenderer.js.map +1 -0
- package/dist/src/ui/ExpandableContent.d.ts +15 -0
- package/dist/src/ui/ExpandableContent.d.ts.map +1 -0
- package/dist/src/ui/ExpandableContent.js +22 -0
- package/dist/src/ui/ExpandableContent.js.map +1 -0
- package/dist/src/ui/ExploreIndicator.d.ts +13 -0
- package/dist/src/ui/ExploreIndicator.d.ts.map +1 -0
- package/dist/src/ui/ExploreIndicator.js +14 -0
- package/dist/src/ui/ExploreIndicator.js.map +1 -0
- package/dist/src/ui/Footer.d.ts +19 -0
- package/dist/src/ui/Footer.d.ts.map +1 -0
- package/dist/src/ui/Footer.js +57 -0
- package/dist/src/ui/Footer.js.map +1 -0
- package/dist/src/ui/FullScreenLayout.d.ts +18 -0
- package/dist/src/ui/FullScreenLayout.d.ts.map +1 -0
- package/dist/src/ui/FullScreenLayout.js +14 -0
- package/dist/src/ui/FullScreenLayout.js.map +1 -0
- package/dist/src/ui/Header.d.ts +14 -0
- package/dist/src/ui/Header.d.ts.map +1 -0
- package/dist/src/ui/Header.js +11 -0
- package/dist/src/ui/Header.js.map +1 -0
- package/dist/src/ui/InputBar.d.ts +17 -0
- package/dist/src/ui/InputBar.d.ts.map +1 -0
- package/dist/src/ui/InputBar.js +49 -0
- package/dist/src/ui/InputBar.js.map +1 -0
- package/dist/src/ui/MessageList.d.ts +18 -0
- package/dist/src/ui/MessageList.d.ts.map +1 -0
- package/dist/src/ui/MessageList.js +9 -0
- package/dist/src/ui/MessageList.js.map +1 -0
- package/dist/src/ui/NotificationBar.d.ts +14 -0
- package/dist/src/ui/NotificationBar.d.ts.map +1 -0
- package/dist/src/ui/NotificationBar.js +38 -0
- package/dist/src/ui/NotificationBar.js.map +1 -0
- package/dist/src/ui/ScrollableMessageList.d.ts +27 -0
- package/dist/src/ui/ScrollableMessageList.d.ts.map +1 -0
- package/dist/src/ui/ScrollableMessageList.js +16 -0
- package/dist/src/ui/ScrollableMessageList.js.map +1 -0
- package/dist/src/ui/SessionBrowser.d.ts +20 -0
- package/dist/src/ui/SessionBrowser.d.ts.map +1 -0
- package/dist/src/ui/SessionBrowser.js +93 -0
- package/dist/src/ui/SessionBrowser.js.map +1 -0
- package/dist/src/ui/StatusMessage.d.ts +13 -0
- package/dist/src/ui/StatusMessage.d.ts.map +1 -0
- package/dist/src/ui/StatusMessage.js +17 -0
- package/dist/src/ui/StatusMessage.js.map +1 -0
- package/dist/src/ui/StreamingView.d.ts +19 -0
- package/dist/src/ui/StreamingView.d.ts.map +1 -0
- package/dist/src/ui/StreamingView.js +18 -0
- package/dist/src/ui/StreamingView.js.map +1 -0
- package/dist/src/ui/SubagentDisplay.d.ts +13 -0
- package/dist/src/ui/SubagentDisplay.d.ts.map +1 -0
- package/dist/src/ui/SubagentDisplay.js +22 -0
- package/dist/src/ui/SubagentDisplay.js.map +1 -0
- package/dist/src/ui/ThinkingDisplay.d.ts +13 -0
- package/dist/src/ui/ThinkingDisplay.d.ts.map +1 -0
- package/dist/src/ui/ThinkingDisplay.js +15 -0
- package/dist/src/ui/ThinkingDisplay.js.map +1 -0
- package/dist/src/ui/ToolCallDisplay.d.ts +16 -0
- package/dist/src/ui/ToolCallDisplay.d.ts.map +1 -0
- package/dist/src/ui/ToolCallDisplay.js +136 -0
- package/dist/src/ui/ToolCallDisplay.js.map +1 -0
- package/dist/src/ui/UserMessage.d.ts +12 -0
- package/dist/src/ui/UserMessage.d.ts.map +1 -0
- package/dist/src/ui/UserMessage.js +5 -0
- package/dist/src/ui/UserMessage.js.map +1 -0
- package/dist/src/ui/commands/clear.d.ts +7 -0
- package/dist/src/ui/commands/clear.d.ts.map +1 -0
- package/dist/src/ui/commands/clear.js +13 -0
- package/dist/src/ui/commands/clear.js.map +1 -0
- package/dist/src/ui/commands/help.d.ts +7 -0
- package/dist/src/ui/commands/help.d.ts.map +1 -0
- package/dist/src/ui/commands/help.js +26 -0
- package/dist/src/ui/commands/help.js.map +1 -0
- package/dist/src/ui/commands/index.d.ts +14 -0
- package/dist/src/ui/commands/index.d.ts.map +1 -0
- package/dist/src/ui/commands/index.js +15 -0
- package/dist/src/ui/commands/index.js.map +1 -0
- package/dist/src/ui/commands/model.d.ts +7 -0
- package/dist/src/ui/commands/model.d.ts.map +1 -0
- package/dist/src/ui/commands/model.js +16 -0
- package/dist/src/ui/commands/model.js.map +1 -0
- package/dist/src/ui/commands/registry.d.ts +30 -0
- package/dist/src/ui/commands/registry.d.ts.map +1 -0
- package/dist/src/ui/commands/registry.js +45 -0
- package/dist/src/ui/commands/registry.js.map +1 -0
- package/dist/src/ui/commands/sessions.d.ts +7 -0
- package/dist/src/ui/commands/sessions.d.ts.map +1 -0
- package/dist/src/ui/commands/sessions.js +13 -0
- package/dist/src/ui/commands/sessions.js.map +1 -0
- package/dist/src/ui/commands/stats.d.ts +7 -0
- package/dist/src/ui/commands/stats.d.ts.map +1 -0
- package/dist/src/ui/commands/stats.js +33 -0
- package/dist/src/ui/commands/stats.js.map +1 -0
- package/dist/src/ui/commands/theme.d.ts +7 -0
- package/dist/src/ui/commands/theme.d.ts.map +1 -0
- package/dist/src/ui/commands/theme.js +35 -0
- package/dist/src/ui/commands/theme.js.map +1 -0
- package/dist/src/ui/markdown/CodeBlock.d.ts +14 -0
- package/dist/src/ui/markdown/CodeBlock.d.ts.map +1 -0
- package/dist/src/ui/markdown/CodeBlock.js +55 -0
- package/dist/src/ui/markdown/CodeBlock.js.map +1 -0
- package/dist/src/ui/markdown/InlineRenderer.d.ts +12 -0
- package/dist/src/ui/markdown/InlineRenderer.d.ts.map +1 -0
- package/dist/src/ui/markdown/InlineRenderer.js +70 -0
- package/dist/src/ui/markdown/InlineRenderer.js.map +1 -0
- package/dist/src/ui/markdown/MarkdownDisplay.d.ts +17 -0
- package/dist/src/ui/markdown/MarkdownDisplay.d.ts.map +1 -0
- package/dist/src/ui/markdown/MarkdownDisplay.js +142 -0
- package/dist/src/ui/markdown/MarkdownDisplay.js.map +1 -0
- package/dist/src/ui/markdown/Table.d.ts +14 -0
- package/dist/src/ui/markdown/Table.d.ts.map +1 -0
- package/dist/src/ui/markdown/Table.js +31 -0
- package/dist/src/ui/markdown/Table.js.map +1 -0
- package/dist/src/ui/theme.d.ts +39 -0
- package/dist/src/ui/theme.d.ts.map +1 -0
- package/dist/src/ui/theme.js +39 -0
- package/dist/src/ui/theme.js.map +1 -0
- package/dist/src/ui/themes/index.d.ts +45 -0
- package/dist/src/ui/themes/index.d.ts.map +1 -0
- package/dist/src/ui/themes/index.js +73 -0
- package/dist/src/ui/themes/index.js.map +1 -0
- package/dist/src/ui/themes/light.d.ts +8 -0
- package/dist/src/ui/themes/light.d.ts.map +1 -0
- package/dist/src/ui/themes/light.js +37 -0
- package/dist/src/ui/themes/light.js.map +1 -0
- package/dist/src/ui/themes/monochrome.d.ts +8 -0
- package/dist/src/ui/themes/monochrome.d.ts.map +1 -0
- package/dist/src/ui/themes/monochrome.js +37 -0
- package/dist/src/ui/themes/monochrome.js.map +1 -0
- package/dist/src/ui/types.d.ts +191 -0
- package/dist/src/ui/types.d.ts.map +1 -0
- package/dist/src/ui/types.js +7 -0
- package/dist/src/ui/types.js.map +1 -0
- package/dist/src/ui/useAlternateBuffer.d.ts +11 -0
- package/dist/src/ui/useAlternateBuffer.d.ts.map +1 -0
- package/dist/src/ui/useAlternateBuffer.js +25 -0
- package/dist/src/ui/useAlternateBuffer.js.map +1 -0
- package/dist/src/ui/useChat.d.ts +18 -0
- package/dist/src/ui/useChat.d.ts.map +1 -0
- package/dist/src/ui/useChat.js +599 -0
- package/dist/src/ui/useChat.js.map +1 -0
- package/dist/src/ui/useElapsedTime.d.ts +11 -0
- package/dist/src/ui/useElapsedTime.d.ts.map +1 -0
- package/dist/src/ui/useElapsedTime.js +39 -0
- package/dist/src/ui/useElapsedTime.js.map +1 -0
- package/dist/src/ui/useResponsiveLayout.d.ts +16 -0
- package/dist/src/ui/useResponsiveLayout.d.ts.map +1 -0
- package/dist/src/ui/useResponsiveLayout.js +24 -0
- package/dist/src/ui/useResponsiveLayout.js.map +1 -0
- package/dist/src/ui/useScroll.d.ts +20 -0
- package/dist/src/ui/useScroll.d.ts.map +1 -0
- package/dist/src/ui/useScroll.js +64 -0
- package/dist/src/ui/useScroll.js.map +1 -0
- package/dist/src/ui/useSessionResume.d.ts +20 -0
- package/dist/src/ui/useSessionResume.d.ts.map +1 -0
- package/dist/src/ui/useSessionResume.js +77 -0
- package/dist/src/ui/useSessionResume.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +60 -0
- package/src/auth/index.ts +13 -0
- package/src/auth/oauth2.test.ts +305 -0
- package/src/auth/oauth2.ts +269 -0
- package/src/auth/prompt.test.ts +205 -0
- package/src/auth/prompt.ts +111 -0
- package/src/auth/test-connection.test.ts +224 -0
- package/src/auth/test-connection.ts +196 -0
- package/src/auth/types.ts +34 -0
- package/src/commands/audit.test.ts +92 -0
- package/src/commands/audit.ts +113 -0
- package/src/commands/automations.test.ts +85 -0
- package/src/commands/automations.ts +205 -0
- package/src/commands/build-manifest-types.ts +35 -0
- package/src/commands/build-tools.ts +281 -0
- package/src/commands/build.test.ts +63 -0
- package/src/commands/build.ts +135 -0
- package/src/commands/chat.ts +147 -0
- package/src/commands/command-exports.test.ts +88 -0
- package/src/commands/connect.test.ts +343 -0
- package/src/commands/connect.ts +237 -0
- package/src/commands/deploy.test.ts +124 -0
- package/src/commands/deploy.ts +153 -0
- package/src/commands/deployments.ts +90 -0
- package/src/commands/dev.test.ts +63 -0
- package/src/commands/dev.ts +124 -0
- package/src/commands/diff.test.ts +183 -0
- package/src/commands/diff.ts +143 -0
- package/src/commands/docker.ts +232 -0
- package/src/commands/eval.test.ts +88 -0
- package/src/commands/eval.ts +268 -0
- package/src/commands/experiment.test.ts +125 -0
- package/src/commands/experiment.ts +153 -0
- package/src/commands/index.ts +76 -0
- package/src/commands/init.test.ts +109 -0
- package/src/commands/init.ts +98 -0
- package/src/commands/inspect.test.ts +234 -0
- package/src/commands/inspect.ts +157 -0
- package/src/commands/install-pkg.test.ts +265 -0
- package/src/commands/install-pkg.ts +234 -0
- package/src/commands/link.ts +270 -0
- package/src/commands/list.test.ts +152 -0
- package/src/commands/list.ts +95 -0
- package/src/commands/login.test.ts +195 -0
- package/src/commands/login.ts +258 -0
- package/src/commands/promote.ts +64 -0
- package/src/commands/publish.test.ts +203 -0
- package/src/commands/publish.ts +142 -0
- package/src/commands/rollback.ts +72 -0
- package/src/commands/search.test.ts +174 -0
- package/src/commands/search.ts +154 -0
- package/src/commands/secrets.test.ts +168 -0
- package/src/commands/secrets.ts +163 -0
- package/src/commands/serve.ts +166 -0
- package/src/commands/status.ts +94 -0
- package/src/commands/sync.test.ts +130 -0
- package/src/commands/sync.ts +119 -0
- package/src/commands/test-query.test.ts +42 -0
- package/src/commands/test-query.ts +129 -0
- package/src/commands/uninstall.test.ts +162 -0
- package/src/commands/uninstall.ts +107 -0
- package/src/commands/update.test.ts +281 -0
- package/src/commands/update.ts +180 -0
- package/src/commands/validate.test.ts +260 -0
- package/src/commands/validate.ts +139 -0
- package/src/e2e-automations.test.ts +305 -0
- package/src/e2e-commands.test.ts +587 -0
- package/src/e2e-incident-response.test.ts +345 -0
- package/src/e2e-plugin-connections.test.ts +415 -0
- package/src/e2e-plugins.test.ts +492 -0
- package/src/e2e.test.ts +602 -0
- package/src/fixtures/incident-response.ts +232 -0
- package/src/main.ts +35 -0
- package/src/shared/platform-client.test.ts +106 -0
- package/src/shared/platform-client.ts +193 -0
- package/src/shared/repo-discovery.test.ts +56 -0
- package/src/shared/repo-discovery.ts +40 -0
- package/src/templates/compose-template.ts +30 -0
- package/src/templates/config-template.ts +44 -0
- package/src/templates/deployment-templates.test.ts +99 -0
- package/src/templates/dockerfile-template.ts +30 -0
- package/src/templates/env-template.ts +27 -0
- package/src/templates/knowledge-template.ts +24 -0
- package/src/templates/skill-template.ts +27 -0
- package/src/ui/AskUserPrompt.tsx +58 -0
- package/src/ui/AssistantMessage.tsx +51 -0
- package/src/ui/ChatApp.tsx +283 -0
- package/src/ui/ConfirmationPrompt.tsx +75 -0
- package/src/ui/DiffRenderer.test.ts +104 -0
- package/src/ui/DiffRenderer.tsx +188 -0
- package/src/ui/ExpandableContent.tsx +68 -0
- package/src/ui/ExploreIndicator.tsx +44 -0
- package/src/ui/Footer.tsx +87 -0
- package/src/ui/FullScreenLayout.tsx +45 -0
- package/src/ui/Header.tsx +50 -0
- package/src/ui/InputBar.tsx +105 -0
- package/src/ui/MessageList.tsx +31 -0
- package/src/ui/NotificationBar.tsx +64 -0
- package/src/ui/ScrollableMessageList.tsx +82 -0
- package/src/ui/SessionBrowser.test.ts +49 -0
- package/src/ui/SessionBrowser.tsx +179 -0
- package/src/ui/StatusMessage.tsx +35 -0
- package/src/ui/StreamingView.tsx +87 -0
- package/src/ui/SubagentDisplay.tsx +57 -0
- package/src/ui/ThinkingDisplay.tsx +45 -0
- package/src/ui/ToolCallDisplay.tsx +268 -0
- package/src/ui/UserMessage.tsx +22 -0
- package/src/ui/chat-e2e.test.ts +581 -0
- package/src/ui/commands/clear.ts +15 -0
- package/src/ui/commands/help.ts +31 -0
- package/src/ui/commands/index.ts +22 -0
- package/src/ui/commands/model.ts +19 -0
- package/src/ui/commands/registry.test.ts +76 -0
- package/src/ui/commands/registry.ts +66 -0
- package/src/ui/commands/sessions.ts +16 -0
- package/src/ui/commands/stats.ts +35 -0
- package/src/ui/commands/theme.ts +41 -0
- package/src/ui/markdown/CodeBlock.tsx +118 -0
- package/src/ui/markdown/InlineRenderer.tsx +115 -0
- package/src/ui/markdown/MarkdownDisplay.tsx +223 -0
- package/src/ui/markdown/Table.tsx +75 -0
- package/src/ui/theme.ts +39 -0
- package/src/ui/themes/index.ts +113 -0
- package/src/ui/themes/light.ts +39 -0
- package/src/ui/themes/monochrome.ts +39 -0
- package/src/ui/types.ts +157 -0
- package/src/ui/useAlternateBuffer.ts +27 -0
- package/src/ui/useChat.test.ts +492 -0
- package/src/ui/useChat.ts +693 -0
- package/src/ui/useElapsedTime.test.ts +33 -0
- package/src/ui/useElapsedTime.ts +43 -0
- package/src/ui/useResponsiveLayout.test.ts +54 -0
- package/src/ui/useResponsiveLayout.ts +32 -0
- package/src/ui/useScroll.test.ts +99 -0
- package/src/ui/useScroll.ts +97 -0
- package/src/ui/useSessionResume.test.ts +80 -0
- package/src/ui/useSessionResume.ts +102 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {describe, it, expect, beforeAll, afterAll} from 'vitest';
|
|
8
|
+
import http from 'node:http';
|
|
9
|
+
import {chatReducer, initialState} from './useChat.js';
|
|
10
|
+
import type {ChatState, ChatAction} from './types.js';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Mock SSE server — simulates the runtime /chat endpoint
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function createMockSSEServer(
|
|
17
|
+
eventsFn: () => Array<Record<string, unknown>>,
|
|
18
|
+
): http.Server {
|
|
19
|
+
return http.createServer((req, res) => {
|
|
20
|
+
if (req.method === 'POST' && req.url === '/chat') {
|
|
21
|
+
// Drain request body
|
|
22
|
+
let _body = '';
|
|
23
|
+
req.on('data', (chunk: Buffer) => {
|
|
24
|
+
_body += chunk.toString();
|
|
25
|
+
});
|
|
26
|
+
req.on('end', () => {
|
|
27
|
+
res.writeHead(200, {
|
|
28
|
+
'Content-Type': 'text/event-stream',
|
|
29
|
+
'Cache-Control': 'no-cache',
|
|
30
|
+
'Connection': 'keep-alive',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const events = eventsFn();
|
|
34
|
+
for (const event of events) {
|
|
35
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
36
|
+
}
|
|
37
|
+
res.end();
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
res.writeHead(404);
|
|
41
|
+
res.end();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Send a chat message to a mock server, collecting all dispatched actions.
|
|
48
|
+
*/
|
|
49
|
+
function sendChatAndCollectActions(
|
|
50
|
+
port: number,
|
|
51
|
+
message: string,
|
|
52
|
+
): Promise<ChatAction[]> {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const actions: ChatAction[] = [];
|
|
55
|
+
const body = JSON.stringify({message, tenant_id: 'test'});
|
|
56
|
+
|
|
57
|
+
const req = http.request(
|
|
58
|
+
{
|
|
59
|
+
hostname: '127.0.0.1',
|
|
60
|
+
port,
|
|
61
|
+
path: '/chat',
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
'Content-Length': Buffer.byteLength(body),
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
(res) => {
|
|
69
|
+
let buffer = '';
|
|
70
|
+
res.setEncoding('utf8');
|
|
71
|
+
|
|
72
|
+
res.on('data', (chunk: string) => {
|
|
73
|
+
buffer += chunk;
|
|
74
|
+
const lines = buffer.split('\n');
|
|
75
|
+
buffer = lines.pop() ?? '';
|
|
76
|
+
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
if (!line.startsWith('data: ')) continue;
|
|
79
|
+
try {
|
|
80
|
+
|
|
81
|
+
const event = JSON.parse(line.slice(6)) as Record<string, unknown>;
|
|
82
|
+
const action = sseEventToAction(event);
|
|
83
|
+
if (action) actions.push(action);
|
|
84
|
+
} catch {
|
|
85
|
+
// skip
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
res.on('end', () => {
|
|
91
|
+
// Process remaining buffer (same as useChat.ts safety net)
|
|
92
|
+
if (buffer.trim()) {
|
|
93
|
+
const line = buffer.trim();
|
|
94
|
+
if (line.startsWith('data: ')) {
|
|
95
|
+
try {
|
|
96
|
+
|
|
97
|
+
const event = JSON.parse(line.slice(6)) as Record<string, unknown>;
|
|
98
|
+
const action = sseEventToAction(event);
|
|
99
|
+
if (action) actions.push(action);
|
|
100
|
+
} catch {
|
|
101
|
+
// skip
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Safety net DONE
|
|
106
|
+
actions.push({type: 'DONE'});
|
|
107
|
+
resolve(actions);
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
req.on('error', reject);
|
|
113
|
+
req.write(body);
|
|
114
|
+
req.end();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Map SSE event to ChatAction (mirrors handleSSEEvent in useChat.ts).
|
|
120
|
+
*/
|
|
121
|
+
function sseEventToAction(event: Record<string, unknown>): ChatAction | null {
|
|
122
|
+
switch (event['type']) {
|
|
123
|
+
case 'init':
|
|
124
|
+
return {type: 'INIT', sessionId: String(event['session_id'])};
|
|
125
|
+
case 'text_delta':
|
|
126
|
+
return {type: 'TEXT_DELTA', content: String(event['content'] ?? '')};
|
|
127
|
+
case 'thinking_delta':
|
|
128
|
+
return {type: 'THINKING_DELTA', content: String(event['content'] ?? '')};
|
|
129
|
+
case 'tool_call_start':
|
|
130
|
+
return {
|
|
131
|
+
type: 'TOOL_CALL_START',
|
|
132
|
+
toolId: String(event['tool_id'] ?? event['tool_name']),
|
|
133
|
+
toolName: String(event['tool_name']),
|
|
134
|
+
|
|
135
|
+
args: (event['parameters'] as Record<string, unknown>) ?? {},
|
|
136
|
+
};
|
|
137
|
+
case 'tool_call_result':
|
|
138
|
+
return {
|
|
139
|
+
type: 'TOOL_CALL_RESULT',
|
|
140
|
+
toolId: String(event['tool_id'] ?? event['tool_name']),
|
|
141
|
+
status: event['status'] === 'error' ? 'error' : 'success',
|
|
142
|
+
result: event['result'] ? String(event['result']) : undefined,
|
|
143
|
+
error: event['error'] ? String(event['error']) : undefined,
|
|
144
|
+
durationMs: typeof event['duration_ms'] === 'number' ? event['duration_ms'] : undefined,
|
|
145
|
+
};
|
|
146
|
+
case 'skill_activated':
|
|
147
|
+
return {type: 'SKILL_ACTIVATED', skillName: String(event['skill_name'])};
|
|
148
|
+
case 'token_usage':
|
|
149
|
+
return {
|
|
150
|
+
type: 'TOKEN_USAGE',
|
|
151
|
+
inputTokens: typeof event['input_tokens'] === 'number' ? event['input_tokens'] : 0,
|
|
152
|
+
outputTokens: typeof event['output_tokens'] === 'number' ? event['output_tokens'] : 0,
|
|
153
|
+
model: typeof event['model'] === 'string' ? event['model'] : undefined,
|
|
154
|
+
};
|
|
155
|
+
case 'error':
|
|
156
|
+
return {type: 'ERROR', message: String(event['message'])};
|
|
157
|
+
case 'done':
|
|
158
|
+
return {type: 'DONE'};
|
|
159
|
+
default:
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Replay actions through the reducer and return final state.
|
|
166
|
+
*/
|
|
167
|
+
function replayActions(actions: ChatAction[]): ChatState {
|
|
168
|
+
// Start with SEND_MESSAGE to simulate user sending a message
|
|
169
|
+
let state = chatReducer(initialState, {type: 'SEND_MESSAGE', text: 'hello'});
|
|
170
|
+
for (const action of actions) {
|
|
171
|
+
state = chatReducer(state, action);
|
|
172
|
+
}
|
|
173
|
+
return state;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Tests
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
describe('Chat E2E: SSE → Reducer integration', () => {
|
|
181
|
+
let server: http.Server;
|
|
182
|
+
let port: number;
|
|
183
|
+
let serverEvents: Array<Record<string, unknown>>;
|
|
184
|
+
|
|
185
|
+
beforeAll(async () => {
|
|
186
|
+
serverEvents = [];
|
|
187
|
+
server = createMockSSEServer(() => serverEvents);
|
|
188
|
+
await new Promise<void>((resolve) => {
|
|
189
|
+
server.listen(0, '127.0.0.1', () => resolve());
|
|
190
|
+
});
|
|
191
|
+
const addr = server.address();
|
|
192
|
+
port = typeof addr === 'object' && addr !== null ? addr.port : 0;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
afterAll(async () => {
|
|
196
|
+
await new Promise<void>((resolve) => {
|
|
197
|
+
server.close(() => resolve());
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('processes a simple text response with init + text + done', async () => {
|
|
202
|
+
serverEvents = [
|
|
203
|
+
{type: 'init', session_id: 'sess-001', timestamp: new Date().toISOString()},
|
|
204
|
+
{type: 'text_delta', content: 'Hello, '},
|
|
205
|
+
{type: 'text_delta', content: 'world!'},
|
|
206
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
const actions = await sendChatAndCollectActions(port, 'hi');
|
|
210
|
+
const state = replayActions(actions);
|
|
211
|
+
|
|
212
|
+
expect(state.sessionId).toBe('sess-001');
|
|
213
|
+
expect(state.isStreaming).toBe(false);
|
|
214
|
+
expect(state.messages).toHaveLength(2); // user + assistant
|
|
215
|
+
expect(state.messages[0]?.role).toBe('user');
|
|
216
|
+
expect(state.messages[1]?.role).toBe('assistant');
|
|
217
|
+
expect(state.messages[1]?.text).toBe('Hello, world!');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('processes response with tool calls', async () => {
|
|
221
|
+
serverEvents = [
|
|
222
|
+
{type: 'init', session_id: 'sess-002', timestamp: new Date().toISOString()},
|
|
223
|
+
{type: 'tool_call_start', tool_name: 'request', tool_id: 't1', parameters: {url: '/api/test'}},
|
|
224
|
+
{type: 'tool_call_result', tool_id: 't1', status: 'success', result: '{"ok":true}', duration_ms: 150},
|
|
225
|
+
{type: 'text_delta', content: 'The API returned OK.'},
|
|
226
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const actions = await sendChatAndCollectActions(port, 'check api');
|
|
230
|
+
const state = replayActions(actions);
|
|
231
|
+
|
|
232
|
+
expect(state.isStreaming).toBe(false);
|
|
233
|
+
expect(state.messages).toHaveLength(2);
|
|
234
|
+
|
|
235
|
+
const assistant = state.messages[1];
|
|
236
|
+
expect(assistant?.text).toBe('The API returned OK.');
|
|
237
|
+
expect(assistant?.toolCalls).toHaveLength(1);
|
|
238
|
+
expect(assistant?.toolCalls?.[0]?.toolName).toBe('request');
|
|
239
|
+
expect(assistant?.toolCalls?.[0]?.status).toBe('success');
|
|
240
|
+
expect(assistant?.toolCalls?.[0]?.durationMs).toBe(150);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('processes response with token_usage event', async () => {
|
|
244
|
+
serverEvents = [
|
|
245
|
+
{type: 'init', session_id: 'sess-003', timestamp: new Date().toISOString()},
|
|
246
|
+
{type: 'text_delta', content: 'Answer.'},
|
|
247
|
+
{type: 'token_usage', input_tokens: 500, output_tokens: 120, model: 'claude-sonnet-4'},
|
|
248
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
const actions = await sendChatAndCollectActions(port, 'test');
|
|
252
|
+
const state = replayActions(actions);
|
|
253
|
+
|
|
254
|
+
expect(state.tokenUsage.totalInputTokens).toBe(500);
|
|
255
|
+
expect(state.tokenUsage.totalOutputTokens).toBe(120);
|
|
256
|
+
expect(state.tokenUsage.totalTokens).toBe(620);
|
|
257
|
+
expect(state.tokenUsage.model).toBe('claude-sonnet-4');
|
|
258
|
+
expect(state.tokenUsage.turnCount).toBe(1);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('handles done event correctly — does not produce empty response', async () => {
|
|
262
|
+
serverEvents = [
|
|
263
|
+
{type: 'init', session_id: 'sess-004', timestamp: new Date().toISOString()},
|
|
264
|
+
{type: 'text_delta', content: 'Non-empty response.'},
|
|
265
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const actions = await sendChatAndCollectActions(port, 'test');
|
|
269
|
+
const state = replayActions(actions);
|
|
270
|
+
|
|
271
|
+
// Should have exactly 2 messages (user + assistant)
|
|
272
|
+
expect(state.messages).toHaveLength(2);
|
|
273
|
+
expect(state.messages[1]?.text).toBe('Non-empty response.');
|
|
274
|
+
|
|
275
|
+
// The safety-net DONE at the end should be idempotent (no extra message)
|
|
276
|
+
expect(state.isStreaming).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('safety-net DONE does not create duplicate assistant message', async () => {
|
|
280
|
+
serverEvents = [
|
|
281
|
+
{type: 'init', session_id: 'sess-005', timestamp: new Date().toISOString()},
|
|
282
|
+
{type: 'text_delta', content: 'First response.'},
|
|
283
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
const actions = await sendChatAndCollectActions(port, 'test');
|
|
287
|
+
|
|
288
|
+
// Should have: INIT, TEXT_DELTA, DONE (from server), DONE (safety net)
|
|
289
|
+
const doneCount = actions.filter((a) => a.type === 'DONE').length;
|
|
290
|
+
expect(doneCount).toBe(2); // server done + safety net done
|
|
291
|
+
|
|
292
|
+
const state = replayActions(actions);
|
|
293
|
+
// Only 2 messages despite 2 DONE dispatches
|
|
294
|
+
expect(state.messages).toHaveLength(2);
|
|
295
|
+
expect(state.messages[1]?.text).toBe('First response.');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('handles error followed by done', async () => {
|
|
299
|
+
serverEvents = [
|
|
300
|
+
{type: 'init', session_id: 'sess-006', timestamp: new Date().toISOString()},
|
|
301
|
+
{type: 'error', message: 'LLM provider timeout'},
|
|
302
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
const actions = await sendChatAndCollectActions(port, 'test');
|
|
306
|
+
const state = replayActions(actions);
|
|
307
|
+
|
|
308
|
+
// ERROR sets isStreaming=false, so DONE from server is ignored
|
|
309
|
+
// Then safety-net DONE is also ignored
|
|
310
|
+
expect(state.isStreaming).toBe(false);
|
|
311
|
+
expect(state.error).toBe('LLM provider timeout');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('handles server that sends no done event', async () => {
|
|
315
|
+
serverEvents = [
|
|
316
|
+
{type: 'init', session_id: 'sess-007', timestamp: new Date().toISOString()},
|
|
317
|
+
{type: 'text_delta', content: 'Response without done.'},
|
|
318
|
+
// No done event — server just ends the stream
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
const actions = await sendChatAndCollectActions(port, 'test');
|
|
322
|
+
const state = replayActions(actions);
|
|
323
|
+
|
|
324
|
+
// The safety-net DONE should finalize the response
|
|
325
|
+
expect(state.isStreaming).toBe(false);
|
|
326
|
+
expect(state.messages).toHaveLength(2);
|
|
327
|
+
expect(state.messages[1]?.text).toBe('Response without done.');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('handles multi-turn with skills', async () => {
|
|
331
|
+
serverEvents = [
|
|
332
|
+
{type: 'init', session_id: 'sess-008', timestamp: new Date().toISOString()},
|
|
333
|
+
{type: 'skill_activated', skill_name: 'triage'},
|
|
334
|
+
{type: 'text_delta', content: 'Analyzing with triage skill.'},
|
|
335
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
const actions = await sendChatAndCollectActions(port, 'investigate');
|
|
339
|
+
const state = replayActions(actions);
|
|
340
|
+
|
|
341
|
+
expect(state.messages).toHaveLength(2);
|
|
342
|
+
expect(state.messages[1]?.skills).toContain('triage');
|
|
343
|
+
expect(state.messages[1]?.text).toBe('Analyzing with triage skill.');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('handles thinking + text response', async () => {
|
|
347
|
+
serverEvents = [
|
|
348
|
+
{type: 'init', session_id: 'sess-009', timestamp: new Date().toISOString()},
|
|
349
|
+
{type: 'thinking_delta', content: 'Let me analyze...'},
|
|
350
|
+
{type: 'text_delta', content: 'Here is the answer.'},
|
|
351
|
+
{type: 'done', timestamp: new Date().toISOString()},
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
const actions = await sendChatAndCollectActions(port, 'think');
|
|
355
|
+
const state = replayActions(actions);
|
|
356
|
+
|
|
357
|
+
expect(state.messages).toHaveLength(2);
|
|
358
|
+
expect(state.messages[1]?.text).toBe('Here is the answer.');
|
|
359
|
+
expect(state.messages[1]?.thinking).toBe('Let me analyze...');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('processes large chunked response correctly', async () => {
|
|
363
|
+
// Simulate many small text deltas
|
|
364
|
+
const events: Array<Record<string, unknown>> = [
|
|
365
|
+
{type: 'init', session_id: 'sess-010', timestamp: new Date().toISOString()},
|
|
366
|
+
];
|
|
367
|
+
const words = 'The quick brown fox jumps over the lazy dog'.split(' ');
|
|
368
|
+
for (const word of words) {
|
|
369
|
+
events.push({type: 'text_delta', content: word + ' '});
|
|
370
|
+
}
|
|
371
|
+
events.push({type: 'done', timestamp: new Date().toISOString()});
|
|
372
|
+
serverEvents = events;
|
|
373
|
+
|
|
374
|
+
const actions = await sendChatAndCollectActions(port, 'test');
|
|
375
|
+
const state = replayActions(actions);
|
|
376
|
+
|
|
377
|
+
expect(state.messages).toHaveLength(2);
|
|
378
|
+
expect(state.messages[1]?.text).toBe('The quick brown fox jumps over the lazy dog ');
|
|
379
|
+
expect(state.isStreaming).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe('Chat E2E: Multi-turn SSE', () => {
|
|
384
|
+
let server: http.Server;
|
|
385
|
+
let port: number;
|
|
386
|
+
let turnCounter: number;
|
|
387
|
+
|
|
388
|
+
beforeAll(async () => {
|
|
389
|
+
turnCounter = 0;
|
|
390
|
+
server = http.createServer((req, res) => {
|
|
391
|
+
if (req.method === 'POST' && req.url === '/chat') {
|
|
392
|
+
let _body = '';
|
|
393
|
+
req.on('data', (chunk: Buffer) => {
|
|
394
|
+
_body += chunk.toString();
|
|
395
|
+
});
|
|
396
|
+
req.on('end', () => {
|
|
397
|
+
res.writeHead(200, {
|
|
398
|
+
'Content-Type': 'text/event-stream',
|
|
399
|
+
'Cache-Control': 'no-cache',
|
|
400
|
+
'Connection': 'keep-alive',
|
|
401
|
+
});
|
|
402
|
+
turnCounter++;
|
|
403
|
+
const turn = turnCounter;
|
|
404
|
+
res.write(`data: ${JSON.stringify({type: 'init', session_id: 'multi-sess', timestamp: new Date().toISOString()})}\n\n`);
|
|
405
|
+
res.write(`data: ${JSON.stringify({type: 'text_delta', content: `Response ${turn}`})}\n\n`);
|
|
406
|
+
res.write(`data: ${JSON.stringify({type: 'done', timestamp: new Date().toISOString()})}\n\n`);
|
|
407
|
+
res.end();
|
|
408
|
+
});
|
|
409
|
+
} else {
|
|
410
|
+
res.writeHead(404);
|
|
411
|
+
res.end();
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
await new Promise<void>((resolve) => {
|
|
415
|
+
server.listen(0, '127.0.0.1', () => resolve());
|
|
416
|
+
});
|
|
417
|
+
const addr = server.address();
|
|
418
|
+
port = typeof addr === 'object' && addr !== null ? addr.port : 0;
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
afterAll(async () => {
|
|
422
|
+
await new Promise<void>((resolve) => {
|
|
423
|
+
server.close(() => resolve());
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('three consecutive turns produce correct non-empty messages', async () => {
|
|
428
|
+
// Simulate 3 turns by sending 3 requests sequentially
|
|
429
|
+
const allActions: ChatAction[][] = [];
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < 3; i++) {
|
|
432
|
+
const actions = await sendChatAndCollectActions(port, `question ${i + 1}`);
|
|
433
|
+
allActions.push(actions);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Replay all turns through the reducer
|
|
437
|
+
let state = initialState;
|
|
438
|
+
for (let i = 0; i < 3; i++) {
|
|
439
|
+
state = chatReducer(state, {type: 'SEND_MESSAGE', text: `question ${i + 1}`});
|
|
440
|
+
for (const action of allActions[i] ?? []) {
|
|
441
|
+
state = chatReducer(state, action);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Should have 6 messages: 3 user + 3 assistant
|
|
446
|
+
expect(state.messages).toHaveLength(6);
|
|
447
|
+
|
|
448
|
+
// All assistant messages should have non-empty text
|
|
449
|
+
for (let i = 0; i < 3; i++) {
|
|
450
|
+
const userMsg = state.messages[i * 2];
|
|
451
|
+
const assistantMsg = state.messages[i * 2 + 1];
|
|
452
|
+
expect(userMsg?.role).toBe('user');
|
|
453
|
+
expect(userMsg?.text).toBe(`question ${i + 1}`);
|
|
454
|
+
expect(assistantMsg?.role).toBe('assistant');
|
|
455
|
+
expect(assistantMsg?.text).toBe(`Response ${i + 1}`);
|
|
456
|
+
expect(assistantMsg?.text.length).toBeGreaterThan(0);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('safety-net DONE does not leak across turns', async () => {
|
|
461
|
+
// This tests the specific race condition:
|
|
462
|
+
// Request 1's end handler fires after SEND_MESSAGE for request 2
|
|
463
|
+
let state = initialState;
|
|
464
|
+
|
|
465
|
+
// Turn 1: normal flow
|
|
466
|
+
state = chatReducer(state, {type: 'SEND_MESSAGE', text: 'q1'});
|
|
467
|
+
state = chatReducer(state, {type: 'INIT', sessionId: 'sess-race'});
|
|
468
|
+
state = chatReducer(state, {type: 'TEXT_DELTA', content: 'Answer 1'});
|
|
469
|
+
state = chatReducer(state, {type: 'DONE'}); // server's done
|
|
470
|
+
expect(state.messages).toHaveLength(2);
|
|
471
|
+
expect(state.isStreaming).toBe(false);
|
|
472
|
+
|
|
473
|
+
// Turn 2: user sends immediately
|
|
474
|
+
state = chatReducer(state, {type: 'SEND_MESSAGE', text: 'q2'});
|
|
475
|
+
expect(state.isStreaming).toBe(true);
|
|
476
|
+
|
|
477
|
+
// If the safety-net DONE from request 1's end handler fires NOW,
|
|
478
|
+
// it would create an empty assistant message. The fix prevents this
|
|
479
|
+
// by using doneDispatched flag per-request in streamToServer.
|
|
480
|
+
// But in the reducer, DONE when isStreaming=true DOES create a message.
|
|
481
|
+
// So the fix must be in streamToServer, not the reducer.
|
|
482
|
+
|
|
483
|
+
// Simulate request 1's delayed end handler dispatching DONE
|
|
484
|
+
// With the fix, this shouldn't happen. But let's verify the reducer
|
|
485
|
+
// would create an empty message if it did:
|
|
486
|
+
const badState = chatReducer(state, {type: 'DONE'});
|
|
487
|
+
// This WOULD create an empty message - that's the bug the dispatchOnce fixes
|
|
488
|
+
expect(badState.messages).toHaveLength(4); // user, assistant, user, EMPTY assistant
|
|
489
|
+
expect(badState.messages[3]?.text).toBe(''); // empty!
|
|
490
|
+
|
|
491
|
+
// Now continue turn 2 properly (without the stale DONE)
|
|
492
|
+
state = chatReducer(state, {type: 'TEXT_DELTA', content: 'Answer 2'});
|
|
493
|
+
state = chatReducer(state, {type: 'DONE'}); // server's done for request 2
|
|
494
|
+
expect(state.messages).toHaveLength(4);
|
|
495
|
+
expect(state.messages[3]?.text).toBe('Answer 2');
|
|
496
|
+
expect(state.messages[3]?.text.length).toBeGreaterThan(0);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe('Chat E2E: Reducer direct — edge cases', () => {
|
|
501
|
+
it('DONE when not streaming is no-op', () => {
|
|
502
|
+
const state = chatReducer(initialState, {type: 'DONE'});
|
|
503
|
+
expect(state).toBe(initialState);
|
|
504
|
+
expect(state.messages).toHaveLength(0);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('multiple rapid SEND_MESSAGE + DONE cycles produce correct message count', () => {
|
|
508
|
+
let state = initialState;
|
|
509
|
+
|
|
510
|
+
// Turn 1
|
|
511
|
+
state = chatReducer(state, {type: 'SEND_MESSAGE', text: 'q1'});
|
|
512
|
+
state = chatReducer(state, {type: 'TEXT_DELTA', content: 'a1'});
|
|
513
|
+
state = chatReducer(state, {type: 'DONE'});
|
|
514
|
+
state = chatReducer(state, {type: 'DONE'}); // safety net duplicate
|
|
515
|
+
expect(state.messages).toHaveLength(2);
|
|
516
|
+
|
|
517
|
+
// Turn 2
|
|
518
|
+
state = chatReducer(state, {type: 'SEND_MESSAGE', text: 'q2'});
|
|
519
|
+
state = chatReducer(state, {type: 'TEXT_DELTA', content: 'a2'});
|
|
520
|
+
state = chatReducer(state, {type: 'DONE'});
|
|
521
|
+
state = chatReducer(state, {type: 'DONE'}); // safety net duplicate
|
|
522
|
+
expect(state.messages).toHaveLength(4);
|
|
523
|
+
|
|
524
|
+
expect(state.messages[0]?.text).toBe('q1');
|
|
525
|
+
expect(state.messages[1]?.text).toBe('a1');
|
|
526
|
+
expect(state.messages[2]?.text).toBe('q2');
|
|
527
|
+
expect(state.messages[3]?.text).toBe('a2');
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('CLEAR_HISTORY + new turn works correctly', () => {
|
|
531
|
+
let state = initialState;
|
|
532
|
+
|
|
533
|
+
// Turn 1
|
|
534
|
+
state = chatReducer(state, {type: 'SEND_MESSAGE', text: 'old'});
|
|
535
|
+
state = chatReducer(state, {type: 'INIT', sessionId: 'sess-1'});
|
|
536
|
+
state = chatReducer(state, {type: 'TEXT_DELTA', content: 'old response'});
|
|
537
|
+
state = chatReducer(state, {type: 'DONE'});
|
|
538
|
+
expect(state.messages).toHaveLength(2);
|
|
539
|
+
|
|
540
|
+
// Clear
|
|
541
|
+
state = chatReducer(state, {type: 'CLEAR_HISTORY'});
|
|
542
|
+
expect(state.messages).toHaveLength(0);
|
|
543
|
+
expect(state.sessionId).toBe('sess-1'); // preserved
|
|
544
|
+
|
|
545
|
+
// Turn 2
|
|
546
|
+
state = chatReducer(state, {type: 'SEND_MESSAGE', text: 'new'});
|
|
547
|
+
state = chatReducer(state, {type: 'TEXT_DELTA', content: 'new response'});
|
|
548
|
+
state = chatReducer(state, {type: 'DONE'});
|
|
549
|
+
expect(state.messages).toHaveLength(2);
|
|
550
|
+
expect(state.messages[1]?.text).toBe('new response');
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('confirmation queue processes in FIFO order', () => {
|
|
554
|
+
let state = initialState;
|
|
555
|
+
|
|
556
|
+
const req1 = {endpoint: '/a', method: 'POST', reason: 'r1', escalated: false};
|
|
557
|
+
const req2 = {endpoint: '/b', method: 'DELETE', reason: 'r2', escalated: true};
|
|
558
|
+
const req3 = {endpoint: '/c', method: 'PUT', reason: 'r3', escalated: false};
|
|
559
|
+
|
|
560
|
+
state = chatReducer(state, {type: 'CONFIRMATION_REQUIRED', request: req1});
|
|
561
|
+
state = chatReducer(state, {type: 'CONFIRMATION_REQUIRED', request: req2});
|
|
562
|
+
state = chatReducer(state, {type: 'CONFIRMATION_REQUIRED', request: req3});
|
|
563
|
+
expect(state.confirmationQueue).toHaveLength(3);
|
|
564
|
+
expect(state.pendingConfirmation).toEqual(req1);
|
|
565
|
+
|
|
566
|
+
// Approve first
|
|
567
|
+
state = chatReducer(state, {type: 'CONFIRMATION_RESPOND', approved: true});
|
|
568
|
+
expect(state.confirmationQueue).toHaveLength(2);
|
|
569
|
+
expect(state.pendingConfirmation).toEqual(req2);
|
|
570
|
+
|
|
571
|
+
// Reject second
|
|
572
|
+
state = chatReducer(state, {type: 'CONFIRMATION_RESPOND', approved: false});
|
|
573
|
+
expect(state.confirmationQueue).toHaveLength(1);
|
|
574
|
+
expect(state.pendingConfirmation).toEqual(req3);
|
|
575
|
+
|
|
576
|
+
// Approve third
|
|
577
|
+
state = chatReducer(state, {type: 'CONFIRMATION_RESPOND', approved: true});
|
|
578
|
+
expect(state.confirmationQueue).toHaveLength(0);
|
|
579
|
+
expect(state.pendingConfirmation).toBeNull();
|
|
580
|
+
});
|
|
581
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {registerCommand} from './registry.js';
|
|
8
|
+
import type {CommandResult} from './registry.js';
|
|
9
|
+
|
|
10
|
+
registerCommand({
|
|
11
|
+
name: 'clear',
|
|
12
|
+
description: 'Clear conversation history',
|
|
13
|
+
aliases: ['cls'],
|
|
14
|
+
execute: (): CommandResult => ({type: 'clear'}),
|
|
15
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {registerCommand, getAllCommands} from './registry.js';
|
|
8
|
+
import type {CommandResult} from './registry.js';
|
|
9
|
+
|
|
10
|
+
registerCommand({
|
|
11
|
+
name: 'help',
|
|
12
|
+
description: 'Show available commands and keyboard shortcuts',
|
|
13
|
+
aliases: ['h', '?'],
|
|
14
|
+
execute: (): CommandResult => {
|
|
15
|
+
const commands = getAllCommands();
|
|
16
|
+
const lines = [
|
|
17
|
+
'Commands:',
|
|
18
|
+
...commands.map(
|
|
19
|
+
(cmd) =>
|
|
20
|
+
` /${cmd.name}${cmd.aliases.length > 0 ? ` (${cmd.aliases.map((a) => '/' + a).join(', ')})` : ''} — ${cmd.description}`,
|
|
21
|
+
),
|
|
22
|
+
'',
|
|
23
|
+
'Keyboard shortcuts:',
|
|
24
|
+
' j/k Scroll down/up one line',
|
|
25
|
+
' PgUp/PgDn Scroll one page',
|
|
26
|
+
' Home/End Jump to top/bottom',
|
|
27
|
+
' Ctrl+C Exit',
|
|
28
|
+
];
|
|
29
|
+
return {type: 'message', text: lines.join('\n')};
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Import to register all commands
|
|
8
|
+
import './help.js';
|
|
9
|
+
import './clear.js';
|
|
10
|
+
import './stats.js';
|
|
11
|
+
import './model.js';
|
|
12
|
+
import './sessions.js';
|
|
13
|
+
import './theme.js';
|
|
14
|
+
|
|
15
|
+
// Re-export registry
|
|
16
|
+
export {
|
|
17
|
+
parseSlashCommand,
|
|
18
|
+
getCommand,
|
|
19
|
+
getAllCommands,
|
|
20
|
+
commandRegistry,
|
|
21
|
+
} from './registry.js';
|
|
22
|
+
export type {SlashCommand, CommandResult} from './registry.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {registerCommand} from './registry.js';
|
|
8
|
+
import type {CommandResult} from './registry.js';
|
|
9
|
+
import type {ChatState} from '../types.js';
|
|
10
|
+
|
|
11
|
+
registerCommand({
|
|
12
|
+
name: 'model',
|
|
13
|
+
description: 'Show current model info',
|
|
14
|
+
aliases: ['m'],
|
|
15
|
+
execute: (_args: string, state: ChatState): CommandResult => {
|
|
16
|
+
const model = state.tokenUsage.model ?? 'unknown';
|
|
17
|
+
return {type: 'message', text: `Model: ${model}`};
|
|
18
|
+
},
|
|
19
|
+
});
|