@vellumai/assistant 0.8.12 → 0.9.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- 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-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- 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-data-persist.test.ts +97 -0
- 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 +219 -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__/mock-gateway-ipc.ts +23 -0
- 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__/resolve-app-id.test.ts +56 -0
- 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/bundler/package-resolver.ts +0 -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/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- 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 +167 -3
- 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 +28 -11
- 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/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -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-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- 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-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -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 +152 -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/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- 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 +91 -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
|
@@ -811,6 +811,7 @@ describe("POST /v1/plugins/install", () => {
|
|
|
811
811
|
fileCount: 7,
|
|
812
812
|
ref: opts.ref ?? "main",
|
|
813
813
|
commit: null,
|
|
814
|
+
committedAt: null,
|
|
814
815
|
}));
|
|
815
816
|
|
|
816
817
|
const result = await invokeInstall({
|
|
@@ -842,6 +843,7 @@ describe("POST /v1/plugins/install", () => {
|
|
|
842
843
|
fileCount: 7,
|
|
843
844
|
ref: opts.ref ?? "main",
|
|
844
845
|
commit: null,
|
|
846
|
+
committedAt: null,
|
|
845
847
|
}));
|
|
846
848
|
|
|
847
849
|
const result = await invokeInstall({
|
|
@@ -940,6 +942,7 @@ function inspection(
|
|
|
940
942
|
? {
|
|
941
943
|
target: "/workspace/.vellum/plugins/level-up",
|
|
942
944
|
commit: "60a392b0000000000000000000000000000000aa",
|
|
945
|
+
committedAt: "2026-06-01T12:34:56.000Z",
|
|
943
946
|
version: "0.1.0",
|
|
944
947
|
description: "Surfaces a Level Up diff card.",
|
|
945
948
|
installedAt: "2026-06-08T00:00:00.000Z",
|
|
@@ -964,6 +967,7 @@ function inspection(
|
|
|
964
967
|
repo: "vellum-ai/level-up",
|
|
965
968
|
path: "",
|
|
966
969
|
commit: "3eae1820000000000000000000000000000000bb",
|
|
970
|
+
committedAt: "2026-06-05T08:12:24.000Z",
|
|
967
971
|
description: "Surfaces a Level Up diff card.",
|
|
968
972
|
homepage: "https://github.com/vellum-ai/level-up",
|
|
969
973
|
license: "MIT",
|
|
@@ -1064,7 +1068,15 @@ function upgradeResult(
|
|
|
1064
1068
|
overrides.fromCommit === undefined
|
|
1065
1069
|
? "60a392b0000000000000000000000000000000aa"
|
|
1066
1070
|
: overrides.fromCommit,
|
|
1071
|
+
fromTimestamp:
|
|
1072
|
+
overrides.fromTimestamp === undefined
|
|
1073
|
+
? "2026-06-01T12:34:56.000Z"
|
|
1074
|
+
: overrides.fromTimestamp,
|
|
1067
1075
|
toCommit: overrides.toCommit ?? "3eae1820000000000000000000000000000000bb",
|
|
1076
|
+
toTimestamp:
|
|
1077
|
+
overrides.toTimestamp === undefined
|
|
1078
|
+
? "2026-06-05T08:12:24.000Z"
|
|
1079
|
+
: overrides.toTimestamp,
|
|
1068
1080
|
target: overrides.target ?? "/workspace/.vellum/plugins/level-up",
|
|
1069
1081
|
fileCount: overrides.fileCount === undefined ? 12 : overrides.fileCount,
|
|
1070
1082
|
dryRun: overrides.dryRun ?? false,
|
|
@@ -1076,7 +1088,9 @@ async function invokeUpgrade(args: RouteHandlerArgs = {}): Promise<{
|
|
|
1076
1088
|
name: string;
|
|
1077
1089
|
outcome: string;
|
|
1078
1090
|
fromCommit: string | null;
|
|
1091
|
+
fromTimestamp: string | null;
|
|
1079
1092
|
toCommit: string;
|
|
1093
|
+
toTimestamp: string | null;
|
|
1080
1094
|
target: string;
|
|
1081
1095
|
fileCount: number | null;
|
|
1082
1096
|
dryRun: boolean;
|
|
@@ -1086,7 +1100,9 @@ async function invokeUpgrade(args: RouteHandlerArgs = {}): Promise<{
|
|
|
1086
1100
|
name: string;
|
|
1087
1101
|
outcome: string;
|
|
1088
1102
|
fromCommit: string | null;
|
|
1103
|
+
fromTimestamp: string | null;
|
|
1089
1104
|
toCommit: string;
|
|
1105
|
+
toTimestamp: string | null;
|
|
1090
1106
|
target: string;
|
|
1091
1107
|
fileCount: number | null;
|
|
1092
1108
|
dryRun: boolean;
|
|
@@ -1114,7 +1130,9 @@ describe("POST /v1/plugins/:name/upgrade", () => {
|
|
|
1114
1130
|
name: "level-up",
|
|
1115
1131
|
outcome: "upgraded",
|
|
1116
1132
|
fromCommit: "60a392b0000000000000000000000000000000aa",
|
|
1133
|
+
fromTimestamp: "2026-06-01T12:34:56.000Z",
|
|
1117
1134
|
toCommit: "3eae1820000000000000000000000000000000bb",
|
|
1135
|
+
toTimestamp: "2026-06-05T08:12:24.000Z",
|
|
1118
1136
|
target: "/workspace/.vellum/plugins/level-up",
|
|
1119
1137
|
fileCount: 12,
|
|
1120
1138
|
dryRun: false,
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asserts `listRetrospectiveRuns` merges background-conversation rows from
|
|
3
|
+
* BOTH retrospective sources (`memory-retrospective` → kind `legacy`,
|
|
4
|
+
* `memory-retrospective-fork` → kind `fork`) into the heartbeat-runs
|
|
5
|
+
* response shape (plus `kind` and `title`), derives `status` / `finishedAt`
|
|
6
|
+
* from assistant-message presence **at or after the conversation's
|
|
7
|
+
* createdAt** (fork runs copy source messages with their original
|
|
8
|
+
* timestamps), and clamps the `limit` query param.
|
|
9
|
+
*
|
|
10
|
+
* Also asserts `getRetrospectiveConfig` gates `available` on
|
|
11
|
+
* `memory.enabled` alone (NOT `memory.v2.enabled`) and always reports
|
|
12
|
+
* `nextRunAt: null` — retrospectives are event-driven per conversation,
|
|
13
|
+
* never globally scheduled.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
20
|
+
|
|
21
|
+
mock.module("../../../util/logger.js", () => ({
|
|
22
|
+
getLogger: () =>
|
|
23
|
+
new Proxy({} as Record<string, unknown>, {
|
|
24
|
+
get: () => () => {},
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import { invalidateConfigCache } from "../../../config/loader.js";
|
|
29
|
+
import { createConversation } from "../../../memory/conversation-crud.js";
|
|
30
|
+
import { getDb } from "../../../memory/db-connection.js";
|
|
31
|
+
import { initializeDb } from "../../../memory/db-init.js";
|
|
32
|
+
import { recordUsageEvent } from "../../../memory/llm-usage-store.js";
|
|
33
|
+
import {
|
|
34
|
+
MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
35
|
+
MEMORY_RETROSPECTIVE_SOURCE,
|
|
36
|
+
} from "../../../memory/memory-retrospective-constants.js";
|
|
37
|
+
import { rawRun } from "../../../memory/raw-query.js";
|
|
38
|
+
import { ROUTES } from "../retrospective-routes.js";
|
|
39
|
+
import type { RouteDefinition } from "../types.js";
|
|
40
|
+
|
|
41
|
+
initializeDb();
|
|
42
|
+
|
|
43
|
+
let workspaceDir: string;
|
|
44
|
+
let origWorkspaceDir: string | undefined;
|
|
45
|
+
let configPath: string;
|
|
46
|
+
|
|
47
|
+
function resetTables(): void {
|
|
48
|
+
const db = getDb();
|
|
49
|
+
db.run(`DELETE FROM llm_usage_events`);
|
|
50
|
+
db.run(`DELETE FROM messages`);
|
|
51
|
+
db.run(`DELETE FROM conversations`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
55
|
+
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
56
|
+
if (!route) throw new Error(`Route ${operationId} not found`);
|
|
57
|
+
return route.handler;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function insertMessage(
|
|
61
|
+
conversationId: string,
|
|
62
|
+
role: string,
|
|
63
|
+
createdAt: number,
|
|
64
|
+
): void {
|
|
65
|
+
rawRun(
|
|
66
|
+
"INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
67
|
+
`msg-${conversationId}-${role}-${createdAt}`,
|
|
68
|
+
conversationId,
|
|
69
|
+
role,
|
|
70
|
+
"x",
|
|
71
|
+
createdAt,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function setCreatedAt(conversationId: string, createdAt: number): void {
|
|
76
|
+
rawRun(
|
|
77
|
+
"UPDATE conversations SET created_at = ? WHERE id = ?",
|
|
78
|
+
createdAt,
|
|
79
|
+
conversationId,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function recordUsageCostAt(
|
|
84
|
+
conversationId: string,
|
|
85
|
+
requestId: string,
|
|
86
|
+
createdAt: number,
|
|
87
|
+
estimatedCostUsd: number,
|
|
88
|
+
): void {
|
|
89
|
+
const event = recordUsageEvent(
|
|
90
|
+
{
|
|
91
|
+
conversationId,
|
|
92
|
+
runId: null,
|
|
93
|
+
requestId,
|
|
94
|
+
actor: "main_agent",
|
|
95
|
+
callSite: "mainAgent",
|
|
96
|
+
inferenceProfile: "balanced",
|
|
97
|
+
provider: "anthropic",
|
|
98
|
+
model: "claude-sonnet-4-20250514",
|
|
99
|
+
inputTokens: 100,
|
|
100
|
+
outputTokens: 50,
|
|
101
|
+
cacheCreationInputTokens: 0,
|
|
102
|
+
cacheReadInputTokens: 0,
|
|
103
|
+
rawUsage: null,
|
|
104
|
+
},
|
|
105
|
+
{ estimatedCostUsd, pricingStatus: "priced" },
|
|
106
|
+
);
|
|
107
|
+
rawRun(
|
|
108
|
+
"UPDATE llm_usage_events SET created_at = ? WHERE id = ?",
|
|
109
|
+
createdAt,
|
|
110
|
+
event.id,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface RunRecord {
|
|
115
|
+
id: string;
|
|
116
|
+
scheduledFor: number;
|
|
117
|
+
startedAt: number | null;
|
|
118
|
+
finishedAt: number | null;
|
|
119
|
+
durationMs: number | null;
|
|
120
|
+
status: "ok" | "running";
|
|
121
|
+
skipReason: string | null;
|
|
122
|
+
error: string | null;
|
|
123
|
+
conversationId: string | null;
|
|
124
|
+
estimatedCostUsd: number;
|
|
125
|
+
createdAt: number;
|
|
126
|
+
kind: "legacy" | "fork";
|
|
127
|
+
title: string | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface ListRunsResponse {
|
|
131
|
+
runs: RunRecord[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface ConfigResponse {
|
|
135
|
+
available: boolean;
|
|
136
|
+
enabled: boolean;
|
|
137
|
+
intervalMs: number;
|
|
138
|
+
nextRunAt: number | null;
|
|
139
|
+
lastRunAt: number | null;
|
|
140
|
+
success: boolean;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-retrospective-routes-"));
|
|
145
|
+
origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
146
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
147
|
+
configPath = join(workspaceDir, "config.json");
|
|
148
|
+
invalidateConfigCache();
|
|
149
|
+
resetTables();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
afterEach(() => {
|
|
153
|
+
if (origWorkspaceDir === undefined) {
|
|
154
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
155
|
+
} else {
|
|
156
|
+
process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
|
|
157
|
+
}
|
|
158
|
+
invalidateConfigCache();
|
|
159
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("listRetrospectiveRuns handler", () => {
|
|
163
|
+
test("merges both retrospective sources with the kind field and excludes others", async () => {
|
|
164
|
+
const legacy = createConversation({
|
|
165
|
+
title: "legacy run",
|
|
166
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
167
|
+
});
|
|
168
|
+
const fork = createConversation({
|
|
169
|
+
title: "Planning chat (Retrospective)",
|
|
170
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
171
|
+
});
|
|
172
|
+
createConversation({ title: "h1", source: "heartbeat" });
|
|
173
|
+
createConversation({ title: "c1", source: "memory_v2_consolidation" });
|
|
174
|
+
createConversation({ title: "u1", source: "user" });
|
|
175
|
+
setCreatedAt(legacy.id, 1000);
|
|
176
|
+
setCreatedAt(fork.id, 2000);
|
|
177
|
+
|
|
178
|
+
const handler = findHandler("listRetrospectiveRuns");
|
|
179
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
180
|
+
|
|
181
|
+
expect(result.runs).toHaveLength(2);
|
|
182
|
+
expect(result.runs.map((r) => [r.id, r.kind, r.title])).toEqual([
|
|
183
|
+
[fork.id, "fork", "Planning chat (Retrospective)"],
|
|
184
|
+
[legacy.id, "legacy", "legacy run"],
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("orders merged runs by createdAt descending across sources", async () => {
|
|
189
|
+
const a = createConversation({
|
|
190
|
+
title: "a",
|
|
191
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
192
|
+
});
|
|
193
|
+
const b = createConversation({
|
|
194
|
+
title: "b",
|
|
195
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
196
|
+
});
|
|
197
|
+
const c = createConversation({
|
|
198
|
+
title: "c",
|
|
199
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
200
|
+
});
|
|
201
|
+
setCreatedAt(a.id, 1000);
|
|
202
|
+
setCreatedAt(b.id, 3000);
|
|
203
|
+
setCreatedAt(c.id, 2000);
|
|
204
|
+
|
|
205
|
+
const handler = findHandler("listRetrospectiveRuns");
|
|
206
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
207
|
+
|
|
208
|
+
expect(result.runs.map((r) => r.id)).toEqual([b.id, c.id, a.id]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("synthesizes status='ok' with finishedAt from latest assistant message", async () => {
|
|
212
|
+
const conv = createConversation({
|
|
213
|
+
title: "r1",
|
|
214
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
215
|
+
});
|
|
216
|
+
setCreatedAt(conv.id, 1000);
|
|
217
|
+
insertMessage(conv.id, "user", 1100);
|
|
218
|
+
insertMessage(conv.id, "assistant", 2000);
|
|
219
|
+
insertMessage(conv.id, "assistant", 2500);
|
|
220
|
+
|
|
221
|
+
const handler = findHandler("listRetrospectiveRuns");
|
|
222
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
223
|
+
|
|
224
|
+
const run = result.runs[0]!;
|
|
225
|
+
expect(run.id).toBe(conv.id);
|
|
226
|
+
expect(run.conversationId).toBe(conv.id);
|
|
227
|
+
expect(run.status).toBe("ok");
|
|
228
|
+
expect(run.scheduledFor).toBe(1000);
|
|
229
|
+
expect(run.startedAt).toBe(1000);
|
|
230
|
+
expect(run.finishedAt).toBe(2500);
|
|
231
|
+
expect(run.durationMs).toBe(1500);
|
|
232
|
+
expect(run.skipReason).toBeNull();
|
|
233
|
+
expect(run.error).toBeNull();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("fork run with only the copied source prefix stays 'running' (no negative duration)", async () => {
|
|
237
|
+
// forkConversation copies source messages with their ORIGINAL
|
|
238
|
+
// timestamps, so a fresh fork contains assistant messages that predate
|
|
239
|
+
// the fork conversation's createdAt. Those must not count as agent
|
|
240
|
+
// output — status stays 'running' until the retrospective agent itself
|
|
241
|
+
// replies (at-or-after the fork's creation).
|
|
242
|
+
const conv = createConversation({
|
|
243
|
+
title: "Planning chat (Retrospective)",
|
|
244
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
245
|
+
});
|
|
246
|
+
setCreatedAt(conv.id, 5000);
|
|
247
|
+
// Copied prefix from the source conversation (pre-fork timestamps).
|
|
248
|
+
insertMessage(conv.id, "user", 2000);
|
|
249
|
+
insertMessage(conv.id, "assistant", 3000);
|
|
250
|
+
|
|
251
|
+
const handler = findHandler("listRetrospectiveRuns");
|
|
252
|
+
const running = (await handler({})) as ListRunsResponse;
|
|
253
|
+
expect(running.runs[0]!.status).toBe("running");
|
|
254
|
+
expect(running.runs[0]!.finishedAt).toBeNull();
|
|
255
|
+
expect(running.runs[0]!.durationMs).toBeNull();
|
|
256
|
+
|
|
257
|
+
// Retrospective agent replies after the fork was created.
|
|
258
|
+
insertMessage(conv.id, "assistant", 6000);
|
|
259
|
+
const done = (await handler({})) as ListRunsResponse;
|
|
260
|
+
expect(done.runs[0]!.status).toBe("ok");
|
|
261
|
+
expect(done.runs[0]!.finishedAt).toBe(6000);
|
|
262
|
+
expect(done.runs[0]!.durationMs).toBe(1000);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("exposes estimatedCostUsd from the conversation total, falling back to window usage", async () => {
|
|
266
|
+
const withTotal = createConversation({
|
|
267
|
+
title: "r1",
|
|
268
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
269
|
+
});
|
|
270
|
+
rawRun(
|
|
271
|
+
"UPDATE conversations SET created_at = ?, total_estimated_cost = ? WHERE id = ?",
|
|
272
|
+
1000,
|
|
273
|
+
0.42,
|
|
274
|
+
withTotal.id,
|
|
275
|
+
);
|
|
276
|
+
insertMessage(withTotal.id, "assistant", 2000);
|
|
277
|
+
|
|
278
|
+
const withWindow = createConversation({
|
|
279
|
+
title: "r2",
|
|
280
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
281
|
+
});
|
|
282
|
+
setCreatedAt(withWindow.id, 1000);
|
|
283
|
+
insertMessage(withWindow.id, "assistant", 2000);
|
|
284
|
+
recordUsageCostAt(withWindow.id, "retro-before", 999, 0.4);
|
|
285
|
+
recordUsageCostAt(withWindow.id, "retro-inside", 1500, 0.07);
|
|
286
|
+
recordUsageCostAt(withWindow.id, "retro-after", 2001, 0.5);
|
|
287
|
+
|
|
288
|
+
const handler = findHandler("listRetrospectiveRuns");
|
|
289
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
290
|
+
|
|
291
|
+
const byId = new Map(result.runs.map((r) => [r.id, r]));
|
|
292
|
+
expect(byId.get(withTotal.id)!.estimatedCostUsd).toBeCloseTo(0.42);
|
|
293
|
+
expect(byId.get(withWindow.id)!.estimatedCostUsd).toBeCloseTo(0.07);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("limit defaults to 20, clamps to [1, 100], and merges correctly under the cap", async () => {
|
|
297
|
+
// 3 legacy (newest) + 3 fork (older) — a merged limit of 4 must take
|
|
298
|
+
// all 3 legacy rows plus the newest fork row, which requires fetching
|
|
299
|
+
// the limit from EACH source before merging.
|
|
300
|
+
const ids: Array<{ id: string; createdAt: number }> = [];
|
|
301
|
+
for (let i = 0; i < 3; i++) {
|
|
302
|
+
const conv = createConversation({
|
|
303
|
+
title: `legacy-${i}`,
|
|
304
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
305
|
+
});
|
|
306
|
+
setCreatedAt(conv.id, 10_000 + i);
|
|
307
|
+
ids.push({ id: conv.id, createdAt: 10_000 + i });
|
|
308
|
+
}
|
|
309
|
+
for (let i = 0; i < 3; i++) {
|
|
310
|
+
const conv = createConversation({
|
|
311
|
+
title: `fork-${i}`,
|
|
312
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
313
|
+
});
|
|
314
|
+
setCreatedAt(conv.id, 5_000 + i);
|
|
315
|
+
ids.push({ id: conv.id, createdAt: 5_000 + i });
|
|
316
|
+
}
|
|
317
|
+
const expectedTop4 = ids
|
|
318
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
319
|
+
.slice(0, 4)
|
|
320
|
+
.map((entry) => entry.id);
|
|
321
|
+
|
|
322
|
+
const handler = findHandler("listRetrospectiveRuns");
|
|
323
|
+
|
|
324
|
+
const def = (await handler({})) as ListRunsResponse;
|
|
325
|
+
expect(def.runs).toHaveLength(6);
|
|
326
|
+
|
|
327
|
+
const lim4 = (await handler({
|
|
328
|
+
queryParams: { limit: "4" },
|
|
329
|
+
})) as ListRunsResponse;
|
|
330
|
+
expect(lim4.runs.map((r) => r.id)).toEqual(expectedTop4);
|
|
331
|
+
|
|
332
|
+
const neg = (await handler({
|
|
333
|
+
queryParams: { limit: "-5" },
|
|
334
|
+
})) as ListRunsResponse;
|
|
335
|
+
expect(neg.runs).toHaveLength(1);
|
|
336
|
+
|
|
337
|
+
const huge = (await handler({
|
|
338
|
+
queryParams: { limit: "5000" },
|
|
339
|
+
})) as ListRunsResponse;
|
|
340
|
+
expect(huge.runs).toHaveLength(6);
|
|
341
|
+
|
|
342
|
+
const bad = (await handler({
|
|
343
|
+
queryParams: { limit: "garbage" },
|
|
344
|
+
})) as ListRunsResponse;
|
|
345
|
+
expect(bad.runs).toHaveLength(6);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe("getRetrospectiveConfig handler", () => {
|
|
350
|
+
test("reports availability and threshold interval with nextRunAt always null", async () => {
|
|
351
|
+
writeFileSync(
|
|
352
|
+
configPath,
|
|
353
|
+
JSON.stringify(
|
|
354
|
+
{
|
|
355
|
+
memory: {
|
|
356
|
+
retrospective: { timeThresholdMs: 45 * 60 * 1000 },
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
null,
|
|
360
|
+
2,
|
|
361
|
+
) + "\n",
|
|
362
|
+
);
|
|
363
|
+
const conv = createConversation({
|
|
364
|
+
title: "r1",
|
|
365
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
366
|
+
});
|
|
367
|
+
setCreatedAt(conv.id, 123_456);
|
|
368
|
+
|
|
369
|
+
const handler = findHandler("getRetrospectiveConfig");
|
|
370
|
+
const result = (await handler({})) as ConfigResponse;
|
|
371
|
+
|
|
372
|
+
expect(result).toEqual({
|
|
373
|
+
available: true,
|
|
374
|
+
enabled: true,
|
|
375
|
+
intervalMs: 45 * 60 * 1000,
|
|
376
|
+
nextRunAt: null,
|
|
377
|
+
lastRunAt: 123_456,
|
|
378
|
+
success: true,
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("lastRunAt is the newest run across both sources, null when none exist", async () => {
|
|
383
|
+
const handler = findHandler("getRetrospectiveConfig");
|
|
384
|
+
const empty = (await handler({})) as ConfigResponse;
|
|
385
|
+
expect(empty.lastRunAt).toBeNull();
|
|
386
|
+
|
|
387
|
+
const legacy = createConversation({
|
|
388
|
+
title: "legacy",
|
|
389
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
390
|
+
});
|
|
391
|
+
const fork = createConversation({
|
|
392
|
+
title: "fork",
|
|
393
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
394
|
+
});
|
|
395
|
+
setCreatedAt(legacy.id, 9000);
|
|
396
|
+
setCreatedAt(fork.id, 4000);
|
|
397
|
+
|
|
398
|
+
const result = (await handler({})) as ConfigResponse;
|
|
399
|
+
expect(result.lastRunAt).toBe(9000);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("reports unavailable when global memory is disabled", async () => {
|
|
403
|
+
writeFileSync(
|
|
404
|
+
configPath,
|
|
405
|
+
JSON.stringify({ memory: { enabled: false } }, null, 2) + "\n",
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const handler = findHandler("getRetrospectiveConfig");
|
|
409
|
+
const result = (await handler({})) as ConfigResponse;
|
|
410
|
+
|
|
411
|
+
expect(result.available).toBe(false);
|
|
412
|
+
expect(result.enabled).toBe(false);
|
|
413
|
+
expect(result.nextRunAt).toBeNull();
|
|
414
|
+
expect(result.success).toBe(true);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("stays available when memory v2 is disabled (retrospectives do not gate on v2)", async () => {
|
|
418
|
+
writeFileSync(
|
|
419
|
+
configPath,
|
|
420
|
+
JSON.stringify({ memory: { v2: { enabled: false } } }, null, 2) + "\n",
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const handler = findHandler("getRetrospectiveConfig");
|
|
424
|
+
const result = (await handler({})) as ConfigResponse;
|
|
425
|
+
|
|
426
|
+
expect(result.available).toBe(true);
|
|
427
|
+
expect(result.enabled).toBe(true);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("no run-now route is exposed (retrospectives are event-driven)", () => {
|
|
431
|
+
expect(ROUTES.some((route) => route.method === "POST")).toBe(false);
|
|
432
|
+
expect(
|
|
433
|
+
ROUTES.find((route) => route.endpoint.includes("run-now")),
|
|
434
|
+
).toBeUndefined();
|
|
435
|
+
});
|
|
436
|
+
});
|
|
@@ -72,6 +72,17 @@ mock.module("../../../memory/raw-query.js", () => ({
|
|
|
72
72
|
},
|
|
73
73
|
}));
|
|
74
74
|
|
|
75
|
+
// Mock guardian-action-service to cut off its deep transitive dependency chain.
|
|
76
|
+
// The apr:* routing is tested in guardian-routing-invariants.test.ts; this file
|
|
77
|
+
// focuses on the surface→conversation rehydration path.
|
|
78
|
+
mock.module("../../guardian-action-service.js", () => ({
|
|
79
|
+
processGuardianDecision: async () => ({ ok: true, applied: true }),
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
mock.module("../channel-route-shared.js", () => ({
|
|
83
|
+
parseCallbackData: () => null,
|
|
84
|
+
}));
|
|
85
|
+
|
|
75
86
|
// Defer route import until after mocks are installed.
|
|
76
87
|
const { ROUTES } = await import("../surface-action-routes.js");
|
|
77
88
|
const { NotFoundError } = await import("../errors.js");
|
|
@@ -11,6 +11,10 @@ import { z } from "zod";
|
|
|
11
11
|
|
|
12
12
|
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
13
13
|
import { getConversationByKey } from "../../memory/conversation-key-store.js";
|
|
14
|
+
import type {
|
|
15
|
+
SecretDelivery,
|
|
16
|
+
SecretPromptResult,
|
|
17
|
+
} from "../../permissions/secret-prompter.js";
|
|
14
18
|
import type { UserDecision } from "../../permissions/types.js";
|
|
15
19
|
import { getLogger } from "../../util/logger.js";
|
|
16
20
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
@@ -127,24 +131,47 @@ function handleSecret({ body }: RouteHandlerArgs) {
|
|
|
127
131
|
throw new BadRequestError('delivery must be "store" or "transient_send"');
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
// Use get() — SecretPrompter.resolveSecret() owns deregistration.
|
|
131
134
|
const interaction = pendingInteractions.get(requestId);
|
|
132
135
|
if (!interaction) {
|
|
133
136
|
throw new NotFoundError("No pending interaction found for this requestId");
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
// /v1/secret only settles secret prompts. A requestId belonging to another
|
|
140
|
+
// interaction kind (a confirmation or host-proxy request posted here from
|
|
141
|
+
// stale or mismatched client state) must not be consumed or resolved with a
|
|
142
|
+
// SecretPromptResult, which would strand its real approval/result endpoint.
|
|
143
|
+
if (interaction.kind !== "secret") {
|
|
138
144
|
throw new NotFoundError(
|
|
139
|
-
"
|
|
145
|
+
"No pending secret request found for this requestId",
|
|
140
146
|
);
|
|
141
147
|
}
|
|
142
148
|
|
|
143
|
-
conversation
|
|
149
|
+
// When a live conversation owns the request, route through it so the
|
|
150
|
+
// SecretPrompter's ownership tracking and dispose path stay consistent (this
|
|
151
|
+
// also drives the voice auto-resolve path). The prompter owns deregistration.
|
|
152
|
+
const conversation = interaction.conversationId
|
|
153
|
+
? findConversation(interaction.conversationId)
|
|
154
|
+
: undefined;
|
|
155
|
+
if (conversation?.hasPendingSecret(requestId)) {
|
|
156
|
+
conversation.handleSecretResponse(
|
|
157
|
+
requestId,
|
|
158
|
+
value,
|
|
159
|
+
delivery as "store" | "transient_send" | undefined,
|
|
160
|
+
);
|
|
161
|
+
return { accepted: true };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Conversation-less requests (e.g. the CLI `credentials prompt` command) and
|
|
165
|
+
// any request no live conversation owns resolve generically via the resolver
|
|
166
|
+
// stored on the interaction, with no Conversation in the loop.
|
|
167
|
+
const resolved = pendingInteractions.resolve(
|
|
144
168
|
requestId,
|
|
145
|
-
value,
|
|
146
|
-
delivery as "store" | "transient_send" | undefined,
|
|
169
|
+
value === undefined ? "cancelled" : "answered",
|
|
147
170
|
);
|
|
171
|
+
(resolved?.rpcResolve as ((r: SecretPromptResult) => void) | undefined)?.({
|
|
172
|
+
value: value ?? null,
|
|
173
|
+
delivery: (delivery as SecretDelivery) ?? "store",
|
|
174
|
+
});
|
|
148
175
|
return { accepted: true };
|
|
149
176
|
}
|
|
150
177
|
|
|
@@ -321,7 +348,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
321
348
|
.array(
|
|
322
349
|
z.object({
|
|
323
350
|
requestId: z.string(),
|
|
324
|
-
conversationId: z.string(),
|
|
351
|
+
conversationId: z.string().optional(),
|
|
325
352
|
kind: z.string(),
|
|
326
353
|
toolName: z.string().optional(),
|
|
327
354
|
riskLevel: z.string().optional(),
|
|
@@ -831,7 +831,8 @@ async function handleAccessRequestApproval(
|
|
|
831
831
|
}
|
|
832
832
|
|
|
833
833
|
// Approved: deliver the verification code to the guardian and notify the requester.
|
|
834
|
-
const requesterIdentifier =
|
|
834
|
+
const requesterIdentifier =
|
|
835
|
+
requesterDisplayName || approval.requesterExternalUserId;
|
|
835
836
|
|
|
836
837
|
let codeDelivered = true;
|
|
837
838
|
if (decisionResult.verificationCode) {
|
|
@@ -22,8 +22,16 @@ import { getAllToolDefinitions } from "../../tools/registry.js";
|
|
|
22
22
|
import { getLogger } from "../../util/logger.js";
|
|
23
23
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
24
24
|
import { runBtwSidechain } from "../btw-sidechain.js";
|
|
25
|
+
import {
|
|
26
|
+
getCachedEmptyStateGreeting,
|
|
27
|
+
setCachedEmptyStateGreeting,
|
|
28
|
+
} from "./empty-state-greeting-cache.js";
|
|
25
29
|
import { BadRequestError, ServiceUnavailableError } from "./errors.js";
|
|
26
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
getCachedIntro,
|
|
32
|
+
readWorkspaceGreetings,
|
|
33
|
+
setCachedIntro,
|
|
34
|
+
} from "./identity-intro-cache.js";
|
|
27
35
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
28
36
|
|
|
29
37
|
const log = getLogger("btw-routes");
|
|
@@ -73,6 +81,24 @@ async function handleBtw({
|
|
|
73
81
|
}
|
|
74
82
|
}
|
|
75
83
|
|
|
84
|
+
// ----- Empty-state greeting fast-path -----
|
|
85
|
+
// User-authored `## Greetings` win; otherwise replay a cached greeting when
|
|
86
|
+
// one is fresh within the configurable TTL (`ui.emptyStateGreetingCacheTtlMs`,
|
|
87
|
+
// 0 = always regenerate). Either path avoids an LLM call and streams the
|
|
88
|
+
// text straight back.
|
|
89
|
+
if (conversationKey === GREETING_KEY) {
|
|
90
|
+
const authored = readWorkspaceGreetings();
|
|
91
|
+
if (authored && authored.length > 0) {
|
|
92
|
+
log.debug("Returning authored empty-state greeting");
|
|
93
|
+
return streamText(pickRandom(authored));
|
|
94
|
+
}
|
|
95
|
+
const cached = getCachedEmptyStateGreeting();
|
|
96
|
+
if (cached) {
|
|
97
|
+
log.debug("Returning cached empty-state greeting");
|
|
98
|
+
return streamText(cached);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
// ----- Greeting context enrichment -----
|
|
77
103
|
let effectiveContent = trimmedContent;
|
|
78
104
|
if (
|
|
@@ -136,6 +162,12 @@ async function handleBtw({
|
|
|
136
162
|
}
|
|
137
163
|
}
|
|
138
164
|
|
|
165
|
+
if (isGreeting && result.text) {
|
|
166
|
+
// setCachedEmptyStateGreeting is a no-op when the TTL is 0.
|
|
167
|
+
setCachedEmptyStateGreeting(result.text);
|
|
168
|
+
log.debug("Cached empty-state greeting text");
|
|
169
|
+
}
|
|
170
|
+
|
|
139
171
|
controller.enqueue(sseEvent("btw_complete", {}));
|
|
140
172
|
controller.close();
|
|
141
173
|
} catch (err: unknown) {
|
|
@@ -153,6 +185,10 @@ async function handleBtw({
|
|
|
153
185
|
});
|
|
154
186
|
}
|
|
155
187
|
|
|
188
|
+
function pickRandom<T>(items: readonly T[]): T {
|
|
189
|
+
return items[Math.floor(Math.random() * items.length)]!;
|
|
190
|
+
}
|
|
191
|
+
|
|
156
192
|
function streamText(text: string): ReadableStream<Uint8Array> {
|
|
157
193
|
return new ReadableStream({
|
|
158
194
|
start(controller) {
|