@vellumai/assistant 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +3 -2
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/memory.md +13 -11
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -2
- package/src/__tests__/config-schema.test.ts +3 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
- package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
- package/src/__tests__/credential-security-e2e.test.ts +1 -67
- package/src/__tests__/credential-security-invariants.test.ts +6 -50
- package/src/__tests__/credentials-cli.test.ts +82 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +2 -7
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +95 -272
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +90 -8
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +129 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +34 -5
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +38 -5
- package/src/calls/voice-session-bridge.ts +7 -12
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +128 -82
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +525 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +47 -2
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +5 -11
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +5 -5
- package/src/config/env.ts +21 -14
- package/src/config/feature-flag-registry.json +49 -9
- package/src/config/loader.ts +106 -42
- package/src/config/schema.ts +9 -29
- package/src/config/schemas/calls.ts +30 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +1 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +21 -1
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +234 -51
- package/src/daemon/message-types/conversations.ts +4 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +32 -95
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/app-store.ts +31 -0
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +328 -321
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +63 -6
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +98 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +194 -0
- package/src/prompts/system-prompt.ts +44 -4
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +29 -179
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +17 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +31 -3
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +19 -30
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +46 -14
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +96 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +75 -29
- package/src/security/ces-rpc-credential-backend.ts +86 -0
- package/src/security/credential-backend.ts +22 -92
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +113 -115
- package/src/skills/catalog-install.ts +6 -32
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +0 -8
- package/src/util/browser.ts +15 -0
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +4 -51
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -56,7 +56,6 @@ import {
|
|
|
56
56
|
import { searchConversations } from "../../memory/conversation-queries.js";
|
|
57
57
|
import { getConfiguredProvider } from "../../providers/provider-send-message.js";
|
|
58
58
|
import type { Provider } from "../../providers/types.js";
|
|
59
|
-
import { checkIngressForSecrets } from "../../security/secret-ingress.js";
|
|
60
59
|
import { getLogger } from "../../util/logger.js";
|
|
61
60
|
import { silentlyWithLog } from "../../util/silently.js";
|
|
62
61
|
import { buildAssistantEvent } from "../assistant-event.js";
|
|
@@ -637,7 +636,6 @@ export async function handleSendMessage(
|
|
|
637
636
|
interface?: string;
|
|
638
637
|
conversationType?: string;
|
|
639
638
|
automated?: boolean;
|
|
640
|
-
bypassSecretCheck?: boolean;
|
|
641
639
|
};
|
|
642
640
|
|
|
643
641
|
const { conversationKey, content, attachmentIds } = body;
|
|
@@ -705,29 +703,6 @@ export async function handleSendMessage(
|
|
|
705
703
|
}
|
|
706
704
|
}
|
|
707
705
|
|
|
708
|
-
// Block inbound messages containing secrets before they reach the model.
|
|
709
|
-
// This mirrors the legacy handleUserMessage behavior: secrets are
|
|
710
|
-
// detected and the message is rejected with a safe notice. The client
|
|
711
|
-
// should prompt the user to use the secure credential flow instead.
|
|
712
|
-
if (trimmedContent.length > 0 && !body.bypassSecretCheck) {
|
|
713
|
-
const ingressCheck = checkIngressForSecrets(trimmedContent);
|
|
714
|
-
if (ingressCheck.blocked) {
|
|
715
|
-
log.warn(
|
|
716
|
-
{ detectedTypes: ingressCheck.detectedTypes },
|
|
717
|
-
"Blocked user message containing secrets (POST /v1/messages)",
|
|
718
|
-
);
|
|
719
|
-
return Response.json(
|
|
720
|
-
{
|
|
721
|
-
accepted: false,
|
|
722
|
-
error: "secret_blocked",
|
|
723
|
-
message: ingressCheck.userNotice,
|
|
724
|
-
detectedTypes: ingressCheck.detectedTypes,
|
|
725
|
-
},
|
|
726
|
-
{ status: 422 },
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
706
|
if (!deps.sendMessageDeps) {
|
|
732
707
|
return httpError(
|
|
733
708
|
"SERVICE_UNAVAILABLE",
|
|
@@ -1259,12 +1234,12 @@ async function generateLlmSuggestion(
|
|
|
1259
1234
|
const truncated =
|
|
1260
1235
|
assistantText.length > 2000 ? assistantText.slice(-2000) : assistantText;
|
|
1261
1236
|
|
|
1262
|
-
const prompt = `Given this assistant message, write a very short tab-complete suggestion
|
|
1237
|
+
const prompt = `Given this assistant message, write a very short tab-complete suggestion the user could send next to keep the conversation going. Be casual, curious, or actionable — like a quick reply, not a formal request. Reply with ONLY the suggestion text.\n\nAssistant's message:\n${truncated}`;
|
|
1263
1238
|
const response = await provider.sendMessage(
|
|
1264
1239
|
[{ role: "user", content: [{ type: "text", text: prompt }] }],
|
|
1265
1240
|
[], // no tools
|
|
1266
1241
|
undefined, // no system prompt
|
|
1267
|
-
{ config: {
|
|
1242
|
+
{ config: { modelIntent: "latency-optimized" } },
|
|
1268
1243
|
);
|
|
1269
1244
|
|
|
1270
1245
|
const textBlock = response.content.find((b) => b.type === "text");
|
|
@@ -1276,7 +1251,7 @@ async function generateLlmSuggestion(
|
|
|
1276
1251
|
return null;
|
|
1277
1252
|
}
|
|
1278
1253
|
|
|
1279
|
-
// Take first line only
|
|
1254
|
+
// Take first line only
|
|
1280
1255
|
const firstLine = stripped.split("\n")[0].trim();
|
|
1281
1256
|
if (!firstLine) {
|
|
1282
1257
|
log.debug(
|
|
@@ -1285,22 +1260,7 @@ async function generateLlmSuggestion(
|
|
|
1285
1260
|
);
|
|
1286
1261
|
return null;
|
|
1287
1262
|
}
|
|
1288
|
-
|
|
1289
|
-
// Truncate at last word boundary within 50 chars.
|
|
1290
|
-
// Only strip the trailing partial word if the slice actually cut mid-word;
|
|
1291
|
-
// if the character right after the cut is whitespace, the slice is already clean.
|
|
1292
|
-
const sliced = firstLine.slice(0, 50);
|
|
1293
|
-
const wordTruncated = (
|
|
1294
|
-
/\s/.test(firstLine[50]) ? sliced : sliced.replace(/\s+\S*$/, "")
|
|
1295
|
-
).trim();
|
|
1296
|
-
if (wordTruncated.length < 15) {
|
|
1297
|
-
log.debug(
|
|
1298
|
-
{ rawLength: firstLine.length, truncatedLength: wordTruncated.length },
|
|
1299
|
-
"Suggestion rejected: too short after word-boundary truncation",
|
|
1300
|
-
);
|
|
1301
|
-
return null;
|
|
1302
|
-
}
|
|
1303
|
-
return wordTruncated;
|
|
1263
|
+
return firstLine;
|
|
1304
1264
|
}
|
|
1305
1265
|
|
|
1306
1266
|
export async function handleGetSuggestion(
|
|
@@ -8,7 +8,10 @@ import { getConfig } from "../../config/loader.js";
|
|
|
8
8
|
import { countConversations } from "../../memory/conversation-queries.js";
|
|
9
9
|
import { rawAll } from "../../memory/db.js";
|
|
10
10
|
import { getMemoryJobCounts } from "../../memory/jobs-store.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
getProviderRoutingSource,
|
|
13
|
+
listProviders,
|
|
14
|
+
} from "../../providers/registry.js";
|
|
12
15
|
import { countSchedules } from "../../schedule/schedule-store.js";
|
|
13
16
|
import { getDbPath } from "../../util/platform.js";
|
|
14
17
|
import type { RouteDefinition } from "../http-router.js";
|
|
@@ -48,13 +51,11 @@ function handleDebug(): Response {
|
|
|
48
51
|
const scheduleCounts = countSchedules();
|
|
49
52
|
|
|
50
53
|
const config = getConfig();
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
providerOrder,
|
|
57
|
-
);
|
|
54
|
+
const registeredProviders = listProviders();
|
|
55
|
+
const routingSources: Record<string, string | undefined> = {};
|
|
56
|
+
for (const name of registeredProviders) {
|
|
57
|
+
routingSources[name] = getProviderRoutingSource(name);
|
|
58
|
+
}
|
|
58
59
|
|
|
59
60
|
return Response.json({
|
|
60
61
|
session: {
|
|
@@ -62,7 +63,9 @@ function handleDebug(): Response {
|
|
|
62
63
|
startedAt: new Date(startedAt).toISOString(),
|
|
63
64
|
},
|
|
64
65
|
provider: {
|
|
65
|
-
|
|
66
|
+
configuredProvider: config.services.inference.provider,
|
|
67
|
+
registeredProviders,
|
|
68
|
+
routingSources,
|
|
66
69
|
inferenceMode: config.services.inference.mode,
|
|
67
70
|
},
|
|
68
71
|
memory: {
|
|
@@ -1,25 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP route handlers for
|
|
3
|
-
*
|
|
4
|
-
* Handles diagnostics export and dictation processing requests.
|
|
2
|
+
* HTTP route handlers for dictation processing.
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
|
-
import { randomBytes } from "node:crypto";
|
|
8
|
-
import {
|
|
9
|
-
createWriteStream,
|
|
10
|
-
mkdirSync,
|
|
11
|
-
readdirSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
rmSync,
|
|
14
|
-
statSync,
|
|
15
|
-
writeFileSync,
|
|
16
|
-
} from "node:fs";
|
|
17
|
-
import { homedir, tmpdir } from "node:os";
|
|
18
|
-
import { basename, join } from "node:path";
|
|
19
|
-
|
|
20
|
-
import archiver from "archiver";
|
|
21
|
-
import { and, asc, desc, eq, gte, lte } from "drizzle-orm";
|
|
22
|
-
|
|
23
5
|
import {
|
|
24
6
|
type ProfileResolution,
|
|
25
7
|
resolveProfile,
|
|
@@ -31,14 +13,6 @@ import {
|
|
|
31
13
|
import { detectDictationModeHeuristic } from "../../daemon/handlers/dictation.js";
|
|
32
14
|
import type { DictationRequest } from "../../daemon/message-types/diagnostics.js";
|
|
33
15
|
import type { DictationContext } from "../../daemon/message-types/shared.js";
|
|
34
|
-
import { resolveConversationId } from "../../memory/conversation-key-store.js";
|
|
35
|
-
import { getDb } from "../../memory/db.js";
|
|
36
|
-
import {
|
|
37
|
-
llmRequestLogs,
|
|
38
|
-
llmUsageEvents,
|
|
39
|
-
messages,
|
|
40
|
-
toolInvocations,
|
|
41
|
-
} from "../../memory/schema.js";
|
|
42
16
|
import {
|
|
43
17
|
createTimeout,
|
|
44
18
|
extractToolUse,
|
|
@@ -51,444 +25,6 @@ import type { RouteDefinition } from "../http-router.js";
|
|
|
51
25
|
|
|
52
26
|
const log = getLogger("diagnostics-routes");
|
|
53
27
|
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// Diagnostics export — redaction helpers
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
|
|
58
|
-
const MAX_CONTENT_LENGTH = 500;
|
|
59
|
-
|
|
60
|
-
const REDACT_PATTERNS = [
|
|
61
|
-
/\b(sk|key|api[_-]?key|token|secret|password|passwd|credential)[_\-]?[a-zA-Z0-9]{16,}\b/gi,
|
|
62
|
-
/Bearer\s+[A-Za-z0-9\-._~+\/]+=*/gi,
|
|
63
|
-
/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g,
|
|
64
|
-
/\b(AKIA|ASIA)[A-Z0-9]{16}\b/g,
|
|
65
|
-
/\b[A-Fa-f0-9]{32,}\b/g,
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
function redact(text: string): string {
|
|
69
|
-
let result = text;
|
|
70
|
-
for (const pattern of REDACT_PATTERNS) {
|
|
71
|
-
result = result.replace(pattern, "[REDACTED]");
|
|
72
|
-
}
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function truncateAndRedact(text: string): string {
|
|
77
|
-
const truncated =
|
|
78
|
-
text.length > MAX_CONTENT_LENGTH
|
|
79
|
-
? text.slice(0, MAX_CONTENT_LENGTH) + "...[truncated]"
|
|
80
|
-
: text;
|
|
81
|
-
return redact(truncated);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const SENSITIVE_KEYS = new Set([
|
|
85
|
-
"api_key",
|
|
86
|
-
"apikey",
|
|
87
|
-
"api-key",
|
|
88
|
-
"authorization",
|
|
89
|
-
"x-api-key",
|
|
90
|
-
"secret",
|
|
91
|
-
"password",
|
|
92
|
-
"token",
|
|
93
|
-
"credential",
|
|
94
|
-
"credentials",
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
|
-
function redactDeep(value: unknown): unknown {
|
|
98
|
-
if (typeof value === "string") return redact(value);
|
|
99
|
-
if (Array.isArray(value)) return value.map(redactDeep);
|
|
100
|
-
if (value != null && typeof value === "object") {
|
|
101
|
-
const out: Record<string, unknown> = {};
|
|
102
|
-
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
103
|
-
if (SENSITIVE_KEYS.has(k.toLowerCase())) {
|
|
104
|
-
out[k] = "[REDACTED]";
|
|
105
|
-
} else {
|
|
106
|
-
out[k] = redactDeep(v);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return out;
|
|
110
|
-
}
|
|
111
|
-
return value;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
// Crash report discovery
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
const CRASH_REPORT_EXTENSIONS = new Set([".crash", ".ips", ".diag"]);
|
|
119
|
-
const CRASH_REPORT_TAR_GZ = ".tar.gz";
|
|
120
|
-
const CRASH_REPORT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
121
|
-
|
|
122
|
-
function findRecentCrashReports(): string[] {
|
|
123
|
-
const diagnosticReportsDir = join(
|
|
124
|
-
homedir(),
|
|
125
|
-
"Library",
|
|
126
|
-
"Logs",
|
|
127
|
-
"DiagnosticReports",
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const entries = readdirSync(diagnosticReportsDir);
|
|
132
|
-
const now = Date.now();
|
|
133
|
-
const results: string[] = [];
|
|
134
|
-
|
|
135
|
-
for (const entry of entries) {
|
|
136
|
-
// Case-insensitive prefix match for "vellum-assistant"
|
|
137
|
-
if (!entry.toLowerCase().startsWith("vellum-assistant")) continue;
|
|
138
|
-
|
|
139
|
-
// Check extension
|
|
140
|
-
const lowerEntry = entry.toLowerCase();
|
|
141
|
-
const hasValidExt =
|
|
142
|
-
CRASH_REPORT_EXTENSIONS.has(
|
|
143
|
-
lowerEntry.slice(lowerEntry.lastIndexOf(".")),
|
|
144
|
-
) || lowerEntry.endsWith(CRASH_REPORT_TAR_GZ);
|
|
145
|
-
|
|
146
|
-
if (!hasValidExt) continue;
|
|
147
|
-
|
|
148
|
-
const filePath = join(diagnosticReportsDir, entry);
|
|
149
|
-
try {
|
|
150
|
-
const stat = statSync(filePath);
|
|
151
|
-
if (!stat.isFile()) continue;
|
|
152
|
-
if (now - stat.mtimeMs > CRASH_REPORT_MAX_AGE_MS) continue;
|
|
153
|
-
results.push(filePath);
|
|
154
|
-
} catch {
|
|
155
|
-
// Skip files we can't stat
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return results;
|
|
160
|
-
} catch {
|
|
161
|
-
// Directory doesn't exist or can't be read — not an error
|
|
162
|
-
return [];
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
// Diagnostics export handler
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
|
|
170
|
-
async function handleDiagnosticsExport(body: {
|
|
171
|
-
conversationId?: string;
|
|
172
|
-
anchorMessageId?: string;
|
|
173
|
-
}): Promise<Response> {
|
|
174
|
-
if (!body.conversationId) {
|
|
175
|
-
return httpError("BAD_REQUEST", "conversationId is required", 400);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// The client may send a conversation key (client-side UUID) rather than
|
|
179
|
-
// the daemon's internal conversation ID. Resolve to the canonical ID.
|
|
180
|
-
const conversationId =
|
|
181
|
-
resolveConversationId(body.conversationId) ?? body.conversationId;
|
|
182
|
-
const { anchorMessageId } = body;
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
const db = getDb();
|
|
186
|
-
|
|
187
|
-
// 1. Find the anchor message.
|
|
188
|
-
// Try in order: specific ID → most recent assistant message → any message.
|
|
189
|
-
// The final fallback handles the race condition where the user clicks
|
|
190
|
-
// "export" before message_complete fires and the assistant message has
|
|
191
|
-
// been persisted — the user message and in-flight tool/usage data are
|
|
192
|
-
// still captured.
|
|
193
|
-
let anchorMessage;
|
|
194
|
-
let anchorIsFallback = false;
|
|
195
|
-
if (anchorMessageId) {
|
|
196
|
-
anchorMessage = db
|
|
197
|
-
.select()
|
|
198
|
-
.from(messages)
|
|
199
|
-
.where(
|
|
200
|
-
and(
|
|
201
|
-
eq(messages.id, anchorMessageId),
|
|
202
|
-
eq(messages.conversationId, conversationId),
|
|
203
|
-
),
|
|
204
|
-
)
|
|
205
|
-
.get();
|
|
206
|
-
}
|
|
207
|
-
if (!anchorMessage) {
|
|
208
|
-
anchorMessage = db
|
|
209
|
-
.select()
|
|
210
|
-
.from(messages)
|
|
211
|
-
.where(
|
|
212
|
-
and(
|
|
213
|
-
eq(messages.conversationId, conversationId),
|
|
214
|
-
eq(messages.role, "assistant"),
|
|
215
|
-
),
|
|
216
|
-
)
|
|
217
|
-
.orderBy(desc(messages.createdAt))
|
|
218
|
-
.limit(1)
|
|
219
|
-
.get();
|
|
220
|
-
}
|
|
221
|
-
if (!anchorMessage) {
|
|
222
|
-
anchorMessage = db
|
|
223
|
-
.select()
|
|
224
|
-
.from(messages)
|
|
225
|
-
.where(eq(messages.conversationId, conversationId))
|
|
226
|
-
.orderBy(desc(messages.createdAt))
|
|
227
|
-
.limit(1)
|
|
228
|
-
.get();
|
|
229
|
-
anchorIsFallback = true;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 2. Compute the export time range.
|
|
233
|
-
// When an anchor message exists, scope from the earliest message in the
|
|
234
|
-
// conversation through the anchor so the full conversation context is
|
|
235
|
-
// captured. When no messages exist at all (empty conversation or race
|
|
236
|
-
// condition), use the current timestamp so the export still captures any
|
|
237
|
-
// in-flight usage/tool data.
|
|
238
|
-
const now = Date.now();
|
|
239
|
-
let rangeEnd: number;
|
|
240
|
-
let rangeStart: number;
|
|
241
|
-
let usageRangeEnd: number;
|
|
242
|
-
|
|
243
|
-
if (anchorMessage) {
|
|
244
|
-
const earliestMessage = db
|
|
245
|
-
.select()
|
|
246
|
-
.from(messages)
|
|
247
|
-
.where(eq(messages.conversationId, conversationId))
|
|
248
|
-
.orderBy(asc(messages.createdAt))
|
|
249
|
-
.limit(1)
|
|
250
|
-
.get();
|
|
251
|
-
|
|
252
|
-
rangeStart = earliestMessage?.createdAt ?? anchorMessage.createdAt - 2000;
|
|
253
|
-
|
|
254
|
-
// When the anchor was selected via the fallback "any message" path
|
|
255
|
-
// (because the assistant reply hasn't been persisted yet), extend the
|
|
256
|
-
// range to the current time so in-flight tool invocations and usage
|
|
257
|
-
// recorded after the user message are captured. An explicit anchor to a
|
|
258
|
-
// non-assistant message uses the message's own timestamp.
|
|
259
|
-
rangeEnd = anchorIsFallback ? now : anchorMessage.createdAt;
|
|
260
|
-
usageRangeEnd = anchorIsFallback
|
|
261
|
-
? now + 5000
|
|
262
|
-
: anchorMessage.createdAt + 5000;
|
|
263
|
-
} else {
|
|
264
|
-
// No messages at all — use the current time so we capture any
|
|
265
|
-
// in-flight LLM usage or tool invocations.
|
|
266
|
-
rangeStart = now - 60_000;
|
|
267
|
-
rangeEnd = now;
|
|
268
|
-
usageRangeEnd = now + 5000;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// 3. Query all messages in the range
|
|
272
|
-
const rangeMessages = db
|
|
273
|
-
.select()
|
|
274
|
-
.from(messages)
|
|
275
|
-
.where(
|
|
276
|
-
and(
|
|
277
|
-
eq(messages.conversationId, conversationId),
|
|
278
|
-
gte(messages.createdAt, rangeStart),
|
|
279
|
-
lte(messages.createdAt, rangeEnd),
|
|
280
|
-
),
|
|
281
|
-
)
|
|
282
|
-
.orderBy(messages.createdAt)
|
|
283
|
-
.all();
|
|
284
|
-
|
|
285
|
-
// 4. Query tool invocations in the range
|
|
286
|
-
const rangeToolInvocations = db
|
|
287
|
-
.select()
|
|
288
|
-
.from(toolInvocations)
|
|
289
|
-
.where(
|
|
290
|
-
and(
|
|
291
|
-
eq(toolInvocations.conversationId, conversationId),
|
|
292
|
-
gte(toolInvocations.createdAt, rangeStart),
|
|
293
|
-
lte(toolInvocations.createdAt, rangeEnd),
|
|
294
|
-
),
|
|
295
|
-
)
|
|
296
|
-
.orderBy(toolInvocations.createdAt)
|
|
297
|
-
.all();
|
|
298
|
-
|
|
299
|
-
// 5. Query LLM usage events
|
|
300
|
-
const rangeUsageEvents = db
|
|
301
|
-
.select()
|
|
302
|
-
.from(llmUsageEvents)
|
|
303
|
-
.where(
|
|
304
|
-
and(
|
|
305
|
-
eq(llmUsageEvents.conversationId, conversationId),
|
|
306
|
-
gte(llmUsageEvents.createdAt, rangeStart),
|
|
307
|
-
lte(llmUsageEvents.createdAt, usageRangeEnd),
|
|
308
|
-
),
|
|
309
|
-
)
|
|
310
|
-
.orderBy(llmUsageEvents.createdAt)
|
|
311
|
-
.all();
|
|
312
|
-
|
|
313
|
-
// 5b. Query raw LLM request/response logs
|
|
314
|
-
const rangeRequestLogs = db
|
|
315
|
-
.select()
|
|
316
|
-
.from(llmRequestLogs)
|
|
317
|
-
.where(
|
|
318
|
-
and(
|
|
319
|
-
eq(llmRequestLogs.conversationId, conversationId),
|
|
320
|
-
gte(llmRequestLogs.createdAt, rangeStart),
|
|
321
|
-
lte(llmRequestLogs.createdAt, usageRangeEnd),
|
|
322
|
-
),
|
|
323
|
-
)
|
|
324
|
-
.orderBy(llmRequestLogs.createdAt)
|
|
325
|
-
.all();
|
|
326
|
-
|
|
327
|
-
// 6. Write export files to a temp directory
|
|
328
|
-
const exportId = `diagnostics-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(4).toString("hex")}`;
|
|
329
|
-
const tempDir = join(tmpdir(), exportId);
|
|
330
|
-
mkdirSync(tempDir, { recursive: true });
|
|
331
|
-
|
|
332
|
-
try {
|
|
333
|
-
const manifest = {
|
|
334
|
-
version: "1.1",
|
|
335
|
-
exportedAt: new Date().toISOString(),
|
|
336
|
-
conversationId,
|
|
337
|
-
messageId: anchorMessage?.id ?? null,
|
|
338
|
-
};
|
|
339
|
-
writeFileSync(
|
|
340
|
-
join(tempDir, "manifest.json"),
|
|
341
|
-
JSON.stringify(manifest, null, 2),
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
const messagesLines = rangeMessages.map((m) =>
|
|
345
|
-
JSON.stringify({
|
|
346
|
-
id: m.id,
|
|
347
|
-
conversationId: m.conversationId,
|
|
348
|
-
role: m.role,
|
|
349
|
-
content: truncateAndRedact(m.content),
|
|
350
|
-
createdAt: m.createdAt,
|
|
351
|
-
}),
|
|
352
|
-
);
|
|
353
|
-
writeFileSync(
|
|
354
|
-
join(tempDir, "messages.jsonl"),
|
|
355
|
-
messagesLines.join("\n") + (messagesLines.length > 0 ? "\n" : ""),
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
const toolLines = rangeToolInvocations.map((t) =>
|
|
359
|
-
JSON.stringify({
|
|
360
|
-
id: t.id,
|
|
361
|
-
conversationId: t.conversationId,
|
|
362
|
-
toolName: t.toolName,
|
|
363
|
-
input: truncateAndRedact(t.input),
|
|
364
|
-
result: truncateAndRedact(t.result),
|
|
365
|
-
decision: t.decision,
|
|
366
|
-
riskLevel: t.riskLevel,
|
|
367
|
-
durationMs: t.durationMs,
|
|
368
|
-
createdAt: t.createdAt,
|
|
369
|
-
}),
|
|
370
|
-
);
|
|
371
|
-
writeFileSync(
|
|
372
|
-
join(tempDir, "tool_invocations.jsonl"),
|
|
373
|
-
toolLines.join("\n") + (toolLines.length > 0 ? "\n" : ""),
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
const usageLines = rangeUsageEvents.map((u) =>
|
|
377
|
-
JSON.stringify({
|
|
378
|
-
id: u.id,
|
|
379
|
-
conversationId: u.conversationId,
|
|
380
|
-
actor: u.actor,
|
|
381
|
-
provider: u.provider,
|
|
382
|
-
model: u.model,
|
|
383
|
-
inputTokens: u.inputTokens,
|
|
384
|
-
outputTokens: u.outputTokens,
|
|
385
|
-
cacheCreationInputTokens: u.cacheCreationInputTokens,
|
|
386
|
-
cacheReadInputTokens: u.cacheReadInputTokens,
|
|
387
|
-
estimatedCostUsd: u.estimatedCostUsd,
|
|
388
|
-
pricingStatus: u.pricingStatus,
|
|
389
|
-
createdAt: u.createdAt,
|
|
390
|
-
}),
|
|
391
|
-
);
|
|
392
|
-
writeFileSync(
|
|
393
|
-
join(tempDir, "usage.jsonl"),
|
|
394
|
-
usageLines.join("\n") + (usageLines.length > 0 ? "\n" : ""),
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
const requestLogLines = rangeRequestLogs.map((r) => {
|
|
398
|
-
let request: unknown;
|
|
399
|
-
let response: unknown;
|
|
400
|
-
try {
|
|
401
|
-
request = JSON.parse(r.requestPayload);
|
|
402
|
-
} catch {
|
|
403
|
-
request = r.requestPayload;
|
|
404
|
-
}
|
|
405
|
-
try {
|
|
406
|
-
response = JSON.parse(r.responsePayload);
|
|
407
|
-
} catch {
|
|
408
|
-
response = r.responsePayload;
|
|
409
|
-
}
|
|
410
|
-
return JSON.stringify({
|
|
411
|
-
id: r.id,
|
|
412
|
-
conversationId: r.conversationId,
|
|
413
|
-
provider: r.provider,
|
|
414
|
-
request: redactDeep(request),
|
|
415
|
-
response: redactDeep(response),
|
|
416
|
-
createdAt: r.createdAt,
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
writeFileSync(
|
|
420
|
-
join(tempDir, "llm_requests.jsonl"),
|
|
421
|
-
requestLogLines.join("\n") + (requestLogLines.length > 0 ? "\n" : ""),
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
// 7. Zip the temp directory
|
|
425
|
-
const downloadsDir = join(homedir(), "Downloads");
|
|
426
|
-
mkdirSync(downloadsDir, { recursive: true });
|
|
427
|
-
const zipFilename = `${exportId}.zip`;
|
|
428
|
-
const zipPath = join(downloadsDir, zipFilename);
|
|
429
|
-
|
|
430
|
-
await new Promise<void>((resolve, reject) => {
|
|
431
|
-
const output = createWriteStream(zipPath);
|
|
432
|
-
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
433
|
-
|
|
434
|
-
output.on("close", () => resolve());
|
|
435
|
-
output.on("error", (err: Error) => reject(err));
|
|
436
|
-
archive.on("error", (err: Error) => reject(err));
|
|
437
|
-
archive.on("warning", (err: Error) => {
|
|
438
|
-
log.warn({ err }, "Archiver warning during diagnostics export");
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
archive.pipe(output);
|
|
442
|
-
archive.directory(tempDir, false);
|
|
443
|
-
|
|
444
|
-
// Add recent crash report files under crash-reports/.
|
|
445
|
-
// Text-based crash files (.crash, .ips, .diag) are redacted using the
|
|
446
|
-
// same patterns as conversation data. Binary archives (.tar.gz) are
|
|
447
|
-
// added as-is since they can't be meaningfully text-redacted.
|
|
448
|
-
const crashReportFiles = findRecentCrashReports();
|
|
449
|
-
for (const filePath of crashReportFiles) {
|
|
450
|
-
try {
|
|
451
|
-
const fileName = basename(filePath);
|
|
452
|
-
if (fileName.toLowerCase().endsWith(CRASH_REPORT_TAR_GZ)) {
|
|
453
|
-
archive.file(filePath, { name: "crash-reports/" + fileName });
|
|
454
|
-
} else {
|
|
455
|
-
const content = readFileSync(filePath, "utf-8");
|
|
456
|
-
archive.append(redact(content), {
|
|
457
|
-
name: "crash-reports/" + fileName,
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
} catch {
|
|
461
|
-
// Skip files that can't be read
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
archive.finalize();
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
log.info(
|
|
469
|
-
{ conversationId, zipPath, messageCount: rangeMessages.length },
|
|
470
|
-
"Diagnostics export completed via HTTP",
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
return Response.json({ success: true, filePath: zipPath });
|
|
474
|
-
} finally {
|
|
475
|
-
try {
|
|
476
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
477
|
-
} catch {
|
|
478
|
-
// Best-effort cleanup
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
} catch (err) {
|
|
482
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
483
|
-
log.error({ err, conversationId }, "Failed to export diagnostics");
|
|
484
|
-
return httpError(
|
|
485
|
-
"INTERNAL_ERROR",
|
|
486
|
-
`Failed to export diagnostics: ${errorMessage}`,
|
|
487
|
-
500,
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
28
|
// ---------------------------------------------------------------------------
|
|
493
29
|
// Dictation
|
|
494
30
|
// ---------------------------------------------------------------------------
|
|
@@ -895,18 +431,6 @@ async function handleCommandMode(
|
|
|
895
431
|
|
|
896
432
|
export function diagnosticsRouteDefinitions(): RouteDefinition[] {
|
|
897
433
|
return [
|
|
898
|
-
{
|
|
899
|
-
endpoint: "diagnostics/export",
|
|
900
|
-
method: "POST",
|
|
901
|
-
policyKey: "diagnostics/export",
|
|
902
|
-
handler: async ({ req }) => {
|
|
903
|
-
const body = (await req.json()) as {
|
|
904
|
-
conversationId?: string;
|
|
905
|
-
anchorMessageId?: string;
|
|
906
|
-
};
|
|
907
|
-
return handleDiagnosticsExport(body);
|
|
908
|
-
},
|
|
909
|
-
},
|
|
910
434
|
{
|
|
911
435
|
endpoint: "dictation",
|
|
912
436
|
method: "POST",
|