@vellumai/assistant 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +48 -7
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +71 -8
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -444,6 +444,7 @@ const WORKSPACE_SKIP_DIRS = new Set([
|
|
|
444
444
|
"embedding-models",
|
|
445
445
|
"data/qdrant",
|
|
446
446
|
"data/attachments",
|
|
447
|
+
"conversations",
|
|
447
448
|
]);
|
|
448
449
|
|
|
449
450
|
/** Files at the workspace root to skip (already covered by sanitized fields). */
|
|
@@ -513,6 +514,8 @@ function collectWorkspaceFiles(): Record<string, string> {
|
|
|
513
514
|
|
|
514
515
|
// SQLite DB handling: dump as SQL text, then enforce size cap
|
|
515
516
|
if (entry.endsWith(".db")) {
|
|
517
|
+
// Skip the dump entirely if the budget is already exhausted
|
|
518
|
+
if (totalBytes >= MAX_WORKSPACE_PAYLOAD_BYTES) continue;
|
|
516
519
|
try {
|
|
517
520
|
const proc = spawnSync("sqlite3", [fullPath, ".dump"], {
|
|
518
521
|
timeout: 10_000,
|
|
@@ -347,6 +347,40 @@ describe("Memory Item Routes", () => {
|
|
|
347
347
|
expect(body.items[1].id).toBe("i1");
|
|
348
348
|
});
|
|
349
349
|
|
|
350
|
+
test("supports sort by accessCount descending", async () => {
|
|
351
|
+
insertItem({
|
|
352
|
+
id: "i1",
|
|
353
|
+
kind: "preference",
|
|
354
|
+
subject: "s1",
|
|
355
|
+
statement: "st1",
|
|
356
|
+
});
|
|
357
|
+
insertItem({
|
|
358
|
+
id: "i2",
|
|
359
|
+
kind: "preference",
|
|
360
|
+
subject: "s2",
|
|
361
|
+
statement: "st2",
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
getDb()
|
|
365
|
+
.update(memoryItems)
|
|
366
|
+
.set({ accessCount: 2 })
|
|
367
|
+
.where(eq(memoryItems.id, "i1"))
|
|
368
|
+
.run();
|
|
369
|
+
getDb()
|
|
370
|
+
.update(memoryItems)
|
|
371
|
+
.set({ accessCount: 7 })
|
|
372
|
+
.where(eq(memoryItems.id, "i2"))
|
|
373
|
+
.run();
|
|
374
|
+
|
|
375
|
+
const ctx = makeCtx({ sort: "accessCount", order: "desc" });
|
|
376
|
+
const res = await handler(ctx);
|
|
377
|
+
const body = (await res.json()) as {
|
|
378
|
+
items: Array<{ id: string }>;
|
|
379
|
+
};
|
|
380
|
+
expect(body.items[0].id).toBe("i2");
|
|
381
|
+
expect(body.items[1].id).toBe("i1");
|
|
382
|
+
});
|
|
383
|
+
|
|
350
384
|
test("rejects invalid kind filter", async () => {
|
|
351
385
|
const ctx = makeCtx({ kind: "bogus" });
|
|
352
386
|
const res = await handler(ctx);
|
|
@@ -37,6 +37,7 @@ type MemoryItemKind = (typeof VALID_KINDS)[number];
|
|
|
37
37
|
const VALID_SORT_FIELDS = [
|
|
38
38
|
"lastSeenAt",
|
|
39
39
|
"importance",
|
|
40
|
+
"accessCount",
|
|
40
41
|
"kind",
|
|
41
42
|
"firstSeenAt",
|
|
42
43
|
] as const;
|
|
@@ -46,6 +47,7 @@ type SortField = (typeof VALID_SORT_FIELDS)[number];
|
|
|
46
47
|
const SORT_COLUMN_MAP = {
|
|
47
48
|
lastSeenAt: memoryItems.lastSeenAt,
|
|
48
49
|
importance: memoryItems.importance,
|
|
50
|
+
accessCount: memoryItems.accessCount,
|
|
49
51
|
kind: memoryItems.kind,
|
|
50
52
|
firstSeenAt: memoryItems.firstSeenAt,
|
|
51
53
|
} as const;
|
|
@@ -103,6 +105,8 @@ export function handleListMemoryItems(url: URL): Response {
|
|
|
103
105
|
|
|
104
106
|
// Build WHERE conditions
|
|
105
107
|
const conditions = [];
|
|
108
|
+
// Hide system-managed capability memories (skill announcements) from the UI
|
|
109
|
+
conditions.push(ne(memoryItems.kind, "capability"));
|
|
106
110
|
if (statusParam && statusParam !== "all") {
|
|
107
111
|
conditions.push(eq(memoryItems.status, statusParam));
|
|
108
112
|
}
|
|
@@ -160,7 +160,10 @@ export async function handleMigrationExport(req: Request): Promise<Response> {
|
|
|
160
160
|
// Best-effort: if the DB can't be checkpointed (e.g. not a valid
|
|
161
161
|
// SQLite file, missing WAL, etc.) we still proceed with the export
|
|
162
162
|
// using whatever is on disk.
|
|
163
|
-
log.warn(
|
|
163
|
+
log.warn(
|
|
164
|
+
{ err },
|
|
165
|
+
"WAL checkpoint failed — exporting without checkpoint",
|
|
166
|
+
);
|
|
164
167
|
}
|
|
165
168
|
},
|
|
166
169
|
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for OAuth app and connection CRUD.
|
|
3
|
+
*
|
|
4
|
+
* Provides endpoints for managing user-supplied OAuth apps (e.g. "your own"
|
|
5
|
+
* Google client credentials) and their connections. All endpoints are
|
|
6
|
+
* bearer-token authenticated via the standard runtime auth middleware.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
|
|
10
|
+
import {
|
|
11
|
+
deleteApp,
|
|
12
|
+
disconnectOAuthProvider,
|
|
13
|
+
getApp,
|
|
14
|
+
getAppClientSecret,
|
|
15
|
+
getConnection,
|
|
16
|
+
getProvider,
|
|
17
|
+
listApps,
|
|
18
|
+
listConnections,
|
|
19
|
+
upsertApp,
|
|
20
|
+
} from "../../oauth/oauth-store.js";
|
|
21
|
+
import { httpError } from "../http-errors.js";
|
|
22
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
23
|
+
|
|
24
|
+
function parseGrantedScopes(grantedScopes: string | string[] | null | undefined): string[] {
|
|
25
|
+
if (Array.isArray(grantedScopes)) {
|
|
26
|
+
return grantedScopes.filter((scope): scope is string => typeof scope === "string");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof grantedScopes !== "string" || grantedScopes.trim() === "") {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(grantedScopes) as unknown;
|
|
35
|
+
if (!Array.isArray(parsed)) return [];
|
|
36
|
+
return parsed.filter((scope): scope is string => typeof scope === "string");
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeHasRefreshToken(
|
|
43
|
+
hasRefreshToken: boolean | number | null | undefined,
|
|
44
|
+
): boolean {
|
|
45
|
+
return hasRefreshToken === true || hasRefreshToken === 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build route definitions for OAuth app and connection CRUD endpoints.
|
|
50
|
+
*/
|
|
51
|
+
export function oauthAppsRouteDefinitions(): RouteDefinition[] {
|
|
52
|
+
return [
|
|
53
|
+
// GET /v1/oauth/apps — List apps filtered by provider_key query param.
|
|
54
|
+
{
|
|
55
|
+
endpoint: "oauth/apps",
|
|
56
|
+
method: "GET",
|
|
57
|
+
handler: ({ url }) => {
|
|
58
|
+
const providerKey = url.searchParams.get("provider_key");
|
|
59
|
+
if (!providerKey) {
|
|
60
|
+
return httpError(
|
|
61
|
+
"BAD_REQUEST",
|
|
62
|
+
"provider_key query parameter is required",
|
|
63
|
+
400,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const allApps = listApps();
|
|
68
|
+
const filtered = allApps.filter(
|
|
69
|
+
(row) => row.providerKey === providerKey,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const providerRow = getProvider(providerKey);
|
|
73
|
+
const provider = providerRow
|
|
74
|
+
? {
|
|
75
|
+
provider_key: providerRow.providerKey,
|
|
76
|
+
display_name: providerRow.displayName ?? null,
|
|
77
|
+
description: providerRow.description ?? null,
|
|
78
|
+
dashboard_url: providerRow.dashboardUrl ?? null,
|
|
79
|
+
client_id_placeholder: providerRow.clientIdPlaceholder ?? null,
|
|
80
|
+
requires_client_secret: providerRow.requiresClientSecret ?? 1,
|
|
81
|
+
}
|
|
82
|
+
: null;
|
|
83
|
+
|
|
84
|
+
return Response.json({
|
|
85
|
+
provider,
|
|
86
|
+
apps: filtered.map((row) => ({
|
|
87
|
+
id: row.id,
|
|
88
|
+
provider_key: row.providerKey,
|
|
89
|
+
client_id: row.clientId,
|
|
90
|
+
created_at: row.createdAt,
|
|
91
|
+
updated_at: row.updatedAt,
|
|
92
|
+
})),
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// POST /v1/oauth/apps — Create an OAuth app.
|
|
98
|
+
{
|
|
99
|
+
endpoint: "oauth/apps",
|
|
100
|
+
method: "POST",
|
|
101
|
+
policyKey: "oauth/apps.create",
|
|
102
|
+
handler: async ({ req }) => {
|
|
103
|
+
const body = (await req.json()) as {
|
|
104
|
+
provider_key?: string;
|
|
105
|
+
client_id?: string;
|
|
106
|
+
client_secret?: string;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const { provider_key, client_id, client_secret } = body;
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
!provider_key ||
|
|
113
|
+
typeof provider_key !== "string" ||
|
|
114
|
+
!client_id ||
|
|
115
|
+
typeof client_id !== "string" ||
|
|
116
|
+
!client_secret ||
|
|
117
|
+
typeof client_secret !== "string"
|
|
118
|
+
) {
|
|
119
|
+
return httpError(
|
|
120
|
+
"BAD_REQUEST",
|
|
121
|
+
"provider_key, client_id, and client_secret are required non-empty strings",
|
|
122
|
+
400,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const provider = getProvider(provider_key);
|
|
127
|
+
if (!provider) {
|
|
128
|
+
return httpError(
|
|
129
|
+
"NOT_FOUND",
|
|
130
|
+
`No OAuth provider registered for "${provider_key}"`,
|
|
131
|
+
404,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const app = await upsertApp(provider_key, client_id, {
|
|
136
|
+
clientSecretValue: client_secret,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return Response.json(
|
|
140
|
+
{
|
|
141
|
+
app: {
|
|
142
|
+
id: app.id,
|
|
143
|
+
provider_key: app.providerKey,
|
|
144
|
+
client_id: app.clientId,
|
|
145
|
+
created_at: app.createdAt,
|
|
146
|
+
updated_at: app.updatedAt,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{ status: 201 },
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// DELETE /v1/oauth/apps/:id — Delete an OAuth app.
|
|
155
|
+
{
|
|
156
|
+
endpoint: "oauth/apps/:id",
|
|
157
|
+
method: "DELETE",
|
|
158
|
+
policyKey: "oauth/apps.delete",
|
|
159
|
+
handler: async ({ params }) => {
|
|
160
|
+
const app = getApp(params.id);
|
|
161
|
+
if (!app) {
|
|
162
|
+
return httpError("NOT_FOUND", `OAuth app not found: ${params.id}`, 404);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Disconnect all connections for this app first to clean up tokens.
|
|
166
|
+
const connections = listConnections(app.providerKey, app.clientId);
|
|
167
|
+
for (const conn of connections) {
|
|
168
|
+
await disconnectOAuthProvider(app.providerKey, app.clientId, conn.id);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await deleteApp(params.id);
|
|
172
|
+
|
|
173
|
+
return Response.json({ ok: true });
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// GET /v1/oauth/apps/:appId/connections — List connections for an app.
|
|
178
|
+
{
|
|
179
|
+
endpoint: "oauth/apps/:appId/connections",
|
|
180
|
+
method: "GET",
|
|
181
|
+
handler: ({ params }) => {
|
|
182
|
+
const app = getApp(params.appId);
|
|
183
|
+
if (!app) {
|
|
184
|
+
return httpError(
|
|
185
|
+
"NOT_FOUND",
|
|
186
|
+
`OAuth app not found: ${params.appId}`,
|
|
187
|
+
404,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const connections = listConnections(app.providerKey, app.clientId);
|
|
192
|
+
|
|
193
|
+
return Response.json({
|
|
194
|
+
connections: connections.map((row) => ({
|
|
195
|
+
id: row.id,
|
|
196
|
+
provider_key: row.providerKey,
|
|
197
|
+
account_info: row.accountInfo,
|
|
198
|
+
granted_scopes: parseGrantedScopes(row.grantedScopes),
|
|
199
|
+
status: row.status,
|
|
200
|
+
has_refresh_token: normalizeHasRefreshToken(row.hasRefreshToken),
|
|
201
|
+
expires_at: row.expiresAt,
|
|
202
|
+
created_at: row.createdAt,
|
|
203
|
+
updated_at: row.updatedAt,
|
|
204
|
+
})),
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
// DELETE /v1/oauth/connections/:id — Disconnect a single connection.
|
|
210
|
+
{
|
|
211
|
+
endpoint: "oauth/connections/:id",
|
|
212
|
+
method: "DELETE",
|
|
213
|
+
handler: async ({ params }) => {
|
|
214
|
+
const conn = getConnection(params.id);
|
|
215
|
+
if (!conn) {
|
|
216
|
+
return httpError(
|
|
217
|
+
"NOT_FOUND",
|
|
218
|
+
`OAuth connection not found: ${params.id}`,
|
|
219
|
+
404,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const result = await disconnectOAuthProvider(
|
|
224
|
+
conn.providerKey,
|
|
225
|
+
undefined,
|
|
226
|
+
conn.id,
|
|
227
|
+
);
|
|
228
|
+
if (result === "error") {
|
|
229
|
+
return httpError(
|
|
230
|
+
"INTERNAL_ERROR",
|
|
231
|
+
"Failed to clean up connection tokens. The connection was not removed.",
|
|
232
|
+
500,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return Response.json({ ok: true });
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// POST /v1/oauth/apps/:appId/connect — Start OAuth connect flow.
|
|
241
|
+
{
|
|
242
|
+
endpoint: "oauth/apps/:appId/connect",
|
|
243
|
+
method: "POST",
|
|
244
|
+
handler: async ({ req, params }) => {
|
|
245
|
+
const app = getApp(params.appId);
|
|
246
|
+
if (!app) {
|
|
247
|
+
return httpError(
|
|
248
|
+
"NOT_FOUND",
|
|
249
|
+
`OAuth app not found: ${params.appId}`,
|
|
250
|
+
404,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let body: { scopes?: string[] } = {};
|
|
255
|
+
try {
|
|
256
|
+
const text = await req.text();
|
|
257
|
+
if (text) {
|
|
258
|
+
body = JSON.parse(text);
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
// No body or invalid JSON — use defaults
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const clientSecret = await getAppClientSecret(app);
|
|
265
|
+
|
|
266
|
+
const result = await orchestrateOAuthConnect({
|
|
267
|
+
service: app.providerKey,
|
|
268
|
+
clientId: app.clientId,
|
|
269
|
+
clientSecret,
|
|
270
|
+
requestedScopes: body.scopes,
|
|
271
|
+
isInteractive: false,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (result.success && result.deferred) {
|
|
275
|
+
return Response.json({
|
|
276
|
+
auth_url: result.authUrl,
|
|
277
|
+
state: result.state,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!result.success) {
|
|
282
|
+
return Response.json({ error: result.error }, { status: 500 });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Interactive success (shouldn't happen with isInteractive: false,
|
|
286
|
+
// but handle gracefully)
|
|
287
|
+
return Response.json({ ok: true });
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
}
|
|
@@ -13,6 +13,8 @@ import type { CesClient } from "../../credential-execution/client.js";
|
|
|
13
13
|
import { setSentryOrganizationId } from "../../instrument.js";
|
|
14
14
|
import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
|
|
15
15
|
import { validateAnthropicApiKey } from "../../providers/anthropic/client.js";
|
|
16
|
+
import { validateGeminiApiKey } from "../../providers/gemini/client.js";
|
|
17
|
+
import { validateOpenAIApiKey } from "../../providers/openai/client.js";
|
|
16
18
|
import { initializeProviders } from "../../providers/registry.js";
|
|
17
19
|
import { credentialKey } from "../../security/credential-key.js";
|
|
18
20
|
import {
|
|
@@ -131,7 +133,7 @@ export async function handleAddSecret(
|
|
|
131
133
|
400,
|
|
132
134
|
);
|
|
133
135
|
}
|
|
134
|
-
// Validate
|
|
136
|
+
// Validate API keys before storing (Anthropic, OpenAI, Gemini)
|
|
135
137
|
if (name === "anthropic") {
|
|
136
138
|
const validation = await validateAnthropicApiKey(value);
|
|
137
139
|
if (!validation.valid) {
|
|
@@ -144,7 +146,32 @@ export async function handleAddSecret(
|
|
|
144
146
|
{ status: 422 },
|
|
145
147
|
);
|
|
146
148
|
}
|
|
149
|
+
} else if (name === "openai") {
|
|
150
|
+
const validation = await validateOpenAIApiKey(value);
|
|
151
|
+
if (!validation.valid) {
|
|
152
|
+
log.warn(
|
|
153
|
+
{ provider: name, reason: validation.reason },
|
|
154
|
+
"API key validation failed",
|
|
155
|
+
);
|
|
156
|
+
return Response.json(
|
|
157
|
+
{ success: false, error: validation.reason },
|
|
158
|
+
{ status: 422 },
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
} else if (name === "gemini") {
|
|
162
|
+
const validation = await validateGeminiApiKey(value);
|
|
163
|
+
if (!validation.valid) {
|
|
164
|
+
log.warn(
|
|
165
|
+
{ provider: name, reason: validation.reason },
|
|
166
|
+
"API key validation failed",
|
|
167
|
+
);
|
|
168
|
+
return Response.json(
|
|
169
|
+
{ success: false, error: validation.reason },
|
|
170
|
+
{ status: 422 },
|
|
171
|
+
);
|
|
172
|
+
}
|
|
147
173
|
}
|
|
174
|
+
// fireworks, openrouter, ollama — no validation (allow storage)
|
|
148
175
|
|
|
149
176
|
const stored = await setSecureKeyAsync(name, value);
|
|
150
177
|
if (!stored) {
|
|
@@ -644,6 +644,20 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
|
|
|
644
644
|
},
|
|
645
645
|
|
|
646
646
|
// OAuth connect
|
|
647
|
+
{
|
|
648
|
+
endpoint: "oauth/start",
|
|
649
|
+
method: "POST",
|
|
650
|
+
policyKey: "oauth/start",
|
|
651
|
+
handler: async ({ req }) => {
|
|
652
|
+
const body = (await req.json()) as {
|
|
653
|
+
service?: string;
|
|
654
|
+
requestedScopes?: string[];
|
|
655
|
+
};
|
|
656
|
+
return handleOAuthConnectStart(body);
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
// Legacy alias for oauth/start (kept for backwards compatibility with
|
|
660
|
+
// older clients and platform proxy routes)
|
|
647
661
|
{
|
|
648
662
|
endpoint: "integrations/oauth/start",
|
|
649
663
|
method: "POST",
|
|
@@ -42,7 +42,10 @@ export function traceEventRouteDefinitions(): RouteDefinition[] {
|
|
|
42
42
|
const afterSequence = afterSequenceParam
|
|
43
43
|
? parseInt(afterSequenceParam, 10)
|
|
44
44
|
: undefined;
|
|
45
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
afterSequenceParam &&
|
|
47
|
+
(isNaN(afterSequence!) || afterSequence! < 0)
|
|
48
|
+
) {
|
|
46
49
|
return httpError(
|
|
47
50
|
"BAD_REQUEST",
|
|
48
51
|
"afterSequence must be a non-negative integer",
|
|
@@ -15,10 +15,7 @@ import type { ScheduleSyntax } from "./recurrence-types.js";
|
|
|
15
15
|
const logger = getLogger("schedule-store");
|
|
16
16
|
|
|
17
17
|
export type ScheduleMode = "notify" | "execute";
|
|
18
|
-
export type RoutingIntent =
|
|
19
|
-
| "single_channel"
|
|
20
|
-
| "multi_channel"
|
|
21
|
-
| "all_channels";
|
|
18
|
+
export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
|
|
22
19
|
export type ScheduleStatus = "active" | "firing" | "fired" | "cancelled";
|
|
23
20
|
|
|
24
21
|
export interface ScheduleJob {
|
|
@@ -193,9 +190,7 @@ export function listSchedules(options?: {
|
|
|
193
190
|
conditions.push(isNull(scheduleJobs.cronExpression));
|
|
194
191
|
}
|
|
195
192
|
if (options?.recurringOnly) {
|
|
196
|
-
conditions.push(
|
|
197
|
-
sql`${scheduleJobs.cronExpression} IS NOT NULL`,
|
|
198
|
-
);
|
|
193
|
+
conditions.push(sql`${scheduleJobs.cronExpression} IS NOT NULL`);
|
|
199
194
|
}
|
|
200
195
|
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
201
196
|
const rows = db
|
|
@@ -415,10 +410,7 @@ export function claimDueSchedules(now: number): ScheduleJob[] {
|
|
|
415
410
|
updatedAt: now,
|
|
416
411
|
})
|
|
417
412
|
.where(
|
|
418
|
-
and(
|
|
419
|
-
eq(scheduleJobs.id, row.id),
|
|
420
|
-
eq(scheduleJobs.status, "active"),
|
|
421
|
-
),
|
|
413
|
+
and(eq(scheduleJobs.id, row.id), eq(scheduleJobs.status, "active")),
|
|
422
414
|
)
|
|
423
415
|
.run();
|
|
424
416
|
|
|
@@ -450,9 +442,7 @@ export function completeOneShot(id: string): void {
|
|
|
450
442
|
enabled: false,
|
|
451
443
|
updatedAt: now,
|
|
452
444
|
})
|
|
453
|
-
.where(
|
|
454
|
-
and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")),
|
|
455
|
-
)
|
|
445
|
+
.where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
|
|
456
446
|
.run();
|
|
457
447
|
}
|
|
458
448
|
|
|
@@ -468,9 +458,7 @@ export function failOneShot(id: string): void {
|
|
|
468
458
|
status: "active",
|
|
469
459
|
updatedAt: now,
|
|
470
460
|
})
|
|
471
|
-
.where(
|
|
472
|
-
and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")),
|
|
473
|
-
)
|
|
461
|
+
.where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
|
|
474
462
|
.run();
|
|
475
463
|
}
|
|
476
464
|
|
|
@@ -487,9 +475,7 @@ export function cancelSchedule(id: string): boolean {
|
|
|
487
475
|
enabled: false,
|
|
488
476
|
updatedAt: now,
|
|
489
477
|
})
|
|
490
|
-
.where(
|
|
491
|
-
and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "active")),
|
|
492
|
-
)
|
|
478
|
+
.where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "active")))
|
|
493
479
|
.run();
|
|
494
480
|
return rawChanges() > 0;
|
|
495
481
|
}
|
|
@@ -770,7 +756,9 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
|
|
|
770
756
|
};
|
|
771
757
|
}
|
|
772
758
|
|
|
773
|
-
function safeParseJson(
|
|
759
|
+
function safeParseJson(
|
|
760
|
+
json: string | null | undefined,
|
|
761
|
+
): Record<string, unknown> {
|
|
774
762
|
if (!json) return {};
|
|
775
763
|
try {
|
|
776
764
|
return JSON.parse(json) as Record<string, unknown>;
|
|
@@ -193,6 +193,27 @@ export async function getProviderKeyAsync(
|
|
|
193
193
|
return envVar ? process.env[envVar] : undefined;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Masked provider key — for safe display in client UIs
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Retrieve a provider API key and return a masked version suitable for
|
|
202
|
+
* display. Shows the first 10 characters and last 4, with `...` in between,
|
|
203
|
+
* always hiding at least 3 characters. Returns `null` if no key is stored.
|
|
204
|
+
*/
|
|
205
|
+
export async function getMaskedProviderKey(
|
|
206
|
+
provider: string,
|
|
207
|
+
): Promise<string | null> {
|
|
208
|
+
const key = await getProviderKeyAsync(provider);
|
|
209
|
+
if (!key || key.length === 0) return null;
|
|
210
|
+
const minHidden = 3;
|
|
211
|
+
const maxVisible = Math.max(1, key.length - minHidden);
|
|
212
|
+
const prefixLen = Math.min(10, maxVisible);
|
|
213
|
+
const suffixLen = Math.min(4, Math.max(0, maxVisible - prefixLen));
|
|
214
|
+
return `${key.slice(0, prefixLen)}...${suffixLen > 0 ? key.slice(-suffixLen) : ""}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
196
217
|
// ---------------------------------------------------------------------------
|
|
197
218
|
// Test helpers
|
|
198
219
|
// ---------------------------------------------------------------------------
|
package/src/signals/bash.ts
CHANGED
|
@@ -80,7 +80,7 @@ export function handleBashSignal(filename: string): void {
|
|
|
80
80
|
exitCode: null,
|
|
81
81
|
timedOut: false,
|
|
82
82
|
error:
|
|
83
|
-
"Bash signals are disabled. The running assistant process must have been started with VELLUM_DEBUG=1 (setting it on the CLI command alone is not enough). Restart the assistant with: VELLUM_DEBUG=1
|
|
83
|
+
"Bash signals are disabled. The running assistant process must have been started with VELLUM_DEBUG=1 (setting it on the CLI command alone is not enough). Restart the assistant with: vellum sleep && VELLUM_DEBUG=1 vellum wake",
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
return;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { resolveModelIntent } from "../providers/model-intents.js";
|
|
9
9
|
import type { ModelIntent } from "../providers/types.js";
|
|
10
|
-
import {
|
|
10
|
+
import { getProviderKeyAsync } from "../security/secure-keys.js";
|
|
11
11
|
import { getLogger } from "../util/logger.js";
|
|
12
12
|
import type {
|
|
13
13
|
SwarmWorkerBackend,
|
|
@@ -29,8 +29,7 @@ export function createClaudeCodeBackend(): SwarmWorkerBackend {
|
|
|
29
29
|
name: "claude_code",
|
|
30
30
|
|
|
31
31
|
async isAvailable(): Promise<boolean> {
|
|
32
|
-
const apiKey =
|
|
33
|
-
(await getSecureKeyAsync("anthropic")) ?? process.env.ANTHROPIC_API_KEY;
|
|
32
|
+
const apiKey = await getProviderKeyAsync("anthropic");
|
|
34
33
|
return !!apiKey;
|
|
35
34
|
},
|
|
36
35
|
|
|
@@ -39,9 +38,7 @@ export function createClaudeCodeBackend(): SwarmWorkerBackend {
|
|
|
39
38
|
const stderrLines: string[] = [];
|
|
40
39
|
try {
|
|
41
40
|
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
42
|
-
const apiKey =
|
|
43
|
-
(await getSecureKeyAsync("anthropic")) ??
|
|
44
|
-
process.env.ANTHROPIC_API_KEY;
|
|
41
|
+
const apiKey = await getProviderKeyAsync("anthropic");
|
|
45
42
|
if (!apiKey) {
|
|
46
43
|
return {
|
|
47
44
|
success: false,
|
|
@@ -97,6 +97,7 @@ mock.module("../version.js", () => ({
|
|
|
97
97
|
// Production import (after mocks)
|
|
98
98
|
// ---------------------------------------------------------------------------
|
|
99
99
|
|
|
100
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
100
101
|
import type { UsageEvent } from "../usage/types.js";
|
|
101
102
|
import { UsageTelemetryReporter } from "./usage-telemetry-reporter.js";
|
|
102
103
|
|
|
@@ -434,7 +435,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
434
435
|
expect(body.user_id).toBeUndefined();
|
|
435
436
|
});
|
|
436
437
|
|
|
437
|
-
test("assistant_id falls back to
|
|
438
|
+
test("assistant_id falls back to DAEMON_INTERNAL_ASSISTANT_ID when getExternalAssistantId returns undefined", async () => {
|
|
438
439
|
mockGetExternalAssistantId.mockReturnValue(undefined);
|
|
439
440
|
const events = [makeUsageEvent()];
|
|
440
441
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
@@ -450,7 +451,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
450
451
|
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
451
452
|
);
|
|
452
453
|
expect(body.installation_id).toBe("test-device-id");
|
|
453
|
-
expect(body.assistant_id).toBe(
|
|
454
|
+
expect(body.assistant_id).toBe(DAEMON_INTERNAL_ASSISTANT_ID);
|
|
454
455
|
});
|
|
455
456
|
|
|
456
457
|
test("turn events are included in the events array with type discriminator", async () => {
|
|
@@ -23,6 +23,7 @@ import { queryUnreportedLifecycleEvents } from "../memory/lifecycle-events-store
|
|
|
23
23
|
import { queryUnreportedUsageEvents } from "../memory/llm-usage-store.js";
|
|
24
24
|
import { queryUnreportedTurnEvents } from "../memory/turn-events-store.js";
|
|
25
25
|
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
|
|
26
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
26
27
|
import { getExternalAssistantId } from "../runtime/auth/external-assistant-id.js";
|
|
27
28
|
import { getDeviceId } from "../util/device-id.js";
|
|
28
29
|
import { getLogger } from "../util/logger.js";
|
|
@@ -187,7 +188,8 @@ export class UsageTelemetryReporter {
|
|
|
187
188
|
),
|
|
188
189
|
];
|
|
189
190
|
|
|
190
|
-
const assistantId =
|
|
191
|
+
const assistantId =
|
|
192
|
+
getExternalAssistantId() ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
191
193
|
const organizationId = getPlatformOrganizationId() || undefined;
|
|
192
194
|
const userId = getPlatformUserId() || undefined;
|
|
193
195
|
const payload = {
|