@vellumai/assistant 0.9.0 → 0.10.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { getLogger } from "../../util/logger.js";
|
|
2
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
const log = getLogger("migration-291");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Restores original platform-provided casing into address from external_user_id
|
|
8
|
+
* for channels where the raw platform ID is the canonical identity (Slack,
|
|
9
|
+
* Telegram, etc.). Phone and WhatsApp channels are excluded because their
|
|
10
|
+
* canonical form (E.164) may differ from the raw external_user_id.
|
|
11
|
+
*
|
|
12
|
+
* Idempotent: rows where address already equals external_user_id are no-ops.
|
|
13
|
+
*/
|
|
14
|
+
export function migrateContactChannelsRenormalizeAddresses(
|
|
15
|
+
database: DrizzleDb,
|
|
16
|
+
): void {
|
|
17
|
+
const raw = getSqliteFrom(database);
|
|
18
|
+
|
|
19
|
+
// A later migration drops external_user_id; once it has run there is nothing
|
|
20
|
+
// to renormalize from. This step re-runs on every startup, so skip when the
|
|
21
|
+
// column is absent rather than referencing it and failing.
|
|
22
|
+
const cols = raw.prepare("PRAGMA table_info(contact_channels)").all() as {
|
|
23
|
+
name: string;
|
|
24
|
+
}[];
|
|
25
|
+
if (!cols.some((c) => c.name === "external_user_id")) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Remove rows that would block normalization due to cross-column collisions.
|
|
30
|
+
raw.run(/*sql*/ `
|
|
31
|
+
DELETE FROM contact_channels
|
|
32
|
+
WHERE external_user_id IS NULL
|
|
33
|
+
AND id IN (
|
|
34
|
+
SELECT blocker.id
|
|
35
|
+
FROM contact_channels AS blocker
|
|
36
|
+
INNER JOIN contact_channels AS normalizer
|
|
37
|
+
ON normalizer.type = blocker.type
|
|
38
|
+
AND normalizer.external_user_id = blocker.address COLLATE NOCASE
|
|
39
|
+
AND normalizer.address != normalizer.external_user_id
|
|
40
|
+
AND normalizer.external_user_id IS NOT NULL
|
|
41
|
+
AND normalizer.id != blocker.id
|
|
42
|
+
)
|
|
43
|
+
`);
|
|
44
|
+
|
|
45
|
+
// Non-phone, non-email channels: restore original platform casing from
|
|
46
|
+
// external_user_id. Phone/WhatsApp are excluded because their canonical
|
|
47
|
+
// form (E.164 with '+' prefix) may differ from the raw external_user_id.
|
|
48
|
+
const nonEmailResult = raw.run(/*sql*/ `
|
|
49
|
+
UPDATE OR IGNORE contact_channels
|
|
50
|
+
SET address = external_user_id
|
|
51
|
+
WHERE external_user_id IS NOT NULL
|
|
52
|
+
AND address != external_user_id
|
|
53
|
+
AND type NOT IN ('email', 'phone', 'whatsapp')
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
// Email channels: ensure address is lowercased (canonical per RFC 5321).
|
|
57
|
+
const emailResult = raw.run(/*sql*/ `
|
|
58
|
+
UPDATE OR IGNORE contact_channels
|
|
59
|
+
SET address = LOWER(external_user_id)
|
|
60
|
+
WHERE type = 'email'
|
|
61
|
+
AND external_user_id IS NOT NULL
|
|
62
|
+
AND address != LOWER(external_user_id)
|
|
63
|
+
`);
|
|
64
|
+
|
|
65
|
+
const totalChanges = nonEmailResult.changes + emailResult.changes;
|
|
66
|
+
if (totalChanges > 0) {
|
|
67
|
+
log.info(
|
|
68
|
+
{ rowsUpdated: totalChanges },
|
|
69
|
+
"Re-normalized contact_channels addresses from external_user_id",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
5
|
+
|
|
6
|
+
import * as schema from "../schema.js";
|
|
7
|
+
import { migrateScheduleDefaultNoReuseConversation } from "./292-schedule-default-no-reuse-conversation.js";
|
|
8
|
+
|
|
9
|
+
function createTestDb() {
|
|
10
|
+
const sqlite = new Database(":memory:");
|
|
11
|
+
// Post-210 shape: reuse_conversation column present (trimmed to the columns
|
|
12
|
+
// the migration and assertions touch).
|
|
13
|
+
sqlite.exec(/*sql*/ `
|
|
14
|
+
CREATE TABLE cron_jobs (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
name TEXT NOT NULL,
|
|
17
|
+
message TEXT NOT NULL,
|
|
18
|
+
next_run_at INTEGER NOT NULL,
|
|
19
|
+
created_by TEXT NOT NULL,
|
|
20
|
+
created_at INTEGER NOT NULL,
|
|
21
|
+
updated_at INTEGER NOT NULL,
|
|
22
|
+
reuse_conversation INTEGER NOT NULL DEFAULT 0
|
|
23
|
+
)
|
|
24
|
+
`);
|
|
25
|
+
return { sqlite, db: drizzle(sqlite, { schema }) };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function insertJob(sqlite: Database, id: string, reuse: number) {
|
|
29
|
+
sqlite
|
|
30
|
+
.query(
|
|
31
|
+
/*sql*/ `INSERT INTO cron_jobs
|
|
32
|
+
(id, name, message, next_run_at, created_by, created_at, updated_at, reuse_conversation)
|
|
33
|
+
VALUES (?, ?, 'msg', 1000, 'agent', 1000, 1000, ?)`,
|
|
34
|
+
)
|
|
35
|
+
.run(id, id, reuse);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function reuseValue(sqlite: Database, id: string): number {
|
|
39
|
+
return (
|
|
40
|
+
sqlite
|
|
41
|
+
.query("SELECT reuse_conversation FROM cron_jobs WHERE id = ?")
|
|
42
|
+
.get(id) as { reuse_conversation: number }
|
|
43
|
+
).reuse_conversation;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("migration 292: schedules default to no conversation reuse", () => {
|
|
47
|
+
test("flips existing reuse_conversation=1 rows to 0", () => {
|
|
48
|
+
const { sqlite, db } = createTestDb();
|
|
49
|
+
insertJob(sqlite, "reusing", 1);
|
|
50
|
+
insertJob(sqlite, "already-fresh", 0);
|
|
51
|
+
|
|
52
|
+
migrateScheduleDefaultNoReuseConversation(db);
|
|
53
|
+
|
|
54
|
+
expect(reuseValue(sqlite, "reusing")).toBe(0);
|
|
55
|
+
expect(reuseValue(sqlite, "already-fresh")).toBe(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("is idempotent — re-run is a no-op", () => {
|
|
59
|
+
const { sqlite, db } = createTestDb();
|
|
60
|
+
insertJob(sqlite, "reusing", 1);
|
|
61
|
+
|
|
62
|
+
migrateScheduleDefaultNoReuseConversation(db);
|
|
63
|
+
expect(() => migrateScheduleDefaultNoReuseConversation(db)).not.toThrow();
|
|
64
|
+
|
|
65
|
+
expect(reuseValue(sqlite, "reusing")).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Disable conversation reuse on all existing schedules.
|
|
5
|
+
*
|
|
6
|
+
* Recurring schedules previously defaulted to `reuse_conversation = 1`, so
|
|
7
|
+
* every fire appended to one long-lived conversation. That unbounded,
|
|
8
|
+
* self-similar transcript is a drift hazard for weaker models (it primes them
|
|
9
|
+
* to repeat or extend the prior run) and grows per-fire token cost without
|
|
10
|
+
* adding correctness — durable cross-run state already lives in workspace files
|
|
11
|
+
* and memory. Schedules now default to a fresh conversation per fire; this
|
|
12
|
+
* aligns existing rows with that contract.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent: the guarded UPDATE matches nothing once every row is already 0.
|
|
15
|
+
*/
|
|
16
|
+
export function migrateScheduleDefaultNoReuseConversation(
|
|
17
|
+
database: DrizzleDb,
|
|
18
|
+
): void {
|
|
19
|
+
const raw = getSqliteFrom(database);
|
|
20
|
+
raw
|
|
21
|
+
.query(
|
|
22
|
+
/*sql*/ `UPDATE cron_jobs SET reuse_conversation = 0 WHERE reuse_conversation != 0`,
|
|
23
|
+
)
|
|
24
|
+
.run();
|
|
25
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Add nullable `input_tokens` / `output_tokens` columns to `workflow_journal`.
|
|
6
|
+
*
|
|
7
|
+
* Persists per-leaf token usage so the journal route can attribute usage to each
|
|
8
|
+
* leaf. The client then computes run-level token metrics from the per-leaf sum
|
|
9
|
+
* (a single source of truth), counting each leaf exactly once regardless of
|
|
10
|
+
* whether its usage arrives via a live `leaf_finished` event or a journal
|
|
11
|
+
* backfill — which avoids the undercount that arose when a mid-run journal
|
|
12
|
+
* aggregate counted a leaf the journal could not itself attribute.
|
|
13
|
+
*
|
|
14
|
+
* Nullable — legacy rows and non-completed leaves (failures, nested
|
|
15
|
+
* `workflow`-kind entries) stay NULL and contribute zero to the sum.
|
|
16
|
+
*
|
|
17
|
+
* Idempotent — each ALTER is wrapped so a re-run (column already present) is a
|
|
18
|
+
* no-op.
|
|
19
|
+
*/
|
|
20
|
+
export function migrateWorkflowJournalLeafTokens(database: DrizzleDb): void {
|
|
21
|
+
const raw = getSqliteFrom(database);
|
|
22
|
+
try {
|
|
23
|
+
raw.exec(`ALTER TABLE workflow_journal ADD COLUMN input_tokens INTEGER`);
|
|
24
|
+
} catch {
|
|
25
|
+
/* Column already exists */
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
raw.exec(`ALTER TABLE workflow_journal ADD COLUMN output_tokens INTEGER`);
|
|
29
|
+
} catch {
|
|
30
|
+
/* Column already exists */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getLogger } from "../../util/logger.js";
|
|
2
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
const log = getLogger("migration-294");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Drops the `external_user_id` column and its index from `contact_channels`.
|
|
8
|
+
*
|
|
9
|
+
* `address` is the single canonical identity column; `external_user_id` is
|
|
10
|
+
* redundant. The index `idx_contact_channels_type_ext_user` must be dropped
|
|
11
|
+
* first — SQLite refuses to drop a column referenced by an index.
|
|
12
|
+
*
|
|
13
|
+
* Idempotent: skips if the column has already been dropped.
|
|
14
|
+
*/
|
|
15
|
+
export function migrateDropExternalUserId(database: DrizzleDb): void {
|
|
16
|
+
const raw = getSqliteFrom(database);
|
|
17
|
+
|
|
18
|
+
const cols = raw.prepare("PRAGMA table_info(contact_channels)").all() as {
|
|
19
|
+
name: string;
|
|
20
|
+
}[];
|
|
21
|
+
const hasColumn = cols.some((c) => c.name === "external_user_id");
|
|
22
|
+
|
|
23
|
+
if (!hasColumn) {
|
|
24
|
+
log.info("external_user_id column already absent — skipping");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
raw.run("DROP INDEX IF EXISTS idx_contact_channels_type_ext_user");
|
|
29
|
+
raw.run("ALTER TABLE contact_channels DROP COLUMN external_user_id");
|
|
30
|
+
log.info("Dropped external_user_id column from contact_channels");
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getLogger } from "../../util/logger.js";
|
|
2
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
const log = getLogger("migration-295");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Drops the `approval_prompt_ts_tracker` table.
|
|
8
|
+
*
|
|
9
|
+
* Guardian approval-by-reaction no longer scopes reactions through a bespoke
|
|
10
|
+
* `(channel, chat, ts)` tracker. The canonical guardian delivery record
|
|
11
|
+
* (`canonical_guardian_deliveries.destination_message_id`) is now the single
|
|
12
|
+
* mapping from a delivered approval card to its request, so this table is dead.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent: `DROP TABLE IF EXISTS` is a no-op once the table is gone.
|
|
15
|
+
*/
|
|
16
|
+
export function dropApprovalPromptTsTrackerTable(database: DrizzleDb): void {
|
|
17
|
+
const raw = getSqliteFrom(database);
|
|
18
|
+
raw.run("DROP TABLE IF EXISTS approval_prompt_ts_tracker");
|
|
19
|
+
log.info("Dropped approval_prompt_ts_tracker table");
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { existsSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
4
|
+
|
|
5
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
6
|
+
|
|
7
|
+
import { getWorkspaceConfigPath } from "../../util/platform.js";
|
|
8
|
+
import * as schema from "../schema.js";
|
|
9
|
+
import { migrateRewriteBalancedEconomyProfilePins } from "./296-rewrite-balanced-economy-profile-pins.js";
|
|
10
|
+
|
|
11
|
+
function writeWorkspaceConfig(config: Record<string, unknown>): void {
|
|
12
|
+
writeFileSync(getWorkspaceConfigPath(), JSON.stringify(config));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
const path = getWorkspaceConfigPath();
|
|
17
|
+
if (existsSync(path)) rmSync(path);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function createTestDb(withColumns = true) {
|
|
21
|
+
const sqlite = new Database(":memory:");
|
|
22
|
+
sqlite.exec(/*sql*/ `
|
|
23
|
+
CREATE TABLE conversations (
|
|
24
|
+
id TEXT PRIMARY KEY
|
|
25
|
+
${withColumns ? ", inference_profile TEXT" : ""}
|
|
26
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
sqlite.exec(/*sql*/ `
|
|
29
|
+
CREATE TABLE cron_jobs (
|
|
30
|
+
id TEXT PRIMARY KEY
|
|
31
|
+
${withColumns ? ", inference_profile TEXT" : ""}
|
|
32
|
+
)
|
|
33
|
+
`);
|
|
34
|
+
return { sqlite, db: drizzle(sqlite, { schema }) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pin(sqlite: Database, table: string, id: string): string | null {
|
|
38
|
+
return (
|
|
39
|
+
sqlite
|
|
40
|
+
.query(`SELECT inference_profile FROM ${table} WHERE id = ?`)
|
|
41
|
+
.get(id) as { inference_profile: string | null }
|
|
42
|
+
).inference_profile;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("migration 296: rewrite balanced-economy profile pins", () => {
|
|
46
|
+
test("rewrites balanced-economy pins to balanced on conversations and schedules", () => {
|
|
47
|
+
const { sqlite, db } = createTestDb();
|
|
48
|
+
sqlite.run(
|
|
49
|
+
`INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy'), ('c2', 'quality-optimized'), ('c3', NULL)`,
|
|
50
|
+
);
|
|
51
|
+
sqlite.run(
|
|
52
|
+
`INSERT INTO cron_jobs (id, inference_profile) VALUES ('j1', 'balanced-economy'), ('j2', 'balanced')`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
migrateRewriteBalancedEconomyProfilePins(db);
|
|
56
|
+
|
|
57
|
+
expect(pin(sqlite, "conversations", "c1")).toBe("balanced");
|
|
58
|
+
// Unrelated pins and NULLs are left untouched.
|
|
59
|
+
expect(pin(sqlite, "conversations", "c2")).toBe("quality-optimized");
|
|
60
|
+
expect(pin(sqlite, "conversations", "c3")).toBeNull();
|
|
61
|
+
expect(pin(sqlite, "cron_jobs", "j1")).toBe("balanced");
|
|
62
|
+
expect(pin(sqlite, "cron_jobs", "j2")).toBe("balanced");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("is idempotent — re-run is a no-op", () => {
|
|
66
|
+
const { sqlite, db } = createTestDb();
|
|
67
|
+
sqlite.run(
|
|
68
|
+
`INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy')`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
migrateRewriteBalancedEconomyProfilePins(db);
|
|
72
|
+
expect(() => migrateRewriteBalancedEconomyProfilePins(db)).not.toThrow();
|
|
73
|
+
expect(pin(sqlite, "conversations", "c1")).toBe("balanced");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("skips a table that has no inference_profile column", () => {
|
|
77
|
+
const { db } = createTestDb(false);
|
|
78
|
+
expect(() => migrateRewriteBalancedEconomyProfilePins(db)).not.toThrow();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("leaves pins alone when balanced-economy is a user-owned profile", () => {
|
|
82
|
+
// The workspace migration keeps a user-owned profile of this name, so its
|
|
83
|
+
// pins still resolve and must not be switched to balanced.
|
|
84
|
+
writeWorkspaceConfig({
|
|
85
|
+
llm: { profiles: { "balanced-economy": { source: "user" } } },
|
|
86
|
+
});
|
|
87
|
+
const { sqlite, db } = createTestDb();
|
|
88
|
+
sqlite.run(
|
|
89
|
+
`INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy')`,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
migrateRewriteBalancedEconomyProfilePins(db);
|
|
93
|
+
|
|
94
|
+
expect(pin(sqlite, "conversations", "c1")).toBe("balanced-economy");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("rewrites pins when balanced-economy is the managed profile in config", () => {
|
|
98
|
+
writeWorkspaceConfig({
|
|
99
|
+
llm: { profiles: { "balanced-economy": { source: "managed" } } },
|
|
100
|
+
});
|
|
101
|
+
const { sqlite, db } = createTestDb();
|
|
102
|
+
sqlite.run(
|
|
103
|
+
`INSERT INTO conversations (id, inference_profile) VALUES ('c1', 'balanced-economy')`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
migrateRewriteBalancedEconomyProfilePins(db);
|
|
107
|
+
|
|
108
|
+
expect(pin(sqlite, "conversations", "c1")).toBe("balanced");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { loadRawConfig } from "../../config/loader.js";
|
|
2
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
const OLD_PROFILE = "balanced-economy";
|
|
5
|
+
const NEW_PROFILE = "balanced";
|
|
6
|
+
|
|
7
|
+
// Active inference-profile pin columns: the per-conversation override and the
|
|
8
|
+
// per-schedule override. Audit/telemetry tables (tool_invocations,
|
|
9
|
+
// llm_usage_events, skill_loaded_events) also carry an `inference_profile`, but
|
|
10
|
+
// those record what actually ran and must keep their historical value.
|
|
11
|
+
const PIN_TABLES = ["conversations", "cron_jobs"] as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Rewrite persisted `balanced-economy` inference-profile pins to `balanced`.
|
|
15
|
+
*
|
|
16
|
+
* The managed `balanced-economy` profile folds into `balanced` (both resolve to
|
|
17
|
+
* MiniMax M3 on Fireworks). The workspace-config migration removes the profile
|
|
18
|
+
* definition, but a profile key can also be pinned outside config.json — on a
|
|
19
|
+
* conversation (`conversations.inference_profile`) or a schedule
|
|
20
|
+
* (`cron_jobs.inference_profile`). Left unrewritten those pins dangle, and
|
|
21
|
+
* `resolveCallSiteConfig` treats an unresolvable override as a silent
|
|
22
|
+
* fall-through to the active/default profile — dropping the user's intended
|
|
23
|
+
* MiniMax route. `balanced-economy` is a reserved managed-profile key, so every
|
|
24
|
+
* persisted pin refers to the now-folded profile.
|
|
25
|
+
*
|
|
26
|
+
* Ownership guard: the companion workspace migration keeps a `balanced-economy`
|
|
27
|
+
* profile the user owns (`source !== "managed"`) intact, so its pins still
|
|
28
|
+
* resolve and must not be touched. Memory migrations run before workspace
|
|
29
|
+
* migrations on boot, so the profile is still in config here either way; gate on
|
|
30
|
+
* its source rather than its presence. A reserved managed key (or no profile at
|
|
31
|
+
* all) means every pin is stale and safe to rewrite.
|
|
32
|
+
*
|
|
33
|
+
* Idempotent: the WHERE clause matches nothing once rewritten. The PRAGMA guard
|
|
34
|
+
* skips a table an install hasn't created the column on yet.
|
|
35
|
+
*/
|
|
36
|
+
export function migrateRewriteBalancedEconomyProfilePins(
|
|
37
|
+
database: DrizzleDb,
|
|
38
|
+
): void {
|
|
39
|
+
if (isUserOwnedEconomyProfile()) return;
|
|
40
|
+
|
|
41
|
+
const raw = getSqliteFrom(database);
|
|
42
|
+
|
|
43
|
+
for (const table of PIN_TABLES) {
|
|
44
|
+
const cols = raw.prepare(`PRAGMA table_info(${table})`).all() as {
|
|
45
|
+
name: string;
|
|
46
|
+
}[];
|
|
47
|
+
if (!cols.some((c) => c.name === "inference_profile")) continue;
|
|
48
|
+
|
|
49
|
+
raw
|
|
50
|
+
.prepare(
|
|
51
|
+
`UPDATE ${table} SET inference_profile = ? WHERE inference_profile = ?`,
|
|
52
|
+
)
|
|
53
|
+
.run(NEW_PROFILE, OLD_PROFILE);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** True when `balanced-economy` exists in config as a user-owned profile. */
|
|
58
|
+
function isUserOwnedEconomyProfile(): boolean {
|
|
59
|
+
const llm = asObject(loadRawConfig().llm);
|
|
60
|
+
const profile = asObject(asObject(llm?.profiles)?.[OLD_PROFILE]);
|
|
61
|
+
return profile !== null && profile.source !== "managed";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function asObject(value: unknown): Record<string, unknown> | null {
|
|
65
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
66
|
+
? (value as Record<string, unknown>)
|
|
67
|
+
: null;
|
|
68
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
5
|
+
|
|
6
|
+
import { getSqliteFrom } from "../../db-connection.js";
|
|
7
|
+
import * as schema from "../../schema.js";
|
|
8
|
+
import { migrateDropLegacyMemberGuardianTables } from "../131-drop-legacy-member-guardian-tables.js";
|
|
9
|
+
|
|
10
|
+
function createTestDb() {
|
|
11
|
+
const sqlite = new Database(":memory:");
|
|
12
|
+
sqlite.exec("PRAGMA journal_mode=WAL");
|
|
13
|
+
return drizzle(sqlite, { schema });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function bootstrap(db: ReturnType<typeof createTestDb>): void {
|
|
17
|
+
const raw = getSqliteFrom(db);
|
|
18
|
+
raw.exec(/*sql*/ `
|
|
19
|
+
CREATE TABLE contacts (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
display_name TEXT NOT NULL,
|
|
22
|
+
role TEXT NOT NULL DEFAULT 'contact',
|
|
23
|
+
principal_id TEXT,
|
|
24
|
+
created_at INTEGER NOT NULL,
|
|
25
|
+
updated_at INTEGER NOT NULL
|
|
26
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
raw.exec(/*sql*/ `
|
|
29
|
+
CREATE TABLE contact_channels (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
contact_id TEXT NOT NULL,
|
|
32
|
+
type TEXT NOT NULL,
|
|
33
|
+
address TEXT NOT NULL,
|
|
34
|
+
external_user_id TEXT,
|
|
35
|
+
external_chat_id TEXT,
|
|
36
|
+
status TEXT NOT NULL DEFAULT 'unverified',
|
|
37
|
+
policy TEXT NOT NULL DEFAULT 'allow',
|
|
38
|
+
invite_id TEXT,
|
|
39
|
+
revoked_reason TEXT,
|
|
40
|
+
blocked_reason TEXT,
|
|
41
|
+
last_seen_at INTEGER,
|
|
42
|
+
verified_at INTEGER,
|
|
43
|
+
verified_via TEXT,
|
|
44
|
+
created_at INTEGER NOT NULL,
|
|
45
|
+
updated_at INTEGER
|
|
46
|
+
)
|
|
47
|
+
`);
|
|
48
|
+
raw.exec(/*sql*/ `
|
|
49
|
+
CREATE INDEX idx_contact_channels_type_ext_user
|
|
50
|
+
ON contact_channels(type, external_user_id)
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createLegacyTables(raw: Database): void {
|
|
55
|
+
raw.exec(/*sql*/ `
|
|
56
|
+
CREATE TABLE channel_guardian_bindings (
|
|
57
|
+
id TEXT PRIMARY KEY,
|
|
58
|
+
channel TEXT NOT NULL,
|
|
59
|
+
status TEXT NOT NULL,
|
|
60
|
+
guardian_external_user_id TEXT,
|
|
61
|
+
guardian_principal_id TEXT,
|
|
62
|
+
guardian_delivery_chat_id TEXT,
|
|
63
|
+
metadata_json TEXT,
|
|
64
|
+
verified_at INTEGER,
|
|
65
|
+
verified_via TEXT,
|
|
66
|
+
created_at INTEGER NOT NULL
|
|
67
|
+
)
|
|
68
|
+
`);
|
|
69
|
+
raw.exec(/*sql*/ `
|
|
70
|
+
CREATE TABLE assistant_ingress_members (
|
|
71
|
+
id TEXT PRIMARY KEY,
|
|
72
|
+
source_channel TEXT NOT NULL,
|
|
73
|
+
external_user_id TEXT,
|
|
74
|
+
external_chat_id TEXT,
|
|
75
|
+
display_name TEXT,
|
|
76
|
+
username TEXT,
|
|
77
|
+
status TEXT NOT NULL,
|
|
78
|
+
policy TEXT,
|
|
79
|
+
invite_id TEXT,
|
|
80
|
+
revoked_reason TEXT,
|
|
81
|
+
blocked_reason TEXT,
|
|
82
|
+
last_seen_at INTEGER,
|
|
83
|
+
created_at INTEGER NOT NULL,
|
|
84
|
+
updated_at INTEGER
|
|
85
|
+
)
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function tableExists(raw: Database, name: string): boolean {
|
|
90
|
+
return !!raw
|
|
91
|
+
.prepare(`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
92
|
+
.get(name);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function seedLegacyRows(raw: Database): void {
|
|
96
|
+
raw.run(
|
|
97
|
+
`INSERT INTO channel_guardian_bindings
|
|
98
|
+
(id, channel, status, guardian_external_user_id, guardian_principal_id,
|
|
99
|
+
guardian_delivery_chat_id, metadata_json, verified_at, verified_via, created_at)
|
|
100
|
+
VALUES ('g1','telegram','active','U-guardian','prin-1','chat-1',NULL,1000,'telegram',1000)`,
|
|
101
|
+
);
|
|
102
|
+
raw.run(
|
|
103
|
+
`INSERT INTO assistant_ingress_members
|
|
104
|
+
(id, source_channel, external_user_id, external_chat_id, display_name,
|
|
105
|
+
username, status, policy, invite_id, revoked_reason, blocked_reason,
|
|
106
|
+
last_seen_at, created_at, updated_at)
|
|
107
|
+
VALUES ('m1','slack','U-member',NULL,'Member One','member1','active','allow',
|
|
108
|
+
NULL,NULL,NULL,2000,2000,2000)`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
describe("migration 131 — drop legacy member/guardian tables", () => {
|
|
113
|
+
test("syncs stragglers then drops the legacy tables when the column is present", () => {
|
|
114
|
+
const db = createTestDb();
|
|
115
|
+
const raw = getSqliteFrom(db);
|
|
116
|
+
bootstrap(db);
|
|
117
|
+
createLegacyTables(raw);
|
|
118
|
+
seedLegacyRows(raw);
|
|
119
|
+
|
|
120
|
+
migrateDropLegacyMemberGuardianTables(db);
|
|
121
|
+
|
|
122
|
+
// The straggler rows were synced into contact_channels.
|
|
123
|
+
const synced = raw
|
|
124
|
+
.prepare(`SELECT external_user_id FROM contact_channels ORDER BY id`)
|
|
125
|
+
.all() as { external_user_id: string | null }[];
|
|
126
|
+
expect(synced.map((r) => r.external_user_id).sort()).toEqual([
|
|
127
|
+
"U-guardian",
|
|
128
|
+
"U-member",
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
// The legacy tables were removed.
|
|
132
|
+
expect(tableExists(raw, "channel_guardian_bindings")).toBe(false);
|
|
133
|
+
expect(tableExists(raw, "assistant_ingress_members")).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("drops the legacy tables without throwing when external_user_id is absent (re-run after migration 294)", () => {
|
|
137
|
+
const db = createTestDb();
|
|
138
|
+
const raw = getSqliteFrom(db);
|
|
139
|
+
bootstrap(db);
|
|
140
|
+
createLegacyTables(raw);
|
|
141
|
+
seedLegacyRows(raw);
|
|
142
|
+
|
|
143
|
+
// Simulate a later startup where migration 294 has already dropped the
|
|
144
|
+
// index + column. The sync references external_user_id, so 131 must skip it
|
|
145
|
+
// and still drop the tables rather than failing on every boot.
|
|
146
|
+
raw.run("DROP INDEX IF EXISTS idx_contact_channels_type_ext_user");
|
|
147
|
+
raw.run("ALTER TABLE contact_channels DROP COLUMN external_user_id");
|
|
148
|
+
|
|
149
|
+
expect(() => migrateDropLegacyMemberGuardianTables(db)).not.toThrow();
|
|
150
|
+
|
|
151
|
+
expect(tableExists(raw, "channel_guardian_bindings")).toBe(false);
|
|
152
|
+
expect(tableExists(raw, "assistant_ingress_members")).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -568,4 +568,35 @@ describe("migration 287 — dedup case collisions + drop ext_user indexes", () =
|
|
|
568
568
|
// Original casing preserved
|
|
569
569
|
expect(channels[0]!.address).toBe("U999");
|
|
570
570
|
});
|
|
571
|
+
|
|
572
|
+
test("no-op when external_user_id column is absent (re-run after migration 294)", () => {
|
|
573
|
+
const db = createTestDb();
|
|
574
|
+
const raw = getSqliteFrom(db);
|
|
575
|
+
bootstrap(db);
|
|
576
|
+
|
|
577
|
+
insertContact(raw, "c1");
|
|
578
|
+
insertChannel(raw, {
|
|
579
|
+
id: "ch1",
|
|
580
|
+
contactId: "c1",
|
|
581
|
+
type: "slack",
|
|
582
|
+
address: "U999",
|
|
583
|
+
externalUserId: "U999",
|
|
584
|
+
status: "active",
|
|
585
|
+
updatedAt: 1000,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Simulate a later startup where migration 294 has already dropped the
|
|
589
|
+
// index and column. Migration steps re-run on every startup, so this must
|
|
590
|
+
// tolerate the dropped column rather than throwing "no such column".
|
|
591
|
+
raw.run("DROP INDEX IF EXISTS idx_contact_channels_type_ext_user");
|
|
592
|
+
raw.run("ALTER TABLE contact_channels DROP COLUMN external_user_id");
|
|
593
|
+
|
|
594
|
+
expect(() => migrateContactChannelsUniqueExtUser(db)).not.toThrow();
|
|
595
|
+
|
|
596
|
+
const rows = raw
|
|
597
|
+
.prepare("SELECT id, address FROM contact_channels")
|
|
598
|
+
.all() as { id: string; address: string }[];
|
|
599
|
+
expect(rows).toHaveLength(1);
|
|
600
|
+
expect(rows[0]!.address).toBe("U999");
|
|
601
|
+
});
|
|
571
602
|
});
|