@vellumai/assistant 0.5.16 → 0.6.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 +1 -1
- package/Dockerfile +0 -3
- package/knip.json +2 -1
- package/openapi.yaml +660 -80
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +68 -0
- package/src/__tests__/agent-loop.test.ts +0 -32
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +57 -3
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/call-conversation-messages.test.ts +2 -6
- package/src/__tests__/call-domain.test.ts +2 -6
- package/src/__tests__/call-pointer-messages.test.ts +2 -14
- package/src/__tests__/call-recovery.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +2 -6
- package/src/__tests__/call-store.test.ts +2 -6
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
- package/src/__tests__/canonical-guardian-store.test.ts +2 -6
- package/src/__tests__/channel-delivery-store.test.ts +2 -6
- package/src/__tests__/channel-retry-sweep.test.ts +2 -6
- package/src/__tests__/checker.test.ts +25 -3
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
- package/src/__tests__/cli-memory.test.ts +74 -69
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
- package/src/__tests__/contacts-tools.test.ts +31 -0
- package/src/__tests__/context-overflow-reducer.test.ts +86 -0
- package/src/__tests__/context-token-estimator.test.ts +175 -10
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
- package/src/__tests__/conversation-agent-loop.test.ts +9 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -6
- package/src/__tests__/conversation-attention-store.test.ts +2 -6
- package/src/__tests__/conversation-clear-safety.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
- package/src/__tests__/conversation-disk-view.test.ts +2 -6
- package/src/__tests__/conversation-error.test.ts +33 -2
- package/src/__tests__/conversation-fork-crud.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
- package/src/__tests__/conversation-media-retry.test.ts +91 -0
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-usage.test.ts +2 -6
- package/src/__tests__/conversation-wipe.test.ts +11 -408
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +2 -0
- package/src/__tests__/followup-tools.test.ts +2 -6
- package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
- package/src/__tests__/guardian-action-store.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
- package/src/__tests__/guardian-dispatch.test.ts +2 -6
- package/src/__tests__/guardian-grant-minting.test.ts +2 -14
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
- package/src/__tests__/guardian-routing-state.test.ts +2 -6
- package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +154 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +292 -0
- package/src/__tests__/invite-redemption-service.test.ts +2 -6
- package/src/__tests__/invite-routes-http.test.ts +2 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/llm-context-route-provider.test.ts +2 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +2 -6
- package/src/__tests__/managed-store.test.ts +38 -11
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +2 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-guardian-path.test.ts +2 -6
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
- package/src/__tests__/outlook-attachments.test.ts +301 -0
- package/src/__tests__/outlook-automation-tools.test.ts +425 -0
- package/src/__tests__/outlook-categories.test.ts +212 -0
- package/src/__tests__/outlook-client-automation.test.ts +246 -0
- package/src/__tests__/outlook-compose-tools.test.ts +325 -0
- package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
- package/src/__tests__/outlook-email-watcher.test.ts +322 -0
- package/src/__tests__/outlook-follow-up.test.ts +196 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/platform-callback-registration.test.ts +4 -4
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/provider-error-scenarios.test.ts +21 -0
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/require-fresh-approval.test.ts +64 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/schedule-store.test.ts +2 -6
- package/src/__tests__/schedule-tools.test.ts +2 -6
- package/src/__tests__/scheduler-recurrence.test.ts +1 -5
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/search-skills-unified.test.ts +421 -0
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +2 -6
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
- package/src/__tests__/skill-memory.test.ts +140 -98
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/task-compiler.test.ts +2 -6
- package/src/__tests__/task-management-tools.test.ts +2 -6
- package/src/__tests__/task-memory-cleanup.test.ts +173 -229
- package/src/__tests__/task-runner.test.ts +2 -6
- package/src/__tests__/task-scheduler.test.ts +2 -6
- package/src/__tests__/test-preload.ts +3 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -6
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
- package/src/__tests__/trust-store.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +2 -6
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
- package/src/__tests__/usage-routes.test.ts +2 -6
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -6
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
- package/src/__tests__/voice-session-bridge.test.ts +2 -6
- package/src/__tests__/volume-security-guard.test.ts +2 -0
- package/src/__tests__/workspace-lifecycle.test.ts +29 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/agent/attachments.ts +7 -2
- package/src/agent/image-optimize.ts +165 -0
- package/src/agent/loop.ts +1 -15
- package/src/bundler/app-compiler.ts +179 -2
- package/src/bundler/package-resolver.ts +3 -5
- package/src/cli/__tests__/notifications.test.ts +1 -2
- package/src/cli/cli-memory.ts +67 -64
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/memory.ts +41 -55
- package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +11 -6
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
- package/src/cli/commands/platform/index.ts +16 -16
- package/src/cli/commands/skills.ts +88 -16
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/contacts/SKILL.md +0 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
- package/src/config/bundled-skills/gmail/SKILL.md +2 -10
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +10 -18
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
- package/src/config/bundled-skills/outlook/SKILL.md +189 -0
- package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
- package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
- package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
- package/src/config/bundled-skills/slack/SKILL.md +1 -7
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +15 -8
- package/src/config/feature-flag-registry.json +21 -124
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/config/skills.ts +18 -7
- package/src/context/token-estimator.ts +25 -18
- package/src/context/window-manager.ts +6 -2
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
- package/src/daemon/conversation-agent-loop.ts +96 -61
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-lifecycle.ts +33 -0
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-runtime-assembly.ts +5 -0
- package/src/daemon/conversation.ts +41 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/handlers/shared.ts +22 -12
- package/src/daemon/handlers/skills.ts +416 -202
- package/src/daemon/lifecycle.ts +40 -1
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +4 -1
- package/src/daemon/message-types/messages.ts +3 -1
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/providers-setup.ts +5 -0
- package/src/daemon/server.ts +11 -2
- package/src/daemon/tool-side-effects.ts +27 -5
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/runner.ts +15 -38
- package/src/inbound/platform-callback-registration.ts +14 -14
- package/src/memory/admin.ts +11 -45
- package/src/memory/conversation-bootstrap.ts +2 -0
- package/src/memory/conversation-crud.ts +242 -348
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +4 -2
- package/src/memory/db-init.ts +30 -3
- package/src/memory/embed.ts +73 -0
- package/src/memory/embedding-backend.ts +8 -14
- package/src/memory/embedding-runtime-manager.ts +12 -114
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/graph/bootstrap.ts +512 -0
- package/src/memory/graph/capability-seed.ts +297 -0
- package/src/memory/graph/consolidation.ts +691 -0
- package/src/memory/graph/conversation-graph-memory.ts +630 -0
- package/src/memory/graph/decay.test.ts +208 -0
- package/src/memory/graph/decay.ts +195 -0
- package/src/memory/graph/extraction-job.ts +69 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1254 -0
- package/src/memory/graph/graph-search.ts +266 -0
- package/src/memory/graph/image-ref-utils.ts +29 -0
- package/src/memory/graph/injection.test.ts +513 -0
- package/src/memory/graph/injection.ts +439 -0
- package/src/memory/graph/inspect.ts +534 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1008 -0
- package/src/memory/graph/scoring.test.ts +548 -0
- package/src/memory/graph/scoring.ts +232 -0
- package/src/memory/graph/serendipity.ts +65 -0
- package/src/memory/graph/store.test.ts +1050 -0
- package/src/memory/graph/store.ts +699 -0
- package/src/memory/graph/tool-handlers.ts +426 -0
- package/src/memory/graph/tools.ts +141 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +271 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +0 -53
- package/src/memory/job-handlers/conversation-starters.ts +91 -53
- package/src/memory/job-handlers/embedding.ts +5 -31
- package/src/memory/job-handlers/index-maintenance.ts +23 -11
- package/src/memory/job-handlers/summarization.ts +32 -17
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +50 -70
- package/src/memory/jobs-worker.ts +147 -112
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
- package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
- package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-graph.ts +139 -0
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/task-memory-cleanup.ts +28 -50
- package/src/messaging/providers/outlook/adapter.ts +8 -1
- package/src/messaging/providers/outlook/client.ts +299 -0
- package/src/messaging/providers/outlook/types.ts +118 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/copy-composer.ts +9 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/seed-providers.ts +2 -1
- package/src/permissions/checker.ts +24 -3
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/workspace-policy.ts +1 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +3 -29
- package/src/providers/anthropic/client.ts +47 -19
- package/src/providers/gemini/client.ts +1 -1
- package/src/providers/openai/client.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +19 -3
- package/src/runtime/actor-trust-resolver.ts +5 -1
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/guardian-reply-router.ts +5 -1
- package/src/runtime/http-server.ts +23 -3
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +8 -0
- package/src/runtime/routes/conversation-management-routes.ts +2 -0
- package/src/runtime/routes/conversation-starter-routes.ts +2 -2
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/global-search-routes.ts +21 -19
- package/src/runtime/routes/group-routes.ts +207 -0
- package/src/runtime/routes/guardian-action-routes.ts +21 -10
- package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
- package/src/runtime/routes/inbound-message-handler.ts +19 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
- package/src/runtime/routes/memory-item-routes.test.ts +2 -14
- package/src/runtime/routes/memory-item-routes.ts +341 -388
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/work-items-routes.test.ts +2 -6
- package/src/schedule/scheduler.ts +8 -1
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +4 -8
- package/src/shared/provider-env-vars.ts +19 -0
- package/src/skills/catalog-cache.ts +5 -0
- package/src/skills/catalog-install.ts +15 -14
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +27 -16
- package/src/skills/skill-memory.ts +152 -77
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/tasks/task-runner.ts +3 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +7 -1
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/tool-manifest.ts +3 -3
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
- package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
- package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/context-memory-e2e.test.ts +0 -415
- package/src/__tests__/journal-context.test.ts +0 -268
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
- package/src/__tests__/memory-query-builder.test.ts +0 -59
- package/src/__tests__/memory-recall-quality.test.ts +0 -1046
- package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
- package/src/__tests__/memory-regressions.test.ts +0 -3696
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
- package/src/daemon/conversation-memory.ts +0 -207
- package/src/memory/conversation-starters-cadence.ts +0 -74
- package/src/memory/items-extractor.ts +0 -860
- package/src/memory/job-handlers/batch-extraction.ts +0 -753
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
- package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
- package/src/memory/journal-memory.ts +0 -224
- package/src/memory/query-builder.ts +0 -47
- package/src/memory/query-expansion.ts +0 -83
- package/src/memory/retriever.test.ts +0 -1592
- package/src/memory/retriever.ts +0 -1331
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -139
- package/src/memory/search/ranking.ts +0 -15
- package/src/memory/search/staleness.ts +0 -40
- package/src/memory/search/tier-classifier.ts +0 -18
- package/src/memory/search/types.ts +0 -121
- package/src/prompts/journal-context.ts +0 -154
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -562
- package/src/tools/memory/handlers.ts +0 -434
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for conversation group management.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/groups — list all groups
|
|
5
|
+
* POST /v1/groups — create a custom group
|
|
6
|
+
* PATCH /v1/groups/:groupId — update a group
|
|
7
|
+
* DELETE /v1/groups/:groupId — delete a group
|
|
8
|
+
* POST /v1/groups/reorder — reorder groups
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createGroup,
|
|
15
|
+
deleteGroup,
|
|
16
|
+
getGroup,
|
|
17
|
+
listGroups,
|
|
18
|
+
reorderGroups,
|
|
19
|
+
updateGroup,
|
|
20
|
+
} from "../../memory/group-crud.js";
|
|
21
|
+
import { httpError } from "../http-errors.js";
|
|
22
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
23
|
+
|
|
24
|
+
function serializeGroup(group: ReturnType<typeof getGroup>) {
|
|
25
|
+
if (!group) return null;
|
|
26
|
+
return {
|
|
27
|
+
id: group.id,
|
|
28
|
+
name: group.name,
|
|
29
|
+
sortPosition: group.sortPosition,
|
|
30
|
+
isSystemGroup: group.isSystemGroup,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function groupRouteDefinitions(): RouteDefinition[] {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
endpoint: "groups",
|
|
38
|
+
method: "GET",
|
|
39
|
+
policyKey: "groups",
|
|
40
|
+
summary: "List groups",
|
|
41
|
+
description: "Return all conversation groups.",
|
|
42
|
+
tags: ["groups"],
|
|
43
|
+
handler: () => {
|
|
44
|
+
const groups = listGroups();
|
|
45
|
+
return Response.json({
|
|
46
|
+
groups: groups.map(serializeGroup),
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
endpoint: "groups",
|
|
52
|
+
method: "POST",
|
|
53
|
+
policyKey: "groups",
|
|
54
|
+
summary: "Create group",
|
|
55
|
+
description:
|
|
56
|
+
"Create a new custom conversation group. Server assigns sort_position.",
|
|
57
|
+
tags: ["groups"],
|
|
58
|
+
requestBody: z.object({
|
|
59
|
+
name: z.string().describe("Group name"),
|
|
60
|
+
}),
|
|
61
|
+
handler: async ({ req }) => {
|
|
62
|
+
const body = (await req.json()) as { name?: string };
|
|
63
|
+
if (!body.name || typeof body.name !== "string") {
|
|
64
|
+
return httpError("BAD_REQUEST", "Missing or invalid name", 400);
|
|
65
|
+
}
|
|
66
|
+
const group = createGroup(body.name);
|
|
67
|
+
return Response.json(serializeGroup(group), { status: 201 });
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
endpoint: "groups/:groupId",
|
|
72
|
+
method: "PATCH",
|
|
73
|
+
policyKey: "groups",
|
|
74
|
+
summary: "Update group",
|
|
75
|
+
description: "Update a conversation group's name or sort position.",
|
|
76
|
+
tags: ["groups"],
|
|
77
|
+
requestBody: z.object({
|
|
78
|
+
name: z.string().optional(),
|
|
79
|
+
sortPosition: z.number().optional(),
|
|
80
|
+
}),
|
|
81
|
+
handler: async ({ req, params }) => {
|
|
82
|
+
const groupId = params.groupId;
|
|
83
|
+
const existing = getGroup(groupId);
|
|
84
|
+
if (!existing) {
|
|
85
|
+
return httpError("NOT_FOUND", "Group not found", 404);
|
|
86
|
+
}
|
|
87
|
+
const body = (await req.json()) as {
|
|
88
|
+
name?: string;
|
|
89
|
+
sortPosition?: number;
|
|
90
|
+
};
|
|
91
|
+
if (body.name !== undefined && typeof body.name !== "string") {
|
|
92
|
+
return httpError("BAD_REQUEST", "name must be a string", 400);
|
|
93
|
+
}
|
|
94
|
+
if (
|
|
95
|
+
body.sortPosition !== undefined &&
|
|
96
|
+
typeof body.sortPosition !== "number"
|
|
97
|
+
) {
|
|
98
|
+
return httpError("BAD_REQUEST", "sortPosition must be a number", 400);
|
|
99
|
+
}
|
|
100
|
+
// System groups allow name changes but block sortPosition/delete.
|
|
101
|
+
if (existing.isSystemGroup && body.sortPosition !== undefined) {
|
|
102
|
+
return httpError(
|
|
103
|
+
"FORBIDDEN",
|
|
104
|
+
"System group sort position cannot be changed",
|
|
105
|
+
403,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
// Custom group sort_position must be >= 3
|
|
109
|
+
if (
|
|
110
|
+
body.sortPosition !== undefined &&
|
|
111
|
+
(typeof body.sortPosition !== "number" ||
|
|
112
|
+
!isFinite(body.sortPosition) ||
|
|
113
|
+
body.sortPosition < 3)
|
|
114
|
+
) {
|
|
115
|
+
return httpError(
|
|
116
|
+
"BAD_REQUEST",
|
|
117
|
+
"Custom group sort_position must be >= 3",
|
|
118
|
+
400,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const updated = updateGroup(groupId, {
|
|
122
|
+
name: body.name,
|
|
123
|
+
sortPosition: body.sortPosition,
|
|
124
|
+
});
|
|
125
|
+
if (!updated) {
|
|
126
|
+
return httpError("NOT_FOUND", "Group not found", 404);
|
|
127
|
+
}
|
|
128
|
+
return Response.json(serializeGroup(updated));
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
endpoint: "groups/:groupId",
|
|
133
|
+
method: "DELETE",
|
|
134
|
+
policyKey: "groups",
|
|
135
|
+
summary: "Delete group",
|
|
136
|
+
description: "Delete a custom conversation group.",
|
|
137
|
+
tags: ["groups"],
|
|
138
|
+
handler: ({ params }) => {
|
|
139
|
+
const groupId = params.groupId;
|
|
140
|
+
const existing = getGroup(groupId);
|
|
141
|
+
if (!existing) {
|
|
142
|
+
return httpError("NOT_FOUND", "Group not found", 404);
|
|
143
|
+
}
|
|
144
|
+
// System groups cannot be deleted
|
|
145
|
+
if (existing.isSystemGroup) {
|
|
146
|
+
return httpError("FORBIDDEN", "System groups cannot be deleted", 403);
|
|
147
|
+
}
|
|
148
|
+
deleteGroup(groupId);
|
|
149
|
+
return new Response(null, { status: 204 });
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
endpoint: "groups/reorder",
|
|
154
|
+
method: "POST",
|
|
155
|
+
policyKey: "groups/reorder",
|
|
156
|
+
summary: "Reorder groups",
|
|
157
|
+
description: "Batch-update sort positions for conversation groups.",
|
|
158
|
+
tags: ["groups"],
|
|
159
|
+
requestBody: z.object({
|
|
160
|
+
updates: z
|
|
161
|
+
.array(
|
|
162
|
+
z.object({
|
|
163
|
+
groupId: z.string(),
|
|
164
|
+
sortPosition: z.number(),
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
.describe("Array of { groupId, sortPosition } objects"),
|
|
168
|
+
}),
|
|
169
|
+
handler: async ({ req }) => {
|
|
170
|
+
const body = (await req.json()) as {
|
|
171
|
+
updates?: Array<{
|
|
172
|
+
groupId: string;
|
|
173
|
+
sortPosition: number;
|
|
174
|
+
}>;
|
|
175
|
+
};
|
|
176
|
+
if (!Array.isArray(body.updates)) {
|
|
177
|
+
return httpError("BAD_REQUEST", "Missing updates array", 400);
|
|
178
|
+
}
|
|
179
|
+
// Validate: no system group reordering, no sort_position < 3 for custom groups
|
|
180
|
+
for (const update of body.updates) {
|
|
181
|
+
const group = getGroup(update.groupId);
|
|
182
|
+
if (!group) continue;
|
|
183
|
+
if (group.isSystemGroup) {
|
|
184
|
+
return httpError(
|
|
185
|
+
"FORBIDDEN",
|
|
186
|
+
`Cannot reorder system group: ${update.groupId}`,
|
|
187
|
+
403,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
typeof update.sortPosition !== "number" ||
|
|
192
|
+
!isFinite(update.sortPosition) ||
|
|
193
|
+
update.sortPosition < 3
|
|
194
|
+
) {
|
|
195
|
+
return httpError(
|
|
196
|
+
"BAD_REQUEST",
|
|
197
|
+
`Custom group sort_position must be >= 3 (got ${update.sortPosition} for ${update.groupId})`,
|
|
198
|
+
400,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
reorderGroups(body.updates);
|
|
203
|
+
return Response.json({ ok: true });
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
@@ -23,7 +23,10 @@ import { requireBoundGuardian } from "../auth/require-bound-guardian.js";
|
|
|
23
23
|
import type { AuthContext } from "../auth/types.js";
|
|
24
24
|
import { processGuardianDecision } from "../guardian-action-service.js";
|
|
25
25
|
import type { GuardianDecisionPrompt } from "../guardian-decision-types.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
buildDecisionActions,
|
|
28
|
+
GUARDIAN_DECISION_ACTIONS,
|
|
29
|
+
} from "../guardian-decision-types.js";
|
|
27
30
|
import { httpError } from "../http-errors.js";
|
|
28
31
|
import type { RouteDefinition } from "../http-router.js";
|
|
29
32
|
|
|
@@ -190,13 +193,15 @@ export function listGuardianDecisionPrompts(params: {
|
|
|
190
193
|
* Map a canonical guardian request to the client-facing prompt format.
|
|
191
194
|
*
|
|
192
195
|
* Generates kind-specific questionText and action sets:
|
|
193
|
-
* - `tool_approval`:
|
|
194
|
-
* - `pending_question`:
|
|
195
|
-
* - `access_request`:
|
|
196
|
-
*
|
|
196
|
+
* - `tool_approval`: temporal modes (approve_once, approve_10m, approve_conversation) + reject
|
|
197
|
+
* - `pending_question`: approve_once + reject only
|
|
198
|
+
* - `access_request`: approve_once + reject only, with text fallback instructions
|
|
199
|
+
* (request code + "open invite flow")
|
|
200
|
+
* - `tool_grant_request`: approve_once + reject only
|
|
197
201
|
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
202
|
+
* Only `tool_approval` receives temporal modes because time-scoped grants
|
|
203
|
+
* are meaningful only for tool execution. All other kinds get a simple
|
|
204
|
+
* approve_once/reject pair.
|
|
200
205
|
*/
|
|
201
206
|
function mapCanonicalRequestToPrompt(
|
|
202
207
|
req: CanonicalGuardianRequest,
|
|
@@ -204,9 +209,15 @@ function mapCanonicalRequestToPrompt(
|
|
|
204
209
|
): GuardianDecisionPrompt {
|
|
205
210
|
const questionText = buildKindAwareQuestionText(req);
|
|
206
211
|
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
const actions =
|
|
212
|
+
// Only tool_approval gets temporal modes (approve_10m, approve_conversation);
|
|
213
|
+
// all other kinds get a simple approve_once + reject pair.
|
|
214
|
+
const actions =
|
|
215
|
+
req.kind === "tool_approval"
|
|
216
|
+
? buildDecisionActions({ forGuardianOnBehalf: true })
|
|
217
|
+
: [
|
|
218
|
+
GUARDIAN_DECISION_ACTIONS.approve_once,
|
|
219
|
+
GUARDIAN_DECISION_ACTIONS.reject,
|
|
220
|
+
];
|
|
210
221
|
|
|
211
222
|
const expiresAt = req.expiresAt
|
|
212
223
|
? new Date(req.expiresAt).getTime()
|
|
@@ -19,7 +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
|
+
import { isLoopbackAddress, isPrivateAddress } from "../middleware/auth.js";
|
|
23
23
|
|
|
24
24
|
/** Bun server shape needed for requestIP -- avoids importing the full Bun type. */
|
|
25
25
|
type ServerWithRequestIP = {
|
|
@@ -87,30 +87,34 @@ export async function handleGuardianBootstrap(
|
|
|
87
87
|
req: Request,
|
|
88
88
|
server: ServerWithRequestIP,
|
|
89
89
|
): Promise<Response> {
|
|
90
|
-
//
|
|
90
|
+
// In non-containerized (bare-metal) mode, restrict to loopback only —
|
|
91
|
+
// the runtime binds to localhost and LAN peers should not be able to
|
|
92
|
+
// bootstrap even if they somehow reach the endpoint.
|
|
93
|
+
// In containerized (Docker) mode, accept any private-network peer because
|
|
94
|
+
// the gateway connects over the Docker bridge (e.g. 172.17.0.1) and the
|
|
95
|
+
// GUARDIAN_BOOTSTRAP_SECRET enforced at the gateway layer provides the
|
|
96
|
+
// real authentication.
|
|
91
97
|
const peerIp = server.requestIP(req)?.address;
|
|
92
|
-
|
|
98
|
+
const containerized = getIsContainerized();
|
|
99
|
+
const peerAllowed = containerized
|
|
100
|
+
? isPrivateAddress(peerIp ?? "")
|
|
101
|
+
: isLoopbackAddress(peerIp ?? "");
|
|
102
|
+
if (!peerAllowed && !isHttpAuthDisabled()) {
|
|
93
103
|
return httpError("FORBIDDEN", "Bootstrap endpoint is local-only", 403);
|
|
94
104
|
}
|
|
95
105
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
106
|
+
// In non-containerized mode, any x-forwarded-for header means the request
|
|
107
|
+
// came through the gateway from a non-loopback client. Legitimate bare-metal
|
|
108
|
+
// bootstrap clients connect from localhost; the gateway does not inject
|
|
109
|
+
// x-forwarded-for for loopback peers (see gateway/src/index.ts). Reject
|
|
110
|
+
// forwarded requests to prevent LAN-adjacent clients from bootstrapping.
|
|
100
111
|
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
// not meaningful for local-only enforcement in this topology.
|
|
112
|
+
// In containerized mode, skip this check: the peer IP was already validated
|
|
113
|
+
// above (Docker bridge network = private), and the x-forwarded-for header
|
|
114
|
+
// reflects the original external client which is not meaningful for
|
|
115
|
+
// local-only enforcement in this topology.
|
|
106
116
|
const forwarded = req.headers.get("x-forwarded-for");
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
forwardedIp &&
|
|
110
|
-
!isPrivateAddress(forwardedIp) &&
|
|
111
|
-
!isHttpAuthDisabled() &&
|
|
112
|
-
!getIsContainerized()
|
|
113
|
-
) {
|
|
117
|
+
if (forwarded && !isHttpAuthDisabled() && !getIsContainerized()) {
|
|
114
118
|
return httpError("FORBIDDEN", "Bootstrap endpoint is local-only", 403);
|
|
115
119
|
}
|
|
116
120
|
|
|
@@ -44,6 +44,7 @@ import { processChannelMessageInBackground } from "./inbound-stages/background-d
|
|
|
44
44
|
import { handleBootstrapIntercept } from "./inbound-stages/bootstrap-intercept.js";
|
|
45
45
|
import { handleEditIntercept } from "./inbound-stages/edit-intercept.js";
|
|
46
46
|
import { handleEscalationIntercept } from "./inbound-stages/escalation-intercept.js";
|
|
47
|
+
import { handleGuardianActivationIntercept } from "./inbound-stages/guardian-activation-intercept.js";
|
|
47
48
|
import { handleGuardianReplyIntercept } from "./inbound-stages/guardian-reply-intercept.js";
|
|
48
49
|
import { runSecretIngressCheck } from "./inbound-stages/secret-ingress-check.js";
|
|
49
50
|
import { tryTranscribeAudioAttachments } from "./inbound-stages/transcribe-audio.js";
|
|
@@ -199,6 +200,24 @@ export async function handleChannelInbound(
|
|
|
199
200
|
// ACL deny path rather than bypassing it.
|
|
200
201
|
const hasSenderIdentityClaim = rawSenderId !== undefined;
|
|
201
202
|
|
|
203
|
+
// ── Guardian channel activation ──
|
|
204
|
+
// When a bare /start arrives on a channel with no guardian, auto-initiate
|
|
205
|
+
// guardian verification so the first user can claim the channel.
|
|
206
|
+
const guardianActivationResponse = await handleGuardianActivationIntercept({
|
|
207
|
+
sourceChannel,
|
|
208
|
+
conversationExternalId,
|
|
209
|
+
rawSenderId,
|
|
210
|
+
canonicalSenderId,
|
|
211
|
+
actorDisplayName: body.actorDisplayName,
|
|
212
|
+
actorUsername: body.actorUsername,
|
|
213
|
+
sourceMetadata: body.sourceMetadata,
|
|
214
|
+
replyCallbackUrl: body.replyCallbackUrl,
|
|
215
|
+
mintBearerToken,
|
|
216
|
+
assistantId,
|
|
217
|
+
externalMessageId,
|
|
218
|
+
});
|
|
219
|
+
if (guardianActivationResponse) return guardianActivationResponse;
|
|
220
|
+
|
|
202
221
|
// ── Ingress ACL enforcement ──
|
|
203
222
|
const aclResult = await enforceIngressAcl({
|
|
204
223
|
canonicalSenderId,
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Mocks — must be set up before importing the module under test
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
let mockGuardian: { contact: unknown; channel: unknown } | null = null;
|
|
8
|
+
let mockActiveSession: Record<string, unknown> | null = null;
|
|
9
|
+
let mockSessionResult = {
|
|
10
|
+
sessionId: "sess-1",
|
|
11
|
+
secret: "123456",
|
|
12
|
+
challengeHash: "hash-1",
|
|
13
|
+
expiresAt: Date.now() + 600_000,
|
|
14
|
+
ttlSeconds: 600,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Track calls manually to avoid TypeScript issues with mock() generics
|
|
18
|
+
let createOutboundSessionCalls: unknown[] = [];
|
|
19
|
+
let deliverChannelReplyCalls: unknown[][] = [];
|
|
20
|
+
let emitNotificationSignalCalls: unknown[] = [];
|
|
21
|
+
let messageIdCounter = 0;
|
|
22
|
+
|
|
23
|
+
mock.module("../../../contacts/contact-store.js", () => ({
|
|
24
|
+
findGuardianForChannel: () => mockGuardian,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module("../../channel-verification-service.js", () => ({
|
|
28
|
+
createOutboundSession: (params: unknown) => {
|
|
29
|
+
createOutboundSessionCalls.push(params);
|
|
30
|
+
return mockSessionResult;
|
|
31
|
+
},
|
|
32
|
+
findActiveSession: () => mockActiveSession,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
mock.module("../../gateway-client.js", () => ({
|
|
36
|
+
deliverChannelReply: (url: unknown, payload: unknown, token: unknown) => {
|
|
37
|
+
deliverChannelReplyCalls.push([url, payload, token]);
|
|
38
|
+
return Promise.resolve({ ok: true });
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
mock.module("../../../notifications/emit-signal.js", () => ({
|
|
43
|
+
emitNotificationSignal: (params: unknown) => {
|
|
44
|
+
emitNotificationSignalCalls.push(params);
|
|
45
|
+
return Promise.resolve({
|
|
46
|
+
signalId: "sig-1",
|
|
47
|
+
deduplicated: false,
|
|
48
|
+
dispatched: true,
|
|
49
|
+
reason: "ok",
|
|
50
|
+
deliveryResults: [],
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
mock.module("../../../util/logger.js", () => ({
|
|
56
|
+
getLogger: () => ({
|
|
57
|
+
debug: () => {},
|
|
58
|
+
info: () => {},
|
|
59
|
+
warn: () => {},
|
|
60
|
+
error: () => {},
|
|
61
|
+
}),
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
// Import after mocks are installed
|
|
65
|
+
const { handleGuardianActivationIntercept } =
|
|
66
|
+
await import("./guardian-activation-intercept.js");
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Helpers
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
function makeParams(
|
|
73
|
+
overrides: Partial<
|
|
74
|
+
Parameters<typeof handleGuardianActivationIntercept>[0]
|
|
75
|
+
> = {},
|
|
76
|
+
) {
|
|
77
|
+
messageIdCounter++;
|
|
78
|
+
return {
|
|
79
|
+
sourceChannel: "telegram" as const,
|
|
80
|
+
conversationExternalId: "chat-123",
|
|
81
|
+
rawSenderId: "user-42",
|
|
82
|
+
canonicalSenderId: "user-42",
|
|
83
|
+
actorDisplayName: "Alice",
|
|
84
|
+
actorUsername: "alice",
|
|
85
|
+
sourceMetadata: { commandIntent: { type: "start" } },
|
|
86
|
+
replyCallbackUrl: "https://gateway/reply",
|
|
87
|
+
mintBearerToken: () => "token-123",
|
|
88
|
+
assistantId: "self",
|
|
89
|
+
externalMessageId: `msg-${messageIdCounter}`,
|
|
90
|
+
...overrides,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Tests
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
describe("handleGuardianActivationIntercept", () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
mockGuardian = null;
|
|
101
|
+
mockActiveSession = null;
|
|
102
|
+
mockSessionResult = {
|
|
103
|
+
sessionId: "sess-1",
|
|
104
|
+
secret: "123456",
|
|
105
|
+
challengeHash: "hash-1",
|
|
106
|
+
expiresAt: Date.now() + 600_000,
|
|
107
|
+
ttlSeconds: 600,
|
|
108
|
+
};
|
|
109
|
+
createOutboundSessionCalls = [];
|
|
110
|
+
deliverChannelReplyCalls = [];
|
|
111
|
+
emitNotificationSignalCalls = [];
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
createOutboundSessionCalls = [];
|
|
116
|
+
deliverChannelReplyCalls = [];
|
|
117
|
+
emitNotificationSignalCalls = [];
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("bare /start with no guardian creates session and returns early", async () => {
|
|
121
|
+
const result = await handleGuardianActivationIntercept(makeParams());
|
|
122
|
+
|
|
123
|
+
expect(result).not.toBeNull();
|
|
124
|
+
const body = await result!.json();
|
|
125
|
+
expect(body).toEqual({ accepted: true, guardianActivation: true });
|
|
126
|
+
|
|
127
|
+
// Verify createOutboundSession was called with correct params
|
|
128
|
+
expect(createOutboundSessionCalls).toHaveLength(1);
|
|
129
|
+
expect(createOutboundSessionCalls[0]).toEqual({
|
|
130
|
+
channel: "telegram",
|
|
131
|
+
expectedExternalUserId: "user-42",
|
|
132
|
+
expectedChatId: "chat-123",
|
|
133
|
+
identityBindingStatus: "bound",
|
|
134
|
+
destinationAddress: "chat-123",
|
|
135
|
+
verificationPurpose: "guardian",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Verify deliverChannelReply was called with the welcome/verify message
|
|
139
|
+
expect(deliverChannelReplyCalls).toHaveLength(1);
|
|
140
|
+
expect(deliverChannelReplyCalls[0][0]).toBe("https://gateway/reply");
|
|
141
|
+
expect(deliverChannelReplyCalls[0][1]).toEqual({
|
|
142
|
+
chatId: "chat-123",
|
|
143
|
+
text: "Welcome! To verify your identity as guardian, check your assistant app for a verification code and enter it here.",
|
|
144
|
+
assistantId: "self",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Verify emitNotificationSignal was called with guardian.channel_activation
|
|
148
|
+
expect(emitNotificationSignalCalls).toHaveLength(1);
|
|
149
|
+
const signalArgs = emitNotificationSignalCalls[0] as Record<string, any>;
|
|
150
|
+
expect(signalArgs.sourceEventName).toBe("guardian.channel_activation");
|
|
151
|
+
expect(signalArgs.contextPayload.verificationCode).toBe("123456");
|
|
152
|
+
expect(signalArgs.contextPayload.sourceChannel).toBe("telegram");
|
|
153
|
+
expect(signalArgs.contextPayload.actorExternalId).toBe("user-42");
|
|
154
|
+
expect(signalArgs.contextPayload.sessionId).toBe("sess-1");
|
|
155
|
+
expect(signalArgs.dedupeKey).toBe("guardian-activation:sess-1");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("bare /start with existing guardian returns null", async () => {
|
|
159
|
+
mockGuardian = {
|
|
160
|
+
contact: { id: "contact-1", role: "guardian" },
|
|
161
|
+
channel: { id: "ch-1", type: "telegram" },
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const result = await handleGuardianActivationIntercept(makeParams());
|
|
165
|
+
expect(result).toBeNull();
|
|
166
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("/start with payload returns null", async () => {
|
|
170
|
+
const result = await handleGuardianActivationIntercept(
|
|
171
|
+
makeParams({
|
|
172
|
+
sourceMetadata: {
|
|
173
|
+
commandIntent: { type: "start", payload: "gv_token" },
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
expect(result).toBeNull();
|
|
178
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("non-/start message returns null", async () => {
|
|
182
|
+
const result = await handleGuardianActivationIntercept(
|
|
183
|
+
makeParams({
|
|
184
|
+
sourceMetadata: { commandIntent: { type: "other" } },
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
expect(result).toBeNull();
|
|
188
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("no commandIntent returns null", async () => {
|
|
192
|
+
const result = await handleGuardianActivationIntercept(
|
|
193
|
+
makeParams({ sourceMetadata: {} }),
|
|
194
|
+
);
|
|
195
|
+
expect(result).toBeNull();
|
|
196
|
+
|
|
197
|
+
const result2 = await handleGuardianActivationIntercept(
|
|
198
|
+
makeParams({ sourceMetadata: undefined }),
|
|
199
|
+
);
|
|
200
|
+
expect(result2).toBeNull();
|
|
201
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("non-telegram channel returns null", async () => {
|
|
205
|
+
const result = await handleGuardianActivationIntercept(
|
|
206
|
+
makeParams({ sourceChannel: "slack" as any }),
|
|
207
|
+
);
|
|
208
|
+
expect(result).toBeNull();
|
|
209
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("missing sender ID returns null", async () => {
|
|
213
|
+
const result = await handleGuardianActivationIntercept(
|
|
214
|
+
makeParams({ rawSenderId: undefined }),
|
|
215
|
+
);
|
|
216
|
+
expect(result).toBeNull();
|
|
217
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("existing active session from same sender sends 'already in progress' reply", async () => {
|
|
221
|
+
mockActiveSession = {
|
|
222
|
+
id: "existing-sess",
|
|
223
|
+
channel: "telegram",
|
|
224
|
+
status: "awaiting_response",
|
|
225
|
+
expectedExternalUserId: "user-42",
|
|
226
|
+
expectedChatId: "chat-123",
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const result = await handleGuardianActivationIntercept(makeParams());
|
|
230
|
+
|
|
231
|
+
expect(result).not.toBeNull();
|
|
232
|
+
const body = await result!.json();
|
|
233
|
+
expect(body).toEqual({ accepted: true, guardianActivationPending: true });
|
|
234
|
+
|
|
235
|
+
// createOutboundSession should NOT be called
|
|
236
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
237
|
+
|
|
238
|
+
// deliverChannelReply should be called with the "already in progress" message
|
|
239
|
+
expect(deliverChannelReplyCalls).toHaveLength(1);
|
|
240
|
+
expect(deliverChannelReplyCalls[0][1]).toEqual({
|
|
241
|
+
chatId: "chat-123",
|
|
242
|
+
text: "A verification is already in progress. Check your assistant app for the code and enter it here.",
|
|
243
|
+
assistantId: "self",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// emitNotificationSignal should NOT be called
|
|
247
|
+
expect(emitNotificationSignalCalls).toHaveLength(0);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("existing active session from different sender allows superseding", async () => {
|
|
251
|
+
mockActiveSession = {
|
|
252
|
+
id: "existing-sess",
|
|
253
|
+
channel: "telegram",
|
|
254
|
+
status: "awaiting_response",
|
|
255
|
+
expectedExternalUserId: "user-OTHER",
|
|
256
|
+
expectedChatId: "chat-OTHER",
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const result = await handleGuardianActivationIntercept(makeParams());
|
|
260
|
+
|
|
261
|
+
// Should proceed and create a new session (superseding the stale one)
|
|
262
|
+
expect(result).not.toBeNull();
|
|
263
|
+
const body = await result!.json();
|
|
264
|
+
expect(body).toEqual({ accepted: true, guardianActivation: true });
|
|
265
|
+
expect(createOutboundSessionCalls).toHaveLength(1);
|
|
266
|
+
expect(emitNotificationSignalCalls).toHaveLength(1);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("duplicate webhook retry is silently deduped", async () => {
|
|
270
|
+
const params = makeParams({ externalMessageId: "dedup-test-msg" });
|
|
271
|
+
|
|
272
|
+
// First call should process normally
|
|
273
|
+
const result1 = await handleGuardianActivationIntercept(params);
|
|
274
|
+
expect(result1).not.toBeNull();
|
|
275
|
+
const body1 = await result1!.json();
|
|
276
|
+
expect(body1).toEqual({ accepted: true, guardianActivation: true });
|
|
277
|
+
expect(createOutboundSessionCalls).toHaveLength(1);
|
|
278
|
+
expect(deliverChannelReplyCalls).toHaveLength(1);
|
|
279
|
+
expect(emitNotificationSignalCalls).toHaveLength(1);
|
|
280
|
+
|
|
281
|
+
// Second call with same externalMessageId should be deduped
|
|
282
|
+
const result2 = await handleGuardianActivationIntercept(params);
|
|
283
|
+
expect(result2).not.toBeNull();
|
|
284
|
+
const body2 = await result2!.json();
|
|
285
|
+
expect(body2).toEqual({ accepted: true, guardianActivation: true });
|
|
286
|
+
|
|
287
|
+
// No additional session/reply/signal calls
|
|
288
|
+
expect(createOutboundSessionCalls).toHaveLength(1);
|
|
289
|
+
expect(deliverChannelReplyCalls).toHaveLength(1);
|
|
290
|
+
expect(emitNotificationSignalCalls).toHaveLength(1);
|
|
291
|
+
});
|
|
292
|
+
});
|