@vellumai/assistant 0.8.10 → 0.8.11-staging.1
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/bun.lock +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
const TABLE = "tool_invocations";
|
|
4
|
+
const COLUMNS: Array<{ name: string; type: string }> = [
|
|
5
|
+
{ name: "arg_bytes", type: "INTEGER" },
|
|
6
|
+
{ name: "result_bytes", type: "INTEGER" },
|
|
7
|
+
{ name: "provider", type: "TEXT" },
|
|
8
|
+
{ name: "model", type: "TEXT" },
|
|
9
|
+
{ name: "inference_profile", type: "TEXT" },
|
|
10
|
+
{ name: "inference_profile_source", type: "TEXT" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Add nullable telemetry columns to `tool_invocations` for the
|
|
15
|
+
* `tool_executed` telemetry projection: serialized payload sizes (only the
|
|
16
|
+
* sizes leave the device, never the payloads) and the conversation's model
|
|
17
|
+
* attribution at invocation time. Nullable so pre-migration and
|
|
18
|
+
* permission-denied rows stay NULL (see the legacy-row filter in
|
|
19
|
+
* tool-executed-events-store.ts).
|
|
20
|
+
*
|
|
21
|
+
* Idempotent pure DDL with a PRAGMA guard, no registry entry needed
|
|
22
|
+
* (matches the 273 / 275 pattern).
|
|
23
|
+
*/
|
|
24
|
+
export function migrateToolInvocationsTelemetryColumns(
|
|
25
|
+
database: DrizzleDb,
|
|
26
|
+
): void {
|
|
27
|
+
const raw = getSqliteFrom(database);
|
|
28
|
+
|
|
29
|
+
const columns = raw.query(`PRAGMA table_info(${TABLE})`).all() as Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
}>;
|
|
32
|
+
const columnNames = new Set(columns.map((c) => c.name));
|
|
33
|
+
|
|
34
|
+
for (const { name, type } of COLUMNS) {
|
|
35
|
+
if (!columnNames.has(name)) {
|
|
36
|
+
raw.exec(`ALTER TABLE ${TABLE} ADD COLUMN ${name} ${type}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { createSkillLoadedEventsTable } from "./279-create-skill-loaded-events.js";
|
|
8
|
+
|
|
9
|
+
function createTestDb() {
|
|
10
|
+
const sqlite = new Database(":memory:");
|
|
11
|
+
return { sqlite, db: drizzle(sqlite, { schema }) };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function columnNames(sqlite: Database): string[] {
|
|
15
|
+
return (
|
|
16
|
+
sqlite.query("PRAGMA table_info(skill_loaded_events)").all() as Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
}>
|
|
19
|
+
).map((c) => c.name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function indexNames(sqlite: Database): string[] {
|
|
23
|
+
return (
|
|
24
|
+
sqlite.query("PRAGMA index_list(skill_loaded_events)").all() as Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
}>
|
|
27
|
+
).map((i) => i.name);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("migration 279: skill_loaded_events table", () => {
|
|
31
|
+
test("creates the table with the expected columns", () => {
|
|
32
|
+
const { sqlite, db } = createTestDb();
|
|
33
|
+
|
|
34
|
+
createSkillLoadedEventsTable(db);
|
|
35
|
+
|
|
36
|
+
expect(columnNames(sqlite)).toEqual([
|
|
37
|
+
"id",
|
|
38
|
+
"created_at",
|
|
39
|
+
"conversation_id",
|
|
40
|
+
"skill_name",
|
|
41
|
+
"skill_updated_at",
|
|
42
|
+
"provider",
|
|
43
|
+
"model",
|
|
44
|
+
"inference_profile",
|
|
45
|
+
"inference_profile_source",
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("creates the (created_at, id) cursor index", () => {
|
|
50
|
+
const { sqlite, db } = createTestDb();
|
|
51
|
+
|
|
52
|
+
createSkillLoadedEventsTable(db);
|
|
53
|
+
|
|
54
|
+
expect(indexNames(sqlite)).toContain(
|
|
55
|
+
"idx_skill_loaded_events_created_at_id",
|
|
56
|
+
);
|
|
57
|
+
const columns = (
|
|
58
|
+
sqlite
|
|
59
|
+
.query("PRAGMA index_info(idx_skill_loaded_events_created_at_id)")
|
|
60
|
+
.all() as Array<{ name: string }>
|
|
61
|
+
).map((c) => c.name);
|
|
62
|
+
expect(columns).toEqual(["created_at", "id"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("is idempotent — re-run is a no-op and preserves existing rows", () => {
|
|
66
|
+
const { sqlite, db } = createTestDb();
|
|
67
|
+
|
|
68
|
+
createSkillLoadedEventsTable(db);
|
|
69
|
+
sqlite.exec(/*sql*/ `
|
|
70
|
+
INSERT INTO skill_loaded_events (id, created_at, skill_name)
|
|
71
|
+
VALUES ('sle-1', 1000, 'web-research')
|
|
72
|
+
`);
|
|
73
|
+
|
|
74
|
+
expect(() => createSkillLoadedEventsTable(db)).not.toThrow();
|
|
75
|
+
|
|
76
|
+
const rows = sqlite.query("SELECT id FROM skill_loaded_events").all();
|
|
77
|
+
expect(rows).toEqual([{ id: "sle-1" }]);
|
|
78
|
+
expect(
|
|
79
|
+
indexNames(sqlite).filter(
|
|
80
|
+
(name) => name === "idx_skill_loaded_events_created_at_id",
|
|
81
|
+
),
|
|
82
|
+
).toHaveLength(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create the skill_loaded_events table for `skill_loaded` telemetry events —
|
|
5
|
+
* one row per activation of a Vellum-produced skill (see
|
|
6
|
+
* skill-loaded-events-store.ts for the data contract). The index backs the
|
|
7
|
+
* telemetry reporter's compound `(created_at, id)` watermark cursor.
|
|
8
|
+
*/
|
|
9
|
+
export function createSkillLoadedEventsTable(database: DrizzleDb): void {
|
|
10
|
+
database.run(/*sql*/ `
|
|
11
|
+
CREATE TABLE IF NOT EXISTS skill_loaded_events (
|
|
12
|
+
id TEXT PRIMARY KEY,
|
|
13
|
+
created_at INTEGER NOT NULL,
|
|
14
|
+
conversation_id TEXT,
|
|
15
|
+
skill_name TEXT NOT NULL,
|
|
16
|
+
skill_updated_at TEXT,
|
|
17
|
+
provider TEXT,
|
|
18
|
+
model TEXT,
|
|
19
|
+
inference_profile TEXT,
|
|
20
|
+
inference_profile_source TEXT
|
|
21
|
+
)
|
|
22
|
+
`);
|
|
23
|
+
database.run(
|
|
24
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_skill_loaded_events_created_at_id ON skill_loaded_events (created_at, id)`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { migrateConversationsSurfacedAt } from "./280-conversations-surfaced-at.js";
|
|
8
|
+
|
|
9
|
+
function createTestDb() {
|
|
10
|
+
const sqlite = new Database(":memory:");
|
|
11
|
+
// Pre-280 shape: no surfaced_at column (trimmed to the columns the
|
|
12
|
+
// migration and assertions touch).
|
|
13
|
+
sqlite.exec(/*sql*/ `
|
|
14
|
+
CREATE TABLE conversations (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
title TEXT,
|
|
17
|
+
created_at INTEGER NOT NULL,
|
|
18
|
+
updated_at INTEGER NOT NULL,
|
|
19
|
+
conversation_type TEXT NOT NULL DEFAULT 'standard',
|
|
20
|
+
archived_at INTEGER
|
|
21
|
+
)
|
|
22
|
+
`);
|
|
23
|
+
return { sqlite, db: drizzle(sqlite, { schema }) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function columnNames(sqlite: Database): string[] {
|
|
27
|
+
return (
|
|
28
|
+
sqlite.query("PRAGMA table_info(conversations)").all() as Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
}>
|
|
31
|
+
).map((c) => c.name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function indexNames(sqlite: Database): string[] {
|
|
35
|
+
return (
|
|
36
|
+
sqlite
|
|
37
|
+
.query(
|
|
38
|
+
"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'conversations'",
|
|
39
|
+
)
|
|
40
|
+
.all() as Array<{ name: string }>
|
|
41
|
+
).map((i) => i.name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("migration 280: conversations surfaced_at", () => {
|
|
45
|
+
test("adds a nullable surfaced_at column and index", () => {
|
|
46
|
+
const { sqlite, db } = createTestDb();
|
|
47
|
+
expect(columnNames(sqlite)).not.toContain("surfaced_at");
|
|
48
|
+
|
|
49
|
+
migrateConversationsSurfacedAt(db);
|
|
50
|
+
|
|
51
|
+
const column = (
|
|
52
|
+
sqlite.query("PRAGMA table_info(conversations)").all() as Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
notnull: number;
|
|
55
|
+
dflt_value: string | null;
|
|
56
|
+
}>
|
|
57
|
+
).find((c) => c.name === "surfaced_at");
|
|
58
|
+
expect(column).toBeDefined();
|
|
59
|
+
expect(column?.notnull).toBe(0);
|
|
60
|
+
expect(indexNames(sqlite)).toContain("idx_conversations_surfaced_at");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("existing rows read back with surfaced_at null", () => {
|
|
64
|
+
const { sqlite, db } = createTestDb();
|
|
65
|
+
sqlite.exec(/*sql*/ `
|
|
66
|
+
INSERT INTO conversations (id, title, created_at, updated_at, conversation_type)
|
|
67
|
+
VALUES ('conv-1', 'Background run', 1000, 1000, 'background')
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
migrateConversationsSurfacedAt(db);
|
|
71
|
+
|
|
72
|
+
const row = sqlite
|
|
73
|
+
.query("SELECT surfaced_at FROM conversations WHERE id = 'conv-1'")
|
|
74
|
+
.get() as { surfaced_at: number | null };
|
|
75
|
+
expect(row.surfaced_at).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("is idempotent — re-run is a no-op", () => {
|
|
79
|
+
const { sqlite, db } = createTestDb();
|
|
80
|
+
|
|
81
|
+
migrateConversationsSurfacedAt(db);
|
|
82
|
+
expect(() => migrateConversationsSurfacedAt(db)).not.toThrow();
|
|
83
|
+
|
|
84
|
+
expect(
|
|
85
|
+
columnNames(sqlite).filter((name) => name === "surfaced_at"),
|
|
86
|
+
).toHaveLength(1);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { tableHasColumn } from "./schema-introspection.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Add a nullable `surfaced_at` column to `conversations`.
|
|
6
|
+
*
|
|
7
|
+
* Non-null marks a background/scheduled conversation as explicitly promoted
|
|
8
|
+
* ("surfaced") into the sidebar's Recents grouping. Promotion is always an
|
|
9
|
+
* explicit API call (`POST /v1/conversations/:id/surface`) — nothing sets
|
|
10
|
+
* this automatically, so all existing rows stay NULL and default sidebar
|
|
11
|
+
* behavior is unchanged.
|
|
12
|
+
*
|
|
13
|
+
* Idempotent — re-running is a no-op once the column and index exist.
|
|
14
|
+
*/
|
|
15
|
+
export function migrateConversationsSurfacedAt(database: DrizzleDb): void {
|
|
16
|
+
if (!tableHasColumn(database, "conversations", "surfaced_at")) {
|
|
17
|
+
database.run(
|
|
18
|
+
`ALTER TABLE conversations ADD COLUMN surfaced_at INTEGER DEFAULT NULL`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
database.run(
|
|
22
|
+
`CREATE INDEX IF NOT EXISTS idx_conversations_surfaced_at ON conversations (surfaced_at)`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -257,11 +257,21 @@ export { migrateLlmUsageEventsAddAssistantVersion } from "./267-llm-usage-events
|
|
|
257
257
|
export { migrateAddMemoryV3Selections } from "./268-add-memory-v3-selections.js";
|
|
258
258
|
export { migrateScheduleScriptTimeout } from "./269-schedule-script-timeout.js";
|
|
259
259
|
export { migrateMessagesRoleCreatedAtIndex } from "./270-messages-role-created-at-index.js";
|
|
260
|
+
export {
|
|
261
|
+
downScheduleDescription,
|
|
262
|
+
migrateScheduleDescription,
|
|
263
|
+
} from "./270-schedule-description.js";
|
|
260
264
|
export { migrateScheduleSourceConversation } from "./270-schedule-source-conversation.js";
|
|
261
265
|
export { createAuthFallbackEventsTable } from "./271-create-auth-fallback-events.js";
|
|
262
266
|
export { migrateAcpSessionHistoryCwd } from "./272-acp-session-history-cwd.js";
|
|
263
267
|
export { migrateOnboardingEventsFunnelColumns } from "./273-onboarding-events-funnel-columns.js";
|
|
264
268
|
export { createActivationSessionsTable } from "./274-create-activation-sessions.js";
|
|
269
|
+
export { migrateToolInvocationsSkillId } from "./275-tool-invocations-add-skill-id.js";
|
|
270
|
+
export { migrateToolInvocationsCreatedAtIdIndex } from "./276-tool-invocations-created-at-id-index.js";
|
|
271
|
+
export { migrateAddMemoryV3EverInjected } from "./277-add-memory-v3-ever-injected.js";
|
|
272
|
+
export { migrateToolInvocationsTelemetryColumns } from "./278-tool-invocations-telemetry-columns.js";
|
|
273
|
+
export { createSkillLoadedEventsTable } from "./279-create-skill-loaded-events.js";
|
|
274
|
+
export { migrateConversationsSurfacedAt } from "./280-conversations-surfaced-at.js";
|
|
265
275
|
export {
|
|
266
276
|
MIGRATION_REGISTRY,
|
|
267
277
|
type MigrationRegistryEntry,
|
|
@@ -57,6 +57,7 @@ import { downRenameCleanedAt } from "./260-rename-cleaned-at.js";
|
|
|
57
57
|
import { downLlmUsageAddRawUsage } from "./261-llm-usage-add-raw-usage.js";
|
|
58
58
|
import { downMemoryV3Coactivation } from "./262-memory-v3-coactivation.js";
|
|
59
59
|
import { downMemoryV3AutoEdges } from "./263-memory-v3-auto-edges.js";
|
|
60
|
+
import { downScheduleDescription } from "./270-schedule-description.js";
|
|
60
61
|
|
|
61
62
|
export interface MigrationRegistryEntry {
|
|
62
63
|
/** The checkpoint key written to memory_checkpoints on completion. */
|
|
@@ -486,6 +487,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
|
|
|
486
487
|
"Create memory_v3_auto_edges table — weighted, decaying learned association graph (distinct from curated edges:) accrued by the edge-learning job from used co-activations and consumed above-threshold by edge expansion",
|
|
487
488
|
down: downMemoryV3AutoEdges,
|
|
488
489
|
},
|
|
490
|
+
{
|
|
491
|
+
key: "migration_schedule_description_backfill_v1",
|
|
492
|
+
version: 57,
|
|
493
|
+
description:
|
|
494
|
+
"Backfill authored schedule descriptions for legacy non-defer schedules from their existing names",
|
|
495
|
+
down: downScheduleDescription,
|
|
496
|
+
},
|
|
489
497
|
];
|
|
490
498
|
|
|
491
499
|
export function getMaxMigrationVersion(): number {
|
|
@@ -41,6 +41,13 @@ export const conversations = sqliteTable(
|
|
|
41
41
|
scheduleJobId: text("schedule_job_id"),
|
|
42
42
|
lastMessageAt: integer("last_message_at"),
|
|
43
43
|
archivedAt: integer("archived_at"),
|
|
44
|
+
/**
|
|
45
|
+
* Epoch-ms timestamp set when a background/scheduled conversation is
|
|
46
|
+
* explicitly promoted into the sidebar's Recents grouping. NULL (the
|
|
47
|
+
* default) means not surfaced. Set/cleared only via the surface API —
|
|
48
|
+
* never automatically.
|
|
49
|
+
*/
|
|
50
|
+
surfacedAt: integer("surfaced_at"),
|
|
44
51
|
inferenceProfile: text("inference_profile"),
|
|
45
52
|
inferenceProfileSessionId: text("inference_profile_session_id"),
|
|
46
53
|
inferenceProfileExpiresAt: integer("inference_profile_expires_at"),
|
|
@@ -51,6 +58,7 @@ export const conversations = sqliteTable(
|
|
|
51
58
|
index("idx_conversations_last_message_at").on(table.lastMessageAt),
|
|
52
59
|
index("idx_conversations_conversation_type").on(table.conversationType),
|
|
53
60
|
index("idx_conversations_archived_at").on(table.archivedAt),
|
|
61
|
+
index("idx_conversations_surfaced_at").on(table.surfacedAt),
|
|
54
62
|
index("idx_conversations_fork_parent_conversation_id").on(
|
|
55
63
|
table.forkParentConversationId,
|
|
56
64
|
),
|
|
@@ -93,6 +101,14 @@ export const toolInvocations = sqliteTable(
|
|
|
93
101
|
matchedTrustRuleId: text("matched_trust_rule_id"),
|
|
94
102
|
durationMs: integer("duration_ms").notNull(),
|
|
95
103
|
createdAt: integer("created_at").notNull(),
|
|
104
|
+
/** Serialized input size in bytes, computed before any redaction. Null pre-migration-278. */
|
|
105
|
+
argBytes: integer("arg_bytes"),
|
|
106
|
+
/** Full serialized result size in bytes, computed before truncation/redaction. Null pre-migration-278 and for denied rows. */
|
|
107
|
+
resultBytes: integer("result_bytes"),
|
|
108
|
+
provider: text("provider"),
|
|
109
|
+
model: text("model"),
|
|
110
|
+
inferenceProfile: text("inference_profile"),
|
|
111
|
+
inferenceProfileSource: text("inference_profile_source"),
|
|
96
112
|
},
|
|
97
113
|
(table) => [
|
|
98
114
|
index("idx_tool_invocations_conversation_id").on(table.conversationId),
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
export const cronJobs = sqliteTable("cron_jobs", {
|
|
11
11
|
id: text("id").primaryKey(),
|
|
12
12
|
name: text("name").notNull(),
|
|
13
|
+
description: text("description").notNull().default(""),
|
|
13
14
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
|
14
15
|
cronExpression: text("cron_expression"), // nullable for one-shot schedules; e.g. '0 9 * * 1-5'
|
|
15
16
|
scheduleSyntax: text("schedule_syntax").notNull().default("cron"), // 'cron' | 'rrule'
|
|
@@ -307,6 +308,31 @@ export const activationSessions = sqliteTable("activation_sessions", {
|
|
|
307
308
|
createdAt: integer("created_at").notNull(),
|
|
308
309
|
});
|
|
309
310
|
|
|
311
|
+
// One row per `skill_loaded` telemetry event, emitted when a Vellum-produced
|
|
312
|
+
// skill is activated in a conversation — see skill-loaded-events-store.ts for
|
|
313
|
+
// the data contract. Flushed by the usage telemetry reporter.
|
|
314
|
+
export const skillLoadedEvents = sqliteTable(
|
|
315
|
+
"skill_loaded_events",
|
|
316
|
+
{
|
|
317
|
+
id: text("id").primaryKey(),
|
|
318
|
+
createdAt: integer("created_at").notNull(),
|
|
319
|
+
conversationId: text("conversation_id"),
|
|
320
|
+
skillName: text("skill_name").notNull(),
|
|
321
|
+
// ISO 8601 timestamp from the merged skill catalog, when known.
|
|
322
|
+
skillUpdatedAt: text("skill_updated_at"),
|
|
323
|
+
provider: text("provider"),
|
|
324
|
+
model: text("model"),
|
|
325
|
+
inferenceProfile: text("inference_profile"),
|
|
326
|
+
inferenceProfileSource: text("inference_profile_source"),
|
|
327
|
+
},
|
|
328
|
+
(table) => [
|
|
329
|
+
index("idx_skill_loaded_events_created_at_id").on(
|
|
330
|
+
table.createdAt,
|
|
331
|
+
table.id,
|
|
332
|
+
),
|
|
333
|
+
],
|
|
334
|
+
);
|
|
335
|
+
|
|
310
336
|
export const traceEvents = sqliteTable(
|
|
311
337
|
"trace_events",
|
|
312
338
|
{
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Silence the logger.
|
|
4
|
+
mock.module("../util/logger.js", () => ({
|
|
5
|
+
getLogger: () =>
|
|
6
|
+
new Proxy({} as Record<string, unknown>, {
|
|
7
|
+
get: () => () => {},
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
let collectUsageData = true;
|
|
12
|
+
|
|
13
|
+
mock.module("../config/loader.js", () => ({
|
|
14
|
+
getConfig: () => ({
|
|
15
|
+
ui: {},
|
|
16
|
+
model: "test",
|
|
17
|
+
provider: "test",
|
|
18
|
+
memory: { enabled: false },
|
|
19
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
20
|
+
secretDetection: { enabled: false },
|
|
21
|
+
collectUsageData,
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
import { getDb } from "./db-connection.js";
|
|
26
|
+
import { initializeDb } from "./db-init.js";
|
|
27
|
+
import { skillLoadedEvents } from "./schema.js";
|
|
28
|
+
import {
|
|
29
|
+
queryUnreportedSkillLoadedEvents,
|
|
30
|
+
recordSkillLoadedEvent,
|
|
31
|
+
} from "./skill-loaded-events-store.js";
|
|
32
|
+
|
|
33
|
+
initializeDb();
|
|
34
|
+
|
|
35
|
+
function insertEvent(
|
|
36
|
+
id: string,
|
|
37
|
+
createdAt: number,
|
|
38
|
+
skillName = "web-research",
|
|
39
|
+
): void {
|
|
40
|
+
getDb().insert(skillLoadedEvents).values({ id, createdAt, skillName }).run();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("skill-loaded-events-store", () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
collectUsageData = true;
|
|
46
|
+
getDb().delete(skillLoadedEvents).run();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("honors the collectUsageData opt-out (records nothing)", () => {
|
|
50
|
+
collectUsageData = false;
|
|
51
|
+
recordSkillLoadedEvent({ skillName: "web-research" });
|
|
52
|
+
expect(queryUnreportedSkillLoadedEvents(0, undefined, 10)).toHaveLength(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("record + query round-trips all fields", () => {
|
|
56
|
+
recordSkillLoadedEvent({
|
|
57
|
+
conversationId: "conv-xyz",
|
|
58
|
+
skillName: "web-research",
|
|
59
|
+
skillUpdatedAt: "2026-06-01T00:00:00.000Z",
|
|
60
|
+
provider: "anthropic",
|
|
61
|
+
model: "model-a",
|
|
62
|
+
inferenceProfile: "balanced",
|
|
63
|
+
inferenceProfileSource: "active-profile",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const rows = queryUnreportedSkillLoadedEvents(0, undefined, 10);
|
|
67
|
+
expect(rows).toHaveLength(1);
|
|
68
|
+
const row = rows[0]!;
|
|
69
|
+
expect(row.id).toBeString();
|
|
70
|
+
expect(row.createdAt).toBeGreaterThan(0);
|
|
71
|
+
expect(row).toMatchObject({
|
|
72
|
+
conversationId: "conv-xyz",
|
|
73
|
+
skillName: "web-research",
|
|
74
|
+
skillUpdatedAt: "2026-06-01T00:00:00.000Z",
|
|
75
|
+
provider: "anthropic",
|
|
76
|
+
model: "model-a",
|
|
77
|
+
inferenceProfile: "balanced",
|
|
78
|
+
inferenceProfileSource: "active-profile",
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("optional fields persist as null", () => {
|
|
83
|
+
recordSkillLoadedEvent({ skillName: "tasks" });
|
|
84
|
+
|
|
85
|
+
const rows = queryUnreportedSkillLoadedEvents(0, undefined, 10);
|
|
86
|
+
expect(rows).toHaveLength(1);
|
|
87
|
+
expect(rows[0]).toMatchObject({
|
|
88
|
+
skillName: "tasks",
|
|
89
|
+
conversationId: null,
|
|
90
|
+
skillUpdatedAt: null,
|
|
91
|
+
provider: null,
|
|
92
|
+
model: null,
|
|
93
|
+
inferenceProfile: null,
|
|
94
|
+
inferenceProfileSource: null,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("returns rows in (createdAt, id) order", () => {
|
|
99
|
+
insertEvent("sle-b", 2000);
|
|
100
|
+
insertEvent("sle-a", 1000);
|
|
101
|
+
|
|
102
|
+
const rows = queryUnreportedSkillLoadedEvents(0, undefined, 100);
|
|
103
|
+
expect(rows.map((r) => r.id)).toEqual(["sle-a", "sle-b"]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("query advances past the compound (createdAt, id) cursor", () => {
|
|
107
|
+
// Two rows in the same millisecond: pagination must use the id
|
|
108
|
+
// tiebreaker to make forward progress, not loop.
|
|
109
|
+
insertEvent("sle-1", 5000);
|
|
110
|
+
insertEvent("sle-2", 5000);
|
|
111
|
+
insertEvent("sle-3", 6000);
|
|
112
|
+
|
|
113
|
+
const first = queryUnreportedSkillLoadedEvents(0, undefined, 1);
|
|
114
|
+
expect(first.map((r) => r.id)).toEqual(["sle-1"]);
|
|
115
|
+
|
|
116
|
+
const second = queryUnreportedSkillLoadedEvents(
|
|
117
|
+
first[0]!.createdAt,
|
|
118
|
+
first[0]!.id,
|
|
119
|
+
100,
|
|
120
|
+
);
|
|
121
|
+
expect(second.map((r) => r.id)).toEqual(["sle-2", "sle-3"]);
|
|
122
|
+
|
|
123
|
+
// Without an id cursor the timestamp-only branch is used.
|
|
124
|
+
expect(
|
|
125
|
+
queryUnreportedSkillLoadedEvents(5000, undefined, 100).map((r) => r.id),
|
|
126
|
+
).toEqual(["sle-3"]);
|
|
127
|
+
|
|
128
|
+
// Cursor past the last row returns nothing.
|
|
129
|
+
const last = second[second.length - 1]!;
|
|
130
|
+
expect(
|
|
131
|
+
queryUnreportedSkillLoadedEvents(last.createdAt, last.id, 100).length,
|
|
132
|
+
).toBe(0);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("resumes from a persisted watermark without re-reporting", () => {
|
|
136
|
+
insertEvent("sle-w1", 1000);
|
|
137
|
+
insertEvent("sle-w2", 2000);
|
|
138
|
+
|
|
139
|
+
const batch = queryUnreportedSkillLoadedEvents(0, undefined, 100);
|
|
140
|
+
const watermark = batch[batch.length - 1]!;
|
|
141
|
+
|
|
142
|
+
insertEvent("sle-w3", 3000);
|
|
143
|
+
|
|
144
|
+
const resumed = queryUnreportedSkillLoadedEvents(
|
|
145
|
+
watermark.createdAt,
|
|
146
|
+
watermark.id,
|
|
147
|
+
100,
|
|
148
|
+
);
|
|
149
|
+
expect(resumed.map((r) => r.id)).toEqual(["sle-w3"]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("honors the limit", () => {
|
|
153
|
+
insertEvent("sle-l1", 1000);
|
|
154
|
+
insertEvent("sle-l2", 2000);
|
|
155
|
+
insertEvent("sle-l3", 3000);
|
|
156
|
+
|
|
157
|
+
const rows = queryUnreportedSkillLoadedEvents(0, undefined, 2);
|
|
158
|
+
expect(rows.map((r) => r.id)).toEqual(["sle-l1", "sle-l2"]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { and, asc, eq, gt, or } from "drizzle-orm";
|
|
2
|
+
import { v4 as uuid } from "uuid";
|
|
3
|
+
|
|
4
|
+
import { getConfig } from "../config/loader.js";
|
|
5
|
+
import type { UsageAttributionColumns } from "../usage/attribution.js";
|
|
6
|
+
import { getDb } from "./db-connection.js";
|
|
7
|
+
import { skillLoadedEvents } from "./schema.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Input for one `skill_loaded` telemetry event. Metadata only — never skill
|
|
11
|
+
* output or conversation content. The attribution columns reuse the shared
|
|
12
|
+
* nullable shape from `toAttributionColumns` so producers can spread it
|
|
13
|
+
* directly; null and absent both persist as NULL.
|
|
14
|
+
*/
|
|
15
|
+
export interface SkillLoadedEventRecord extends Partial<UsageAttributionColumns> {
|
|
16
|
+
conversationId?: string;
|
|
17
|
+
skillName: string;
|
|
18
|
+
/** ISO 8601 timestamp from the merged skill catalog, when known. */
|
|
19
|
+
skillUpdatedAt?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A persisted skill_loaded event row. */
|
|
23
|
+
export interface SkillLoadedEvent {
|
|
24
|
+
id: string;
|
|
25
|
+
createdAt: number;
|
|
26
|
+
conversationId: string | null;
|
|
27
|
+
skillName: string;
|
|
28
|
+
skillUpdatedAt: string | null;
|
|
29
|
+
provider: string | null;
|
|
30
|
+
model: string | null;
|
|
31
|
+
inferenceProfile: string | null;
|
|
32
|
+
inferenceProfileSource: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Record a `skill_loaded` telemetry event for a skill activation. No-ops when
|
|
37
|
+
* usage data collection is disabled (the event is dropped to honor the
|
|
38
|
+
* opt-out, matching the rest of telemetry).
|
|
39
|
+
*/
|
|
40
|
+
export function recordSkillLoadedEvent(record: SkillLoadedEventRecord): void {
|
|
41
|
+
if (!getConfig().collectUsageData) return;
|
|
42
|
+
const db = getDb();
|
|
43
|
+
db.insert(skillLoadedEvents)
|
|
44
|
+
.values({
|
|
45
|
+
id: uuid(),
|
|
46
|
+
createdAt: Date.now(),
|
|
47
|
+
conversationId: record.conversationId,
|
|
48
|
+
skillName: record.skillName,
|
|
49
|
+
skillUpdatedAt: record.skillUpdatedAt,
|
|
50
|
+
provider: record.provider,
|
|
51
|
+
model: record.model,
|
|
52
|
+
inferenceProfile: record.inferenceProfile,
|
|
53
|
+
inferenceProfileSource: record.inferenceProfileSource,
|
|
54
|
+
})
|
|
55
|
+
.run();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Query skill_loaded events that haven't been reported to telemetry yet.
|
|
60
|
+
* Uses a compound cursor (createdAt + id) for reliable watermarking.
|
|
61
|
+
*/
|
|
62
|
+
export function queryUnreportedSkillLoadedEvents(
|
|
63
|
+
afterCreatedAt: number,
|
|
64
|
+
afterId: string | undefined,
|
|
65
|
+
limit: number,
|
|
66
|
+
): SkillLoadedEvent[] {
|
|
67
|
+
const db = getDb();
|
|
68
|
+
return db
|
|
69
|
+
.select({
|
|
70
|
+
id: skillLoadedEvents.id,
|
|
71
|
+
createdAt: skillLoadedEvents.createdAt,
|
|
72
|
+
conversationId: skillLoadedEvents.conversationId,
|
|
73
|
+
skillName: skillLoadedEvents.skillName,
|
|
74
|
+
skillUpdatedAt: skillLoadedEvents.skillUpdatedAt,
|
|
75
|
+
provider: skillLoadedEvents.provider,
|
|
76
|
+
model: skillLoadedEvents.model,
|
|
77
|
+
inferenceProfile: skillLoadedEvents.inferenceProfile,
|
|
78
|
+
inferenceProfileSource: skillLoadedEvents.inferenceProfileSource,
|
|
79
|
+
})
|
|
80
|
+
.from(skillLoadedEvents)
|
|
81
|
+
.where(
|
|
82
|
+
afterId
|
|
83
|
+
? or(
|
|
84
|
+
gt(skillLoadedEvents.createdAt, afterCreatedAt),
|
|
85
|
+
and(
|
|
86
|
+
eq(skillLoadedEvents.createdAt, afterCreatedAt),
|
|
87
|
+
gt(skillLoadedEvents.id, afterId),
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
: gt(skillLoadedEvents.createdAt, afterCreatedAt),
|
|
91
|
+
)
|
|
92
|
+
.orderBy(asc(skillLoadedEvents.createdAt), asc(skillLoadedEvents.id))
|
|
93
|
+
.limit(limit)
|
|
94
|
+
.all();
|
|
95
|
+
}
|