@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
|
@@ -23,11 +23,23 @@ type JobRow = {
|
|
|
23
23
|
status: string;
|
|
24
24
|
payload: string;
|
|
25
25
|
};
|
|
26
|
+
type MessageRow = {
|
|
27
|
+
role: string;
|
|
28
|
+
createdAt: number;
|
|
29
|
+
metadata: string | null;
|
|
30
|
+
};
|
|
26
31
|
|
|
27
32
|
let mockConversations: ConvRow[] = [];
|
|
28
33
|
let mockJobs: JobRow[] = [];
|
|
34
|
+
let mockMessages: Record<string, MessageRow[]> = {};
|
|
29
35
|
let deletedIds: string[] = [];
|
|
30
36
|
|
|
37
|
+
const RETRO_SOURCES = ["memory-retrospective", "memory-retrospective-fork"];
|
|
38
|
+
|
|
39
|
+
function isRetroSource(source: string | null): boolean {
|
|
40
|
+
return source !== null && RETRO_SOURCES.includes(source);
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
mock.module("../db-connection.js", () => ({
|
|
32
44
|
getDb: () => ({
|
|
33
45
|
select: (cols?: Record<string, unknown>) => ({
|
|
@@ -60,18 +72,20 @@ mock.module("../db-connection.js", () => ({
|
|
|
60
72
|
return { conversationId: convId };
|
|
61
73
|
});
|
|
62
74
|
}
|
|
63
|
-
// The "all retros" query (used to compute
|
|
64
|
-
//
|
|
65
|
-
// with only the source +
|
|
75
|
+
// The "all retros" query (used to compute the preserved
|
|
76
|
+
// dedup-baseline per source) requests id + source +
|
|
77
|
+
// forkParentConversationId + createdAt with only the source +
|
|
78
|
+
// isNotNull(forkParent) predicate.
|
|
66
79
|
if (
|
|
67
80
|
colKeys.includes("forkParentConversationId") &&
|
|
68
81
|
colKeys.includes("createdAt")
|
|
69
82
|
) {
|
|
70
83
|
return mockConversations
|
|
71
|
-
.filter((c) => c.source
|
|
84
|
+
.filter((c) => isRetroSource(c.source))
|
|
72
85
|
.filter((c) => c.fork_parent_conversation_id !== null)
|
|
73
86
|
.map((c) => ({
|
|
74
87
|
id: c.id,
|
|
88
|
+
source: c.source,
|
|
75
89
|
forkParentConversationId: c.fork_parent_conversation_id,
|
|
76
90
|
createdAt: c.created_at,
|
|
77
91
|
}));
|
|
@@ -81,7 +95,7 @@ mock.module("../db-connection.js", () => ({
|
|
|
81
95
|
// encoded on the background conversation row) against the set
|
|
82
96
|
// of source IDs extracted from active jobs.
|
|
83
97
|
return mockConversations
|
|
84
|
-
.filter((c) => c.source
|
|
98
|
+
.filter((c) => isRetroSource(c.source))
|
|
85
99
|
.filter(
|
|
86
100
|
(c) =>
|
|
87
101
|
c.last_message_at !== null &&
|
|
@@ -102,12 +116,22 @@ mock.module("../db-connection.js", () => ({
|
|
|
102
116
|
|
|
103
117
|
let activeJobSourceConvIds = new Set<string>();
|
|
104
118
|
let injectedNowMinusOrphanAgeMs = 0;
|
|
119
|
+
let mockKeepSupersededRuns = false;
|
|
120
|
+
|
|
121
|
+
mock.module("../../config/loader.js", () => ({
|
|
122
|
+
getConfig: () => ({
|
|
123
|
+
memory: {
|
|
124
|
+
retrospective: { keepSupersededRuns: mockKeepSupersededRuns },
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
}));
|
|
105
128
|
|
|
106
129
|
mock.module("../conversation-crud.js", () => ({
|
|
107
130
|
deleteConversation: (id: string) => {
|
|
108
131
|
deletedIds.push(id);
|
|
109
132
|
mockConversations = mockConversations.filter((c) => c.id !== id);
|
|
110
133
|
},
|
|
134
|
+
getMessages: (conversationId: string) => mockMessages[conversationId] ?? [],
|
|
111
135
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
112
136
|
}));
|
|
113
137
|
|
|
@@ -139,14 +163,17 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
139
163
|
beforeEach(() => {
|
|
140
164
|
mockConversations = [];
|
|
141
165
|
mockJobs = [];
|
|
166
|
+
mockMessages = {};
|
|
142
167
|
deletedIds = [];
|
|
143
168
|
activeJobSourceConvIds = new Set();
|
|
144
169
|
injectedNowMinusOrphanAgeMs = 0;
|
|
170
|
+
mockKeepSupersededRuns = false;
|
|
145
171
|
});
|
|
146
172
|
|
|
147
173
|
afterEach(() => {
|
|
148
174
|
mockConversations = [];
|
|
149
175
|
mockJobs = [];
|
|
176
|
+
mockMessages = {};
|
|
150
177
|
});
|
|
151
178
|
|
|
152
179
|
test("sweeps an old memory-retrospective orphan that has been superseded by a newer retro for the same source", () => {
|
|
@@ -316,4 +343,199 @@ describe("sweepOrphanMemoryRetrospectiveConversations", () => {
|
|
|
316
343
|
expect(result.swept).toBe(0);
|
|
317
344
|
expect(deletedIds).toEqual([]);
|
|
318
345
|
});
|
|
346
|
+
|
|
347
|
+
test("keepSupersededRuns=true skips the sweep entirely — sweepable orphans are retained", () => {
|
|
348
|
+
mockKeepSupersededRuns = true;
|
|
349
|
+
const now = Date.now();
|
|
350
|
+
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
351
|
+
// Same shape as the first test's sweepable orphan — with the flag off
|
|
352
|
+
// this would be swept (older retro superseded by a newer one for the
|
|
353
|
+
// same source).
|
|
354
|
+
mockConversations = [
|
|
355
|
+
{
|
|
356
|
+
id: "old-orphan",
|
|
357
|
+
source: "memory-retrospective",
|
|
358
|
+
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
359
|
+
fork_parent_conversation_id: "source-A",
|
|
360
|
+
created_at: now - 3 * ORPHAN_AGE_MS,
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: "newer-retro",
|
|
364
|
+
source: "memory-retrospective",
|
|
365
|
+
last_message_at: now - 90 * 60 * 1000,
|
|
366
|
+
fork_parent_conversation_id: "source-A",
|
|
367
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
368
|
+
},
|
|
369
|
+
];
|
|
370
|
+
rebuildActiveJobSet();
|
|
371
|
+
|
|
372
|
+
const result = sweepOrphanMemoryRetrospectiveConversations(now);
|
|
373
|
+
|
|
374
|
+
expect(result.swept).toBe(0);
|
|
375
|
+
expect(deletedIds).toEqual([]);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// -------------------------------------------------------------------------
|
|
379
|
+
// Dedup-baseline selection: prefer rows that actually produced output.
|
|
380
|
+
// -------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Build a fork-kind retrospective's message rows: a copied source prefix
|
|
384
|
+
* (stamped with `forkSourceMessageId`, ending in an assistant turn AT the
|
|
385
|
+
* boundary — exercising the strictly-greater check), the post-fork
|
|
386
|
+
* user-role instruction, and optionally the retrospective's own assistant
|
|
387
|
+
* output after the boundary.
|
|
388
|
+
*/
|
|
389
|
+
function forkKindMessages(opts: {
|
|
390
|
+
boundaryAt: number;
|
|
391
|
+
withOutput: boolean;
|
|
392
|
+
}): MessageRow[] {
|
|
393
|
+
const rows: MessageRow[] = [
|
|
394
|
+
{
|
|
395
|
+
role: "user",
|
|
396
|
+
createdAt: opts.boundaryAt - 1000,
|
|
397
|
+
metadata: JSON.stringify({ forkSourceMessageId: "src-msg-1" }),
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
role: "assistant",
|
|
401
|
+
createdAt: opts.boundaryAt,
|
|
402
|
+
metadata: JSON.stringify({ forkSourceMessageId: "src-msg-2" }),
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
role: "user",
|
|
406
|
+
createdAt: opts.boundaryAt + 500,
|
|
407
|
+
metadata: JSON.stringify({ kind: "memory_retrospective_instruction" }),
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
if (opts.withOutput) {
|
|
411
|
+
rows.push({
|
|
412
|
+
role: "assistant",
|
|
413
|
+
createdAt: opts.boundaryAt + 1000,
|
|
414
|
+
metadata: null,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return rows;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
test("sweeps an orphan MOST-RECENT fork-kind row (no post-fork output) and preserves an older row WITH output as the dedup baseline", () => {
|
|
421
|
+
const now = Date.now();
|
|
422
|
+
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
423
|
+
mockConversations = [
|
|
424
|
+
{
|
|
425
|
+
id: "fork-with-output",
|
|
426
|
+
source: "memory-retrospective-fork",
|
|
427
|
+
last_message_at: now - 3 * ORPHAN_AGE_MS,
|
|
428
|
+
fork_parent_conversation_id: "source-A",
|
|
429
|
+
created_at: now - 3 * ORPHAN_AGE_MS,
|
|
430
|
+
},
|
|
431
|
+
// Most recent for source-A, but a crash orphan: its only post-fork
|
|
432
|
+
// message is the instruction — no assistant output after the boundary.
|
|
433
|
+
// The copied prefix DOES contain an assistant turn (at the boundary),
|
|
434
|
+
// which must not count.
|
|
435
|
+
{
|
|
436
|
+
id: "fork-orphan",
|
|
437
|
+
source: "memory-retrospective-fork",
|
|
438
|
+
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
439
|
+
fork_parent_conversation_id: "source-A",
|
|
440
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
441
|
+
},
|
|
442
|
+
];
|
|
443
|
+
mockMessages = {
|
|
444
|
+
"fork-with-output": forkKindMessages({
|
|
445
|
+
boundaryAt: now - 3 * ORPHAN_AGE_MS,
|
|
446
|
+
withOutput: true,
|
|
447
|
+
}),
|
|
448
|
+
"fork-orphan": forkKindMessages({
|
|
449
|
+
boundaryAt: now - 2 * ORPHAN_AGE_MS,
|
|
450
|
+
withOutput: false,
|
|
451
|
+
}),
|
|
452
|
+
};
|
|
453
|
+
rebuildActiveJobSet();
|
|
454
|
+
|
|
455
|
+
const result = sweepOrphanMemoryRetrospectiveConversations(now);
|
|
456
|
+
|
|
457
|
+
expect(result.swept).toBe(1);
|
|
458
|
+
expect(deletedIds).toEqual(["fork-orphan"]);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
test("falls back to preserving the plain most-recent row when EVERY row is an orphan", () => {
|
|
462
|
+
const now = Date.now();
|
|
463
|
+
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
464
|
+
mockConversations = [
|
|
465
|
+
{
|
|
466
|
+
id: "older-orphan",
|
|
467
|
+
source: "memory-retrospective-fork",
|
|
468
|
+
last_message_at: now - 3 * ORPHAN_AGE_MS,
|
|
469
|
+
fork_parent_conversation_id: "source-A",
|
|
470
|
+
created_at: now - 3 * ORPHAN_AGE_MS,
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
id: "newest-orphan",
|
|
474
|
+
source: "memory-retrospective-fork",
|
|
475
|
+
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
476
|
+
fork_parent_conversation_id: "source-A",
|
|
477
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
478
|
+
},
|
|
479
|
+
];
|
|
480
|
+
mockMessages = {
|
|
481
|
+
"older-orphan": forkKindMessages({
|
|
482
|
+
boundaryAt: now - 3 * ORPHAN_AGE_MS,
|
|
483
|
+
withOutput: false,
|
|
484
|
+
}),
|
|
485
|
+
"newest-orphan": forkKindMessages({
|
|
486
|
+
boundaryAt: now - 2 * ORPHAN_AGE_MS,
|
|
487
|
+
withOutput: false,
|
|
488
|
+
}),
|
|
489
|
+
};
|
|
490
|
+
rebuildActiveJobSet();
|
|
491
|
+
|
|
492
|
+
const result = sweepOrphanMemoryRetrospectiveConversations(now);
|
|
493
|
+
|
|
494
|
+
// Current behavior preserved: the most-recent row survives even though
|
|
495
|
+
// it has no output; the older orphan is swept.
|
|
496
|
+
expect(result.swept).toBe(1);
|
|
497
|
+
expect(deletedIds).toEqual(["older-orphan"]);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("legacy-kind selection: ANY assistant message counts as output (no fork boundary required)", () => {
|
|
501
|
+
const now = Date.now();
|
|
502
|
+
injectedNowMinusOrphanAgeMs = now - ORPHAN_AGE_MS;
|
|
503
|
+
mockConversations = [
|
|
504
|
+
// Older legacy retro that produced output — preserved.
|
|
505
|
+
{
|
|
506
|
+
id: "legacy-with-output",
|
|
507
|
+
source: "memory-retrospective",
|
|
508
|
+
last_message_at: now - 3 * ORPHAN_AGE_MS,
|
|
509
|
+
fork_parent_conversation_id: "source-A",
|
|
510
|
+
created_at: now - 3 * ORPHAN_AGE_MS,
|
|
511
|
+
},
|
|
512
|
+
// Most-recent legacy retro with no assistant message — a wake that
|
|
513
|
+
// never produced output. Swept.
|
|
514
|
+
{
|
|
515
|
+
id: "legacy-orphan",
|
|
516
|
+
source: "memory-retrospective",
|
|
517
|
+
last_message_at: now - 2 * ORPHAN_AGE_MS,
|
|
518
|
+
fork_parent_conversation_id: "source-A",
|
|
519
|
+
created_at: now - 2 * ORPHAN_AGE_MS,
|
|
520
|
+
},
|
|
521
|
+
];
|
|
522
|
+
mockMessages = {
|
|
523
|
+
"legacy-with-output": [
|
|
524
|
+
// Legacy rows carry no forkSourceMessageId stamps — any assistant
|
|
525
|
+
// message qualifies.
|
|
526
|
+
{
|
|
527
|
+
role: "assistant",
|
|
528
|
+
createdAt: now - 3 * ORPHAN_AGE_MS,
|
|
529
|
+
metadata: null,
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
"legacy-orphan": [],
|
|
533
|
+
};
|
|
534
|
+
rebuildActiveJobSet();
|
|
535
|
+
|
|
536
|
+
const result = sweepOrphanMemoryRetrospectiveConversations(now);
|
|
537
|
+
|
|
538
|
+
expect(result.swept).toBe(1);
|
|
539
|
+
expect(deletedIds).toEqual(["legacy-orphan"]);
|
|
540
|
+
});
|
|
319
541
|
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
|
|
6
|
+
|
|
7
|
+
mock.module("../../util/logger.js", () => ({
|
|
8
|
+
getLogger: () => makeMockLogger(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
mock.module("../../config/loader.js", () => ({
|
|
12
|
+
getConfig: () => ({
|
|
13
|
+
ui: {},
|
|
14
|
+
model: "test",
|
|
15
|
+
provider: "test",
|
|
16
|
+
memory: { enabled: false },
|
|
17
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
18
|
+
secretDetection: { enabled: false },
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import { createConversation } from "../conversation-crud.js";
|
|
23
|
+
import { getDb } from "../db-connection.js";
|
|
24
|
+
import { initializeDb } from "../db-init.js";
|
|
25
|
+
import {
|
|
26
|
+
appendToRememberedLog,
|
|
27
|
+
bumpRetrospectiveLastRunAt,
|
|
28
|
+
forkRetrospectiveState,
|
|
29
|
+
getRetrospectiveState,
|
|
30
|
+
REMEMBERED_LOG_MAX_BYTES,
|
|
31
|
+
REMEMBERED_LOG_MAX_ENTRIES,
|
|
32
|
+
upsertRetrospectiveState,
|
|
33
|
+
} from "../memory-retrospective-state.js";
|
|
34
|
+
import { memoryRetrospectiveState } from "../schema.js";
|
|
35
|
+
|
|
36
|
+
initializeDb();
|
|
37
|
+
|
|
38
|
+
function resetTables(): void {
|
|
39
|
+
const db = getDb();
|
|
40
|
+
db.delete(memoryRetrospectiveState).run();
|
|
41
|
+
db.run("DELETE FROM messages");
|
|
42
|
+
db.run("DELETE FROM conversations");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("appendToRememberedLog", () => {
|
|
46
|
+
test("appends new entries after existing ones, order preserved", () => {
|
|
47
|
+
expect(appendToRememberedLog(["a", "b"], ["c"])).toEqual(["a", "b", "c"]);
|
|
48
|
+
expect(appendToRememberedLog([], ["x"])).toEqual(["x"]);
|
|
49
|
+
expect(appendToRememberedLog(["x"], [])).toEqual(["x"]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("entry cap keeps the most recent entries", () => {
|
|
53
|
+
const existing = Array.from({ length: 99 }, (_, i) => `old-${i}`);
|
|
54
|
+
const result = appendToRememberedLog(existing, ["new-1", "new-2", "new-3"]);
|
|
55
|
+
|
|
56
|
+
expect(result).toHaveLength(REMEMBERED_LOG_MAX_ENTRIES);
|
|
57
|
+
// Oldest entries dropped first.
|
|
58
|
+
expect(result[0]).toBe("old-2");
|
|
59
|
+
expect(result.at(-1)).toBe("new-3");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("byte cap binds before the entry cap when entries are large", () => {
|
|
63
|
+
// 20 entries of ~1 KB each — well under the entry cap but over 8 KB.
|
|
64
|
+
const entries = Array.from(
|
|
65
|
+
{ length: 20 },
|
|
66
|
+
(_, i) => `${i}-${"x".repeat(1024)}`,
|
|
67
|
+
);
|
|
68
|
+
const result = appendToRememberedLog([], entries);
|
|
69
|
+
|
|
70
|
+
expect(result.length).toBeLessThan(entries.length);
|
|
71
|
+
expect(result.length).toBeGreaterThan(0);
|
|
72
|
+
expect(
|
|
73
|
+
Buffer.byteLength(JSON.stringify(result), "utf8"),
|
|
74
|
+
).toBeLessThanOrEqual(REMEMBERED_LOG_MAX_BYTES);
|
|
75
|
+
// Most recent entries survive.
|
|
76
|
+
expect(result.at(-1)).toBe(entries.at(-1));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("a single entry larger than the byte cap yields an empty log", () => {
|
|
80
|
+
const huge = "y".repeat(REMEMBERED_LOG_MAX_BYTES + 1);
|
|
81
|
+
expect(appendToRememberedLog([], [huge])).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("memory-retrospective-state remembered log persistence", () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
resetTables();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("upsert persists the log and getRetrospectiveState parses it back", () => {
|
|
91
|
+
const conv = createConversation("Log thread");
|
|
92
|
+
upsertRetrospectiveState({
|
|
93
|
+
conversationId: conv.id,
|
|
94
|
+
lastProcessedMessageId: "m1",
|
|
95
|
+
lastRunAt: 1000,
|
|
96
|
+
rememberedLog: ["save one", "save two"],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const state = getRetrospectiveState(conv.id);
|
|
100
|
+
expect(state?.rememberedLog).toEqual(["save one", "save two"]);
|
|
101
|
+
expect(state?.lastProcessedMessageId).toBe("m1");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("upsert without rememberedLog preserves the stored log", () => {
|
|
105
|
+
const conv = createConversation("Preserve thread");
|
|
106
|
+
upsertRetrospectiveState({
|
|
107
|
+
conversationId: conv.id,
|
|
108
|
+
lastProcessedMessageId: "m1",
|
|
109
|
+
lastRunAt: 1000,
|
|
110
|
+
rememberedLog: ["keep me"],
|
|
111
|
+
});
|
|
112
|
+
upsertRetrospectiveState({
|
|
113
|
+
conversationId: conv.id,
|
|
114
|
+
lastProcessedMessageId: "m2",
|
|
115
|
+
lastRunAt: 2000,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const state = getRetrospectiveState(conv.id);
|
|
119
|
+
expect(state?.lastProcessedMessageId).toBe("m2");
|
|
120
|
+
expect(state?.rememberedLog).toEqual(["keep me"]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("missing/NULL column value parses as an empty log", () => {
|
|
124
|
+
const conv = createConversation("Null-log thread");
|
|
125
|
+
upsertRetrospectiveState({
|
|
126
|
+
conversationId: conv.id,
|
|
127
|
+
lastProcessedMessageId: "m1",
|
|
128
|
+
lastRunAt: 1000,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(getRetrospectiveState(conv.id)?.rememberedLog).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("malformed stored JSON degrades to an empty log instead of throwing", () => {
|
|
135
|
+
const conv = createConversation("Corrupt thread");
|
|
136
|
+
upsertRetrospectiveState({
|
|
137
|
+
conversationId: conv.id,
|
|
138
|
+
lastProcessedMessageId: "m1",
|
|
139
|
+
lastRunAt: 1000,
|
|
140
|
+
rememberedLog: ["x"],
|
|
141
|
+
});
|
|
142
|
+
getDb()
|
|
143
|
+
.update(memoryRetrospectiveState)
|
|
144
|
+
.set({ rememberedLog: "not-json{{" })
|
|
145
|
+
.where(eq(memoryRetrospectiveState.conversationId, conv.id))
|
|
146
|
+
.run();
|
|
147
|
+
|
|
148
|
+
expect(getRetrospectiveState(conv.id)?.rememberedLog).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("bumpRetrospectiveLastRunAt seeds the empty-string sentinel with an empty log and preserves an existing log", () => {
|
|
152
|
+
// Failure-only row: "" sentinel, empty log.
|
|
153
|
+
const fresh = createConversation("Failure-only thread");
|
|
154
|
+
bumpRetrospectiveLastRunAt(fresh.id, 1000);
|
|
155
|
+
const freshState = getRetrospectiveState(fresh.id);
|
|
156
|
+
expect(freshState?.lastProcessedMessageId).toBe("");
|
|
157
|
+
expect(freshState?.rememberedLog).toEqual([]);
|
|
158
|
+
|
|
159
|
+
// Existing row with a log: a failure bump must not clobber it.
|
|
160
|
+
const seasoned = createConversation("Seasoned thread");
|
|
161
|
+
upsertRetrospectiveState({
|
|
162
|
+
conversationId: seasoned.id,
|
|
163
|
+
lastProcessedMessageId: "m1",
|
|
164
|
+
lastRunAt: 1000,
|
|
165
|
+
rememberedLog: ["survives failures"],
|
|
166
|
+
});
|
|
167
|
+
bumpRetrospectiveLastRunAt(seasoned.id, 2000);
|
|
168
|
+
const seasonedState = getRetrospectiveState(seasoned.id);
|
|
169
|
+
expect(seasonedState?.lastRunAt).toBe(2000);
|
|
170
|
+
expect(seasonedState?.rememberedLog).toEqual(["survives failures"]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("forkRetrospectiveState copies the log verbatim to the forked child", () => {
|
|
174
|
+
const source = createConversation("Fork source");
|
|
175
|
+
const child = createConversation("Fork child");
|
|
176
|
+
upsertRetrospectiveState({
|
|
177
|
+
conversationId: source.id,
|
|
178
|
+
lastProcessedMessageId: "",
|
|
179
|
+
lastRunAt: 1000,
|
|
180
|
+
rememberedLog: ["parent baseline"],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
forkRetrospectiveState({
|
|
184
|
+
database: getDb(),
|
|
185
|
+
sourceConversationId: source.id,
|
|
186
|
+
forkedConversationId: child.id,
|
|
187
|
+
forkedMessageIds: new Map(),
|
|
188
|
+
lastCopiedSourceMessageId: null,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(getRetrospectiveState(child.id)?.rememberedLog).toEqual([
|
|
192
|
+
"parent baseline",
|
|
193
|
+
]);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import { setOverridesForTesting } from "../../__tests__/feature-flag-test-helpers.js";
|
|
12
|
+
import type { AssistantConfig } from "../../config/schema.js";
|
|
13
|
+
import { getAppsDir } from "../app-store.js";
|
|
14
|
+
import {
|
|
15
|
+
ACTIVATION_FLOW_FLAG,
|
|
16
|
+
PERSONAL_PAGE_APP_ID,
|
|
17
|
+
PERSONAL_PAGE_ARM,
|
|
18
|
+
seedPreloadedApps,
|
|
19
|
+
} from "../preloaded-apps.js";
|
|
20
|
+
|
|
21
|
+
// The flag resolver reads the override cache and registry; the config
|
|
22
|
+
// argument is part of the signature but not consulted for resolution.
|
|
23
|
+
const config = {} as AssistantConfig;
|
|
24
|
+
|
|
25
|
+
function definitionPath(): string {
|
|
26
|
+
return join(getAppsDir(), `${PERSONAL_PAGE_APP_ID}.json`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function appDir(): string {
|
|
30
|
+
return join(getAppsDir(), PERSONAL_PAGE_APP_ID);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
setOverridesForTesting({});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
setOverridesForTesting({});
|
|
39
|
+
rmSync(definitionPath(), { force: true });
|
|
40
|
+
rmSync(appDir(), { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("seedPreloadedApps gating", () => {
|
|
44
|
+
test("does not seed on the control arm", async () => {
|
|
45
|
+
setOverridesForTesting({ [ACTIVATION_FLOW_FLAG]: "control" });
|
|
46
|
+
await seedPreloadedApps(config);
|
|
47
|
+
expect(existsSync(definitionPath())).toBe(false);
|
|
48
|
+
expect(existsSync(appDir())).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("does not seed on other treatment arms", async () => {
|
|
52
|
+
setOverridesForTesting({ [ACTIVATION_FLOW_FLAG]: "variant-a" });
|
|
53
|
+
await seedPreloadedApps(config);
|
|
54
|
+
expect(existsSync(definitionPath())).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("does not seed when the flag resolves to the registry default", async () => {
|
|
58
|
+
// No override → declared default "control".
|
|
59
|
+
await seedPreloadedApps(config);
|
|
60
|
+
expect(existsSync(definitionPath())).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("leaves an already-seeded, compiled app untouched", async () => {
|
|
64
|
+
setOverridesForTesting({ [ACTIVATION_FLOW_FLAG]: PERSONAL_PAGE_ARM });
|
|
65
|
+
|
|
66
|
+
const sentinelDefinition = JSON.stringify({
|
|
67
|
+
id: PERSONAL_PAGE_APP_ID,
|
|
68
|
+
userEdited: true,
|
|
69
|
+
});
|
|
70
|
+
mkdirSync(join(appDir(), "dist"), { recursive: true });
|
|
71
|
+
writeFileSync(definitionPath(), sentinelDefinition, "utf-8");
|
|
72
|
+
writeFileSync(
|
|
73
|
+
join(appDir(), "dist", "index.html"),
|
|
74
|
+
"<!-- compiled -->",
|
|
75
|
+
"utf-8",
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
await seedPreloadedApps(config);
|
|
79
|
+
|
|
80
|
+
// Seeding must not re-copy the template or rewrite the definition once
|
|
81
|
+
// the app exists — its content is considered user-owned.
|
|
82
|
+
expect(readFileSync(definitionPath(), "utf-8")).toBe(sentinelDefinition);
|
|
83
|
+
expect(existsSync(join(appDir(), "src"))).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { getLogger } from "../util/logger.js";
|
|
8
8
|
import { isAutoAnalysisConversation } from "./auto-analysis-guard.js";
|
|
9
9
|
import { isMemoryEnabled, upsertAutoAnalysisJob } from "./jobs-store.js";
|
|
10
|
+
import { isMemoryRetrospectiveConversation } from "./memory-retrospective-enqueue.js";
|
|
10
11
|
|
|
11
12
|
const log = getLogger("auto-analysis-enqueue");
|
|
12
13
|
|
|
@@ -31,7 +32,11 @@ export type AutoAnalysisTrigger = "batch" | "idle" | "lifecycle" | "compaction";
|
|
|
31
32
|
* conversation. Skips silently when:
|
|
32
33
|
* - the `auto-analyze` feature flag is disabled, OR
|
|
33
34
|
* - the source conversation is itself an auto-analysis conversation
|
|
34
|
-
* (recursion guard — we never analyze our own analysis output)
|
|
35
|
+
* (recursion guard — we never analyze our own analysis output), OR
|
|
36
|
+
* - the source conversation is a memory-retrospective conversation.
|
|
37
|
+
* Fork-kind retrospectives carry a full copy of the source
|
|
38
|
+
* conversation's history, so auto-analyzing one would re-process the
|
|
39
|
+
* entire source conversation and double-write memory.
|
|
35
40
|
*
|
|
36
41
|
* Immediate triggers (`"batch"`, `"compaction"`) and debounced triggers
|
|
37
42
|
* (`"idle"`, `"lifecycle"`) are written to separate rows keyed by a
|
|
@@ -73,6 +78,14 @@ export function enqueueAutoAnalysisIfEnabled(args: {
|
|
|
73
78
|
return;
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
if (isMemoryRetrospectiveConversation(conversationId)) {
|
|
82
|
+
log.debug(
|
|
83
|
+
{ conversationId, trigger },
|
|
84
|
+
"Skipping auto-analysis enqueue: source is a memory-retrospective conversation",
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
const idleTimeoutMs = config.analysis?.idleTimeoutMs ?? 600_000;
|
|
77
90
|
const runImmediately = trigger === "batch" || trigger === "compaction";
|
|
78
91
|
const triggerGroup: "immediate" | "debounced" = runImmediately
|
|
@@ -213,10 +213,12 @@ export class ClickHouseCompactionLogStore {
|
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
215
|
* Read all compaction attempts whose `started_at` falls strictly inside
|
|
216
|
-
* the
|
|
217
|
-
* the strict `>` / `<` predicate
|
|
218
|
-
* `getCompactionLogsBetween` on the llm-request-log sources
|
|
219
|
-
* route can
|
|
216
|
+
* the open window `(afterStartedAt, beforeStartedAt)`, pairing start/end
|
|
217
|
+
* rows by `compaction_id`. Mirrors the strict `>` / `<` predicate
|
|
218
|
+
* contract of `getCompactionLogsBetween` on the llm-request-log sources
|
|
219
|
+
* so the compaction route can scope both stores to the same call window
|
|
220
|
+
* (floor = previous real call's `createdAt`, ceiling = selected call's
|
|
221
|
+
* `createdAt`).
|
|
220
222
|
*/
|
|
221
223
|
async getEventsBetween(
|
|
222
224
|
conversationId: string,
|
|
@@ -731,6 +731,13 @@ export function findAnalysisConversationFor(
|
|
|
731
731
|
* Once the fork accumulates its own retros, those are found at the first
|
|
732
732
|
* iteration and we never walk up.
|
|
733
733
|
*
|
|
734
|
+
* The returned `forkParentConversationId` is the conversation the
|
|
735
|
+
* retrospective row is rooted at — `parentConversationId` itself when it has
|
|
736
|
+
* its own retros, or an ancestor when the chain walk found the row higher
|
|
737
|
+
* up. Callers that mutate the prior (e.g. GC of superseded runs) must check
|
|
738
|
+
* it against the source conversation: an ancestor's row is that ancestor's
|
|
739
|
+
* dedup baseline, not the caller's to delete.
|
|
740
|
+
*
|
|
734
741
|
* Returns `null` when no prior retrospective exists anywhere in the fork
|
|
735
742
|
* chain (true first-run case).
|
|
736
743
|
*
|
|
@@ -741,7 +748,7 @@ const MAX_FORK_CHAIN_DEPTH = 16;
|
|
|
741
748
|
|
|
742
749
|
export function findMostRecentRetrospectiveFor(
|
|
743
750
|
parentConversationId: string,
|
|
744
|
-
): { id: string } | null {
|
|
751
|
+
): { id: string; forkParentConversationId: string } | null {
|
|
745
752
|
const db = getDb();
|
|
746
753
|
let currentId: string | null = parentConversationId;
|
|
747
754
|
for (let depth = 0; depth < MAX_FORK_CHAIN_DEPTH && currentId; depth++) {
|
|
@@ -757,7 +764,7 @@ export function findMostRecentRetrospectiveFor(
|
|
|
757
764
|
.orderBy(desc(conversations.createdAt))
|
|
758
765
|
.limit(1)
|
|
759
766
|
.get();
|
|
760
|
-
if (row) return { id: row.id };
|
|
767
|
+
if (row) return { id: row.id, forkParentConversationId: currentId };
|
|
761
768
|
|
|
762
769
|
const parent = db
|
|
763
770
|
.select({
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Write-through disk view for conversations.
|
|
3
3
|
*
|
|
4
4
|
* Projects conversation metadata, messages, and attachments to a browsable
|
|
5
|
-
* filesystem layout under
|
|
5
|
+
* filesystem layout under $VELLUM_WORKSPACE_DIR/conversations/. This enables
|
|
6
6
|
* the assistant to search/read/manipulate conversation data using standard
|
|
7
7
|
* file tools.
|
|
8
8
|
*
|