@vellumai/assistant 0.5.0 → 0.5.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/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/filesystem-tools.test.ts +4 -2
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +103 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
- package/src/__tests__/skill-feature-flags.test.ts +13 -13
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +8 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/history-repair.ts +28 -8
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +62 -19
- package/src/prompts/system-prompt.ts +2 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +4 -21
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the GET /v1/suggestion endpoint (handleGetSuggestion).
|
|
3
|
+
*
|
|
4
|
+
* Validates happy path, all null-return paths, caching, staleness check,
|
|
5
|
+
* quote stripping, word-boundary truncation, empty response rejection,
|
|
6
|
+
* and modelIntent verification.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Mocks — must be defined before importing the module under test
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
mock.module("../util/logger.js", () => ({
|
|
16
|
+
getLogger: () =>
|
|
17
|
+
new Proxy({} as Record<string, unknown>, {
|
|
18
|
+
get: () => () => {},
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const mockGetConversationByKey = mock(
|
|
23
|
+
(_key: string): { conversationId: string } | null => ({
|
|
24
|
+
conversationId: "conv-test",
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
mock.module("../memory/conversation-key-store.js", () => ({
|
|
29
|
+
getConversationByKey: mockGetConversationByKey,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const mockGetMessages = mock((_conversationId: string) => [
|
|
33
|
+
{
|
|
34
|
+
id: "msg-user-1",
|
|
35
|
+
conversationId: "conv-test",
|
|
36
|
+
role: "user",
|
|
37
|
+
content: JSON.stringify([{ type: "text", text: "Hello there" }]),
|
|
38
|
+
createdAt: Date.now() - 2000,
|
|
39
|
+
metadata: null,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "msg-asst-1",
|
|
43
|
+
conversationId: "conv-test",
|
|
44
|
+
role: "assistant",
|
|
45
|
+
content: JSON.stringify([
|
|
46
|
+
{ type: "text", text: "Hi! How can I help you today?" },
|
|
47
|
+
]),
|
|
48
|
+
createdAt: Date.now() - 1000,
|
|
49
|
+
metadata: null,
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
54
|
+
getMessages: mockGetMessages,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
const mockGetConfiguredProvider = mock(async () => ({
|
|
58
|
+
name: "test-provider",
|
|
59
|
+
sendMessage: mock(async () => ({
|
|
60
|
+
content: [{ type: "text", text: "Let's do round two!" }],
|
|
61
|
+
model: "test",
|
|
62
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
63
|
+
stopReason: "end_turn",
|
|
64
|
+
})),
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
mock.module("../providers/provider-send-message.js", () => ({
|
|
68
|
+
getConfiguredProvider: mockGetConfiguredProvider,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
mock.module("../daemon/handlers/shared.js", () => ({
|
|
72
|
+
renderHistoryContent: (content: unknown) => {
|
|
73
|
+
// Extract text from content blocks, mirroring the real function
|
|
74
|
+
if (Array.isArray(content)) {
|
|
75
|
+
const texts = content
|
|
76
|
+
.filter((b: { type: string }) => b.type === "text" && "text" in b)
|
|
77
|
+
.map((b: { text: string }) => b.text);
|
|
78
|
+
return {
|
|
79
|
+
text: texts.join("\n"),
|
|
80
|
+
toolCalls: [],
|
|
81
|
+
toolCallsBeforeText: false,
|
|
82
|
+
textSegments: [],
|
|
83
|
+
contentOrder: [],
|
|
84
|
+
surfaces: [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
text: typeof content === "string" ? content : "",
|
|
89
|
+
toolCalls: [],
|
|
90
|
+
toolCallsBeforeText: false,
|
|
91
|
+
textSegments: [],
|
|
92
|
+
contentOrder: [],
|
|
93
|
+
surfaces: [],
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Imports (after mocks)
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
import { handleGetSuggestion } from "../runtime/routes/conversation-routes.js";
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Helpers
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
function makeUrl(params: {
|
|
109
|
+
conversationKey?: string;
|
|
110
|
+
messageId?: string;
|
|
111
|
+
}): URL {
|
|
112
|
+
const url = new URL("http://localhost/v1/suggestion");
|
|
113
|
+
if (params.conversationKey) {
|
|
114
|
+
url.searchParams.set("conversationKey", params.conversationKey);
|
|
115
|
+
}
|
|
116
|
+
if (params.messageId) {
|
|
117
|
+
url.searchParams.set("messageId", params.messageId);
|
|
118
|
+
}
|
|
119
|
+
return url;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function makeDeps() {
|
|
123
|
+
return {
|
|
124
|
+
suggestionCache: new Map<string, string>(),
|
|
125
|
+
suggestionInFlight: new Map<string, Promise<string | null>>(),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function makeMockProvider(text: string) {
|
|
130
|
+
return {
|
|
131
|
+
name: "test-provider",
|
|
132
|
+
sendMessage: mock(async () => ({
|
|
133
|
+
content: [{ type: "text" as const, text }],
|
|
134
|
+
model: "test",
|
|
135
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
136
|
+
stopReason: "end_turn",
|
|
137
|
+
})),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Tests
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
describe("GET /v1/suggestion", () => {
|
|
146
|
+
test("returns suggestion from LLM", async () => {
|
|
147
|
+
const provider = makeMockProvider("Let's do round two!");
|
|
148
|
+
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
149
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
150
|
+
conversationId: "conv-test",
|
|
151
|
+
}));
|
|
152
|
+
mockGetMessages.mockImplementation(() => [
|
|
153
|
+
{
|
|
154
|
+
id: "msg-user-1",
|
|
155
|
+
conversationId: "conv-test",
|
|
156
|
+
role: "user",
|
|
157
|
+
content: JSON.stringify([{ type: "text", text: "Hello" }]),
|
|
158
|
+
createdAt: Date.now() - 2000,
|
|
159
|
+
metadata: null,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "msg-asst-1",
|
|
163
|
+
conversationId: "conv-test",
|
|
164
|
+
role: "assistant",
|
|
165
|
+
content: JSON.stringify([
|
|
166
|
+
{ type: "text", text: "Hi! How can I help you today?" },
|
|
167
|
+
]),
|
|
168
|
+
createdAt: Date.now() - 1000,
|
|
169
|
+
metadata: null,
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
174
|
+
const deps = makeDeps();
|
|
175
|
+
const res = await handleGetSuggestion(url, deps);
|
|
176
|
+
const body = (await res.json()) as {
|
|
177
|
+
suggestion: string;
|
|
178
|
+
source: string;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
expect(body.suggestion).toBe("Let's do round two!");
|
|
182
|
+
expect(body.source).toBe("llm");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("returns null when no conversation found", async () => {
|
|
186
|
+
mockGetConversationByKey.mockImplementation(() => null);
|
|
187
|
+
|
|
188
|
+
const url = makeUrl({ conversationKey: "nonexistent-key" });
|
|
189
|
+
const deps = makeDeps();
|
|
190
|
+
const res = await handleGetSuggestion(url, deps);
|
|
191
|
+
const body = (await res.json()) as { suggestion: string | null };
|
|
192
|
+
|
|
193
|
+
expect(body.suggestion).toBeNull();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("returns null when no messages", async () => {
|
|
197
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
198
|
+
conversationId: "conv-test",
|
|
199
|
+
}));
|
|
200
|
+
mockGetMessages.mockImplementation(() => []);
|
|
201
|
+
|
|
202
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
203
|
+
const deps = makeDeps();
|
|
204
|
+
const res = await handleGetSuggestion(url, deps);
|
|
205
|
+
const body = (await res.json()) as { suggestion: string | null };
|
|
206
|
+
|
|
207
|
+
expect(body.suggestion).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("returns null when provider unavailable", async () => {
|
|
211
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
212
|
+
conversationId: "conv-test",
|
|
213
|
+
}));
|
|
214
|
+
mockGetMessages.mockImplementation(() => [
|
|
215
|
+
{
|
|
216
|
+
id: "msg-asst-1",
|
|
217
|
+
conversationId: "conv-test",
|
|
218
|
+
role: "assistant",
|
|
219
|
+
content: JSON.stringify([{ type: "text", text: "Hello there" }]),
|
|
220
|
+
createdAt: Date.now(),
|
|
221
|
+
metadata: null,
|
|
222
|
+
},
|
|
223
|
+
]);
|
|
224
|
+
mockGetConfiguredProvider.mockImplementation(
|
|
225
|
+
async () =>
|
|
226
|
+
null as unknown as Awaited<
|
|
227
|
+
ReturnType<typeof mockGetConfiguredProvider>
|
|
228
|
+
>,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
232
|
+
const deps = makeDeps();
|
|
233
|
+
const res = await handleGetSuggestion(url, deps);
|
|
234
|
+
const body = (await res.json()) as { suggestion: string | null };
|
|
235
|
+
|
|
236
|
+
expect(body.suggestion).toBeNull();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("returns null when provider throws", async () => {
|
|
240
|
+
const throwingProvider = {
|
|
241
|
+
name: "test-provider",
|
|
242
|
+
sendMessage: mock(async () => {
|
|
243
|
+
throw new Error("Provider error");
|
|
244
|
+
}),
|
|
245
|
+
};
|
|
246
|
+
mockGetConfiguredProvider.mockImplementation(async () => throwingProvider);
|
|
247
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
248
|
+
conversationId: "conv-test",
|
|
249
|
+
}));
|
|
250
|
+
mockGetMessages.mockImplementation(() => [
|
|
251
|
+
{
|
|
252
|
+
id: "msg-asst-1",
|
|
253
|
+
conversationId: "conv-test",
|
|
254
|
+
role: "assistant",
|
|
255
|
+
content: JSON.stringify([{ type: "text", text: "Hello there" }]),
|
|
256
|
+
createdAt: Date.now(),
|
|
257
|
+
metadata: null,
|
|
258
|
+
},
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
262
|
+
const deps = makeDeps();
|
|
263
|
+
const res = await handleGetSuggestion(url, deps);
|
|
264
|
+
|
|
265
|
+
// Should return null gracefully, not a 500
|
|
266
|
+
expect(res.status).toBe(200);
|
|
267
|
+
const body = (await res.json()) as { suggestion: string | null };
|
|
268
|
+
expect(body.suggestion).toBeNull();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("strips quotes from LLM response", async () => {
|
|
272
|
+
const provider = makeMockProvider('"Sure, let\'s go!"');
|
|
273
|
+
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
274
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
275
|
+
conversationId: "conv-test",
|
|
276
|
+
}));
|
|
277
|
+
mockGetMessages.mockImplementation(() => [
|
|
278
|
+
{
|
|
279
|
+
id: "msg-asst-1",
|
|
280
|
+
conversationId: "conv-test",
|
|
281
|
+
role: "assistant",
|
|
282
|
+
content: JSON.stringify([{ type: "text", text: "Hello there" }]),
|
|
283
|
+
createdAt: Date.now(),
|
|
284
|
+
metadata: null,
|
|
285
|
+
},
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
289
|
+
const deps = makeDeps();
|
|
290
|
+
const res = await handleGetSuggestion(url, deps);
|
|
291
|
+
const body = (await res.json()) as { suggestion: string };
|
|
292
|
+
|
|
293
|
+
expect(body.suggestion).toBe("Sure, let's go!");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("truncates long suggestions at word boundary", async () => {
|
|
297
|
+
// A 60-char string that will exceed the 50-char limit
|
|
298
|
+
const longText =
|
|
299
|
+
"This is a really long suggestion that goes well beyond fifty chars";
|
|
300
|
+
const provider = makeMockProvider(longText);
|
|
301
|
+
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
302
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
303
|
+
conversationId: "conv-test",
|
|
304
|
+
}));
|
|
305
|
+
mockGetMessages.mockImplementation(() => [
|
|
306
|
+
{
|
|
307
|
+
id: "msg-asst-1",
|
|
308
|
+
conversationId: "conv-test",
|
|
309
|
+
role: "assistant",
|
|
310
|
+
content: JSON.stringify([{ type: "text", text: "Hello there" }]),
|
|
311
|
+
createdAt: Date.now(),
|
|
312
|
+
metadata: null,
|
|
313
|
+
},
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
317
|
+
const deps = makeDeps();
|
|
318
|
+
const res = await handleGetSuggestion(url, deps);
|
|
319
|
+
const body = (await res.json()) as { suggestion: string };
|
|
320
|
+
|
|
321
|
+
expect(body.suggestion.length).toBeLessThanOrEqual(50);
|
|
322
|
+
// Should end at a word boundary (no partial words)
|
|
323
|
+
expect(body.suggestion).not.toMatch(/\s$/);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("rejects empty LLM response", async () => {
|
|
327
|
+
const provider = makeMockProvider("");
|
|
328
|
+
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
329
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
330
|
+
conversationId: "conv-test",
|
|
331
|
+
}));
|
|
332
|
+
mockGetMessages.mockImplementation(() => [
|
|
333
|
+
{
|
|
334
|
+
id: "msg-asst-1",
|
|
335
|
+
conversationId: "conv-test",
|
|
336
|
+
role: "assistant",
|
|
337
|
+
content: JSON.stringify([{ type: "text", text: "Hello there" }]),
|
|
338
|
+
createdAt: Date.now(),
|
|
339
|
+
metadata: null,
|
|
340
|
+
},
|
|
341
|
+
]);
|
|
342
|
+
|
|
343
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
344
|
+
const deps = makeDeps();
|
|
345
|
+
const res = await handleGetSuggestion(url, deps);
|
|
346
|
+
const body = (await res.json()) as { suggestion: string | null };
|
|
347
|
+
|
|
348
|
+
expect(body.suggestion).toBeNull();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("returns cached suggestion", async () => {
|
|
352
|
+
const provider = makeMockProvider("Fresh suggestion");
|
|
353
|
+
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
354
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
355
|
+
conversationId: "conv-test",
|
|
356
|
+
}));
|
|
357
|
+
mockGetMessages.mockImplementation(() => [
|
|
358
|
+
{
|
|
359
|
+
id: "msg-asst-cache",
|
|
360
|
+
conversationId: "conv-test",
|
|
361
|
+
role: "assistant",
|
|
362
|
+
content: JSON.stringify([{ type: "text", text: "Some response" }]),
|
|
363
|
+
createdAt: Date.now(),
|
|
364
|
+
metadata: null,
|
|
365
|
+
},
|
|
366
|
+
]);
|
|
367
|
+
|
|
368
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
369
|
+
const deps = makeDeps();
|
|
370
|
+
|
|
371
|
+
// First call — should hit the LLM
|
|
372
|
+
const res1 = await handleGetSuggestion(url, deps);
|
|
373
|
+
const body1 = (await res1.json()) as { suggestion: string };
|
|
374
|
+
expect(body1.suggestion).toBe("Fresh suggestion");
|
|
375
|
+
|
|
376
|
+
// Second call — should return from cache
|
|
377
|
+
const res2 = await handleGetSuggestion(url, deps);
|
|
378
|
+
const body2 = (await res2.json()) as { suggestion: string };
|
|
379
|
+
expect(body2.suggestion).toBe("Fresh suggestion");
|
|
380
|
+
|
|
381
|
+
// Provider sendMessage should have been called only once
|
|
382
|
+
expect(provider.sendMessage).toHaveBeenCalledTimes(1);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("returns stale when messageId doesn't match", async () => {
|
|
386
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
387
|
+
conversationId: "conv-test",
|
|
388
|
+
}));
|
|
389
|
+
mockGetMessages.mockImplementation(() => [
|
|
390
|
+
{
|
|
391
|
+
id: "msg-asst-latest",
|
|
392
|
+
conversationId: "conv-test",
|
|
393
|
+
role: "assistant",
|
|
394
|
+
content: JSON.stringify([{ type: "text", text: "Latest response" }]),
|
|
395
|
+
createdAt: Date.now(),
|
|
396
|
+
metadata: null,
|
|
397
|
+
},
|
|
398
|
+
]);
|
|
399
|
+
|
|
400
|
+
const url = makeUrl({
|
|
401
|
+
conversationKey: "test-key",
|
|
402
|
+
messageId: "msg-asst-old",
|
|
403
|
+
});
|
|
404
|
+
const deps = makeDeps();
|
|
405
|
+
const res = await handleGetSuggestion(url, deps);
|
|
406
|
+
const body = (await res.json()) as {
|
|
407
|
+
suggestion: string | null;
|
|
408
|
+
stale: boolean;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
expect(body.stale).toBe(true);
|
|
412
|
+
expect(body.suggestion).toBeNull();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("uses latency-optimized model intent", async () => {
|
|
416
|
+
const provider = makeMockProvider("Quick reply");
|
|
417
|
+
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
418
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
419
|
+
conversationId: "conv-test",
|
|
420
|
+
}));
|
|
421
|
+
mockGetMessages.mockImplementation(() => [
|
|
422
|
+
{
|
|
423
|
+
id: "msg-asst-intent",
|
|
424
|
+
conversationId: "conv-test",
|
|
425
|
+
role: "assistant",
|
|
426
|
+
content: JSON.stringify([{ type: "text", text: "Hello!" }]),
|
|
427
|
+
createdAt: Date.now(),
|
|
428
|
+
metadata: null,
|
|
429
|
+
},
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
const url = makeUrl({ conversationKey: "test-key" });
|
|
433
|
+
const deps = makeDeps();
|
|
434
|
+
await handleGetSuggestion(url, deps);
|
|
435
|
+
|
|
436
|
+
expect(provider.sendMessage).toHaveBeenCalledTimes(1);
|
|
437
|
+
const callArgs = provider.sendMessage.mock.calls[0] as unknown[];
|
|
438
|
+
const options = callArgs[3] as
|
|
439
|
+
| { config?: { modelIntent?: string } }
|
|
440
|
+
| undefined;
|
|
441
|
+
expect(options?.config?.modelIntent).toBe("latency-optimized");
|
|
442
|
+
});
|
|
443
|
+
});
|
|
@@ -70,6 +70,7 @@ const mockTestProvider = {
|
|
|
70
70
|
let mockAnthropicKey: string | undefined = "test-api-key";
|
|
71
71
|
mock.module("../security/secure-keys.js", () => ({
|
|
72
72
|
getSecureKeyAsync: async () => mockAnthropicKey,
|
|
73
|
+
getProviderKeyAsync: async () => mockAnthropicKey,
|
|
73
74
|
}));
|
|
74
75
|
|
|
75
76
|
mock.module("../providers/registry.js", () => ({
|
|
@@ -237,6 +237,14 @@ describe("buildSystemPrompt", () => {
|
|
|
237
237
|
expect(result).toContain("browser automation as last resort");
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
+
test("includes inline media attachment guidance", () => {
|
|
241
|
+
const result = buildSystemPrompt();
|
|
242
|
+
expect(result).toContain(
|
|
243
|
+
"Image and video attachments can render inline in chat.",
|
|
244
|
+
);
|
|
245
|
+
expect(result).toContain("attach it instead of only printing its path");
|
|
246
|
+
});
|
|
247
|
+
|
|
240
248
|
test("does not include removed sections", () => {
|
|
241
249
|
const result = buildSystemPrompt();
|
|
242
250
|
expect(result).not.toContain("## External Communications Identity");
|
|
@@ -79,6 +79,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
79
79
|
getRootDir: () => "/tmp",
|
|
80
80
|
getDataDir: () => "/tmp",
|
|
81
81
|
getWorkspaceDir: () => "/tmp/workspace",
|
|
82
|
+
getConversationsDir: () => "/tmp/workspace/conversations",
|
|
82
83
|
getDbPath: () => "/tmp/assistant.db",
|
|
83
84
|
ensureDataDir: () => {},
|
|
84
85
|
}));
|
|
@@ -68,6 +68,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
68
68
|
|
|
69
69
|
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
70
70
|
recordRequestLog: () => {},
|
|
71
|
+
backfillMessageIdOnLogs: () => {},
|
|
71
72
|
}));
|
|
72
73
|
|
|
73
74
|
// ── Imports (after mocks) ─────────────────────────────────────────────────────
|
|
@@ -238,13 +239,13 @@ describe("tool preview lifecycle", () => {
|
|
|
238
239
|
expect(activity.statusText).toMatch(/^Preparing/);
|
|
239
240
|
});
|
|
240
241
|
|
|
241
|
-
test("handleInputJsonDelta includes toolUseId", () => {
|
|
242
|
+
test("handleInputJsonDelta includes toolUseId for app tools", () => {
|
|
242
243
|
const collector = createEventCollector();
|
|
243
244
|
const deps = createMockDeps({ onEvent: collector.onEvent });
|
|
244
245
|
|
|
245
246
|
handleInputJsonDelta(state, deps, {
|
|
246
247
|
type: "input_json_delta",
|
|
247
|
-
toolName: "
|
|
248
|
+
toolName: "app_create",
|
|
248
249
|
toolUseId: "toolu_delta456",
|
|
249
250
|
accumulatedJson: '{"command": "ls"}',
|
|
250
251
|
});
|
|
@@ -253,7 +254,7 @@ describe("tool preview lifecycle", () => {
|
|
|
253
254
|
const emitted = collector.events[0];
|
|
254
255
|
expect(emitted.type).toBe("tool_input_delta");
|
|
255
256
|
expect((emitted as any).toolUseId).toBe("toolu_delta456");
|
|
256
|
-
expect((emitted as any).toolName).toBe("
|
|
257
|
+
expect((emitted as any).toolName).toBe("app_create");
|
|
257
258
|
expect((emitted as any).content).toBe('{"command": "ls"}');
|
|
258
259
|
expect((emitted as any).conversationId).toBe("test-session-id");
|
|
259
260
|
});
|
|
@@ -305,7 +306,8 @@ describe("tool preview lifecycle", () => {
|
|
|
305
306
|
});
|
|
306
307
|
|
|
307
308
|
const toolUseId = "toolu_ordering_test";
|
|
308
|
-
|
|
309
|
+
// Use an app tool so input_json_delta is forwarded to the client
|
|
310
|
+
const toolName = "app_create";
|
|
309
311
|
|
|
310
312
|
// Step 1: tool_use_preview_start (emitted by provider on content_block_start)
|
|
311
313
|
handleToolUsePreviewStart(state, deps, {
|
|
@@ -344,6 +346,30 @@ describe("tool preview lifecycle", () => {
|
|
|
344
346
|
}
|
|
345
347
|
});
|
|
346
348
|
|
|
349
|
+
test("non-app tool input_json_delta events are not forwarded to client", () => {
|
|
350
|
+
const collector = createEventCollector();
|
|
351
|
+
const deps = createMockDeps({
|
|
352
|
+
onEvent: collector.onEvent,
|
|
353
|
+
ctx: {
|
|
354
|
+
...createMockDeps().ctx,
|
|
355
|
+
emitActivityState: collector.emitActivityState,
|
|
356
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const toolUseId = "toolu_non_app_delta";
|
|
360
|
+
const toolName = "file_read";
|
|
361
|
+
|
|
362
|
+
handleInputJsonDelta(state, deps, {
|
|
363
|
+
type: "input_json_delta",
|
|
364
|
+
toolName,
|
|
365
|
+
toolUseId,
|
|
366
|
+
accumulatedJson: '{"path": "/test"}',
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Non-app tools should not emit tool_input_delta to the client
|
|
370
|
+
expect(collector.events).toEqual([]);
|
|
371
|
+
});
|
|
372
|
+
|
|
347
373
|
test("full lifecycle: preview_start → input_delta → tool_use → tool_result", () => {
|
|
348
374
|
const collector = createEventCollector();
|
|
349
375
|
const deps = createMockDeps({
|
|
@@ -355,7 +381,8 @@ describe("tool preview lifecycle", () => {
|
|
|
355
381
|
});
|
|
356
382
|
|
|
357
383
|
const toolUseId = "toolu_full_lifecycle";
|
|
358
|
-
|
|
384
|
+
// Use an app tool so input_json_delta is forwarded to the client
|
|
385
|
+
const toolName = "app_create";
|
|
359
386
|
|
|
360
387
|
// 1. Preview start
|
|
361
388
|
handleToolUsePreviewStart(state, deps, {
|
|
@@ -123,4 +123,26 @@ describe("renderWorkspaceTopLevelContext", () => {
|
|
|
123
123
|
expect(result).toContain("Directories: ");
|
|
124
124
|
expect(result).toContain("Files: a.txt, b.txt");
|
|
125
125
|
});
|
|
126
|
+
|
|
127
|
+
test("renders current conversation and attachment paths when provided", () => {
|
|
128
|
+
const snapshot: TopLevelSnapshot = {
|
|
129
|
+
rootPath: "/sandbox",
|
|
130
|
+
directories: ["src"],
|
|
131
|
+
files: ["package.json"],
|
|
132
|
+
truncated: false,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const result = renderWorkspaceTopLevelContext(snapshot, {
|
|
136
|
+
currentConversationPath: "conversations/2026-03-19T12-00-00.000Z_conv-1/",
|
|
137
|
+
currentConversationAttachmentsPath:
|
|
138
|
+
"conversations/2026-03-19T12-00-00.000Z_conv-1/attachments/",
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(result).toContain(
|
|
142
|
+
"Current conversation folder: conversations/2026-03-19T12-00-00.000Z_conv-1/",
|
|
143
|
+
);
|
|
144
|
+
expect(result).toContain(
|
|
145
|
+
"Attachment files: conversations/2026-03-19T12-00-00.000Z_conv-1/attachments/",
|
|
146
|
+
);
|
|
147
|
+
});
|
|
126
148
|
});
|