@vellumai/assistant 0.5.1 → 0.5.3
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 +163 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/docs/skills.md +100 -0
- 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__/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 +156 -5
- package/src/__tests__/conversation-agent-loop.test.ts +297 -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-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- 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-wipe.test.ts +226 -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-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__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- 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 +32 -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__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -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-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +7 -7
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +699 -0
- package/src/__tests__/memory-reducer.test.ts +698 -0
- package/src/__tests__/memory-regressions.test.ts +6 -4
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- 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__/parse-identity-fields.test.ts +129 -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-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- 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__/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__/vellum-self-knowledge-inline-command.test.ts +320 -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/SKILL.md +1 -1
- 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 +24 -0
- package/src/config/loader.ts +65 -0
- package/src/config/raw-config-utils.ts +58 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +20 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +8 -6
- package/src/config/skills.ts +50 -4
- 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 +54 -8
- package/src/daemon/conversation-agent-loop.ts +127 -20
- 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 +50 -16
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +22 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +170 -24
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +69 -30
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +156 -0
- package/src/daemon/handlers/config-model.ts +62 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +115 -65
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +18 -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/followups/followup-store.ts +47 -1
- 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/archive-store.ts +400 -0
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +161 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +591 -8
- 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 +40 -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 +114 -21
- 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 +2 -4
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +6 -0
- package/src/memory/jobs-worker.ts +12 -0
- 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/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +99 -0
- package/src/memory/reducer.ts +453 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/schema/oauth.ts +6 -0
- package/src/memory/search/semantic.ts +17 -4
- 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 +99 -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 +160 -14
- package/src/permissions/defaults.ts +14 -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 +74 -0
- package/src/runtime/routes/conversation-query-routes.ts +187 -10
- package/src/runtime/routes/conversation-routes.ts +269 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1212 -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 +94 -5
- 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 +30 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +30 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/tasks/task-store.ts +43 -1
- 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/permission-checker.ts +8 -1
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +140 -6
- 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 +24 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- 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/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
- 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 +11 -1
- 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
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
deleteApp,
|
|
47
47
|
deleteConnection,
|
|
48
48
|
disconnectOAuthProvider,
|
|
49
|
+
getActiveConnection,
|
|
49
50
|
getApp,
|
|
50
51
|
getAppByProviderAndClientId,
|
|
51
52
|
getConnection,
|
|
@@ -631,6 +632,120 @@ describe("connection operations", () => {
|
|
|
631
632
|
});
|
|
632
633
|
});
|
|
633
634
|
|
|
635
|
+
describe("getActiveConnection", () => {
|
|
636
|
+
test("returns the most recent active connection with no filters", async () => {
|
|
637
|
+
const app = await createTestApp("github", "client-1");
|
|
638
|
+
|
|
639
|
+
createConnection({
|
|
640
|
+
oauthAppId: app.id,
|
|
641
|
+
providerKey: "github",
|
|
642
|
+
grantedScopes: ["repo"],
|
|
643
|
+
hasRefreshToken: false,
|
|
644
|
+
createdAt: 1000,
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
const conn2 = createConnection({
|
|
648
|
+
oauthAppId: app.id,
|
|
649
|
+
providerKey: "github",
|
|
650
|
+
grantedScopes: ["repo", "user"],
|
|
651
|
+
hasRefreshToken: true,
|
|
652
|
+
createdAt: 2000,
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const result = getActiveConnection("github");
|
|
656
|
+
expect(result).toBeDefined();
|
|
657
|
+
expect(result!.id).toBe(conn2.id);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test("narrows by account when provided", async () => {
|
|
661
|
+
const app = await createTestApp("github", "client-1");
|
|
662
|
+
|
|
663
|
+
const conn1 = createConnection({
|
|
664
|
+
oauthAppId: app.id,
|
|
665
|
+
providerKey: "github",
|
|
666
|
+
accountInfo: "user1@example.com",
|
|
667
|
+
grantedScopes: ["repo"],
|
|
668
|
+
hasRefreshToken: false,
|
|
669
|
+
createdAt: 1000,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
createConnection({
|
|
673
|
+
oauthAppId: app.id,
|
|
674
|
+
providerKey: "github",
|
|
675
|
+
accountInfo: "user2@example.com",
|
|
676
|
+
grantedScopes: ["repo"],
|
|
677
|
+
hasRefreshToken: false,
|
|
678
|
+
createdAt: 2000,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const result = getActiveConnection("github", {
|
|
682
|
+
account: "user1@example.com",
|
|
683
|
+
});
|
|
684
|
+
expect(result).toBeDefined();
|
|
685
|
+
expect(result!.id).toBe(conn1.id);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("narrows by clientId when provided", async () => {
|
|
689
|
+
const app1 = await createTestApp("github", "client-a");
|
|
690
|
+
const app2 = await createTestApp("github", "client-b");
|
|
691
|
+
|
|
692
|
+
const conn1 = createConnection({
|
|
693
|
+
oauthAppId: app1.id,
|
|
694
|
+
providerKey: "github",
|
|
695
|
+
grantedScopes: ["repo"],
|
|
696
|
+
hasRefreshToken: false,
|
|
697
|
+
createdAt: 1000,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
createConnection({
|
|
701
|
+
oauthAppId: app2.id,
|
|
702
|
+
providerKey: "github",
|
|
703
|
+
grantedScopes: ["repo"],
|
|
704
|
+
hasRefreshToken: false,
|
|
705
|
+
createdAt: 2000,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const result = getActiveConnection("github", { clientId: "client-a" });
|
|
709
|
+
expect(result).toBeDefined();
|
|
710
|
+
expect(result!.id).toBe(conn1.id);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
test("returns undefined when clientId has no matching app", async () => {
|
|
714
|
+
const app = await createTestApp("github", "client-1");
|
|
715
|
+
|
|
716
|
+
createConnection({
|
|
717
|
+
oauthAppId: app.id,
|
|
718
|
+
providerKey: "github",
|
|
719
|
+
grantedScopes: ["repo"],
|
|
720
|
+
hasRefreshToken: false,
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const result = getActiveConnection("github", {
|
|
724
|
+
clientId: "nonexistent",
|
|
725
|
+
});
|
|
726
|
+
expect(result).toBeUndefined();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test("skips revoked connections", async () => {
|
|
730
|
+
const app = await createTestApp("github", "client-1");
|
|
731
|
+
|
|
732
|
+
const conn = createConnection({
|
|
733
|
+
oauthAppId: app.id,
|
|
734
|
+
providerKey: "github",
|
|
735
|
+
grantedScopes: ["repo"],
|
|
736
|
+
hasRefreshToken: false,
|
|
737
|
+
});
|
|
738
|
+
updateConnection(conn.id, { status: "revoked" });
|
|
739
|
+
|
|
740
|
+
const result = getActiveConnection("github");
|
|
741
|
+
expect(result).toBeUndefined();
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test("returns undefined when no connections exist", () => {
|
|
745
|
+
expect(getActiveConnection("github")).toBeUndefined();
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
|
|
634
749
|
describe("getConnectionByProvider", () => {
|
|
635
750
|
test("returns the most recent active connection", async () => {
|
|
636
751
|
const app = await createTestApp("github", "client-1");
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for identity field parsing and template placeholder filtering.
|
|
3
|
+
*
|
|
4
|
+
* Validates that parseIdentityFields correctly extracts real values from
|
|
5
|
+
* IDENTITY.md content while treating template placeholders (e.g.
|
|
6
|
+
* `_(not yet chosen)_`) as empty/unset.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
isTemplatePlaceholder,
|
|
13
|
+
parseIdentityFields,
|
|
14
|
+
} from "../daemon/handlers/identity.js";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// isTemplatePlaceholder
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
describe("isTemplatePlaceholder", () => {
|
|
21
|
+
test("returns true for _(not yet chosen)_", () => {
|
|
22
|
+
expect(isTemplatePlaceholder("_(not yet chosen)_")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("returns true for _(not yet established)_", () => {
|
|
26
|
+
expect(isTemplatePlaceholder("_(not yet established)_")).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("returns true for any value matching _(…)_ pattern", () => {
|
|
30
|
+
expect(isTemplatePlaceholder("_(something else)_")).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("returns false for normal values", () => {
|
|
34
|
+
expect(isTemplatePlaceholder("Your helpful coding assistant")).toBe(false);
|
|
35
|
+
expect(isTemplatePlaceholder("Jarvis")).toBe(false);
|
|
36
|
+
expect(isTemplatePlaceholder("")).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("returns false for partial matches", () => {
|
|
40
|
+
expect(isTemplatePlaceholder("_(incomplete")).toBe(false);
|
|
41
|
+
expect(isTemplatePlaceholder("incomplete)_")).toBe(false);
|
|
42
|
+
expect(isTemplatePlaceholder("_(")).toBe(false);
|
|
43
|
+
expect(isTemplatePlaceholder(")_")).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// parseIdentityFields — placeholder filtering
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
describe("parseIdentityFields", () => {
|
|
52
|
+
test("returns empty strings for all template placeholder values", () => {
|
|
53
|
+
const content = [
|
|
54
|
+
"- **Name:** _(not yet chosen)_",
|
|
55
|
+
"- **Role:** _(not yet established)_",
|
|
56
|
+
"- **Personality:** _(not yet chosen)_",
|
|
57
|
+
"- **Emoji:** _(not yet chosen)_",
|
|
58
|
+
"- **Home:** _(not yet chosen)_",
|
|
59
|
+
].join("\n");
|
|
60
|
+
|
|
61
|
+
const fields = parseIdentityFields(content);
|
|
62
|
+
expect(fields.name).toBe("");
|
|
63
|
+
expect(fields.role).toBe("");
|
|
64
|
+
expect(fields.personality).toBe("");
|
|
65
|
+
expect(fields.emoji).toBe("");
|
|
66
|
+
expect(fields.home).toBe("");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("preserves real user-provided values", () => {
|
|
70
|
+
const content = [
|
|
71
|
+
"- **Name:** Jarvis",
|
|
72
|
+
"- **Role:** Coding assistant",
|
|
73
|
+
"- **Personality:** Friendly and helpful",
|
|
74
|
+
"- **Emoji:** 🤖",
|
|
75
|
+
"- **Home:** ~/projects",
|
|
76
|
+
].join("\n");
|
|
77
|
+
|
|
78
|
+
const fields = parseIdentityFields(content);
|
|
79
|
+
expect(fields.name).toBe("Jarvis");
|
|
80
|
+
expect(fields.role).toBe("Coding assistant");
|
|
81
|
+
expect(fields.personality).toBe("Friendly and helpful");
|
|
82
|
+
expect(fields.emoji).toBe("🤖");
|
|
83
|
+
expect(fields.home).toBe("~/projects");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("handles a mix of real and placeholder values", () => {
|
|
87
|
+
const content = [
|
|
88
|
+
"- **Name:** Jarvis",
|
|
89
|
+
"- **Role:** _(not yet established)_",
|
|
90
|
+
"- **Personality:** Friendly",
|
|
91
|
+
"- **Emoji:** _(not yet chosen)_",
|
|
92
|
+
"- **Home:** ~/dev",
|
|
93
|
+
].join("\n");
|
|
94
|
+
|
|
95
|
+
const fields = parseIdentityFields(content);
|
|
96
|
+
expect(fields.name).toBe("Jarvis");
|
|
97
|
+
expect(fields.role).toBe("");
|
|
98
|
+
expect(fields.personality).toBe("Friendly");
|
|
99
|
+
expect(fields.emoji).toBe("");
|
|
100
|
+
expect(fields.home).toBe("~/dev");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("returns role: '' when IDENTITY.md contains placeholder role", () => {
|
|
104
|
+
const content = "- **Role:** _(not yet established)_";
|
|
105
|
+
const fields = parseIdentityFields(content);
|
|
106
|
+
expect(fields.role).toBe("");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("returns name: '' when IDENTITY.md contains placeholder name", () => {
|
|
110
|
+
const content = "- **Name:** _(not yet chosen)_";
|
|
111
|
+
const fields = parseIdentityFields(content);
|
|
112
|
+
expect(fields.name).toBe("");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('parses role: "Coding assistant" for real values', () => {
|
|
116
|
+
const content = "- **Role:** Coding assistant";
|
|
117
|
+
const fields = parseIdentityFields(content);
|
|
118
|
+
expect(fields.role).toBe("Coding assistant");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("returns empty strings when content has no identity fields", () => {
|
|
122
|
+
const fields = parseIdentityFields("# Some other content\nHello world");
|
|
123
|
+
expect(fields.name).toBe("");
|
|
124
|
+
expect(fields.role).toBe("");
|
|
125
|
+
expect(fields.personality).toBe("");
|
|
126
|
+
expect(fields.emoji).toBe("");
|
|
127
|
+
expect(fields.home).toBe("");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -68,9 +68,7 @@ mock.module("../util/retry.js", () => {
|
|
|
68
68
|
const causeCode = (error.cause as NodeJS.ErrnoException).code;
|
|
69
69
|
if (causeCode && retryableCodes.has(causeCode)) return true;
|
|
70
70
|
}
|
|
71
|
-
if (
|
|
72
|
-
RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))
|
|
73
|
-
) {
|
|
71
|
+
if (RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))) {
|
|
74
72
|
return true;
|
|
75
73
|
}
|
|
76
74
|
const cause = error.cause;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
mock.module("../util/logger.js", () => ({
|
|
4
|
+
getLogger: () =>
|
|
5
|
+
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
import { FailoverProvider } from "../providers/failover.js";
|
|
9
|
+
import type {
|
|
10
|
+
Message,
|
|
11
|
+
Provider,
|
|
12
|
+
ProviderResponse,
|
|
13
|
+
} from "../providers/types.js";
|
|
14
|
+
import { ProviderError } from "../util/errors.js";
|
|
15
|
+
|
|
16
|
+
const MESSAGES: Message[] = [
|
|
17
|
+
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function successResponse(
|
|
21
|
+
overrides?: Partial<ProviderResponse>,
|
|
22
|
+
): ProviderResponse {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: "ok" }],
|
|
25
|
+
model: "test-model",
|
|
26
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
27
|
+
stopReason: "end_turn",
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("FailoverProvider actual provider propagation", () => {
|
|
33
|
+
test("stamps the winning provider when failover uses a fallback", async () => {
|
|
34
|
+
const primary: Provider = {
|
|
35
|
+
name: "openrouter",
|
|
36
|
+
async sendMessage() {
|
|
37
|
+
throw new ProviderError("down", "openrouter", 500);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const secondary: Provider = {
|
|
41
|
+
name: "fireworks",
|
|
42
|
+
async sendMessage() {
|
|
43
|
+
return successResponse();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const provider = new FailoverProvider([primary, secondary]);
|
|
48
|
+
const response = await provider.sendMessage(MESSAGES);
|
|
49
|
+
|
|
50
|
+
expect(response.actualProvider).toBe("fireworks");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("preserves an inner provider's actual provider when already set", async () => {
|
|
54
|
+
const inner: Provider = {
|
|
55
|
+
name: "retry-wrapper",
|
|
56
|
+
async sendMessage() {
|
|
57
|
+
return successResponse({ actualProvider: "anthropic" });
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const provider = new FailoverProvider([inner]);
|
|
62
|
+
const response = await provider.sendMessage(MESSAGES);
|
|
63
|
+
|
|
64
|
+
expect(response.actualProvider).toBe("anthropic");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -81,6 +81,23 @@ const mockAttachments: Array<{
|
|
|
81
81
|
let mockAttachmentIdCounter = 0;
|
|
82
82
|
|
|
83
83
|
mock.module("../memory/attachments-store.js", () => ({
|
|
84
|
+
attachFileBackedAttachmentToMessage: (
|
|
85
|
+
_messageId: string,
|
|
86
|
+
_position: number,
|
|
87
|
+
filename: string,
|
|
88
|
+
mimeType: string,
|
|
89
|
+
_filePath: string,
|
|
90
|
+
sizeBytes: number,
|
|
91
|
+
) => {
|
|
92
|
+
const att = {
|
|
93
|
+
id: `att-${++mockAttachmentIdCounter}`,
|
|
94
|
+
originalFilename: filename,
|
|
95
|
+
mimeType,
|
|
96
|
+
sizeBytes,
|
|
97
|
+
};
|
|
98
|
+
mockAttachments.push(att);
|
|
99
|
+
return att;
|
|
100
|
+
},
|
|
84
101
|
uploadFileBackedAttachment: (
|
|
85
102
|
filename: string,
|
|
86
103
|
mimeType: string,
|
|
@@ -109,7 +109,7 @@ describe("tool registry dynamic-tools tools", () => {
|
|
|
109
109
|
|
|
110
110
|
describe("tool manifest", () => {
|
|
111
111
|
test("eager module tool names list contains expected count", () => {
|
|
112
|
-
expect(eagerModuleToolNames.length).toBe(
|
|
112
|
+
expect(eagerModuleToolNames.length).toBe(9);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test("explicit tools list includes memory and credential tools", () => {
|
|
@@ -187,14 +187,9 @@ describe("baseline characterization: core app tool surface", () => {
|
|
|
187
187
|
|
|
188
188
|
const nonProxyAppTools = [
|
|
189
189
|
"app_create",
|
|
190
|
-
"app_list",
|
|
191
|
-
"app_query",
|
|
192
|
-
"app_update",
|
|
193
190
|
"app_delete",
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"app_file_edit",
|
|
197
|
-
"app_file_write",
|
|
191
|
+
"app_generate_icon",
|
|
192
|
+
"app_refresh",
|
|
198
193
|
];
|
|
199
194
|
|
|
200
195
|
for (const name of nonProxyAppTools) {
|
|
@@ -159,7 +159,7 @@ mock.module("../providers/registry.js", () => {
|
|
|
159
159
|
getDefaultModel: (providerName: string) => {
|
|
160
160
|
const defaults: Record<string, string> = {
|
|
161
161
|
anthropic: "claude-opus-4-6",
|
|
162
|
-
openai: "gpt-5.
|
|
162
|
+
openai: "gpt-5.4",
|
|
163
163
|
gemini: "gemini-3-flash",
|
|
164
164
|
ollama: "llama3.2",
|
|
165
165
|
fireworks: "accounts/fireworks/models/kimi-k2p5",
|
|
@@ -116,7 +116,7 @@ describe("Runtime attachment metadata", () => {
|
|
|
116
116
|
);
|
|
117
117
|
|
|
118
118
|
// Upload and link an attachment using "self" as the assistantId
|
|
119
|
-
const stored = uploadAttachment("chart.png", "image/png", "
|
|
119
|
+
const stored = uploadAttachment("chart.png", "image/png", "iVBORw==");
|
|
120
120
|
linkAttachmentToMessage(assistantMsg.id, stored.id, 0);
|
|
121
121
|
|
|
122
122
|
const res = await fetch(
|
|
@@ -181,7 +181,11 @@ describe("Runtime attachment metadata", () => {
|
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
test("GET /attachments/:id returns attachment with payload", async () => {
|
|
184
|
-
const stored = uploadAttachment(
|
|
184
|
+
const stored = uploadAttachment(
|
|
185
|
+
"report.pdf",
|
|
186
|
+
"application/pdf",
|
|
187
|
+
"JVBERA==",
|
|
188
|
+
);
|
|
185
189
|
|
|
186
190
|
const res = await fetch(
|
|
187
191
|
`http://127.0.0.1:${port}/v1/attachments/${stored.id}`,
|
|
@@ -201,7 +205,7 @@ describe("Runtime attachment metadata", () => {
|
|
|
201
205
|
expect(body.filename).toBe("report.pdf");
|
|
202
206
|
expect(body.mimeType).toBe("application/pdf");
|
|
203
207
|
expect(body.kind).toBe("document");
|
|
204
|
-
expect(body.data).toBe("
|
|
208
|
+
expect(body.data).toBe("JVBERA==");
|
|
205
209
|
expect(body.sizeBytes).toBeGreaterThan(0);
|
|
206
210
|
});
|
|
207
211
|
|
|
@@ -70,21 +70,53 @@ describe("injectActivityField", () => {
|
|
|
70
70
|
expect("activity" in props0).toBe(false);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
test("
|
|
73
|
+
test("returns unchanged when activity is in top-level properties but not required", () => {
|
|
74
74
|
const defs = [
|
|
75
75
|
makeDef("my_tool", {
|
|
76
76
|
type: "object",
|
|
77
77
|
properties: { activity: { type: "number" } },
|
|
78
|
-
required: [],
|
|
78
|
+
required: ["foo"],
|
|
79
79
|
}),
|
|
80
80
|
];
|
|
81
81
|
const result = injectActivityField(defs);
|
|
82
|
-
// Should be the exact same object reference (no
|
|
82
|
+
// Should be the exact same object reference (no modification)
|
|
83
83
|
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
84
84
|
const schema = result[0].input_schema as Record<string, unknown>;
|
|
85
85
|
const props = schema.properties as Record<string, unknown>;
|
|
86
86
|
// Original activity type preserved
|
|
87
87
|
expect(props.activity).toEqual({ type: "number" });
|
|
88
|
+
// required NOT modified — don't promote server-defined optional activity
|
|
89
|
+
expect(schema.required).toEqual(["foo"]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("returns unchanged when activity is in both top-level properties AND required", () => {
|
|
93
|
+
const defs = [
|
|
94
|
+
makeDef("my_tool", {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: { activity: { type: "string" } },
|
|
97
|
+
required: ["activity"],
|
|
98
|
+
}),
|
|
99
|
+
];
|
|
100
|
+
const result = injectActivityField(defs);
|
|
101
|
+
const schema = result[0].input_schema as Record<string, unknown>;
|
|
102
|
+
const props = schema.properties as Record<string, unknown>;
|
|
103
|
+
// Original activity type preserved
|
|
104
|
+
expect(props.activity).toEqual({ type: "string" });
|
|
105
|
+
// activity must be in required even though it was already in properties
|
|
106
|
+
expect(schema.required).toEqual(["activity"]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("skips tools that already have activity in both properties and required", () => {
|
|
110
|
+
const defs = [
|
|
111
|
+
makeDef("my_tool", {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: { activity: { type: "number" } },
|
|
114
|
+
required: ["activity"],
|
|
115
|
+
}),
|
|
116
|
+
];
|
|
117
|
+
const result = injectActivityField(defs);
|
|
118
|
+
// Should be the exact same object reference (no clone needed)
|
|
119
|
+
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
88
120
|
});
|
|
89
121
|
|
|
90
122
|
test("does NOT mutate original definition objects", () => {
|
|
@@ -125,6 +157,60 @@ describe("injectActivityField", () => {
|
|
|
125
157
|
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
126
158
|
});
|
|
127
159
|
|
|
160
|
+
test("does NOT add activity to top-level required when only in oneOf branch", () => {
|
|
161
|
+
const defs = [
|
|
162
|
+
makeDef("my_tool", {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: { shared: { type: "string" } },
|
|
165
|
+
oneOf: [
|
|
166
|
+
{
|
|
167
|
+
properties: {
|
|
168
|
+
activity: { type: "string" },
|
|
169
|
+
branch_a: { type: "number" },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
properties: { branch_b: { type: "boolean" } },
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
required: ["shared"],
|
|
177
|
+
}),
|
|
178
|
+
];
|
|
179
|
+
const result = injectActivityField(defs);
|
|
180
|
+
// Should be the exact same object reference (no modification)
|
|
181
|
+
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
182
|
+
const schema = result[0].input_schema as Record<string, unknown>;
|
|
183
|
+
// Top-level required should NOT include activity
|
|
184
|
+
expect(schema.required).toEqual(["shared"]);
|
|
185
|
+
// Top-level properties should NOT have activity injected
|
|
186
|
+
const props = schema.properties as Record<string, unknown>;
|
|
187
|
+
expect("activity" in props).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("does NOT add activity to top-level required when only in allOf sub-schema with additionalProperties: false", () => {
|
|
191
|
+
const defs = [
|
|
192
|
+
makeDef("my_tool", {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: { foo: { type: "string" } },
|
|
195
|
+
additionalProperties: false,
|
|
196
|
+
allOf: [
|
|
197
|
+
{
|
|
198
|
+
properties: { activity: { type: "string" } },
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
required: ["foo"],
|
|
202
|
+
}),
|
|
203
|
+
];
|
|
204
|
+
const result = injectActivityField(defs);
|
|
205
|
+
// Should be the exact same object reference (no modification)
|
|
206
|
+
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
207
|
+
const schema = result[0].input_schema as Record<string, unknown>;
|
|
208
|
+
// Top-level required should NOT include activity
|
|
209
|
+
expect(schema.required).toEqual(["foo"]);
|
|
210
|
+
const props = schema.properties as Record<string, unknown>;
|
|
211
|
+
expect("activity" in props).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
128
214
|
test("skips tools with activity defined inside allOf member (composite schema)", () => {
|
|
129
215
|
const defs = [
|
|
130
216
|
makeDef("my_tool", {
|
|
@@ -139,18 +225,92 @@ describe("injectActivityField", () => {
|
|
|
139
225
|
}),
|
|
140
226
|
];
|
|
141
227
|
const result = injectActivityField(defs);
|
|
142
|
-
// Should be the exact same object reference (no
|
|
228
|
+
// Should be the exact same object reference (no modification)
|
|
143
229
|
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
144
230
|
const schema = result[0].input_schema as Record<string, unknown>;
|
|
145
231
|
const props = schema.properties as Record<string, unknown>;
|
|
146
|
-
// Top-level properties should NOT have activity injected
|
|
232
|
+
// Top-level properties should NOT have activity injected (it's in allOf)
|
|
147
233
|
expect("activity" in props).toBe(false);
|
|
234
|
+
// Top-level required should NOT include activity (it's only in composite sub-schemas)
|
|
235
|
+
expect(schema.required).toEqual([]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("skips allOf composite schema where activity is already required", () => {
|
|
239
|
+
const defs = [
|
|
240
|
+
makeDef("my_tool", {
|
|
241
|
+
type: "object",
|
|
242
|
+
properties: { foo: { type: "string" } },
|
|
243
|
+
allOf: [
|
|
244
|
+
{
|
|
245
|
+
properties: { activity: { type: "string" } },
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
required: ["activity"],
|
|
249
|
+
}),
|
|
250
|
+
];
|
|
251
|
+
const result = injectActivityField(defs);
|
|
252
|
+
// Should be the exact same object reference (no change needed)
|
|
253
|
+
expect(Object.is(result[0], defs[0])).toBe(true);
|
|
148
254
|
});
|
|
149
255
|
|
|
150
256
|
test("handles empty definitions array", () => {
|
|
151
257
|
const result = injectActivityField([]);
|
|
152
258
|
expect(result).toEqual([]);
|
|
153
259
|
});
|
|
260
|
+
|
|
261
|
+
test("injects activity only on tools that don't define it at all", () => {
|
|
262
|
+
const defs = [
|
|
263
|
+
// Normal tool without activity — should get it injected
|
|
264
|
+
makeDef("tool_a", {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: { foo: { type: "string" } },
|
|
267
|
+
required: ["foo"],
|
|
268
|
+
}),
|
|
269
|
+
// Tool defines activity in properties but NOT in required — left unchanged
|
|
270
|
+
makeDef("tool_b", {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: {
|
|
273
|
+
bar: { type: "string" },
|
|
274
|
+
activity: { type: "string", description: "custom activity" },
|
|
275
|
+
},
|
|
276
|
+
required: ["bar"],
|
|
277
|
+
}),
|
|
278
|
+
// Tool that defines activity in both properties AND required — left unchanged
|
|
279
|
+
makeDef("tool_c", {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
baz: { type: "number" },
|
|
283
|
+
activity: { type: "string", description: "custom activity" },
|
|
284
|
+
},
|
|
285
|
+
required: ["baz", "activity"],
|
|
286
|
+
}),
|
|
287
|
+
// Non-object schema — should be left alone
|
|
288
|
+
makeDef("tool_d", { type: "string" }),
|
|
289
|
+
// Object schema without properties — should be left alone
|
|
290
|
+
makeDef("tool_e", { type: "object" }),
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
const result = injectActivityField(defs);
|
|
294
|
+
|
|
295
|
+
// tool_a: activity injected and required
|
|
296
|
+
const schemaA = result[0].input_schema as Record<string, unknown>;
|
|
297
|
+
expect(
|
|
298
|
+
(schemaA.properties as Record<string, unknown>).activity,
|
|
299
|
+
).toBeDefined();
|
|
300
|
+
expect(schemaA.required).toEqual(["foo", "activity"]);
|
|
301
|
+
|
|
302
|
+
// tool_b: unchanged (activity optional, not promoted)
|
|
303
|
+
expect(Object.is(result[1], defs[1])).toBe(true);
|
|
304
|
+
const schemaB = result[1].input_schema as Record<string, unknown>;
|
|
305
|
+
expect(schemaB.required).toEqual(["bar"]);
|
|
306
|
+
|
|
307
|
+
// tool_c: unchanged (activity already present and required)
|
|
308
|
+
expect(Object.is(result[2], defs[2])).toBe(true);
|
|
309
|
+
|
|
310
|
+
// tool_d, tool_e: unchanged
|
|
311
|
+
expect(Object.is(result[3], defs[3])).toBe(true);
|
|
312
|
+
expect(Object.is(result[4], defs[4])).toBe(true);
|
|
313
|
+
});
|
|
154
314
|
});
|
|
155
315
|
|
|
156
316
|
describe("schemaDefinesProperty", () => {
|
|
@@ -330,7 +330,7 @@ describe("getAttachmentsForMessage", () => {
|
|
|
330
330
|
|
|
331
331
|
test("returns attachments linked to a message", async () => {
|
|
332
332
|
const msgId = await createMessage("assistant", "Here is a chart");
|
|
333
|
-
const stored = uploadAttachment("chart.png", "image/png", "
|
|
333
|
+
const stored = uploadAttachment("chart.png", "image/png", "iVBORw==");
|
|
334
334
|
linkAttachmentToMessage(msgId, stored.id, 0);
|
|
335
335
|
|
|
336
336
|
const result = getAttachmentsForMessage(msgId);
|
|
@@ -338,7 +338,7 @@ describe("getAttachmentsForMessage", () => {
|
|
|
338
338
|
expect(result[0].id).toBe(stored.id);
|
|
339
339
|
expect(result[0].originalFilename).toBe("chart.png");
|
|
340
340
|
expect(result[0].mimeType).toBe("image/png");
|
|
341
|
-
expect(result[0].dataBase64).toBe("
|
|
341
|
+
expect(result[0].dataBase64).toBe("iVBORw==");
|
|
342
342
|
});
|
|
343
343
|
|
|
344
344
|
test("returns empty array when no attachments are linked", () => {
|