@vellumai/assistant 0.8.12 → 0.9.0-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/openapi.yaml +6935 -5545
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +125 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +75 -0
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +1 -1
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +26 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +122 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +48 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -50,7 +50,11 @@ describe("schedule_syntax column migration", () => {
|
|
|
50
50
|
reuse_conversation INTEGER NOT NULL DEFAULT 0,
|
|
51
51
|
script TEXT,
|
|
52
52
|
wake_conversation_id TEXT,
|
|
53
|
+
workflow_name TEXT,
|
|
54
|
+
workflow_args_json TEXT,
|
|
53
55
|
timeout_ms INTEGER,
|
|
56
|
+
inference_profile TEXT,
|
|
57
|
+
capabilities_json TEXT,
|
|
54
58
|
description TEXT NOT NULL DEFAULT '',
|
|
55
59
|
created_at INTEGER NOT NULL,
|
|
56
60
|
updated_at INTEGER NOT NULL
|
|
@@ -138,6 +142,16 @@ describe("schedule_syntax column migration", () => {
|
|
|
138
142
|
} catch {
|
|
139
143
|
/* already exists */
|
|
140
144
|
}
|
|
145
|
+
try {
|
|
146
|
+
raw.exec(`ALTER TABLE cron_jobs ADD COLUMN workflow_name TEXT`);
|
|
147
|
+
} catch {
|
|
148
|
+
/* already exists */
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
raw.exec(`ALTER TABLE cron_jobs ADD COLUMN workflow_args_json TEXT`);
|
|
152
|
+
} catch {
|
|
153
|
+
/* already exists */
|
|
154
|
+
}
|
|
141
155
|
migrateScheduleSourceConversation(db);
|
|
142
156
|
migrateScheduleDescription(db);
|
|
143
157
|
|
|
@@ -203,6 +217,16 @@ describe("schedule_syntax column migration", () => {
|
|
|
203
217
|
} catch {
|
|
204
218
|
/* ok */
|
|
205
219
|
}
|
|
220
|
+
try {
|
|
221
|
+
raw.exec(`ALTER TABLE cron_jobs ADD COLUMN workflow_name TEXT`);
|
|
222
|
+
} catch {
|
|
223
|
+
/* ok */
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
raw.exec(`ALTER TABLE cron_jobs ADD COLUMN workflow_args_json TEXT`);
|
|
227
|
+
} catch {
|
|
228
|
+
/* ok */
|
|
229
|
+
}
|
|
206
230
|
migrateScheduleSourceConversation(db);
|
|
207
231
|
migrateScheduleSourceConversation(db);
|
|
208
232
|
migrateScheduleDescription(db);
|
|
@@ -125,6 +125,56 @@ describe("ui_show dynamic_page app substitute guard", () => {
|
|
|
125
125
|
expect(proxied).toBe(false);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
test("rejects dynamic_page with empty data and does not proxy", async () => {
|
|
129
|
+
let proxied = false;
|
|
130
|
+
|
|
131
|
+
const result = await uiShowTool.execute(
|
|
132
|
+
{
|
|
133
|
+
surface_type: "dynamic_page",
|
|
134
|
+
title: "SKILL.md — elevenlabs-tts",
|
|
135
|
+
activity: "Reopening SKILL.md with rendered content",
|
|
136
|
+
data: {},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
conversationId: "conversation-123",
|
|
140
|
+
workingDir: "/tmp",
|
|
141
|
+
trustClass: "guardian",
|
|
142
|
+
proxyToolResolver: async () => {
|
|
143
|
+
proxied = true;
|
|
144
|
+
return { content: "proxied", isError: false };
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(result.isError).toBe(true);
|
|
150
|
+
expect(result.content).toContain("data.html");
|
|
151
|
+
expect(proxied).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("rejects dynamic_page with whitespace-only html and does not proxy", async () => {
|
|
155
|
+
let proxied = false;
|
|
156
|
+
|
|
157
|
+
const result = await uiShowTool.execute(
|
|
158
|
+
{
|
|
159
|
+
surface_type: "dynamic_page",
|
|
160
|
+
title: "Blank",
|
|
161
|
+
data: { html: " \n " },
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
conversationId: "conversation-123",
|
|
165
|
+
workingDir: "/tmp",
|
|
166
|
+
trustClass: "guardian",
|
|
167
|
+
proxyToolResolver: async () => {
|
|
168
|
+
proxied = true;
|
|
169
|
+
return { content: "proxied", isError: false };
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(result.isError).toBe(true);
|
|
175
|
+
expect(proxied).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
128
178
|
test("allows transient non-app dynamic_page surfaces", async () => {
|
|
129
179
|
let proxied = false;
|
|
130
180
|
|
|
@@ -156,6 +206,81 @@ describe("ui_show dynamic_page app substitute guard", () => {
|
|
|
156
206
|
});
|
|
157
207
|
});
|
|
158
208
|
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// task_progress ui_show appends the update hint to its return value
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
describe("ui_show task_progress update hint", () => {
|
|
214
|
+
const ctx = {
|
|
215
|
+
conversationId: "conversation-123",
|
|
216
|
+
workingDir: "/tmp",
|
|
217
|
+
trustClass: "guardian" as const,
|
|
218
|
+
proxyToolResolver: async () => ({
|
|
219
|
+
content: "Surface displayed (surface_id: surf-1).",
|
|
220
|
+
isError: false,
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
test("appends the ui_update hint after a task_progress card is shown", async () => {
|
|
225
|
+
const result = await uiShowTool.execute(
|
|
226
|
+
{
|
|
227
|
+
surface_type: "card",
|
|
228
|
+
template: "task_progress",
|
|
229
|
+
templateData: { status: "in_progress", steps: [] },
|
|
230
|
+
},
|
|
231
|
+
ctx,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(result.isError).toBe(false);
|
|
235
|
+
expect(result.content).toContain("Surface displayed (surface_id: surf-1).");
|
|
236
|
+
expect(result.content).toContain("call ui_update with this surface_id");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("recognizes task_progress nested under data", async () => {
|
|
240
|
+
const result = await uiShowTool.execute(
|
|
241
|
+
{
|
|
242
|
+
surface_type: "card",
|
|
243
|
+
data: {
|
|
244
|
+
template: "task_progress",
|
|
245
|
+
templateData: { status: "in_progress" },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
ctx,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(result.content).toContain("call ui_update with this surface_id");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("does not append the hint for a non-task_progress card", async () => {
|
|
255
|
+
const result = await uiShowTool.execute(
|
|
256
|
+
{ surface_type: "card", data: { title: "Plain", body: "hi" } },
|
|
257
|
+
ctx,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
expect(result.content).toBe("Surface displayed (surface_id: surf-1).");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("does not append the hint when the surface call errors", async () => {
|
|
264
|
+
const result = await uiShowTool.execute(
|
|
265
|
+
{
|
|
266
|
+
surface_type: "card",
|
|
267
|
+
template: "task_progress",
|
|
268
|
+
templateData: { status: "in_progress" },
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
...ctx,
|
|
272
|
+
proxyToolResolver: async () => ({
|
|
273
|
+
content: "blocked on this channel",
|
|
274
|
+
isError: true,
|
|
275
|
+
}),
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
expect(result.isError).toBe(true);
|
|
280
|
+
expect(result.content).toBe("blocked on this channel");
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
159
284
|
// ---------------------------------------------------------------------------
|
|
160
285
|
// UiSurfaceShowDynamicPage structure
|
|
161
286
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the empty-state greeting cache
|
|
3
|
+
* (runtime/routes/empty-state-greeting-cache.ts).
|
|
4
|
+
*
|
|
5
|
+
* Validates TTL round-tripping, expiry, and the TTL=0 "always regenerate"
|
|
6
|
+
* behavior that disables caching entirely.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Mocks — defined before importing the module under test
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
const checkpointStore = new Map<string, string>();
|
|
16
|
+
|
|
17
|
+
mock.module("../memory/checkpoints.js", () => ({
|
|
18
|
+
getMemoryCheckpoint: (key: string) => checkpointStore.get(key) ?? null,
|
|
19
|
+
setMemoryCheckpoint: (key: string, value: string) => {
|
|
20
|
+
checkpointStore.set(key, value);
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
let cacheTtlMs = 4 * 60 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
mock.module("../config/loader.js", () => ({
|
|
27
|
+
getConfig: () => ({ ui: { emptyStateGreetingCacheTtlMs: cacheTtlMs } }),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Imports (after mocks)
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
import {
|
|
35
|
+
getCachedEmptyStateGreeting,
|
|
36
|
+
setCachedEmptyStateGreeting,
|
|
37
|
+
} from "../runtime/routes/empty-state-greeting-cache.js";
|
|
38
|
+
|
|
39
|
+
const TIMESTAMP_KEY = "empty_state:greeting:cached_at";
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
cacheTtlMs = 4 * 60 * 60 * 1000;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
checkpointStore.clear();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("empty-state greeting cache", () => {
|
|
50
|
+
test("returns null when the cache is empty", () => {
|
|
51
|
+
expect(getCachedEmptyStateGreeting()).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("round-trips set then get within the TTL", () => {
|
|
55
|
+
setCachedEmptyStateGreeting("hey there");
|
|
56
|
+
expect(getCachedEmptyStateGreeting()).toBe("hey there");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("returns null once the TTL is exceeded", () => {
|
|
60
|
+
setCachedEmptyStateGreeting("stale");
|
|
61
|
+
checkpointStore.set(
|
|
62
|
+
TIMESTAMP_KEY,
|
|
63
|
+
String(Date.now() - (4 * 60 + 1) * 60 * 1000),
|
|
64
|
+
);
|
|
65
|
+
expect(getCachedEmptyStateGreeting()).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("returns the cached value just within the TTL", () => {
|
|
69
|
+
setCachedEmptyStateGreeting("fresh enough");
|
|
70
|
+
checkpointStore.set(
|
|
71
|
+
TIMESTAMP_KEY,
|
|
72
|
+
String(Date.now() - (3 * 60 + 59) * 60 * 1000),
|
|
73
|
+
);
|
|
74
|
+
expect(getCachedEmptyStateGreeting()).toBe("fresh enough");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("TTL of 0 disables caching: writes are skipped and reads miss", () => {
|
|
78
|
+
cacheTtlMs = 0;
|
|
79
|
+
setCachedEmptyStateGreeting("should not persist");
|
|
80
|
+
expect(checkpointStore.size).toBe(0);
|
|
81
|
+
expect(getCachedEmptyStateGreeting()).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("TTL of 0 ignores a value cached while caching was enabled", () => {
|
|
85
|
+
setCachedEmptyStateGreeting("cached while on");
|
|
86
|
+
cacheTtlMs = 0;
|
|
87
|
+
expect(getCachedEmptyStateGreeting()).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("returns null when the timestamp checkpoint is missing", () => {
|
|
91
|
+
checkpointStore.set("empty_state:greeting:text", "orphaned");
|
|
92
|
+
expect(getCachedEmptyStateGreeting()).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
|
2
2
|
import { createServer, type Server, type Socket } from "node:net";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { afterEach,beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// Isolated temp directory for the IPC socket
|
|
@@ -48,12 +48,23 @@ mock.module("../runtime/sync/sync-publisher.js", () => ({
|
|
|
48
48
|
},
|
|
49
49
|
}));
|
|
50
50
|
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Track calls to syncFlagGatedTools so we can assert the listener registers
|
|
53
|
+
// newly-enabled flag-gated tools after a runtime refresh (not just the cache).
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
let syncToolsCallCount = 0;
|
|
56
|
+
|
|
57
|
+
mock.module("../tools/registry.js", () => ({
|
|
58
|
+
syncFlagGatedTools: async () => {
|
|
59
|
+
syncToolsCallCount++;
|
|
60
|
+
},
|
|
61
|
+
}));
|
|
62
|
+
|
|
51
63
|
// ---------------------------------------------------------------------------
|
|
52
64
|
// Dynamic imports (after mock.module)
|
|
53
65
|
// ---------------------------------------------------------------------------
|
|
54
|
-
const { startGatewayFlagListener, stopGatewayFlagListener } =
|
|
55
|
-
"../ipc/gateway-flag-listener.js"
|
|
56
|
-
);
|
|
66
|
+
const { startGatewayFlagListener, stopGatewayFlagListener } =
|
|
67
|
+
await import("../ipc/gateway-flag-listener.js");
|
|
57
68
|
|
|
58
69
|
// ---------------------------------------------------------------------------
|
|
59
70
|
// Helpers
|
|
@@ -104,6 +115,7 @@ describe("gateway-flag-listener", () => {
|
|
|
104
115
|
beforeEach(() => {
|
|
105
116
|
mkdirSync(testRoot, { recursive: true });
|
|
106
117
|
refreshCallCount = 0;
|
|
118
|
+
syncToolsCallCount = 0;
|
|
107
119
|
publishedTagSets = [];
|
|
108
120
|
testServer = createTestServer();
|
|
109
121
|
});
|
|
@@ -123,7 +135,7 @@ describe("gateway-flag-listener", () => {
|
|
|
123
135
|
}
|
|
124
136
|
});
|
|
125
137
|
|
|
126
|
-
test("refreshes flag cache on connect and on feature_flags_changed event", async () => {
|
|
138
|
+
test("refreshes flag cache AND syncs gated tools on connect and on feature_flags_changed event", async () => {
|
|
127
139
|
await new Promise<void>((resolve) => {
|
|
128
140
|
testServer.server.listen(socketPath, resolve);
|
|
129
141
|
});
|
|
@@ -133,11 +145,17 @@ describe("gateway-flag-listener", () => {
|
|
|
133
145
|
await new Promise((r) => setTimeout(r, 100));
|
|
134
146
|
|
|
135
147
|
expect(refreshCallCount).toBe(1);
|
|
148
|
+
// Connect must also sync gated tools, so a flag flipped while disconnected
|
|
149
|
+
// registers its tools without waiting for a restart.
|
|
150
|
+
expect(syncToolsCallCount).toBe(1);
|
|
136
151
|
|
|
137
152
|
testServer.emit("feature_flags_changed");
|
|
138
153
|
await new Promise((r) => setTimeout(r, 100));
|
|
139
154
|
|
|
140
155
|
expect(refreshCallCount).toBe(2);
|
|
156
|
+
// The runtime flag change must register newly-enabled tools too — not just
|
|
157
|
+
// refresh the cache (the bug: routes pass the gate but tools stay absent).
|
|
158
|
+
expect(syncToolsCallCount).toBe(2);
|
|
141
159
|
});
|
|
142
160
|
|
|
143
161
|
test("broadcasts feature-flags sync_changed when flags change", async () => {
|
|
@@ -207,8 +225,7 @@ describe("gateway-flag-listener", () => {
|
|
|
207
225
|
const countAfterReconnect = refreshCallCount;
|
|
208
226
|
expect(countAfterReconnect).toBeGreaterThan(0);
|
|
209
227
|
|
|
210
|
-
const payload =
|
|
211
|
-
JSON.stringify({ event: "feature_flags_changed" }) + "\n";
|
|
228
|
+
const payload = JSON.stringify({ event: "feature_flags_changed" }) + "\n";
|
|
212
229
|
secondClient!.write(payload);
|
|
213
230
|
await new Promise((r) => setTimeout(r, 200));
|
|
214
231
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
mock.module("../util/logger.js", () => ({
|
|
4
4
|
getLogger: () =>
|
|
@@ -18,27 +18,11 @@ mock.module("../runtime/gateway-client.js", () => ({
|
|
|
18
18
|
},
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
createPendingQuestion,
|
|
24
|
-
getPendingQuestion,
|
|
25
|
-
} from "../calls/call-store.js";
|
|
26
|
-
import {
|
|
27
|
-
startGuardianActionSweep,
|
|
28
|
-
stopGuardianActionSweep,
|
|
29
|
-
sweepExpiredGuardianActions,
|
|
30
|
-
} from "../calls/guardian-action-sweep.js";
|
|
21
|
+
import type { ExpiryDeliveryInfo } from "../calls/guardian-action-sweep.js";
|
|
22
|
+
import { sendGuardianExpiryNotices } from "../calls/guardian-action-sweep.js";
|
|
31
23
|
import { getDb } from "../memory/db-connection.js";
|
|
32
24
|
import { initializeDb } from "../memory/db-init.js";
|
|
33
|
-
import {
|
|
34
|
-
createGuardianActionDelivery,
|
|
35
|
-
createGuardianActionRequest,
|
|
36
|
-
getDeliveriesByRequestId,
|
|
37
|
-
getGuardianActionRequest,
|
|
38
|
-
updateDeliveryStatus,
|
|
39
|
-
} from "../memory/guardian-action-store.js";
|
|
40
|
-
import { conversations } from "../memory/schema.js";
|
|
41
|
-
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
25
|
+
import { conversations, messages } from "../memory/schema.js";
|
|
42
26
|
|
|
43
27
|
initializeDb();
|
|
44
28
|
|
|
@@ -55,218 +39,71 @@ function ensureConversation(id: string): void {
|
|
|
55
39
|
.run();
|
|
56
40
|
}
|
|
57
41
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
42
|
+
function makeDelivery(
|
|
43
|
+
overrides: Partial<ExpiryDeliveryInfo> = {},
|
|
44
|
+
): ExpiryDeliveryInfo {
|
|
45
|
+
return {
|
|
46
|
+
id: "delivery-1",
|
|
47
|
+
status: "sent",
|
|
48
|
+
destinationChannel: "telegram",
|
|
49
|
+
destinationConversationId: null,
|
|
50
|
+
destinationChatId: "chat-123",
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
68
53
|
}
|
|
69
54
|
|
|
70
|
-
describe("
|
|
55
|
+
describe("sendGuardianExpiryNotices", () => {
|
|
71
56
|
beforeEach(() => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
stopGuardianActionSweep();
|
|
77
|
-
resetDbForTesting();
|
|
57
|
+
const db = getDb();
|
|
58
|
+
db.run("DELETE FROM messages");
|
|
59
|
+
db.run("DELETE FROM conversations");
|
|
60
|
+
deliveredMessages.length = 0;
|
|
78
61
|
});
|
|
79
62
|
|
|
80
|
-
test("
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const session = createCallSession({
|
|
85
|
-
conversationId: convId,
|
|
86
|
-
provider: "twilio",
|
|
87
|
-
fromNumber: "+15550001111",
|
|
88
|
-
toNumber: "+15550002222",
|
|
89
|
-
});
|
|
90
|
-
const pq = createPendingQuestion(session.id, "What is the code?");
|
|
91
|
-
|
|
92
|
-
// Create request that has already expired
|
|
93
|
-
const request = createGuardianActionRequest({
|
|
94
|
-
kind: "ask_guardian",
|
|
95
|
-
sourceChannel: "phone",
|
|
96
|
-
sourceConversationId: convId,
|
|
97
|
-
callSessionId: session.id,
|
|
98
|
-
pendingQuestionId: pq.id,
|
|
99
|
-
questionText: pq.questionText,
|
|
100
|
-
expiresAt: Date.now() - 10_000, // expired 10s ago
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
ensureConversation("conv-mac-1");
|
|
104
|
-
createGuardianActionDelivery({
|
|
105
|
-
requestId: request.id,
|
|
106
|
-
destinationChannel: "vellum",
|
|
107
|
-
destinationConversationId: "conv-mac-1",
|
|
108
|
-
});
|
|
109
|
-
updateDeliveryStatus(
|
|
110
|
-
getDeliveriesByRequestId(request.id).find(
|
|
111
|
-
(d) => d.destinationChannel === "vellum",
|
|
112
|
-
)!.id,
|
|
113
|
-
"sent",
|
|
63
|
+
test("sends external channel expiry notices for sent deliveries", async () => {
|
|
64
|
+
await sendGuardianExpiryNotices(
|
|
65
|
+
[makeDelivery({ status: "sent" }), makeDelivery({ id: "d2", status: "pending", destinationChatId: "chat-456" })],
|
|
66
|
+
"assistant-1",
|
|
114
67
|
);
|
|
115
68
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
expect(
|
|
120
|
-
expect(
|
|
121
|
-
|
|
122
|
-
const deliveries = getDeliveriesByRequestId(request.id);
|
|
123
|
-
expect(deliveries).toHaveLength(1);
|
|
124
|
-
expect(deliveries[0].status).toBe("expired");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("sweepExpiredGuardianActions expires pending questions", async () => {
|
|
128
|
-
const convId = "conv-sweep-2";
|
|
129
|
-
ensureConversation(convId);
|
|
130
|
-
|
|
131
|
-
const session = createCallSession({
|
|
132
|
-
conversationId: convId,
|
|
133
|
-
provider: "twilio",
|
|
134
|
-
fromNumber: "+15550001111",
|
|
135
|
-
toNumber: "+15550002222",
|
|
136
|
-
});
|
|
137
|
-
const pq = createPendingQuestion(session.id, "What is the gate code?");
|
|
138
|
-
|
|
139
|
-
createGuardianActionRequest({
|
|
140
|
-
kind: "ask_guardian",
|
|
141
|
-
sourceChannel: "phone",
|
|
142
|
-
sourceConversationId: convId,
|
|
143
|
-
callSessionId: session.id,
|
|
144
|
-
pendingQuestionId: pq.id,
|
|
145
|
-
questionText: pq.questionText,
|
|
146
|
-
expiresAt: Date.now() - 5_000,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Verify the question is still pending before sweep
|
|
150
|
-
expect(getPendingQuestion(session.id)).not.toBeNull();
|
|
151
|
-
|
|
152
|
-
await sweepExpiredGuardianActions();
|
|
153
|
-
|
|
154
|
-
// Pending question should be expired
|
|
155
|
-
expect(getPendingQuestion(session.id)).toBeNull();
|
|
69
|
+
expect(deliveredMessages).toHaveLength(2);
|
|
70
|
+
expect(deliveredMessages[0].url).toBe("/deliver/telegram");
|
|
71
|
+
expect(deliveredMessages[0].body.chatId).toBe("chat-123");
|
|
72
|
+
expect(deliveredMessages[0].body.assistantId).toBe("assistant-1");
|
|
73
|
+
expect(deliveredMessages[1].body.chatId).toBe("chat-456");
|
|
156
74
|
});
|
|
157
75
|
|
|
158
|
-
test("
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
toNumber: "+15550002222",
|
|
167
|
-
});
|
|
168
|
-
const pq = createPendingQuestion(session.id, "Still valid?");
|
|
169
|
-
|
|
170
|
-
// Request that expires in the future
|
|
171
|
-
const request = createGuardianActionRequest({
|
|
172
|
-
kind: "ask_guardian",
|
|
173
|
-
sourceChannel: "phone",
|
|
174
|
-
sourceConversationId: convId,
|
|
175
|
-
callSessionId: session.id,
|
|
176
|
-
pendingQuestionId: pq.id,
|
|
177
|
-
questionText: pq.questionText,
|
|
178
|
-
expiresAt: Date.now() + 60_000, // expires in 60s
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
await sweepExpiredGuardianActions();
|
|
182
|
-
|
|
183
|
-
const updatedRequest = getGuardianActionRequest(request.id);
|
|
184
|
-
expect(updatedRequest!.status).toBe("pending");
|
|
185
|
-
expect(getPendingQuestion(session.id)).not.toBeNull();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("sweepExpiredGuardianActions sends external channel expiry notices for sent deliveries", async () => {
|
|
189
|
-
const convId = "conv-sweep-4";
|
|
190
|
-
ensureConversation(convId);
|
|
191
|
-
|
|
192
|
-
const session = createCallSession({
|
|
193
|
-
conversationId: convId,
|
|
194
|
-
provider: "twilio",
|
|
195
|
-
fromNumber: "+15550001111",
|
|
196
|
-
toNumber: "+15550002222",
|
|
197
|
-
});
|
|
198
|
-
const pq = createPendingQuestion(session.id, "External question?");
|
|
199
|
-
|
|
200
|
-
const request = createGuardianActionRequest({
|
|
201
|
-
kind: "ask_guardian",
|
|
202
|
-
sourceChannel: "phone",
|
|
203
|
-
sourceConversationId: convId,
|
|
204
|
-
callSessionId: session.id,
|
|
205
|
-
pendingQuestionId: pq.id,
|
|
206
|
-
questionText: pq.questionText,
|
|
207
|
-
expiresAt: Date.now() - 5_000,
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const delivery = createGuardianActionDelivery({
|
|
211
|
-
requestId: request.id,
|
|
212
|
-
destinationChannel: "telegram",
|
|
213
|
-
destinationChatId: "chat-123",
|
|
214
|
-
});
|
|
215
|
-
updateDeliveryStatus(delivery.id, "sent");
|
|
216
|
-
|
|
217
|
-
await sweepExpiredGuardianActions();
|
|
218
|
-
|
|
219
|
-
// Wait for the fire-and-forget async delivery to complete
|
|
220
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
221
|
-
|
|
222
|
-
// The external delivery should trigger an HTTP POST to the gateway
|
|
223
|
-
expect(deliveredMessages.length).toBeGreaterThanOrEqual(1);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test("sweepExpiredGuardianActions skips failed deliveries", async () => {
|
|
227
|
-
const convId = "conv-sweep-5";
|
|
228
|
-
ensureConversation(convId);
|
|
229
|
-
|
|
230
|
-
const session = createCallSession({
|
|
231
|
-
conversationId: convId,
|
|
232
|
-
provider: "twilio",
|
|
233
|
-
fromNumber: "+15550001111",
|
|
234
|
-
toNumber: "+15550002222",
|
|
235
|
-
});
|
|
236
|
-
const pq = createPendingQuestion(session.id, "Skip this?");
|
|
237
|
-
|
|
238
|
-
const request = createGuardianActionRequest({
|
|
239
|
-
kind: "ask_guardian",
|
|
240
|
-
sourceChannel: "phone",
|
|
241
|
-
sourceConversationId: convId,
|
|
242
|
-
callSessionId: session.id,
|
|
243
|
-
pendingQuestionId: pq.id,
|
|
244
|
-
questionText: pq.questionText,
|
|
245
|
-
expiresAt: Date.now() - 5_000,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const delivery = createGuardianActionDelivery({
|
|
249
|
-
requestId: request.id,
|
|
250
|
-
destinationChannel: "telegram",
|
|
251
|
-
destinationChatId: "chat-456",
|
|
252
|
-
});
|
|
253
|
-
updateDeliveryStatus(delivery.id, "failed", "Network error");
|
|
254
|
-
|
|
255
|
-
deliveredMessages.length = 0;
|
|
256
|
-
|
|
257
|
-
await sweepExpiredGuardianActions();
|
|
76
|
+
test("skips deliveries that are not sent or pending", async () => {
|
|
77
|
+
await sendGuardianExpiryNotices(
|
|
78
|
+
[
|
|
79
|
+
makeDelivery({ status: "failed" }),
|
|
80
|
+
makeDelivery({ id: "d2", status: "expired" }),
|
|
81
|
+
],
|
|
82
|
+
"assistant-1",
|
|
83
|
+
);
|
|
258
84
|
|
|
259
|
-
// Should NOT have sent an expiry notice for a failed delivery
|
|
260
85
|
expect(deliveredMessages).toHaveLength(0);
|
|
261
86
|
});
|
|
262
87
|
|
|
263
|
-
test("
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
88
|
+
test("adds an expiry message to vellum guardian conversations", async () => {
|
|
89
|
+
ensureConversation("guardian-conv");
|
|
90
|
+
|
|
91
|
+
await sendGuardianExpiryNotices(
|
|
92
|
+
[
|
|
93
|
+
makeDelivery({
|
|
94
|
+
destinationChannel: "vellum",
|
|
95
|
+
destinationConversationId: "guardian-conv",
|
|
96
|
+
destinationChatId: null,
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
"assistant-1",
|
|
100
|
+
);
|
|
267
101
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
102
|
+
expect(deliveredMessages).toHaveLength(0);
|
|
103
|
+
const db = getDb();
|
|
104
|
+
const rows = db.select().from(messages).all();
|
|
105
|
+
expect(rows).toHaveLength(1);
|
|
106
|
+
expect(rows[0].conversationId).toBe("guardian-conv");
|
|
107
|
+
expect(rows[0].content).toContain("expired");
|
|
271
108
|
});
|
|
272
109
|
});
|