@vellumai/assistant 0.5.5 → 0.5.7
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +4 -5
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +1 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +3 -3
- package/src/__tests__/context-window-manager.test.ts +78 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +117 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-regressions.test.ts +8 -30
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/require-fresh-approval.test.ts +4 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/tool-executor.test.ts +4 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/conversations.ts +0 -18
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +16 -16
- package/src/config/feature-flag-registry.json +48 -16
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -25
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/platform.ts +1 -1
- package/src/config/schemas/security.ts +4 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/context/window-manager.ts +53 -2
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +6 -4
- package/src/daemon/conversation-agent-loop.ts +0 -60
- package/src/daemon/conversation-memory.ts +0 -117
- package/src/daemon/conversation-runtime-assembly.ts +0 -2
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/conversations.ts +0 -11
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +229 -96
- package/src/daemon/message-types/conversations.ts +3 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/followups/followup-store.ts +5 -2
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-crud.ts +0 -236
- package/src/memory/conversation-title-service.ts +76 -11
- package/src/memory/db-init.ts +15 -11
- package/src/memory/indexer.ts +15 -106
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/embedding.ts +0 -79
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +30 -13
- package/src/memory/jobs-worker.ts +31 -27
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/index.ts +5 -3
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-client.ts +4 -6
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/conversations.ts +0 -3
- package/src/memory/schema/index.ts +0 -2
- package/src/messaging/draft-store.ts +2 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/defaults.ts +3 -3
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/trust-client.ts +2 -13
- package/src/permissions/trust-store.ts +8 -3
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +29 -1
- package/src/runtime/auth/token-service.ts +53 -15
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +29 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-management-routes.ts +0 -36
- package/src/runtime/routes/conversation-query-routes.ts +106 -2
- package/src/runtime/routes/conversation-routes.ts +4 -43
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +221 -3
- package/src/runtime/routes/memory-item-routes.ts +144 -4
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/schedule/schedule-store.ts +0 -21
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/skills/inline-command-render.ts +5 -1
- package/src/skills/inline-command-runner.ts +30 -2
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/handlers.ts +1 -129
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +9 -2
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +8 -55
- package/src/util/xml.ts +8 -0
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/heartbeat-service.ts +5 -24
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/archive-recall.test.ts +0 -560
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
- package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
- package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
- package/src/__tests__/memory-brief-time.test.ts +0 -285
- package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
- package/src/__tests__/memory-chunk-archive.test.ts +0 -400
- package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
- package/src/__tests__/memory-episode-archive.test.ts +0 -370
- package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
- package/src/__tests__/memory-observation-archive.test.ts +0 -375
- package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
- package/src/__tests__/memory-reducer-job.test.ts +0 -538
- package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
- package/src/__tests__/memory-reducer-store.test.ts +0 -728
- package/src/__tests__/memory-reducer-types.test.ts +0 -707
- package/src/__tests__/memory-reducer.test.ts +0 -704
- package/src/__tests__/memory-simplified-config.test.ts +0 -281
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
- package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/memory-simplified.ts +0 -101
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/archive-recall.ts +0 -516
- package/src/memory/archive-store.ts +0 -400
- package/src/memory/brief-formatting.ts +0 -33
- package/src/memory/brief-open-loops.ts +0 -266
- package/src/memory/brief-time.ts +0 -162
- package/src/memory/brief.ts +0 -75
- package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
- package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
- package/src/memory/migrations/185-memory-brief-state.ts +0 -52
- package/src/memory/migrations/186-memory-archive.ts +0 -109
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
- package/src/memory/reducer-scheduler.ts +0 -242
- package/src/memory/reducer-store.ts +0 -271
- package/src/memory/reducer-types.ts +0 -106
- package/src/memory/reducer.ts +0 -467
- package/src/memory/schema/memory-archive.ts +0 -121
- package/src/memory/schema/memory-brief.ts +0 -55
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock,
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
let mockTwilioPhoneNumber: string | undefined;
|
|
4
4
|
let mockRawConfig: Record<string, unknown> | undefined;
|
|
5
5
|
let mockSecureKeys: Record<string, string>;
|
|
6
6
|
let mockHasTwilioCredentials: boolean;
|
|
7
|
-
let mockGatewayHealth = {
|
|
8
|
-
target: "http://127.0.0.1:7830",
|
|
9
|
-
healthy: true,
|
|
10
|
-
localDeployment: true,
|
|
11
|
-
error: undefined as string | undefined,
|
|
12
|
-
};
|
|
13
7
|
|
|
14
8
|
mock.module("../calls/twilio-rest.js", () => ({
|
|
15
9
|
getPhoneNumberSid: async () => null,
|
|
@@ -57,14 +51,12 @@ mock.module("../runtime/channel-invite-transports/whatsapp.js", () => ({
|
|
|
57
51
|
import type { ChannelId } from "../channels/types.js";
|
|
58
52
|
import {
|
|
59
53
|
ChannelReadinessService,
|
|
60
|
-
createReadinessService,
|
|
61
54
|
REMOTE_TTL_MS,
|
|
62
55
|
} from "../runtime/channel-readiness-service.js";
|
|
63
56
|
import type {
|
|
64
57
|
ChannelProbe,
|
|
65
58
|
ReadinessCheckResult,
|
|
66
59
|
} from "../runtime/channel-readiness-types.js";
|
|
67
|
-
import * as localGatewayHealth from "../runtime/local-gateway-health.js";
|
|
68
60
|
|
|
69
61
|
// ── Test helpers ────────────────────────────────────────────────────────────
|
|
70
62
|
|
|
@@ -104,12 +96,6 @@ describe("ChannelReadinessService", () => {
|
|
|
104
96
|
mockRawConfig = undefined;
|
|
105
97
|
mockSecureKeys = {};
|
|
106
98
|
mockHasTwilioCredentials = false;
|
|
107
|
-
mockGatewayHealth = {
|
|
108
|
-
target: "http://127.0.0.1:7830",
|
|
109
|
-
healthy: true,
|
|
110
|
-
localDeployment: true,
|
|
111
|
-
error: undefined,
|
|
112
|
-
};
|
|
113
99
|
});
|
|
114
100
|
|
|
115
101
|
test("local checks run on every call (no caching of local results)", async () => {
|
|
@@ -403,49 +389,4 @@ describe("ChannelReadinessService", () => {
|
|
|
403
389
|
|
|
404
390
|
expect(probe.remoteCallCount).toBe(1);
|
|
405
391
|
});
|
|
406
|
-
|
|
407
|
-
test("voice readiness includes gateway_health when ingress is configured", async () => {
|
|
408
|
-
mockHasTwilioCredentials = true;
|
|
409
|
-
mockTwilioPhoneNumber = "+15550001111";
|
|
410
|
-
mockRawConfig = {
|
|
411
|
-
ingress: {
|
|
412
|
-
enabled: true,
|
|
413
|
-
publicBaseUrl: "https://voice.example.com",
|
|
414
|
-
},
|
|
415
|
-
};
|
|
416
|
-
mockGatewayHealth = {
|
|
417
|
-
target: "http://127.0.0.1:7830",
|
|
418
|
-
healthy: false,
|
|
419
|
-
localDeployment: true,
|
|
420
|
-
error: "connect ECONNREFUSED 127.0.0.1:7830",
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
const probeLocalGatewayHealthSpy = spyOn(
|
|
424
|
-
localGatewayHealth,
|
|
425
|
-
"probeLocalGatewayHealth",
|
|
426
|
-
).mockImplementation(async () => ({
|
|
427
|
-
...mockGatewayHealth,
|
|
428
|
-
}));
|
|
429
|
-
|
|
430
|
-
let snapshot: Awaited<
|
|
431
|
-
ReturnType<ChannelReadinessService["getReadiness"]>
|
|
432
|
-
>[number];
|
|
433
|
-
try {
|
|
434
|
-
const readinessService = createReadinessService();
|
|
435
|
-
[snapshot] = await readinessService.getReadiness("phone");
|
|
436
|
-
} finally {
|
|
437
|
-
probeLocalGatewayHealthSpy.mockRestore();
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const gatewayHealthCheck = snapshot.localChecks.find(
|
|
441
|
-
(check) => check.name === "gateway_health",
|
|
442
|
-
);
|
|
443
|
-
expect(gatewayHealthCheck).toBeDefined();
|
|
444
|
-
expect(gatewayHealthCheck?.passed).toBe(false);
|
|
445
|
-
expect(snapshot.reasons).toContainEqual({
|
|
446
|
-
code: "gateway_health",
|
|
447
|
-
text: "Local gateway is not serving requests at http://127.0.0.1:7830: connect ECONNREFUSED 127.0.0.1:7830",
|
|
448
|
-
});
|
|
449
|
-
expect(snapshot.ready).toBe(false);
|
|
450
|
-
});
|
|
451
392
|
});
|
|
@@ -1606,13 +1606,15 @@ describe("Permission Checker", () => {
|
|
|
1606
1606
|
expect(options[0].description).toContain("compound");
|
|
1607
1607
|
});
|
|
1608
1608
|
|
|
1609
|
-
test("compound command via pipeline yields exact-
|
|
1609
|
+
test("compound command via pipeline yields exact + action-key allowlist options", async () => {
|
|
1610
1610
|
const options = await generateAllowlistOptions("bash", {
|
|
1611
1611
|
command: "git log | grep fix",
|
|
1612
1612
|
});
|
|
1613
|
-
expect(options).
|
|
1613
|
+
expect(options.length).toBeGreaterThanOrEqual(2);
|
|
1614
1614
|
expect(options[0].description).toContain("compound");
|
|
1615
1615
|
expect(options[0].pattern).toBe("git log | grep fix");
|
|
1616
|
+
// Pipeline action keys should be offered as broader options
|
|
1617
|
+
expect(options.some((o) => o.pattern.startsWith("action:"))).toBe(true);
|
|
1616
1618
|
});
|
|
1617
1619
|
|
|
1618
1620
|
test("compound command via && yields exact-only allowlist option", async () => {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Guard test: assistant CLI commands must always classify as Low risk.
|
|
2
|
+
//
|
|
3
|
+
// The assistant uses its own CLI tools during normal operation. If these
|
|
4
|
+
// commands require user approval, it blocks autonomous assistant workflows.
|
|
5
|
+
// See #18982 / #18998 for the regression that motivated this guard.
|
|
6
|
+
|
|
7
|
+
import { mkdtempSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
const guardTestDir = mkdtempSync(join(tmpdir(), "cli-risk-guard-test-"));
|
|
13
|
+
|
|
14
|
+
mock.module("../util/platform.js", () => ({
|
|
15
|
+
getRootDir: () => guardTestDir,
|
|
16
|
+
getDataDir: () => join(guardTestDir, "data"),
|
|
17
|
+
getWorkspaceSkillsDir: () => join(guardTestDir, "skills"),
|
|
18
|
+
isMacOS: () => process.platform === "darwin",
|
|
19
|
+
isLinux: () => process.platform === "linux",
|
|
20
|
+
isWindows: () => process.platform === "win32",
|
|
21
|
+
getPidPath: () => join(guardTestDir, "test.pid"),
|
|
22
|
+
getDbPath: () => join(guardTestDir, "test.db"),
|
|
23
|
+
getLogPath: () => join(guardTestDir, "test.log"),
|
|
24
|
+
ensureDataDir: () => {},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module("../util/logger.js", () => ({
|
|
28
|
+
getLogger: () =>
|
|
29
|
+
new Proxy({} as Record<string, unknown>, {
|
|
30
|
+
get: (_target: Record<string, unknown>, _prop: string) => {
|
|
31
|
+
return () => {};
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
mock.module("../config/loader.js", () => ({
|
|
37
|
+
getConfig: () => ({
|
|
38
|
+
permissions: { mode: "workspace" },
|
|
39
|
+
skills: { load: { extraDirs: [] } },
|
|
40
|
+
sandbox: { enabled: true },
|
|
41
|
+
}),
|
|
42
|
+
loadConfig: () => ({}),
|
|
43
|
+
invalidateConfigCache: () => {},
|
|
44
|
+
saveConfig: () => {},
|
|
45
|
+
loadRawConfig: () => ({}),
|
|
46
|
+
saveRawConfig: () => {},
|
|
47
|
+
getNestedValue: () => undefined,
|
|
48
|
+
setNestedValue: () => {},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
import { buildCliProgram } from "../cli/program.js";
|
|
52
|
+
import { classifyRisk } from "../permissions/checker.js";
|
|
53
|
+
import { RiskLevel } from "../permissions/types.js";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Assert that a command classifies as Low risk, with a descriptive failure
|
|
57
|
+
* message that guides developers toward the correct fix.
|
|
58
|
+
*/
|
|
59
|
+
function expectLowRisk(command: string, actual: RiskLevel): void {
|
|
60
|
+
if (actual !== RiskLevel.Low) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`"${command}" classified as ${actual} instead of Low. ` +
|
|
63
|
+
`assistant CLI commands must always be Low risk — the assistant ` +
|
|
64
|
+
`uses its own CLI during normal operation. If you need risk ` +
|
|
65
|
+
`escalation for specific subcommands, add them to an allowlist ` +
|
|
66
|
+
`in this guard test with justification.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
expect(actual).toBe(RiskLevel.Low);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Dynamically extract subcommand names from the CLI program definition.
|
|
73
|
+
// This ensures new commands added to program.ts are automatically covered
|
|
74
|
+
// by this guard test without manual list maintenance.
|
|
75
|
+
const program = buildCliProgram();
|
|
76
|
+
const ASSISTANT_SUBCOMMANDS = program.commands.map((c) => c.name());
|
|
77
|
+
|
|
78
|
+
describe("CLI command risk guard: assistant commands", () => {
|
|
79
|
+
test("subcommand discovery found a reasonable number of commands", () => {
|
|
80
|
+
// Sanity check: if mocking breaks and no commands are registered,
|
|
81
|
+
// the risk guard would vacuously pass. Require a minimum count to
|
|
82
|
+
// catch that failure mode. Update this threshold when commands are
|
|
83
|
+
// removed (but it should only grow).
|
|
84
|
+
expect(ASSISTANT_SUBCOMMANDS.length).toBeGreaterThanOrEqual(20);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("all assistant CLI subcommands classify as Low risk", async () => {
|
|
88
|
+
for (const subcommand of ASSISTANT_SUBCOMMANDS) {
|
|
89
|
+
const command = `assistant ${subcommand}`;
|
|
90
|
+
const risk = await classifyRisk("bash", { command });
|
|
91
|
+
expectLowRisk(command, risk);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("bare assistant command classifies as Low risk", async () => {
|
|
96
|
+
const risk = await classifyRisk("bash", { command: "assistant" });
|
|
97
|
+
expectLowRisk("assistant", risk);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("assistant with flags classifies as Low risk", async () => {
|
|
101
|
+
const flagCommands = [
|
|
102
|
+
"assistant --version",
|
|
103
|
+
"assistant --help",
|
|
104
|
+
"assistant doctor --verbose",
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (const command of flagCommands) {
|
|
108
|
+
const risk = await classifyRisk("bash", { command });
|
|
109
|
+
expectLowRisk(command, risk);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -137,7 +137,6 @@ describe("AssistantConfigSchema", () => {
|
|
|
137
137
|
action: "redact",
|
|
138
138
|
entropyThreshold: 4.0,
|
|
139
139
|
allowOneTimeSend: false,
|
|
140
|
-
blockIngress: true,
|
|
141
140
|
});
|
|
142
141
|
expect(result.auditLog).toEqual({ retentionDays: 0 });
|
|
143
142
|
});
|
|
@@ -437,7 +436,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
437
436
|
|
|
438
437
|
test("defaults permissions.mode to workspace", () => {
|
|
439
438
|
const result = AssistantConfigSchema.parse({});
|
|
440
|
-
expect(result.permissions).toEqual({ mode: "workspace" });
|
|
439
|
+
expect(result.permissions).toEqual({ mode: "workspace", dangerouslySkipPermissions: false });
|
|
441
440
|
});
|
|
442
441
|
|
|
443
442
|
test("accepts explicit permissions.mode strict", () => {
|
|
@@ -638,6 +637,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
638
637
|
voice: {
|
|
639
638
|
language: "en-US",
|
|
640
639
|
transcriptionProvider: "Deepgram",
|
|
640
|
+
ttsProvider: "elevenlabs",
|
|
641
641
|
},
|
|
642
642
|
callerIdentity: {
|
|
643
643
|
allowPerCallOverride: true,
|
|
@@ -1139,7 +1139,7 @@ describe("loadConfig with schema validation", () => {
|
|
|
1139
1139
|
test("defaults permissions.mode to workspace when not specified", () => {
|
|
1140
1140
|
writeConfig({});
|
|
1141
1141
|
const config = loadConfig();
|
|
1142
|
-
expect(config.permissions).toEqual({ mode: "workspace" });
|
|
1142
|
+
expect(config.permissions).toEqual({ mode: "workspace", dangerouslySkipPermissions: false });
|
|
1143
1143
|
});
|
|
1144
1144
|
|
|
1145
1145
|
test("loads explicit permissions.mode strict", () => {
|
|
@@ -344,6 +344,84 @@ describe("ContextWindowManager", () => {
|
|
|
344
344
|
expect(result.compactedPersistedMessages).toBe(4);
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
+
test("adjusts keep boundary to preserve tool_use/tool_result pairs", async () => {
|
|
348
|
+
const provider = createProvider(() => ({
|
|
349
|
+
content: [{ type: "text", text: "## Goals\n- compacted summary" }],
|
|
350
|
+
model: "mock-model",
|
|
351
|
+
usage: { inputTokens: 75, outputTokens: 20 },
|
|
352
|
+
stopReason: "end_turn",
|
|
353
|
+
}));
|
|
354
|
+
// Configure budget so compaction keeps only the last user turn,
|
|
355
|
+
// which would normally split the tool pair because the last user
|
|
356
|
+
// turn start is a mixed message (tool_result + text) whose matching
|
|
357
|
+
// tool_use lives in the preceding assistant message.
|
|
358
|
+
const manager = new ContextWindowManager({
|
|
359
|
+
provider,
|
|
360
|
+
systemPrompt: "system prompt",
|
|
361
|
+
config: makeConfig({
|
|
362
|
+
maxInputTokens: 320,
|
|
363
|
+
targetBudgetRatio: 0.58,
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
const long = "k".repeat(220);
|
|
367
|
+
const history: Message[] = [
|
|
368
|
+
message("user", `u1 ${long}`), // index 0: old user turn (long)
|
|
369
|
+
message("assistant", `a1 ${long}`), // index 1: assistant reply (long)
|
|
370
|
+
message("user", `u2 ${long}`), // index 2: second user turn (long)
|
|
371
|
+
{
|
|
372
|
+
// index 3: assistant with tool_use
|
|
373
|
+
role: "assistant",
|
|
374
|
+
content: [
|
|
375
|
+
{
|
|
376
|
+
type: "tool_use",
|
|
377
|
+
id: "t1",
|
|
378
|
+
name: "read_file",
|
|
379
|
+
input: { path: "/tmp/a" },
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
// index 4: user with tool_result AND text (mixed = user turn start)
|
|
385
|
+
// Without adjustForToolPairs, the raw boundary would land here,
|
|
386
|
+
// orphaning the tool_result from its tool_use at index 3.
|
|
387
|
+
role: "user",
|
|
388
|
+
content: [
|
|
389
|
+
{ type: "tool_result", tool_use_id: "t1", content: "file contents" },
|
|
390
|
+
{ type: "text", text: "thanks, now continue" },
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
const result = await manager.maybeCompact(history);
|
|
396
|
+
expect(result.compacted).toBe(true);
|
|
397
|
+
// The kept messages must include the tool_use assistant message (index 3)
|
|
398
|
+
// and tool_result user message (index 4) as a pair, not split them.
|
|
399
|
+
// Verify no orphaned tool_result blocks exist in the kept messages.
|
|
400
|
+
const keptMessages = result.messages;
|
|
401
|
+
for (let i = 0; i < keptMessages.length; i++) {
|
|
402
|
+
const msg = keptMessages[i];
|
|
403
|
+
if (msg.role !== "user") continue;
|
|
404
|
+
for (const block of msg.content) {
|
|
405
|
+
if (block.type === "tool_result") {
|
|
406
|
+
// Every tool_result must have a matching tool_use in a preceding assistant message
|
|
407
|
+
const toolUseId = (block as { tool_use_id: string }).tool_use_id;
|
|
408
|
+
const hasMatchingToolUse = keptMessages
|
|
409
|
+
.slice(0, i)
|
|
410
|
+
.some(
|
|
411
|
+
(prev) =>
|
|
412
|
+
prev.role === "assistant" &&
|
|
413
|
+
prev.content.some(
|
|
414
|
+
(b) =>
|
|
415
|
+
b.type === "tool_use" &&
|
|
416
|
+
(b as { id: string }).id === toolUseId,
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
expect(hasMatchingToolUse).toBe(true);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
347
425
|
test("counts mixed tool_result+text user messages as persisted", async () => {
|
|
348
426
|
const provider = createProvider(() => ({
|
|
349
427
|
content: [{ type: "text", text: "## Goals\n- mixed summary" }],
|
|
@@ -31,11 +31,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
31
31
|
truncateForLog: (value: string) => value,
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
-
// Mock security check to always pass
|
|
35
|
-
mock.module("../security/secret-ingress.js", () => ({
|
|
36
|
-
checkIngressForSecrets: () => ({ blocked: false }),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
34
|
// Mock render to return the raw content as text
|
|
40
35
|
mock.module("../daemon/handlers/shared.js", () => ({
|
|
41
36
|
renderHistoryContent: (content: unknown) => ({
|
|
@@ -111,10 +111,8 @@ mock.module("../util/platform.js", () => ({
|
|
|
111
111
|
getTCPPort: () => 8765,
|
|
112
112
|
isIOSPairingEnabled: () => false,
|
|
113
113
|
isTCPEnabled: () => false,
|
|
114
|
-
readLockfile: () => null,
|
|
115
114
|
readPlatformToken: () => null,
|
|
116
115
|
readSessionToken: () => null,
|
|
117
|
-
writeLockfile: () => {},
|
|
118
116
|
ensureDataDir: () => {},
|
|
119
117
|
}));
|
|
120
118
|
|
|
@@ -209,11 +209,9 @@ mock.module("../util/logger.js", () => ({
|
|
|
209
209
|
mock.module("../config/loader.js", () => ({
|
|
210
210
|
getConfig: () => ({
|
|
211
211
|
skills: { entries: {}, allowBundled: null },
|
|
212
|
-
assistantFeatureFlagValues: {},
|
|
213
212
|
}),
|
|
214
213
|
loadConfig: () => ({
|
|
215
214
|
skills: { entries: {}, allowBundled: null },
|
|
216
|
-
assistantFeatureFlagValues: {},
|
|
217
215
|
}),
|
|
218
216
|
invalidateConfigCache: () => {},
|
|
219
217
|
}));
|
|
@@ -1721,58 +1719,6 @@ describe("bundled skill: gmail", () => {
|
|
|
1721
1719
|
});
|
|
1722
1720
|
});
|
|
1723
1721
|
|
|
1724
|
-
describe("bundled skill: claude-code", () => {
|
|
1725
|
-
let sessionState: Map<string, string>;
|
|
1726
|
-
|
|
1727
|
-
beforeEach(() => {
|
|
1728
|
-
mockCatalog = [];
|
|
1729
|
-
mockManifests = {};
|
|
1730
|
-
mockRegisteredTools = new Map();
|
|
1731
|
-
mockUnregisteredSkillIds = [];
|
|
1732
|
-
mockSkillRefCount = new Map();
|
|
1733
|
-
mockSkillRefCount = new Map();
|
|
1734
|
-
mockVersionHashes = {};
|
|
1735
|
-
mockVersionHashErrors = new Set();
|
|
1736
|
-
sessionState = new Map<string, string>();
|
|
1737
|
-
});
|
|
1738
|
-
|
|
1739
|
-
test("claude-code skill activation registers claude_code in allowedToolNames", () => {
|
|
1740
|
-
mockCatalog = [
|
|
1741
|
-
makeSkill("claude-code", "/path/to/bundled-skills/claude-code"),
|
|
1742
|
-
];
|
|
1743
|
-
mockManifests = { "claude-code": makeManifest(["claude_code"]) };
|
|
1744
|
-
|
|
1745
|
-
const history: Message[] = [
|
|
1746
|
-
...skillLoadMessages('<loaded_skill id="claude-code" />'),
|
|
1747
|
-
];
|
|
1748
|
-
|
|
1749
|
-
const result = projectSkillTools(history, {
|
|
1750
|
-
previouslyActiveSkillIds: sessionState,
|
|
1751
|
-
});
|
|
1752
|
-
|
|
1753
|
-
expect(result.toolDefinitions).toEqual([]);
|
|
1754
|
-
expect(result.allowedToolNames).toEqual(new Set(["claude_code"]));
|
|
1755
|
-
});
|
|
1756
|
-
|
|
1757
|
-
test("claude_code tool is absent when claude-code skill is not active", () => {
|
|
1758
|
-
mockCatalog = [
|
|
1759
|
-
makeSkill("claude-code", "/path/to/bundled-skills/claude-code"),
|
|
1760
|
-
];
|
|
1761
|
-
mockManifests = { "claude-code": makeManifest(["claude_code"]) };
|
|
1762
|
-
|
|
1763
|
-
const history: Message[] = [
|
|
1764
|
-
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
1765
|
-
];
|
|
1766
|
-
|
|
1767
|
-
const result = projectSkillTools(history, {
|
|
1768
|
-
previouslyActiveSkillIds: sessionState,
|
|
1769
|
-
});
|
|
1770
|
-
|
|
1771
|
-
expect(result.toolDefinitions).toHaveLength(0);
|
|
1772
|
-
expect(result.allowedToolNames.has("claude_code")).toBe(false);
|
|
1773
|
-
});
|
|
1774
|
-
});
|
|
1775
|
-
|
|
1776
1722
|
// ---------------------------------------------------------------------------
|
|
1777
1723
|
// Bundled skill: app-builder
|
|
1778
1724
|
// ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
const mockRunBtwSidechain = mock(async () => ({
|
|
3
|
+
const mockRunBtwSidechain = mock(async (_params: Record<string, unknown>) => ({
|
|
4
4
|
text: "Project kickoff",
|
|
5
5
|
hadTextDeltas: true,
|
|
6
6
|
response: {
|
|
@@ -93,6 +93,8 @@ describe("conversation-title-service", () => {
|
|
|
93
93
|
expect(mockRunBtwSidechain).toHaveBeenCalledWith(
|
|
94
94
|
expect.objectContaining({
|
|
95
95
|
provider,
|
|
96
|
+
systemPrompt: expect.stringContaining("conversation titles"),
|
|
97
|
+
tools: [],
|
|
96
98
|
maxTokens: 37,
|
|
97
99
|
modelIntent: "latency-optimized",
|
|
98
100
|
timeoutMs: 10_000,
|
|
@@ -105,6 +107,93 @@ describe("conversation-title-service", () => {
|
|
|
105
107
|
);
|
|
106
108
|
});
|
|
107
109
|
|
|
110
|
+
test("regeneration extracts text from JSON content blocks", async () => {
|
|
111
|
+
mockGetMessages.mockReturnValueOnce([
|
|
112
|
+
{
|
|
113
|
+
role: "user",
|
|
114
|
+
content: JSON.stringify([
|
|
115
|
+
{ type: "text", text: "Help me plan the kickoff" },
|
|
116
|
+
]),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
role: "assistant",
|
|
120
|
+
content: JSON.stringify([
|
|
121
|
+
{ type: "text", text: "Sure, here's a plan" },
|
|
122
|
+
{ type: "tool_use", id: "toolu_1", name: "web_search", input: {} },
|
|
123
|
+
]),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
role: "user",
|
|
127
|
+
content: JSON.stringify([{ type: "text", text: "Looks good" }]),
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
const provider = {
|
|
132
|
+
name: "test-provider",
|
|
133
|
+
sendMessage: mock(async () => {
|
|
134
|
+
throw new Error("should not call directly");
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
await regenerateConversationTitle({ conversationId: "conv-1", provider });
|
|
139
|
+
|
|
140
|
+
// The prompt sent to the sidechain should contain plain text, not raw JSON
|
|
141
|
+
const prompt = (mockRunBtwSidechain.mock.calls[0] as any)?.[0]
|
|
142
|
+
?.content as string;
|
|
143
|
+
expect(prompt).not.toContain('"type":"text"');
|
|
144
|
+
expect(prompt).not.toContain('"type":"tool_use"');
|
|
145
|
+
// Tool metadata should NOT appear in the title prompt
|
|
146
|
+
expect(prompt).not.toContain("Tool use");
|
|
147
|
+
expect(prompt).not.toContain("web_search");
|
|
148
|
+
expect(prompt).toContain("Help me plan the kickoff");
|
|
149
|
+
expect(prompt).toContain("Sure, here's a plan");
|
|
150
|
+
expect(prompt).toContain("Looks good");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("regeneration extracts text from tool_result content blocks", async () => {
|
|
154
|
+
mockGetMessages.mockReturnValueOnce([
|
|
155
|
+
{
|
|
156
|
+
role: "user",
|
|
157
|
+
content: JSON.stringify([
|
|
158
|
+
{ type: "text", text: "Search for restaurants" },
|
|
159
|
+
]),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
role: "assistant",
|
|
163
|
+
content: JSON.stringify([
|
|
164
|
+
{ type: "tool_use", id: "toolu_1", name: "web_search", input: {} },
|
|
165
|
+
]),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
role: "user",
|
|
169
|
+
content: JSON.stringify([
|
|
170
|
+
{
|
|
171
|
+
type: "tool_result",
|
|
172
|
+
tool_use_id: "toolu_1",
|
|
173
|
+
content: "Found 3 restaurants nearby",
|
|
174
|
+
},
|
|
175
|
+
]),
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
const provider = {
|
|
180
|
+
name: "test-provider",
|
|
181
|
+
sendMessage: mock(async () => {
|
|
182
|
+
throw new Error("should not call directly");
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
await regenerateConversationTitle({ conversationId: "conv-1", provider });
|
|
187
|
+
|
|
188
|
+
const prompt = (mockRunBtwSidechain.mock.calls[0] as any)?.[0]
|
|
189
|
+
?.content as string;
|
|
190
|
+
expect(prompt).not.toContain('"type":"tool_result"');
|
|
191
|
+
// Tool-only assistant message should be skipped entirely
|
|
192
|
+
expect(prompt).not.toContain("Tool use");
|
|
193
|
+
expect(prompt).toContain("Search for restaurants");
|
|
194
|
+
expect(prompt).toContain("Found 3 restaurants nearby");
|
|
195
|
+
});
|
|
196
|
+
|
|
108
197
|
test("uses the BTW side-chain helper for title regeneration", async () => {
|
|
109
198
|
const provider = {
|
|
110
199
|
name: "test-provider",
|
|
@@ -123,6 +212,8 @@ describe("conversation-title-service", () => {
|
|
|
123
212
|
expect(mockRunBtwSidechain).toHaveBeenCalledWith(
|
|
124
213
|
expect.objectContaining({
|
|
125
214
|
provider,
|
|
215
|
+
systemPrompt: expect.stringContaining("conversation titles"),
|
|
216
|
+
tools: [],
|
|
126
217
|
maxTokens: 37,
|
|
127
218
|
modelIntent: "latency-optimized",
|
|
128
219
|
timeoutMs: 10_000,
|
|
@@ -134,4 +225,29 @@ describe("conversation-title-service", () => {
|
|
|
134
225
|
1,
|
|
135
226
|
);
|
|
136
227
|
});
|
|
228
|
+
|
|
229
|
+
test("title prompt content does not contain generation instructions", async () => {
|
|
230
|
+
const provider = {
|
|
231
|
+
name: "test-provider",
|
|
232
|
+
sendMessage: mock(async () => {
|
|
233
|
+
throw new Error("provider.sendMessage should not be called directly");
|
|
234
|
+
}),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
await generateAndPersistConversationTitle({
|
|
238
|
+
conversationId: "conv-1",
|
|
239
|
+
provider,
|
|
240
|
+
userMessage: "Help me plan the kickoff",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const call = mockRunBtwSidechain.mock.calls[0]![0] as {
|
|
244
|
+
content: string;
|
|
245
|
+
systemPrompt: string;
|
|
246
|
+
};
|
|
247
|
+
// Instructions should be in systemPrompt, not in content
|
|
248
|
+
expect(call.content).not.toContain("Generate a very short title");
|
|
249
|
+
expect(call.content).not.toContain("do NOT respond");
|
|
250
|
+
expect(call.systemPrompt).toContain("Do NOT respond");
|
|
251
|
+
expect(call.systemPrompt).toContain("Maximum 5 words");
|
|
252
|
+
});
|
|
137
253
|
});
|