@vellumai/assistant 0.8.12 → 0.9.0-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/openapi.yaml +6935 -5545
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +125 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +75 -0
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +1 -1
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +26 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +122 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +48 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -86,7 +86,40 @@ export async function ensureSectionCollection(
|
|
|
86
86
|
const onDisk = config.memory.qdrant.onDisk;
|
|
87
87
|
|
|
88
88
|
const exists = await client.collectionExists(SECTION_COLLECTION);
|
|
89
|
-
|
|
89
|
+
let needsCreate = !exists.exists;
|
|
90
|
+
|
|
91
|
+
// An existing collection sized to a different embedding dimension (e.g. a
|
|
92
|
+
// 384-dim collection now serving a 3072-dim embedder) carries the right
|
|
93
|
+
// vector at the wrong size — every upsert then fails with HTTP 400 and the
|
|
94
|
+
// collection is never repaired. Probe the schema and recreate on dimension
|
|
95
|
+
// drift; on a probe failure, assume compatible rather than risk a destructive
|
|
96
|
+
// recreate (mirrors the v2 concept-page collection posture). The next
|
|
97
|
+
// `backfillAllSections` regenerates the embeddings.
|
|
98
|
+
if (exists.exists) {
|
|
99
|
+
try {
|
|
100
|
+
const info = await client.getCollection(SECTION_COLLECTION);
|
|
101
|
+
const vectors = info.config?.params?.vectors;
|
|
102
|
+
const size =
|
|
103
|
+
vectors && typeof vectors === "object" && "size" in vectors
|
|
104
|
+
? (vectors as { size?: unknown }).size
|
|
105
|
+
: undefined;
|
|
106
|
+
if (typeof size === "number" && size !== vectorSize) {
|
|
107
|
+
log.warn(
|
|
108
|
+
{ collection: SECTION_COLLECTION, expected: vectorSize, found: size },
|
|
109
|
+
"Memory v3 section collection dimension drift — deleting and recreating; embeddings will be regenerated by the next backfill",
|
|
110
|
+
);
|
|
111
|
+
await client.deleteCollection(SECTION_COLLECTION);
|
|
112
|
+
needsCreate = true;
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
log.warn(
|
|
116
|
+
{ err, collection: SECTION_COLLECTION },
|
|
117
|
+
"Failed to probe v3 section collection schema; assuming compatible",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (needsCreate) {
|
|
90
123
|
log.info(
|
|
91
124
|
{ collection: SECTION_COLLECTION, vectorSize },
|
|
92
125
|
"Creating Qdrant collection for memory v3 sections",
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read-side store for the inspector's Memory V3
|
|
3
|
-
* `memory_v3_selections` rows for a turn
|
|
4
|
-
* `<memory>` block for what v3 selected, so the inspector can
|
|
5
|
-
* selection without re-running orchestration — which would be
|
|
6
|
-
* since the hot lane is frecency-stateful and can't be reproduced
|
|
7
|
-
* fact.
|
|
2
|
+
* Read-side store for the inspector's Memory V3 panel. Reads the persisted
|
|
3
|
+
* `memory_v3_selections` rows for a turn (by the turn's message ids) and
|
|
4
|
+
* re-renders the `<memory>` block for what v3 selected, so the inspector can
|
|
5
|
+
* show the turn's selection without re-running orchestration — which would be
|
|
6
|
+
* wrong anyway, since the hot lane is frecency-stateful and can't be reproduced
|
|
7
|
+
* after the fact.
|
|
8
8
|
*
|
|
9
|
-
* The rendered text is inspector-only and NOT byte-identical to live
|
|
10
|
-
*
|
|
11
|
-
* (`renderV3CardContent`) plus an ephemeral spotlight
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* The rendered text is inspector-only and NOT byte-identical to live injection:
|
|
10
|
+
* the live injector freezes net-new compact CARDS into history
|
|
11
|
+
* (`renderV3CardContent`) plus an ephemeral spotlight. Here we re-render each
|
|
12
|
+
* selection's MATCHED SECTION — resolved from the persisted `(slug, ordinal)`
|
|
13
|
+
* against the current page — when one was recorded, falling back to the
|
|
14
|
+
* full/lead page otherwise. Section text is re-derived from the current page,
|
|
15
|
+
* so it reflects bounded page-drift if the page changed since the turn (the
|
|
16
|
+
* same approximation the v2 inspector accepts).
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
import type { MemoryV3SelectionLog } from "../../../api/responses/memory-v3-selection-log.js";
|
|
18
20
|
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
19
21
|
import { getConfig } from "../../../config/loader.js";
|
|
20
22
|
import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
|
|
23
|
+
import { readPage } from "../../../memory/v2/page-store.js";
|
|
24
|
+
import { getWorkspaceDir } from "../../../util/platform.js";
|
|
25
|
+
import { capabilityOrDiskBody } from "./capabilities.js";
|
|
26
|
+
import { sectionByOrdinal } from "./orchestrate.js";
|
|
21
27
|
import { renderV3SectionContent } from "./page-content.js";
|
|
22
28
|
import { renderMemoryBlock } from "./render-injection.js";
|
|
29
|
+
import { buildSectionIndex } from "./sections.js";
|
|
23
30
|
import {
|
|
24
31
|
type Section,
|
|
25
32
|
SELECTION_SOURCES,
|
|
@@ -31,16 +38,21 @@ const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
|
|
|
31
38
|
const MEMORY_V3_LIVE = "memory-v3-live" as const;
|
|
32
39
|
|
|
33
40
|
interface SelectionRow {
|
|
41
|
+
turn: number;
|
|
34
42
|
slug: string;
|
|
35
43
|
source: string;
|
|
36
44
|
pinned: number;
|
|
45
|
+
section_ordinal: number | null;
|
|
46
|
+
section_title: string | null;
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
const SELECTION_COLUMNS = `turn, slug, source, pinned, section_ordinal, section_title`;
|
|
50
|
+
|
|
39
51
|
function rowsForTurn(conversationId: string, turn: number): SelectionRow[] {
|
|
40
52
|
return getSqliteFrom(getDb())
|
|
41
53
|
.query(
|
|
42
54
|
/*sql*/ `
|
|
43
|
-
SELECT
|
|
55
|
+
SELECT ${SELECTION_COLUMNS} FROM memory_v3_selections
|
|
44
56
|
WHERE conversation_id = ? AND turn = ?
|
|
45
57
|
ORDER BY rowid
|
|
46
58
|
`,
|
|
@@ -48,32 +60,59 @@ function rowsForTurn(conversationId: string, turn: number): SelectionRow[] {
|
|
|
48
60
|
.all(conversationId, turn) as SelectionRow[];
|
|
49
61
|
}
|
|
50
62
|
|
|
63
|
+
function rowsForMessageIds(messageIds: string[]): SelectionRow[] {
|
|
64
|
+
if (messageIds.length === 0) return [];
|
|
65
|
+
const placeholders = messageIds.map(() => "?").join(", ");
|
|
66
|
+
return getSqliteFrom(getDb())
|
|
67
|
+
.query(
|
|
68
|
+
/*sql*/ `
|
|
69
|
+
SELECT ${SELECTION_COLUMNS} FROM memory_v3_selections
|
|
70
|
+
WHERE message_id IN (${placeholders})
|
|
71
|
+
ORDER BY rowid
|
|
72
|
+
`,
|
|
73
|
+
)
|
|
74
|
+
.all(...messageIds) as SelectionRow[];
|
|
75
|
+
}
|
|
76
|
+
|
|
51
77
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* would attribute another turn's selection (e.g. a later turn's pages and
|
|
59
|
-
* rendered block) to the inspected message, which corrupts shadow validation.
|
|
60
|
-
*
|
|
61
|
-
* Caveat: v3 rows are keyed by the orchestrator turn counter (`ctx.turnCount`)
|
|
62
|
-
* while `turn` here is the v2 memory-tracker turn. They coincide for normal
|
|
63
|
-
* turns, but if they diverge this simply yields `null` (no section) rather than
|
|
64
|
-
* wrong data. Tying v3 rows to a message id for exact per-message attribution
|
|
65
|
-
* regardless of counter drift is a documented follow-up.
|
|
66
|
-
*
|
|
67
|
-
* Selection rows are stored in selection order, so rendering them in row order
|
|
68
|
-
* reproduces the block v3 would inject.
|
|
78
|
+
* Resolve each selection's persisted matched section `(slug, ordinal)` to the
|
|
79
|
+
* concrete `Section` in the CURRENT page, so the injected block renders the
|
|
80
|
+
* matched section rather than the full page. Only slugs with a recorded ordinal
|
|
81
|
+
* are resolved (core/hot/fresh/edge selections have none and render full-page).
|
|
82
|
+
* A page edited since the turn re-derives the current section at that ordinal,
|
|
83
|
+
* or falls back to full-page when the ordinal no longer exists.
|
|
69
84
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
async function reconstructMatchedSections(
|
|
86
|
+
rows: SelectionRow[],
|
|
87
|
+
): Promise<Map<Slug, Section>> {
|
|
88
|
+
const sectionSlugs = rows
|
|
89
|
+
.filter((r) => r.section_ordinal != null)
|
|
90
|
+
.map((r) => r.slug);
|
|
91
|
+
if (sectionSlugs.length === 0) return new Map();
|
|
92
|
+
|
|
93
|
+
const workspaceDir = getWorkspaceDir();
|
|
94
|
+
const pageBody = (slug: Slug): Promise<string> =>
|
|
95
|
+
capabilityOrDiskBody(slug, async (s) => {
|
|
96
|
+
try {
|
|
97
|
+
return (await readPage(workspaceDir, s))?.body ?? "";
|
|
98
|
+
} catch {
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
const index = await buildSectionIndex(sectionSlugs, pageBody);
|
|
103
|
+
|
|
104
|
+
const sectionBySlug = new Map<Slug, Section>();
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
if (row.section_ordinal == null) continue;
|
|
107
|
+
const section = sectionByOrdinal(index, row.slug, row.section_ordinal);
|
|
108
|
+
if (section) sectionBySlug.set(row.slug, section);
|
|
109
|
+
}
|
|
110
|
+
return sectionBySlug;
|
|
111
|
+
}
|
|
75
112
|
|
|
76
|
-
|
|
113
|
+
async function buildSelectionLog(
|
|
114
|
+
rows: SelectionRow[],
|
|
115
|
+
): Promise<MemoryV3SelectionLog | null> {
|
|
77
116
|
if (rows.length === 0) return null;
|
|
78
117
|
|
|
79
118
|
const config = getConfig();
|
|
@@ -81,25 +120,19 @@ export async function getMemoryV3SelectionForInspector(
|
|
|
81
120
|
slug: r.slug,
|
|
82
121
|
source: r.source,
|
|
83
122
|
pinned: r.pinned === 1,
|
|
123
|
+
sectionOrdinal: r.section_ordinal,
|
|
124
|
+
sectionHeading: r.section_title,
|
|
84
125
|
}));
|
|
85
126
|
const slugs: Slug[] = selections.map((s) => s.slug);
|
|
86
|
-
|
|
87
|
-
// every slug, not the exact matched section live injection used. The inspector
|
|
88
|
-
// re-renders from persisted rows without re-running orchestration, and the
|
|
89
|
-
// section identity is not persisted (`memory_v3_selections` stores
|
|
90
|
-
// slug/source/pinned, not the section ordinal), so the section map is empty
|
|
91
|
-
// and `renderV3SectionContent` falls back to the full page. Persisting the
|
|
92
|
-
// injected section (a schema migration) is deliberately deferred: the A/B's
|
|
93
|
-
// primary signal is slug-level recall via `summarizeSelections`, which is
|
|
94
|
-
// exact — only this debug-oriented injected text is an approximation.
|
|
127
|
+
const sectionBySlug = await reconstructMatchedSections(rows);
|
|
95
128
|
const injectedText = await renderMemoryBlock(
|
|
96
129
|
slugs,
|
|
97
|
-
|
|
130
|
+
sectionBySlug,
|
|
98
131
|
renderV3SectionContent,
|
|
99
132
|
);
|
|
100
133
|
|
|
101
134
|
return {
|
|
102
|
-
turn,
|
|
135
|
+
turn: rows[0]!.turn,
|
|
103
136
|
live: isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config),
|
|
104
137
|
shadow: isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config),
|
|
105
138
|
selections,
|
|
@@ -107,6 +140,38 @@ export async function getMemoryV3SelectionForInspector(
|
|
|
107
140
|
};
|
|
108
141
|
}
|
|
109
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Build the inspector's v3 selection log for the inspected message's turn,
|
|
145
|
+
* keyed by the turn's message ids. This is the durable join: `writeSelections`
|
|
146
|
+
* logs rows with `message_id = NULL` and the turn-end backfill stamps them with
|
|
147
|
+
* the assistant message id, so a per-message lookup is robust against the drift
|
|
148
|
+
* between v2's tracker turn and v3's orchestrator `turnCount`. Returns `null`
|
|
149
|
+
* when no v3 rows match (e.g. a turn predating the message-id backfill, or a
|
|
150
|
+
* conversation with no v3 data). Message ids are globally unique, so no
|
|
151
|
+
* conversation scope is needed.
|
|
152
|
+
*
|
|
153
|
+
* Selection rows are stored in selection order, so rendering them in row order
|
|
154
|
+
* reproduces the block v3 would inject.
|
|
155
|
+
*/
|
|
156
|
+
export async function getMemoryV3SelectionForInspectorByMessageIds(
|
|
157
|
+
messageIds: string[],
|
|
158
|
+
): Promise<MemoryV3SelectionLog | null> {
|
|
159
|
+
return buildSelectionLog(rowsForMessageIds(messageIds));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Turn-keyed variant, retained for callers/tests that look up by an exact
|
|
164
|
+
* `(conversation, turn)`. Returns `null` when `turn` is null or there are no
|
|
165
|
+
* rows for it.
|
|
166
|
+
*/
|
|
167
|
+
export async function getMemoryV3SelectionForInspector(
|
|
168
|
+
conversationId: string,
|
|
169
|
+
turn: number | null | undefined,
|
|
170
|
+
): Promise<MemoryV3SelectionLog | null> {
|
|
171
|
+
if (turn == null) return null;
|
|
172
|
+
return buildSelectionLog(rowsForTurn(conversationId, turn));
|
|
173
|
+
}
|
|
174
|
+
|
|
110
175
|
/**
|
|
111
176
|
* Offline A/B aggregate over a conversation's logged v3 selections. Reads every
|
|
112
177
|
* `memory_v3_selections` row for the conversation (all turns) and rolls them up
|
|
@@ -55,6 +55,7 @@ import { computeHotSet } from "./hot-set.js";
|
|
|
55
55
|
import { computeLearnedEdgeGraph } from "./learned-edges.js";
|
|
56
56
|
import type { OrchestrateResult } from "./orchestrate.js";
|
|
57
57
|
import { orchestrate } from "./orchestrate.js";
|
|
58
|
+
import { MemoryV3RetrievalUnavailableError } from "./pool-select.js";
|
|
58
59
|
import { ensureSectionCollection } from "./section-dense-store.js";
|
|
59
60
|
import type { SectionNeedle } from "./section-needle.js";
|
|
60
61
|
import { buildSectionNeedle } from "./section-needle.js";
|
|
@@ -418,6 +419,11 @@ interface SelectionRow {
|
|
|
418
419
|
slug: Slug;
|
|
419
420
|
source: SelectionSource;
|
|
420
421
|
pinned: number;
|
|
422
|
+
/** Ordinal of the matched section a finder lane surfaced; null for
|
|
423
|
+
* core/hot/fresh/edge selections with no matched section. */
|
|
424
|
+
sectionOrdinal: number | null;
|
|
425
|
+
/** Heading of the matched section; null when there is no matched section. */
|
|
426
|
+
sectionTitle: string | null;
|
|
421
427
|
}
|
|
422
428
|
|
|
423
429
|
/**
|
|
@@ -438,17 +444,24 @@ export function attributeSelections(result: OrchestrateResult): SelectionRow[] {
|
|
|
438
444
|
const finderLane = new Map(
|
|
439
445
|
result.lanes.finder.map((c) => [c.slug, c.lane] as const),
|
|
440
446
|
);
|
|
441
|
-
return result.selections.map((sel) =>
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
447
|
+
return result.selections.map((sel) => {
|
|
448
|
+
// The matched section is populated only for finder-lane hits (including
|
|
449
|
+
// hits on core/hot pages); core/hot/fresh/edge-only selections have none.
|
|
450
|
+
const section = result.matchedSections.get(sel.slug);
|
|
451
|
+
return {
|
|
452
|
+
slug: sel.slug,
|
|
453
|
+
source: core.has(sel.slug)
|
|
454
|
+
? ("core" as const)
|
|
455
|
+
: hot.has(sel.slug)
|
|
456
|
+
? ("hot" as const)
|
|
457
|
+
: fresh.has(sel.slug)
|
|
458
|
+
? ("fresh" as const)
|
|
459
|
+
: (finderLane.get(sel.slug) ?? "needle"),
|
|
460
|
+
pinned: sel.pinned ? 1 : 0,
|
|
461
|
+
sectionOrdinal: section?.ordinal ?? null,
|
|
462
|
+
sectionTitle: section?.title ?? null,
|
|
463
|
+
};
|
|
464
|
+
});
|
|
452
465
|
}
|
|
453
466
|
|
|
454
467
|
/** Write the attributed selection rows to `memory_v3_selections`. */
|
|
@@ -461,17 +474,51 @@ export function writeSelections(
|
|
|
461
474
|
const raw = getSqliteFrom(getDb());
|
|
462
475
|
// PK is (conversation_id, turn, slug); OR REPLACE keeps the write
|
|
463
476
|
// idempotent if the same turn is observed twice (e.g. a retried turn).
|
|
477
|
+
// `message_id` is written NULL here (the assistant message does not exist at
|
|
478
|
+
// injection time) and stamped at turn end by
|
|
479
|
+
// `backfillMemoryV3SelectionMessageId`.
|
|
464
480
|
const stmt = raw.query(/*sql*/ `
|
|
465
481
|
INSERT OR REPLACE INTO memory_v3_selections (
|
|
466
|
-
conversation_id, turn, slug, source, pinned, created_at
|
|
467
|
-
|
|
482
|
+
conversation_id, turn, slug, source, pinned, created_at,
|
|
483
|
+
message_id, section_ordinal, section_title
|
|
484
|
+
) VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
468
485
|
`);
|
|
469
486
|
const now = Date.now();
|
|
470
487
|
for (const row of rows) {
|
|
471
|
-
stmt.run(
|
|
488
|
+
stmt.run(
|
|
489
|
+
conversationId,
|
|
490
|
+
turn,
|
|
491
|
+
row.slug,
|
|
492
|
+
row.source,
|
|
493
|
+
row.pinned,
|
|
494
|
+
now,
|
|
495
|
+
row.sectionOrdinal,
|
|
496
|
+
row.sectionTitle,
|
|
497
|
+
);
|
|
472
498
|
}
|
|
473
499
|
}
|
|
474
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Stamp the turn's assistant message id onto the selection rows just written
|
|
503
|
+
* for it. Mirrors the v2 activation-log backfill: `writeSelections` writes
|
|
504
|
+
* `message_id = NULL` at injection time, and this runs at turn end once the
|
|
505
|
+
* assistant message exists. Relies on the single-threaded-per-conversation turn
|
|
506
|
+
* invariant — every NULL-`message_id` row for the conversation belongs to the
|
|
507
|
+
* turn that just finished. Lets the inspector look v3 selections up by the
|
|
508
|
+
* turn's message ids (robust against v2/v3 turn-counter drift).
|
|
509
|
+
*/
|
|
510
|
+
export function backfillMemoryV3SelectionMessageId(
|
|
511
|
+
conversationId: string,
|
|
512
|
+
assistantMessageId: string,
|
|
513
|
+
): void {
|
|
514
|
+
getSqliteFrom(getDb())
|
|
515
|
+
.query(
|
|
516
|
+
/*sql*/ `UPDATE memory_v3_selections SET message_id = ?
|
|
517
|
+
WHERE conversation_id = ? AND message_id IS NULL`,
|
|
518
|
+
)
|
|
519
|
+
.run(assistantMessageId, conversationId);
|
|
520
|
+
}
|
|
521
|
+
|
|
475
522
|
/**
|
|
476
523
|
* Run v3 orchestration for one turn and log the selection set, returning the
|
|
477
524
|
* orchestrate result so a live caller can render it. Never throws — all
|
|
@@ -530,6 +577,16 @@ export async function observeTurn(
|
|
|
530
577
|
writeSelections(conversationId, turnIndex, rows);
|
|
531
578
|
return result;
|
|
532
579
|
} catch (err) {
|
|
580
|
+
// An INFRASTRUCTURE failure (the selector lost its provider — e.g. a
|
|
581
|
+
// transient CES credential blip) must NOT be silently swallowed: re-throw
|
|
582
|
+
// so the LIVE injector hard-fails the turn (a clean, retryable failure)
|
|
583
|
+
// rather than shipping it with no `<memory>` block. The shadow/observation
|
|
584
|
+
// callers (the injector in shadow mode, runShadowObservation) catch this
|
|
585
|
+
// and swallow it, so observation never fails a turn. Other (non-infra)
|
|
586
|
+
// errors stay non-fatal and degrade to no v3 block, as before.
|
|
587
|
+
if (err instanceof MemoryV3RetrievalUnavailableError) {
|
|
588
|
+
throw err;
|
|
589
|
+
}
|
|
533
590
|
log.warn(
|
|
534
591
|
{ err: err instanceof Error ? err.message : String(err), conversationId },
|
|
535
592
|
"memory-v3 orchestration failed (non-fatal)",
|
|
@@ -553,5 +610,11 @@ export async function runShadowObservation(
|
|
|
553
610
|
const config = getConfig();
|
|
554
611
|
if (config.memory.enabled === false) return;
|
|
555
612
|
if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config)) return;
|
|
556
|
-
|
|
613
|
+
try {
|
|
614
|
+
await observeTurn(conversationId, turnIndex);
|
|
615
|
+
} catch {
|
|
616
|
+
// Shadow observation is fire-and-forget and must NEVER fail a turn.
|
|
617
|
+
// `observeTurn` now re-throws infra failures so the LIVE injector can
|
|
618
|
+
// hard-fail on them; here (observation only) we swallow them.
|
|
619
|
+
}
|
|
557
620
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `post-tool-use` hook: when an interactive turn accumulates several
|
|
3
|
+
* tool-call rounds without the model having shown a `task_progress` card,
|
|
4
|
+
* surface a soft notice via `additionalContext` reminding it to show progress.
|
|
5
|
+
*
|
|
6
|
+
* Motivation: the system prompt and the `ui_show` tool description both ask
|
|
7
|
+
* the model to show a `task_progress` card on long multi-step turns, but
|
|
8
|
+
* weaker models (e.g. MiniMax M3) disregard the static instruction and run
|
|
9
|
+
* 30+ tool calls with no user-visible progress. A reminder injected mid-turn,
|
|
10
|
+
* right after a tool result, is far more salient than static prompt text.
|
|
11
|
+
*
|
|
12
|
+
* The nudge is strictly best-effort:
|
|
13
|
+
* - It fires at most once per turn (no nagging).
|
|
14
|
+
* - It is advisory — the model may decline if it is about to finish or judges
|
|
15
|
+
* a card unnecessary.
|
|
16
|
+
* - A failed or ignored card never blocks the turn.
|
|
17
|
+
*
|
|
18
|
+
* It is scoped to weaker open models (Kimi, DeepSeek, MiniMax) that disregard
|
|
19
|
+
* the static progress-card instruction; capable models follow the prompt and
|
|
20
|
+
* never need the reminder. It also self-targets: a model that already showed a
|
|
21
|
+
* card this turn is never nudged.
|
|
22
|
+
*
|
|
23
|
+
* The turn state is derived from conversation history on every call (mirroring
|
|
24
|
+
* the tool-error and exploration-drift plugins) so the signal survives
|
|
25
|
+
* mid-turn compaction rewriting the array. The current turn is the trailing
|
|
26
|
+
* window bounded by the last genuine user message (a user row with no
|
|
27
|
+
* tool_result blocks). Within it we count tool-use rounds and detect whether a
|
|
28
|
+
* `ui_show` task_progress card was issued.
|
|
29
|
+
*
|
|
30
|
+
* Gating:
|
|
31
|
+
* - weaker open models only (Kimi, DeepSeek, MiniMax) — checked first on the
|
|
32
|
+
* hot path so capable-model turns short-circuit immediately.
|
|
33
|
+
* - mainAgent call site only — background turns (wake, title-gen, memory) and
|
|
34
|
+
* subagents have no live user watching, so a progress card is pointless.
|
|
35
|
+
* - the client must support dynamic UI — otherwise `ui_show` is blocked
|
|
36
|
+
* client-side and the nudge would only provoke a wasted, erroring call.
|
|
37
|
+
*
|
|
38
|
+
* The call-site, capability, and subagent gates are resolved lazily on the
|
|
39
|
+
* would-nudge path; the model gate is a cheap regex test run up front.
|
|
40
|
+
*
|
|
41
|
+
* Dedup uses a per-conversation high-water mark of the round count at the last
|
|
42
|
+
* nudge: a non-zero mark means "already nudged this turn", which also dedupes
|
|
43
|
+
* the parallel tool results of one batch (they observe identical history and
|
|
44
|
+
* compute the same round count). The mark resets when the round count drops
|
|
45
|
+
* below it (a new turn restarts counting low).
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
|
|
49
|
+
|
|
50
|
+
import type { ContentBlock, Message } from "../../../../providers/types.js";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Canonical nudge notice. Module-level constant so tests and wrapping plugins
|
|
54
|
+
* can match it without duplicating the string. Shown to the model as
|
|
55
|
+
* provider-only context, never to the user. Deliberately soft: coarse steps
|
|
56
|
+
* are fine and the model may skip it when wrapping up.
|
|
57
|
+
*/
|
|
58
|
+
export const TASK_PROGRESS_NUDGE_TEXT =
|
|
59
|
+
'<system_notice>You are several tool calls into this turn and have not shown the user a progress card. If you are doing multi-step work, call ui_show now with surface_type "card" and template "task_progress" (coarse steps are fine — a rough "Working on X" beats no signal) so the user can see what is happening, and keep it updated with ui_update as you go. Skip this if you are about to finish; never let it interrupt the actual work.</system_notice>';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Number of tool-use rounds in a turn, with no task_progress card shown, that
|
|
63
|
+
* triggers the nudge. Tuned conservatively: a turn of one or two rounds (the
|
|
64
|
+
* common simple case) is never touched; only a clearly multi-step turn is
|
|
65
|
+
* nudged, and only once. Lower from telemetry if cards still arrive too late.
|
|
66
|
+
*/
|
|
67
|
+
export const TASK_PROGRESS_NUDGE_ROUND_THRESHOLD = 3;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Weaker open models that disregard the static progress-card instruction and
|
|
71
|
+
* so get the mid-turn nudge: Kimi, DeepSeek, and MiniMax. Family-level matching
|
|
72
|
+
* spans provider naming conventions (OpenRouter `moonshotai/kimi-k2.6`,
|
|
73
|
+
* `deepseek/deepseek-chat`, `minimax/minimax-m3`; Fireworks
|
|
74
|
+
* `accounts/fireworks/models/minimax-m3`, `kimi-k2p6`). Extend as other models
|
|
75
|
+
* show the same gap. Capable models (Claude, GPT) follow the prompt and are
|
|
76
|
+
* intentionally excluded.
|
|
77
|
+
*/
|
|
78
|
+
const WEAK_MODEL_PATTERN = /kimi|deepseek|minimax/i;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Round count at the last nudge, per conversation. A non-zero entry means the
|
|
82
|
+
* turn has already been nudged; it resets when the round count drops below the
|
|
83
|
+
* mark (a new turn). Mirrors the exploration-drift high-water mark so parallel
|
|
84
|
+
* results of one batch dedupe to a single notice.
|
|
85
|
+
*/
|
|
86
|
+
const lastNudgedRoundsByConversation = new Map<string, number>();
|
|
87
|
+
|
|
88
|
+
/** Test-only: clear the per-conversation nudge high-water marks. */
|
|
89
|
+
export function resetTaskProgressNudgeStateForTests(): void {
|
|
90
|
+
lastNudgedRoundsByConversation.clear();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* True when a `ui_show` tool input shows a `task_progress` card — accepting the
|
|
95
|
+
* template either at the top level or nested under `data`, mirroring the
|
|
96
|
+
* server-side normalization tolerance.
|
|
97
|
+
*/
|
|
98
|
+
function isTaskProgressShowInput(input: unknown): boolean {
|
|
99
|
+
if (input === null || typeof input !== "object") return false;
|
|
100
|
+
const record = input as Record<string, unknown>;
|
|
101
|
+
if (record.template === "task_progress") return true;
|
|
102
|
+
const data = record.data;
|
|
103
|
+
return (
|
|
104
|
+
data !== null &&
|
|
105
|
+
typeof data === "object" &&
|
|
106
|
+
(data as Record<string, unknown>).template === "task_progress"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Scan the trailing turn (walking back to the last genuine user message) for
|
|
112
|
+
* the number of tool-use rounds and whether a task_progress card was shown.
|
|
113
|
+
* The current round's assistant tool_use is already in history; its result is
|
|
114
|
+
* not, so the count includes the current round.
|
|
115
|
+
*/
|
|
116
|
+
function scanTurn(messages: ReadonlyArray<Message>): {
|
|
117
|
+
rounds: number;
|
|
118
|
+
taskProgressShown: boolean;
|
|
119
|
+
} {
|
|
120
|
+
let rounds = 0;
|
|
121
|
+
let taskProgressShown = false;
|
|
122
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
123
|
+
const message = messages[i];
|
|
124
|
+
if (message.role === "user") {
|
|
125
|
+
const carriesToolResult = message.content.some(
|
|
126
|
+
(block: ContentBlock) =>
|
|
127
|
+
block.type === "tool_result" ||
|
|
128
|
+
block.type === "web_search_tool_result",
|
|
129
|
+
);
|
|
130
|
+
if (!carriesToolResult) break; // genuine user prompt — turn boundary
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (message.role !== "assistant") continue;
|
|
134
|
+
let hasToolUse = false;
|
|
135
|
+
for (const block of message.content) {
|
|
136
|
+
if (block.type !== "tool_use") continue;
|
|
137
|
+
hasToolUse = true;
|
|
138
|
+
if (block.name === "ui_show" && isTaskProgressShowInput(block.input)) {
|
|
139
|
+
taskProgressShown = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (hasToolUse) rounds++;
|
|
143
|
+
}
|
|
144
|
+
return { rounds, taskProgressShown };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
|
|
148
|
+
if (!WEAK_MODEL_PATTERN.test(ctx.model)) return;
|
|
149
|
+
|
|
150
|
+
const { rounds, taskProgressShown } = scanTurn(ctx.messages);
|
|
151
|
+
|
|
152
|
+
let lastNudged = lastNudgedRoundsByConversation.get(ctx.conversationId) ?? 0;
|
|
153
|
+
if (rounds < lastNudged) {
|
|
154
|
+
// New turn (round count restarted low) — drop the stale mark.
|
|
155
|
+
lastNudgedRoundsByConversation.delete(ctx.conversationId);
|
|
156
|
+
lastNudged = 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A card now exists this turn: clear any stale mark and never nudge.
|
|
160
|
+
if (taskProgressShown) {
|
|
161
|
+
if (lastNudged !== 0) {
|
|
162
|
+
lastNudgedRoundsByConversation.delete(ctx.conversationId);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (rounds < TASK_PROGRESS_NUDGE_ROUND_THRESHOLD) return;
|
|
168
|
+
if (lastNudged !== 0) return; // already nudged this turn
|
|
169
|
+
|
|
170
|
+
// Resolve gating lazily on the rare would-nudge path to keep the hot path
|
|
171
|
+
// free of the conversation-registry and subagent module graphs.
|
|
172
|
+
const { findConversation } =
|
|
173
|
+
await import("../../../../daemon/conversation-registry.js");
|
|
174
|
+
const conversation = findConversation(ctx.conversationId);
|
|
175
|
+
if (!conversation) return;
|
|
176
|
+
|
|
177
|
+
if (
|
|
178
|
+
conversation.currentCallSite &&
|
|
179
|
+
conversation.currentCallSite !== "mainAgent"
|
|
180
|
+
) {
|
|
181
|
+
return; // background call site — no live user watching
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const capabilities =
|
|
185
|
+
conversation.currentTurnChannelCapabilities ??
|
|
186
|
+
conversation.channelCapabilities;
|
|
187
|
+
if (capabilities && capabilities.supportsDynamicUi === false) {
|
|
188
|
+
return; // client cannot render surfaces — a nudge would only error
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { getSubagentManager } = await import("../../../../subagent/index.js");
|
|
192
|
+
if (getSubagentManager().getParentInfo(ctx.conversationId) !== undefined) {
|
|
193
|
+
return; // subagents have no live user
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
lastNudgedRoundsByConversation.set(ctx.conversationId, rounds);
|
|
197
|
+
ctx.logger.info(
|
|
198
|
+
{ plugin: "task-progress-nudge", rounds },
|
|
199
|
+
"Multi-step turn with no task_progress card — nudging the model to show progress",
|
|
200
|
+
);
|
|
201
|
+
ctx.additionalContext = ctx.additionalContext
|
|
202
|
+
? `${ctx.additionalContext}\n${TASK_PROGRESS_NUDGE_TEXT}`
|
|
203
|
+
: TASK_PROGRESS_NUDGE_TEXT;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export default postToolUse;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "default-task-progress-nudge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "First-party default plugin contributing a post-tool-use hook that nudges the model to show a task_progress card when an interactive turn accumulates several tool-call rounds without one.",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./register.ts",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20.12.0"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@vellumai/plugin-api": "^0.8.0"
|
|
14
|
+
}
|
|
15
|
+
}
|