@vellumai/assistant 0.4.56 → 0.5.0
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 +10 -10
- package/Dockerfile +3 -0
- package/README.md +11 -11
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/memory.md +3 -4
- package/docs/credential-execution-service.md +13 -20
- package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +7 -7
- package/src/__tests__/anthropic-provider.test.ts +172 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
- package/src/__tests__/approval-cascade.test.ts +2 -2
- package/src/__tests__/approval-routes-http.test.ts +3 -4
- package/src/__tests__/asset-materialize-tool.test.ts +5 -5
- package/src/__tests__/asset-search-tool.test.ts +1 -1
- package/src/__tests__/assistant-attachments.test.ts +5 -5
- package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
- package/src/__tests__/attachments-store.test.ts +2 -2
- package/src/__tests__/avatar-e2e.test.ts +5 -3
- package/src/__tests__/browser-skill-endstate.test.ts +0 -1
- package/src/__tests__/call-routes-http.test.ts +2 -2
- package/src/__tests__/callback-handoff-copy.test.ts +1 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +0 -1
- package/src/__tests__/checker.test.ts +31 -32
- package/src/__tests__/chrome-cdp.test.ts +47 -18
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +9 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop.test.ts +11 -4
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +33 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
- package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
- package/src/__tests__/conversation-queue.test.ts +23 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +204 -185
- package/src/__tests__/conversation-seed-composer.test.ts +1 -1
- package/src/__tests__/conversation-slash-queue.test.ts +4 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
- package/src/__tests__/conversation-starter-routes.test.ts +291 -0
- package/src/__tests__/conversation-wipe.test.ts +438 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
- package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
- package/src/__tests__/credential-security-e2e.test.ts +20 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +227 -0
- package/src/__tests__/credentials-cli.test.ts +3 -0
- package/src/__tests__/date-context.test.ts +59 -377
- package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
- package/src/__tests__/encrypted-store.test.ts +249 -15
- package/src/__tests__/ephemeral-permissions.test.ts +4 -5
- package/src/__tests__/event-bus.test.ts +3 -3
- package/src/__tests__/file-read-tool.test.ts +40 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +4 -4
- package/src/__tests__/gemini-provider.test.ts +6 -9
- package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/host-file-read-tool.test.ts +87 -0
- package/src/__tests__/host-shell-tool.test.ts +6 -6
- package/src/__tests__/http-user-message-parity.test.ts +2 -2
- package/src/__tests__/identity-intro-cache.test.ts +209 -0
- package/src/__tests__/intent-routing.test.ts +51 -99
- package/src/__tests__/invite-routes-http.test.ts +5 -0
- package/src/__tests__/list-messages-attachments.test.ts +1 -1
- package/src/__tests__/managed-proxy-context.test.ts +2 -5
- package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
- package/src/__tests__/media-generate-image.test.ts +32 -15
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
- package/src/__tests__/memory-recall-quality.test.ts +4 -3
- package/src/__tests__/memory-regressions.test.ts +86 -90
- package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
- package/src/__tests__/migration-export-http.test.ts +26 -27
- package/src/__tests__/migration-import-commit-http.test.ts +165 -37
- package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
- package/src/__tests__/migration-validate-http.test.ts +16 -16
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
- package/src/__tests__/non-member-access-request.test.ts +3 -3
- package/src/__tests__/notification-broadcaster.test.ts +1 -1
- package/src/__tests__/notification-decision-fallback.test.ts +2 -2
- package/src/__tests__/notification-decision-identity.test.ts +8 -9
- package/src/__tests__/notification-decision-strategy.test.ts +1 -1
- package/src/__tests__/notification-deep-link.test.ts +1 -1
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
- package/src/__tests__/oauth-store.test.ts +1 -3
- package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
- package/src/__tests__/onboarding-template-contract.test.ts +23 -59
- package/src/__tests__/provider-error-scenarios.test.ts +154 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
- package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
- package/src/__tests__/provider-registry-ollama.test.ts +5 -2
- package/src/__tests__/qdrant-manager.test.ts +7 -7
- package/src/__tests__/ratelimit.test.ts +0 -74
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/scheduler-recurrence.test.ts +46 -2
- package/src/__tests__/schema-transforms.test.ts +114 -54
- package/src/__tests__/secret-onetime-send.test.ts +20 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
- package/src/__tests__/secret-scanner-executor.test.ts +1 -2
- package/src/__tests__/send-endpoint-busy.test.ts +63 -4
- package/src/__tests__/send-notification-tool.test.ts +2 -2
- package/src/__tests__/shell-credential-ref.test.ts +0 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
- package/src/__tests__/skill-memory.test.ts +549 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
- package/src/__tests__/slack-channel-config.test.ts +109 -94
- package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
- package/src/__tests__/swarm-recursion.test.ts +2 -2
- package/src/__tests__/swarm-tool.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +19 -66
- package/src/__tests__/telegram-config.test.ts +121 -0
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +1 -1
- package/src/__tests__/trace-emitter.test.ts +8 -1
- package/src/__tests__/trust-store.test.ts +7 -8
- package/src/__tests__/twilio-routes.test.ts +1 -18
- package/src/__tests__/user-reference.test.ts +82 -2
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +3 -3
- package/src/avatar/ascii-renderer.ts +2 -2
- package/src/avatar/png-renderer.ts +2 -2
- package/src/avatar/resvg-lazy.ts +21 -0
- package/src/calls/guardian-dispatch.ts +1 -1
- package/src/calls/relay-access-wait.ts +2 -2
- package/src/calls/twilio-rest.ts +0 -248
- package/src/cli/AGENTS.md +5 -8
- package/src/cli/__tests__/notifications.test.ts +5 -5
- package/src/cli/commands/avatar.ts +64 -2
- package/src/cli/commands/conversations.ts +131 -1
- package/src/cli/commands/credentials.ts +2 -0
- package/src/cli/commands/notifications.ts +3 -3
- package/src/cli.ts +10 -0
- package/src/config/bundled-skills/acp/SKILL.md +5 -5
- package/src/config/bundled-skills/acp/TOOLS.json +6 -6
- package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
- package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
- package/src/config/bundled-skills/browser/SKILL.md +15 -15
- package/src/config/bundled-skills/browser/TOOLS.json +14 -14
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
- package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
- package/src/config/bundled-skills/contacts/SKILL.md +3 -3
- package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
- package/src/config/bundled-skills/document/SKILL.md +4 -4
- package/src/config/bundled-skills/document/TOOLS.json +2 -2
- package/src/config/bundled-skills/followups/TOOLS.json +3 -3
- package/src/config/bundled-skills/gmail/SKILL.md +32 -32
- package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
- package/src/config/bundled-skills/google-calendar/types.ts +1 -1
- package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
- package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
- package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
- package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +29 -25
- package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/notifications/SKILL.md +3 -3
- package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
- package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
- package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
- package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
- package/src/config/bundled-skills/schedule/SKILL.md +26 -26
- package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
- package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
- package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
- package/src/config/bundled-skills/sequences/SKILL.md +2 -2
- package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
- package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
- package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
- package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
- package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
- package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
- package/src/config/bundled-skills/slack/SKILL.md +2 -2
- package/src/config/bundled-skills/slack/TOOLS.json +8 -8
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
- package/src/config/bundled-skills/tasks/SKILL.md +1 -1
- package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
- package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
- package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
- package/src/config/bundled-skills/watcher/SKILL.md +4 -4
- package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
- package/src/config/feature-flag-registry.json +33 -17
- package/src/config/schemas/sandbox.ts +1 -1
- package/src/config/schemas/services.ts +13 -3
- package/src/config/schemas/timeouts.ts +0 -10
- package/src/contacts/contact-store.ts +63 -0
- package/src/contacts/contacts-write.ts +1 -1
- package/src/daemon/assistant-attachments.ts +2 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
- package/src/daemon/conversation-agent-loop.ts +7 -30
- package/src/daemon/conversation-error.ts +24 -0
- package/src/daemon/conversation-memory.ts +8 -7
- package/src/daemon/conversation-runtime-assembly.ts +141 -275
- package/src/daemon/conversation-slash.ts +7 -26
- package/src/daemon/conversation-surfaces.ts +14 -0
- package/src/daemon/conversation-tool-setup.ts +9 -8
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +10 -83
- package/src/daemon/handlers/config-channels.ts +12 -2
- package/src/daemon/handlers/config-slack-channel.ts +7 -1
- package/src/daemon/handlers/config-telegram.ts +6 -1
- package/src/daemon/handlers/conversations.ts +2 -2
- package/src/daemon/handlers/skills.ts +4 -0
- package/src/daemon/lifecycle.ts +28 -4
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +1 -5
- package/src/daemon/shutdown-handlers.ts +9 -3
- package/src/daemon/tool-side-effects.ts +40 -0
- package/src/daemon/trace-emitter.ts +26 -2
- package/src/events/domain-events.ts +1 -1
- package/src/events/tool-permission-telemetry-listener.ts +46 -0
- package/src/inbound/platform-callback-registration.ts +0 -18
- package/src/media/app-icon-generator.ts +15 -8
- package/src/media/avatar-router.ts +15 -8
- package/src/media/gemini-image-service.ts +125 -21
- package/src/memory/attachments-store.ts +3 -3
- package/src/memory/channel-verification-sessions.ts +6 -6
- package/src/memory/conversation-crud.ts +196 -1
- package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
- package/src/memory/conversation-title-service.ts +2 -3
- package/src/memory/db-init.ts +25 -1
- package/src/memory/invite-store.ts +4 -4
- package/src/memory/items-extractor.ts +4 -4
- package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/jobs-worker.ts +7 -5
- package/src/memory/lifecycle-events-store.ts +63 -0
- package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
- package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
- package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
- package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
- package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
- package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +223 -96
- package/src/memory/retriever.ts +115 -138
- package/src/memory/schema/calls.ts +1 -1
- package/src/memory/schema/contacts.ts +1 -1
- package/src/memory/schema/infrastructure.ts +29 -0
- package/src/memory/schema/memory-core.ts +7 -17
- package/src/memory/schema/notifications.ts +1 -1
- package/src/memory/search/formatting.ts +23 -6
- package/src/memory/search/lexical.ts +2 -0
- package/src/memory/search/semantic.ts +2 -0
- package/src/memory/search/staleness.ts +5 -1
- package/src/memory/search/types.ts +4 -0
- package/src/memory/task-memory-cleanup.ts +96 -6
- package/src/memory/trace-event-store.ts +148 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/decision-engine.ts +45 -4
- package/src/notifications/emit-signal.ts +5 -4
- package/src/notifications/events-store.ts +4 -4
- package/src/notifications/signal.ts +1 -1
- package/src/oauth/manual-token-connection.ts +49 -25
- package/src/permissions/checker.ts +6 -5
- package/src/permissions/defaults.ts +4 -4
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
- package/src/prompts/cache-boundary.ts +8 -0
- package/src/prompts/system-prompt.ts +105 -634
- package/src/prompts/templates/BOOTSTRAP.md +172 -33
- package/src/prompts/templates/IDENTITY.md +8 -24
- package/src/prompts/templates/SOUL.md +20 -41
- package/src/prompts/templates/USER.md +3 -19
- package/src/prompts/user-reference.ts +14 -16
- package/src/providers/anthropic/client.ts +51 -19
- package/src/providers/gemini/client.ts +6 -9
- package/src/providers/managed-proxy/constants.ts +1 -7
- package/src/providers/managed-proxy/context.ts +0 -1
- package/src/providers/model-intents.ts +5 -5
- package/src/providers/openai/client.ts +10 -1
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/ratelimit.ts +0 -35
- package/src/providers/registry.ts +3 -5
- package/src/providers/retry.ts +18 -1
- package/src/runtime/access-request-helper.ts +16 -2
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/channel-verification-service.ts +1 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/guardian-vellum-migration.ts +61 -1
- package/src/runtime/http-server.ts +8 -4
- package/src/runtime/migrations/vbundle-builder.ts +212 -32
- package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
- package/src/runtime/migrations/vbundle-importer.ts +66 -1
- package/src/runtime/migrations/vbundle-validator.ts +17 -3
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
- package/src/runtime/routes/attachment-routes.ts +2 -2
- package/src/runtime/routes/btw-routes.ts +93 -0
- package/src/runtime/routes/channel-verification-routes.ts +19 -2
- package/src/runtime/routes/conversation-management-routes.ts +55 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +49 -5
- package/src/runtime/routes/conversation-starter-routes.ts +207 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
- package/src/runtime/routes/identity-intro-cache.ts +105 -0
- package/src/runtime/routes/identity-routes.ts +51 -0
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +25 -13
- package/src/runtime/routes/secret-routes.ts +18 -0
- package/src/runtime/routes/settings-routes.ts +9 -9
- package/src/runtime/routes/telemetry-routes.ts +53 -0
- package/src/runtime/routes/trace-event-routes.ts +62 -0
- package/src/runtime/tool-grant-request-helper.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +47 -31
- package/src/security/encrypted-store.ts +262 -78
- package/src/skills/catalog-install.ts +10 -0
- package/src/skills/managed-store.ts +2 -0
- package/src/skills/skill-memory.ts +222 -0
- package/src/subagent/manager.ts +1 -4
- package/src/telemetry/types.ts +10 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +7 -2
- package/src/telemetry/usage-telemetry-reporter.ts +53 -4
- package/src/tools/AGENTS.md +11 -11
- package/src/tools/acp/spawn.ts +1 -1
- package/src/tools/apps/executors.ts +8 -8
- package/src/tools/apps/registry.ts +1 -1
- package/src/tools/assets/materialize.ts +6 -6
- package/src/tools/assets/search.ts +10 -10
- package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
- package/src/tools/browser/auth-detector.ts +6 -6
- package/src/tools/browser/browser-execution.ts +13 -13
- package/src/tools/browser/browser-manager.ts +3 -3
- package/src/tools/browser/chrome-cdp.ts +5 -5
- package/src/tools/browser/jit-auth.ts +2 -2
- package/src/tools/browser/network-recorder.test.ts +2 -2
- package/src/tools/browser/network-recorder.ts +3 -3
- package/src/tools/browser/runtime-check.ts +3 -3
- package/src/tools/claude-code/claude-code.ts +2 -2
- package/src/tools/computer-use/definitions.ts +18 -18
- package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
- package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
- package/src/tools/credentials/broker-types.ts +5 -5
- package/src/tools/credentials/broker.ts +15 -15
- package/src/tools/credentials/metadata-store.ts +2 -2
- package/src/tools/credentials/resolve.ts +1 -1
- package/src/tools/credentials/selection.ts +1 -1
- package/src/tools/credentials/tool-policy.ts +1 -1
- package/src/tools/credentials/vault.ts +115 -25
- package/src/tools/execution-target.ts +2 -2
- package/src/tools/executor.ts +7 -7
- package/src/tools/filesystem/edit.ts +2 -2
- package/src/tools/filesystem/read.ts +15 -4
- package/src/tools/filesystem/write.ts +1 -1
- package/src/tools/host-filesystem/edit.ts +2 -1
- package/src/tools/host-filesystem/read.ts +18 -1
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +9 -8
- package/src/tools/mcp/mcp-tool-factory.ts +7 -6
- package/src/tools/memory/definitions.ts +6 -5
- package/src/tools/memory/handlers.test.ts +1 -1
- package/src/tools/network/__tests__/web-search.test.ts +3 -3
- package/src/tools/network/domain-normalize.ts +2 -2
- package/src/tools/network/script-proxy/session-manager.ts +10 -10
- package/src/tools/network/web-fetch.ts +1 -1
- package/src/tools/network/web-search.ts +3 -3
- package/src/tools/permission-checker.ts +8 -8
- package/src/tools/registry.ts +7 -7
- package/src/tools/schedule/list.ts +2 -2
- package/src/tools/schema-transforms.ts +31 -21
- package/src/tools/secret-detection-handler.ts +1 -1
- package/src/tools/sensitive-output-placeholders.ts +1 -1
- package/src/tools/shared/filesystem/edit-engine.ts +1 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
- package/src/tools/shared/filesystem/image-read.ts +25 -5
- package/src/tools/shared/filesystem/path-policy.ts +2 -2
- package/src/tools/shared/shell-output.ts +1 -1
- package/src/tools/side-effects.ts +1 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +3 -3
- package/src/tools/skills/sandbox-runner.ts +3 -3
- package/src/tools/subagent/read.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -2
- package/src/tools/swarm/delegate.ts +3 -3
- package/src/tools/system/request-permission.ts +5 -4
- package/src/tools/terminal/backends/native.ts +4 -4
- package/src/tools/terminal/parser.ts +6 -6
- package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
- package/src/tools/terminal/shell.ts +16 -16
- package/src/tools/tool-approval-handler.ts +21 -12
- package/src/tools/tool-manifest.ts +4 -4
- package/src/tools/types.ts +3 -3
- package/src/tools/ui-surface/definitions.ts +9 -37
- package/src/tools/watcher/list.ts +1 -1
- package/src/util/logger.ts +7 -2
- package/src/util/pricing.ts +4 -0
- package/src/util/retry.ts +29 -1
- package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
- package/src/cli/reference.ts +0 -38
- package/src/memory/job-handlers/capability-cards.ts +0 -420
- package/src/runtime/routes/thread-starter-routes.ts +0 -294
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* No messages are persisted. `conversation.processing` is never set or checked.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
|
|
15
17
|
import { buildToolDefinitions } from "../../daemon/conversation-tool-setup.js";
|
|
16
18
|
import { getConversationByKey } from "../../memory/conversation-key-store.js";
|
|
17
19
|
import { buildSystemPrompt } from "../../prompts/system-prompt.js";
|
|
@@ -21,13 +23,45 @@ import {
|
|
|
21
23
|
} from "../../providers/provider-send-message.js";
|
|
22
24
|
import { checkIngressForSecrets } from "../../security/secret-ingress.js";
|
|
23
25
|
import { getLogger } from "../../util/logger.js";
|
|
26
|
+
import { getWorkspacePromptPath } from "../../util/platform.js";
|
|
24
27
|
import type { AuthContext } from "../auth/types.js";
|
|
25
28
|
import { httpError } from "../http-errors.js";
|
|
26
29
|
import type { RouteDefinition } from "../http-router.js";
|
|
27
30
|
import type { SendMessageDeps } from "../http-types.js";
|
|
31
|
+
import { getCachedIntro, setCachedIntro } from "./identity-intro-cache.js";
|
|
28
32
|
|
|
29
33
|
const log = getLogger("btw-routes");
|
|
30
34
|
|
|
35
|
+
/** Conversation key used by the client for identity intro generation. */
|
|
36
|
+
const IDENTITY_INTRO_KEY = "identity-intro";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse the `## Identity Intro` section from SOUL.md.
|
|
40
|
+
* Returns the first non-empty line under that heading, or null.
|
|
41
|
+
*/
|
|
42
|
+
function readSoulIdentityIntro(): string | null {
|
|
43
|
+
try {
|
|
44
|
+
const soulPath = getWorkspacePromptPath("SOUL.md");
|
|
45
|
+
if (!existsSync(soulPath)) return null;
|
|
46
|
+
const content = readFileSync(soulPath, "utf-8");
|
|
47
|
+
|
|
48
|
+
let inSection = false;
|
|
49
|
+
for (const line of content.split("\n")) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
if (/^#+\s/.test(trimmed)) {
|
|
52
|
+
inSection = trimmed.toLowerCase().includes("identity intro");
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (inSection && trimmed.length > 0) {
|
|
56
|
+
return trimmed;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// Fall through — no SOUL.md intro available
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
31
65
|
// ---------------------------------------------------------------------------
|
|
32
66
|
// Handler
|
|
33
67
|
// ---------------------------------------------------------------------------
|
|
@@ -77,6 +111,43 @@ async function handleBtw(
|
|
|
77
111
|
);
|
|
78
112
|
}
|
|
79
113
|
|
|
114
|
+
// ----- Identity intro fast-path -----
|
|
115
|
+
// When the client requests the identity intro, check SOUL.md first (persisted
|
|
116
|
+
// during onboarding), then the LLM-generated cache. Only fall through to a
|
|
117
|
+
// live LLM call when neither source has a value.
|
|
118
|
+
if (conversationKey === IDENTITY_INTRO_KEY) {
|
|
119
|
+
const soulIntro = readSoulIdentityIntro();
|
|
120
|
+
const fastText = soulIntro ?? getCachedIntro()?.text;
|
|
121
|
+
if (fastText) {
|
|
122
|
+
log.debug(
|
|
123
|
+
soulIntro
|
|
124
|
+
? "Returning SOUL.md identity intro"
|
|
125
|
+
: "Returning cached identity intro",
|
|
126
|
+
);
|
|
127
|
+
const encoder = new TextEncoder();
|
|
128
|
+
const stream = new ReadableStream({
|
|
129
|
+
start(controller) {
|
|
130
|
+
controller.enqueue(
|
|
131
|
+
encoder.encode(
|
|
132
|
+
`event: btw_text_delta\ndata: ${JSON.stringify({ text: fastText })}\n\n`,
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
controller.enqueue(
|
|
136
|
+
encoder.encode(`event: btw_complete\ndata: {}\n\n`),
|
|
137
|
+
);
|
|
138
|
+
controller.close();
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
return new Response(stream, {
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "text/event-stream",
|
|
144
|
+
"Cache-Control": "no-cache",
|
|
145
|
+
Connection: "keep-alive",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
80
151
|
// Look up an existing conversation — never create one. BTW is ephemeral
|
|
81
152
|
// (the file header promises "No messages are persisted"), so we must not
|
|
82
153
|
// call getOrCreateConversation which would insert a DB row. When no
|
|
@@ -116,6 +187,9 @@ async function handleBtw(
|
|
|
116
187
|
? conversation.systemPrompt
|
|
117
188
|
: buildSystemPrompt({ excludeBootstrap: true });
|
|
118
189
|
|
|
190
|
+
const isIntroRequest = conversationKey === IDENTITY_INTRO_KEY;
|
|
191
|
+
let textDeltaCount = 0;
|
|
192
|
+
let collectedText = "";
|
|
119
193
|
await conversation.provider.sendMessage(
|
|
120
194
|
messages,
|
|
121
195
|
tools,
|
|
@@ -128,6 +202,8 @@ async function handleBtw(
|
|
|
128
202
|
},
|
|
129
203
|
onEvent: (event) => {
|
|
130
204
|
if (event.type === "text_delta") {
|
|
205
|
+
textDeltaCount++;
|
|
206
|
+
if (isIntroRequest) collectedText += event.text;
|
|
131
207
|
controller.enqueue(
|
|
132
208
|
encoder.encode(
|
|
133
209
|
`event: btw_text_delta\ndata: ${JSON.stringify({ text: event.text })}\n\n`,
|
|
@@ -139,6 +215,23 @@ async function handleBtw(
|
|
|
139
215
|
},
|
|
140
216
|
);
|
|
141
217
|
|
|
218
|
+
if (textDeltaCount === 0) {
|
|
219
|
+
log.warn(
|
|
220
|
+
{ conversationKey, messageCount: messages.length },
|
|
221
|
+
"btw side-chain completed with no text deltas",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Cache the generated identity intro for subsequent requests.
|
|
226
|
+
if (isIntroRequest && collectedText.trim()) {
|
|
227
|
+
try {
|
|
228
|
+
setCachedIntro(collectedText.trim());
|
|
229
|
+
log.debug("Cached identity intro text");
|
|
230
|
+
} catch {
|
|
231
|
+
// Non-fatal — next request will regenerate.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
142
235
|
controller.enqueue(
|
|
143
236
|
encoder.encode(`event: btw_complete\ndata: {}\n\n`),
|
|
144
237
|
);
|
|
@@ -21,6 +21,7 @@ import { httpError } from "../http-errors.js";
|
|
|
21
21
|
import type { RouteDefinition } from "../http-router.js";
|
|
22
22
|
import {
|
|
23
23
|
cancelOutbound,
|
|
24
|
+
deliverVerificationSlack,
|
|
24
25
|
normalizeTelegramDestination,
|
|
25
26
|
resendOutbound,
|
|
26
27
|
startOutbound,
|
|
@@ -118,12 +119,20 @@ export async function handleCreateVerificationSession(
|
|
|
118
119
|
verificationRateLimiter.recordFailure(rateLimitKey);
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
// Dispatch Slack DM delivery from the daemon process (not sandboxed).
|
|
123
|
+
if (result._pendingSlackDm) {
|
|
124
|
+
const { userId, text, assistantId: aid } = result._pendingSlackDm;
|
|
125
|
+
deliverVerificationSlack(userId, text, aid);
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
const status = result.success
|
|
122
129
|
? 200
|
|
123
130
|
: result.error === "rate_limited"
|
|
124
131
|
? 429
|
|
125
132
|
: 400;
|
|
126
|
-
|
|
133
|
+
// Strip internal field from the response
|
|
134
|
+
const { _pendingSlackDm: _, ...publicResult } = result;
|
|
135
|
+
return Response.json(publicResult, { status });
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
// Inbound challenge path
|
|
@@ -167,12 +176,20 @@ export async function handleResendVerificationSession(
|
|
|
167
176
|
channel: body.channel,
|
|
168
177
|
originConversationId: body.originConversationId,
|
|
169
178
|
});
|
|
179
|
+
|
|
180
|
+
// Dispatch Slack DM delivery from the daemon process (not sandboxed).
|
|
181
|
+
if (result._pendingSlackDm) {
|
|
182
|
+
const { userId, text, assistantId: aid } = result._pendingSlackDm;
|
|
183
|
+
deliverVerificationSlack(userId, text, aid);
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
const status = result.success
|
|
171
187
|
? 200
|
|
172
188
|
: result.error === "rate_limited"
|
|
173
189
|
? 429
|
|
174
190
|
: 400;
|
|
175
|
-
|
|
191
|
+
const { _pendingSlackDm: _, ...publicResult } = result;
|
|
192
|
+
return Response.json(publicResult, { status });
|
|
176
193
|
}
|
|
177
194
|
|
|
178
195
|
/**
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* POST /v1/conversations/switch — switch to an existing conversation
|
|
5
5
|
* PATCH /v1/conversations/:id/name — rename a conversation
|
|
6
6
|
* DELETE /v1/conversations — clear all conversations
|
|
7
|
+
* POST /v1/conversations/:id/wipe — wipe conversation and revert memory
|
|
7
8
|
* DELETE /v1/conversations/:id — delete a single conversation
|
|
8
9
|
* POST /v1/conversations/:id/cancel — cancel generation
|
|
9
10
|
* POST /v1/conversations/:id/undo — undo last message
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
import {
|
|
15
16
|
batchSetDisplayOrders,
|
|
16
17
|
deleteConversation,
|
|
18
|
+
wipeConversation,
|
|
17
19
|
} from "../../memory/conversation-crud.js";
|
|
18
20
|
import {
|
|
19
21
|
resolveConversationId,
|
|
@@ -121,6 +123,57 @@ export function conversationManagementRouteDefinitions(
|
|
|
121
123
|
return new Response(null, { status: 204 });
|
|
122
124
|
},
|
|
123
125
|
},
|
|
126
|
+
{
|
|
127
|
+
endpoint: "conversations/:id/wipe",
|
|
128
|
+
method: "POST",
|
|
129
|
+
policyKey: "conversations/wipe",
|
|
130
|
+
handler: async ({ params }) => {
|
|
131
|
+
const resolvedId = resolveConversationId(params.id);
|
|
132
|
+
if (!resolvedId) {
|
|
133
|
+
return httpError(
|
|
134
|
+
"NOT_FOUND",
|
|
135
|
+
`Conversation ${params.id} not found`,
|
|
136
|
+
404,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
deps.destroyConversation(resolvedId);
|
|
140
|
+
const result = wipeConversation(resolvedId);
|
|
141
|
+
// Enqueue Qdrant vector cleanup jobs
|
|
142
|
+
for (const segId of result.segmentIds) {
|
|
143
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
144
|
+
targetType: "segment",
|
|
145
|
+
targetId: segId,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
for (const itemId of result.orphanedItemIds) {
|
|
149
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
150
|
+
targetType: "item",
|
|
151
|
+
targetId: itemId,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
for (const summaryId of result.deletedSummaryIds) {
|
|
155
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
156
|
+
targetType: "summary",
|
|
157
|
+
targetId: summaryId,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
log.info(
|
|
161
|
+
{
|
|
162
|
+
conversationId: resolvedId,
|
|
163
|
+
unsuperseded: result.unsupersededItemIds.length,
|
|
164
|
+
summariesDeleted: result.deletedSummaryIds.length,
|
|
165
|
+
jobsCancelled: result.cancelledJobCount,
|
|
166
|
+
},
|
|
167
|
+
"Wiped conversation and reverted memory changes",
|
|
168
|
+
);
|
|
169
|
+
return Response.json({
|
|
170
|
+
wiped: true,
|
|
171
|
+
unsupersededItems: result.unsupersededItemIds.length,
|
|
172
|
+
deletedSummaries: result.deletedSummaryIds.length,
|
|
173
|
+
cancelledJobs: result.cancelledJobCount,
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
},
|
|
124
177
|
{
|
|
125
178
|
endpoint: "conversations/:id",
|
|
126
179
|
method: "DELETE",
|
|
@@ -163,7 +216,8 @@ export function conversationManagementRouteDefinitions(
|
|
|
163
216
|
method: "POST",
|
|
164
217
|
policyKey: "conversations/cancel",
|
|
165
218
|
handler: ({ params }) => {
|
|
166
|
-
|
|
219
|
+
const resolvedId = resolveConversationId(params.id) ?? params.id;
|
|
220
|
+
deps.cancelGeneration(resolvedId);
|
|
167
221
|
return new Response(null, { status: 202 });
|
|
168
222
|
},
|
|
169
223
|
},
|
|
@@ -200,7 +200,7 @@ export function conversationQueryRouteDefinitions(
|
|
|
200
200
|
requestId: params.id,
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
|
-
if (result.reason === "
|
|
203
|
+
if (result.reason === "conversation_not_found") {
|
|
204
204
|
return httpError("NOT_FOUND", "Conversation not found", 404);
|
|
205
205
|
}
|
|
206
206
|
return httpError("NOT_FOUND", "Queued message not found", 404);
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
createCanonicalGuardianRequest,
|
|
36
36
|
generateCanonicalRequestCode,
|
|
37
37
|
listPendingRequestsByConversationScope,
|
|
38
|
+
resolveCanonicalGuardianRequest,
|
|
38
39
|
} from "../../memory/canonical-guardian-store.js";
|
|
39
40
|
import {
|
|
40
41
|
addMessage,
|
|
@@ -58,6 +59,7 @@ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
|
58
59
|
import type { AuthContext } from "../auth/types.js";
|
|
59
60
|
import { bridgeConfirmationRequestToGuardian } from "../confirmation-request-guardian-bridge.js";
|
|
60
61
|
import { routeGuardianReply } from "../guardian-reply-router.js";
|
|
62
|
+
import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
|
|
61
63
|
import { httpError } from "../http-errors.js";
|
|
62
64
|
import type { RouteDefinition } from "../http-router.js";
|
|
63
65
|
import type {
|
|
@@ -492,16 +494,16 @@ function makeHubPublisher(
|
|
|
492
494
|
}
|
|
493
495
|
|
|
494
496
|
// ServerMessage is a large union; conversationId exists on most but not all variants.
|
|
495
|
-
const
|
|
497
|
+
const msgConversationId =
|
|
496
498
|
"conversationId" in msg &&
|
|
497
499
|
typeof (msg as { conversationId?: unknown }).conversationId === "string"
|
|
498
500
|
? (msg as { conversationId: string }).conversationId
|
|
499
501
|
: undefined;
|
|
500
|
-
const
|
|
502
|
+
const resolvedConversationId = msgConversationId ?? conversationId;
|
|
501
503
|
const event = buildAssistantEvent(
|
|
502
504
|
DAEMON_INTERNAL_ASSISTANT_ID,
|
|
503
505
|
msg,
|
|
504
|
-
|
|
506
|
+
resolvedConversationId,
|
|
505
507
|
);
|
|
506
508
|
hubChain = (async () => {
|
|
507
509
|
await hubChain;
|
|
@@ -646,12 +648,44 @@ export async function handleSendMessage(
|
|
|
646
648
|
// the same trust resolution pipeline that channel ingress uses.
|
|
647
649
|
if (authContext.actorPrincipalId) {
|
|
648
650
|
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
649
|
-
|
|
651
|
+
let trustCtx = resolveTrustContext({
|
|
650
652
|
assistantId,
|
|
651
653
|
sourceChannel: "vellum",
|
|
652
654
|
conversationExternalId: "local",
|
|
653
655
|
actorExternalId: authContext.actorPrincipalId,
|
|
654
656
|
});
|
|
657
|
+
if (trustCtx.trustClass === "unknown") {
|
|
658
|
+
// Attempt to heal guardian binding drift: after a DB reset the
|
|
659
|
+
// guardian binding gets a new vellum-principal-* UUID while the
|
|
660
|
+
// client still holds a valid JWT with the old one. The signing
|
|
661
|
+
// key survives the reset, so the JWT is authentic — just stale.
|
|
662
|
+
const healed = healGuardianBindingDrift(authContext.actorPrincipalId);
|
|
663
|
+
if (healed) {
|
|
664
|
+
trustCtx = resolveTrustContext({
|
|
665
|
+
assistantId,
|
|
666
|
+
sourceChannel: "vellum",
|
|
667
|
+
conversationExternalId: "local",
|
|
668
|
+
actorExternalId: authContext.actorPrincipalId,
|
|
669
|
+
});
|
|
670
|
+
log.info(
|
|
671
|
+
{
|
|
672
|
+
actorPrincipalId: authContext.actorPrincipalId,
|
|
673
|
+
trustClass: trustCtx.trustClass,
|
|
674
|
+
},
|
|
675
|
+
"Trust re-resolved after guardian binding drift heal",
|
|
676
|
+
);
|
|
677
|
+
} else {
|
|
678
|
+
log.warn(
|
|
679
|
+
{
|
|
680
|
+
actorPrincipalId: authContext.actorPrincipalId,
|
|
681
|
+
sourceChannel,
|
|
682
|
+
trustClass: trustCtx.trustClass,
|
|
683
|
+
principalType: authContext.principalType,
|
|
684
|
+
},
|
|
685
|
+
"JWT-verified actor resolved to unknown trust class — possible guardian binding drift (e.g. DB reset without re-bootstrap)",
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
655
689
|
conversation.setTrustContext(withSourceChannel(sourceChannel, trustCtx));
|
|
656
690
|
} else {
|
|
657
691
|
// Service principals (svc_gateway) or tokens without an actor ID
|
|
@@ -811,6 +845,11 @@ export async function handleSendMessage(
|
|
|
811
845
|
state: "denied" as const,
|
|
812
846
|
source: "auto_deny" as const,
|
|
813
847
|
});
|
|
848
|
+
// Sync canonical guardian request status so stale "pending" DB
|
|
849
|
+
// records don't get matched by later guardian reply routing.
|
|
850
|
+
resolveCanonicalGuardianRequest(interaction.requestId, "pending", {
|
|
851
|
+
status: "denied",
|
|
852
|
+
});
|
|
814
853
|
}
|
|
815
854
|
}
|
|
816
855
|
conversation.denyAllPendingConfirmations();
|
|
@@ -842,6 +881,11 @@ export async function handleSendMessage(
|
|
|
842
881
|
state: "denied" as const,
|
|
843
882
|
source: "auto_deny" as const,
|
|
844
883
|
});
|
|
884
|
+
// Sync canonical guardian request status so stale "pending" DB
|
|
885
|
+
// records don't get matched by later guardian reply routing.
|
|
886
|
+
resolveCanonicalGuardianRequest(interaction.requestId, "pending", {
|
|
887
|
+
status: "denied",
|
|
888
|
+
});
|
|
845
889
|
}
|
|
846
890
|
}
|
|
847
891
|
conversation.denyAllPendingConfirmations();
|
|
@@ -931,7 +975,7 @@ export async function handleSendMessage(
|
|
|
931
975
|
);
|
|
932
976
|
|
|
933
977
|
// Defer event publishing to next tick so the HTTP response reaches the
|
|
934
|
-
// client first. This ensures the client's
|
|
978
|
+
// client first. This ensures the client's serverToLocalConversationMap is
|
|
935
979
|
// populated before SSE events arrive, preventing dropped events in new
|
|
936
980
|
// desktop conversations.
|
|
937
981
|
//
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for conversation starter endpoints.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/conversation-starters — list conversation starters (chips)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { and, desc, eq, inArray, like } from "drizzle-orm";
|
|
8
|
+
|
|
9
|
+
import { getDb } from "../../memory/db.js";
|
|
10
|
+
import { enqueueMemoryJob } from "../../memory/jobs-store.js";
|
|
11
|
+
import { rawGet } from "../../memory/raw-query.js";
|
|
12
|
+
import { conversationStarters, memoryJobs } from "../../memory/schema.js";
|
|
13
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Strongest-first ordering — maximize category diversity so the top four
|
|
17
|
+
// chips form a coherent, non-repetitive row.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
interface StarterItem {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
prompt: string;
|
|
24
|
+
category: string | null;
|
|
25
|
+
batch: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Re-order starters so adjacent items have distinct categories wherever
|
|
30
|
+
* possible. Within each category, preserve the original (batch-desc) order.
|
|
31
|
+
* This is deterministic — same input always produces the same output.
|
|
32
|
+
*/
|
|
33
|
+
export function orderStrongestFirst<T extends StarterItem>(items: T[]): T[] {
|
|
34
|
+
if (items.length <= 1) return items;
|
|
35
|
+
|
|
36
|
+
// Group by category, preserving original order within each group
|
|
37
|
+
const byCategory = new Map<string, T[]>();
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
const cat = item.category ?? "other";
|
|
40
|
+
let group = byCategory.get(cat);
|
|
41
|
+
if (!group) {
|
|
42
|
+
group = [];
|
|
43
|
+
byCategory.set(cat, group);
|
|
44
|
+
}
|
|
45
|
+
group.push(item);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Prefer categories with the most remaining items so the row stays varied
|
|
49
|
+
// early without burying the dominant themes entirely.
|
|
50
|
+
const sortedGroups = [...byCategory.entries()]
|
|
51
|
+
.sort((a, b) => b[1].length - a[1].length)
|
|
52
|
+
.map(([, group]) => ({ items: group, idx: 0 }));
|
|
53
|
+
|
|
54
|
+
const result: T[] = [];
|
|
55
|
+
const seenCategories = new Set<string>();
|
|
56
|
+
let lastCategory: string | null = null;
|
|
57
|
+
|
|
58
|
+
while (result.length < items.length) {
|
|
59
|
+
let picked = false;
|
|
60
|
+
const availableGroups = sortedGroups.filter(
|
|
61
|
+
(group) => group.idx < group.items.length,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const unseenGroups = availableGroups.filter((group) => {
|
|
65
|
+
const category = group.items[group.idx]?.category ?? "other";
|
|
66
|
+
return category !== lastCategory && !seenCategories.has(category);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const nextGroups =
|
|
70
|
+
unseenGroups.length > 0
|
|
71
|
+
? unseenGroups
|
|
72
|
+
: availableGroups.filter((group) => {
|
|
73
|
+
const category = group.items[group.idx]?.category ?? "other";
|
|
74
|
+
return category !== lastCategory;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// First pass: prefer unseen categories, then avoid adjacent duplicates.
|
|
78
|
+
for (const group of nextGroups) {
|
|
79
|
+
if (group.idx >= group.items.length) continue;
|
|
80
|
+
const candidate = group.items[group.idx];
|
|
81
|
+
const cat = candidate.category ?? "other";
|
|
82
|
+
result.push(candidate);
|
|
83
|
+
group.idx++;
|
|
84
|
+
seenCategories.add(cat);
|
|
85
|
+
lastCategory = cat;
|
|
86
|
+
picked = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Fallback: if all remaining items share the same category, just pick next
|
|
91
|
+
if (!picked) {
|
|
92
|
+
for (const group of availableGroups) {
|
|
93
|
+
if (group.idx < group.items.length) {
|
|
94
|
+
const candidate = group.items[group.idx];
|
|
95
|
+
const cat = candidate.category ?? "other";
|
|
96
|
+
result.push(candidate);
|
|
97
|
+
group.idx++;
|
|
98
|
+
seenCategories.add(cat);
|
|
99
|
+
lastCategory = cat;
|
|
100
|
+
picked = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!picked) break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// GET /v1/conversation-starters
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
function handleListConversationStarters(url: URL): Response {
|
|
117
|
+
const limitParam = Math.min(
|
|
118
|
+
Math.max(1, Number(url.searchParams.get("limit") ?? 4)),
|
|
119
|
+
20,
|
|
120
|
+
);
|
|
121
|
+
const offsetParam = Math.max(0, Number(url.searchParams.get("offset") ?? 0));
|
|
122
|
+
const scopeId = url.searchParams.get("scope_id") ?? "default";
|
|
123
|
+
|
|
124
|
+
const db = getDb();
|
|
125
|
+
|
|
126
|
+
// Fetch chips (ranked by model, newest batch first)
|
|
127
|
+
const rawItems = db
|
|
128
|
+
.select({
|
|
129
|
+
id: conversationStarters.id,
|
|
130
|
+
label: conversationStarters.label,
|
|
131
|
+
prompt: conversationStarters.prompt,
|
|
132
|
+
category: conversationStarters.category,
|
|
133
|
+
batch: conversationStarters.generationBatch,
|
|
134
|
+
})
|
|
135
|
+
.from(conversationStarters)
|
|
136
|
+
.where(
|
|
137
|
+
and(
|
|
138
|
+
eq(conversationStarters.scopeId, scopeId),
|
|
139
|
+
eq(conversationStarters.cardType, "chip"),
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
.orderBy(
|
|
143
|
+
desc(conversationStarters.generationBatch),
|
|
144
|
+
desc(conversationStarters.createdAt),
|
|
145
|
+
)
|
|
146
|
+
.limit(limitParam)
|
|
147
|
+
.offset(offsetParam)
|
|
148
|
+
.all();
|
|
149
|
+
|
|
150
|
+
const countRow = rawGet<{ c: number }>(
|
|
151
|
+
`SELECT COUNT(*) AS c FROM conversation_starters WHERE scope_id = ? AND card_type = 'chip'`,
|
|
152
|
+
scopeId,
|
|
153
|
+
);
|
|
154
|
+
const total = countRow?.c ?? 0;
|
|
155
|
+
|
|
156
|
+
// If starters exist, return them immediately.
|
|
157
|
+
if (total > 0) {
|
|
158
|
+
return Response.json({
|
|
159
|
+
starters: orderStrongestFirst(rawItems),
|
|
160
|
+
total,
|
|
161
|
+
status: "ready",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// No starters — check whether we have memory items to generate from.
|
|
166
|
+
const memoryCount = rawGet<{ c: number }>(
|
|
167
|
+
`SELECT COUNT(*) AS c FROM memory_items WHERE status = 'active' AND scope_id = ?`,
|
|
168
|
+
scopeId,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (!memoryCount || memoryCount.c === 0) {
|
|
172
|
+
return Response.json({ starters: [], total: 0, status: "empty" });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Memory items exist but no starters yet — ensure a generation job is queued.
|
|
176
|
+
const existing = db
|
|
177
|
+
.select({ id: memoryJobs.id })
|
|
178
|
+
.from(memoryJobs)
|
|
179
|
+
.where(
|
|
180
|
+
and(
|
|
181
|
+
eq(memoryJobs.type, "generate_conversation_starters"),
|
|
182
|
+
inArray(memoryJobs.status, ["pending", "running"]),
|
|
183
|
+
like(memoryJobs.payload, `%"scopeId":"${scopeId}"%`),
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
.get();
|
|
187
|
+
|
|
188
|
+
if (!existing) {
|
|
189
|
+
enqueueMemoryJob("generate_conversation_starters", { scopeId });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return Response.json({ starters: [], total: 0, status: "generating" });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Route definitions
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
export function conversationStarterRouteDefinitions(): RouteDefinition[] {
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
endpoint: "conversation-starters",
|
|
203
|
+
method: "GET",
|
|
204
|
+
handler: (ctx) => handleListConversationStarters(ctx.url),
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
@@ -19,6 +19,7 @@ import { getLogger } from "../../util/logger.js";
|
|
|
19
19
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
20
20
|
import { mintCredentialPair } from "../auth/credential-service.js";
|
|
21
21
|
import { httpError } from "../http-errors.js";
|
|
22
|
+
import { isPrivateAddress } from "../middleware/auth.js";
|
|
22
23
|
|
|
23
24
|
/** Bun server shape needed for requestIP -- avoids importing the full Bun type. */
|
|
24
25
|
type ServerWithRequestIP = {
|
|
@@ -71,30 +72,33 @@ function ensureGuardianPrincipal(assistantId: string): {
|
|
|
71
72
|
return { guardianPrincipalId, isNew: true };
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
/** Loopback addresses — used to gate the bootstrap endpoint to local-only. */
|
|
75
|
-
const LOOPBACK_ADDRESSES = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
76
|
-
|
|
77
75
|
/**
|
|
78
76
|
* Handle POST /v1/guardian/init
|
|
79
77
|
*
|
|
80
78
|
* Body: { platform: 'macos', deviceId: string }
|
|
81
79
|
* Returns: { guardianPrincipalId, accessToken, isNew }
|
|
82
80
|
*
|
|
83
|
-
* This endpoint is
|
|
84
|
-
* obtain actor tokens exclusively through
|
|
81
|
+
* This endpoint is restricted to private-network peers (loopback, RFC 1918,
|
|
82
|
+
* Docker bridge, etc.). iOS devices obtain actor tokens exclusively through
|
|
83
|
+
* the QR pairing flow.
|
|
85
84
|
*/
|
|
86
85
|
export async function handleGuardianBootstrap(
|
|
87
86
|
req: Request,
|
|
88
87
|
server: ServerWithRequestIP,
|
|
89
88
|
): Promise<Response> {
|
|
90
|
-
// Reject
|
|
91
|
-
|
|
89
|
+
// Reject requests forwarded from public networks. The gateway sets
|
|
90
|
+
// x-forwarded-for to the real client IP; if that IP is on a private
|
|
91
|
+
// network (loopback, Docker bridge, RFC 1918) the request is still
|
|
92
|
+
// considered local. Only reject when the forwarded IP is public.
|
|
93
|
+
const forwarded = req.headers.get("x-forwarded-for");
|
|
94
|
+
const forwardedIp = forwarded ? forwarded.split(",")[0].trim() : null;
|
|
95
|
+
if (forwardedIp && !isPrivateAddress(forwardedIp) && !isHttpAuthDisabled()) {
|
|
92
96
|
return httpError("FORBIDDEN", "Bootstrap endpoint is local-only", 403);
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
// Reject non-loopback
|
|
99
|
+
// Reject non-private-network peers (allows loopback, Docker bridge, etc.)
|
|
96
100
|
const peerIp = server.requestIP(req)?.address;
|
|
97
|
-
if ((!peerIp || !
|
|
101
|
+
if ((!peerIp || !isPrivateAddress(peerIp)) && !isHttpAuthDisabled()) {
|
|
98
102
|
return httpError("FORBIDDEN", "Bootstrap endpoint is local-only", 403);
|
|
99
103
|
}
|
|
100
104
|
|