@vellumai/assistant 0.8.10 → 0.8.11-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/bun.lock +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickHouse compaction log writer.
|
|
3
|
+
*
|
|
4
|
+
* Consumes the agent loop's compaction start/end event pair
|
|
5
|
+
* (`context_compacting` / `compaction_completed`, correlated by
|
|
6
|
+
* `compactionId`) and writes one row per event to the ClickHouse table
|
|
7
|
+
* configured under `compactionLogs` in workspace config. Disabled unless
|
|
8
|
+
* `compactionLogs.destination === "clickhouse"` — there is no SQLite
|
|
9
|
+
* destination.
|
|
10
|
+
*
|
|
11
|
+
* The table is event-sourced rather than upserted: a `start` row records
|
|
12
|
+
* that an attempt began (so attempts that never complete — pipeline throw,
|
|
13
|
+
* turn abort — are still visible), and an `end` row carries the full
|
|
14
|
+
* `ContextWindowResult`. Consumers pair rows by `compaction_id`.
|
|
15
|
+
*
|
|
16
|
+
* Writes are strictly best-effort: every entry point swallows and logs
|
|
17
|
+
* failures so a ClickHouse outage can never abort a turn. URL and password
|
|
18
|
+
* come from the credential store (`clickhouse:url`, `clickhouse:password`),
|
|
19
|
+
* matching the `llmRequestLogs` mirror convention; database/table/user come
|
|
20
|
+
* from config. The table is created lazily with `CREATE TABLE IF NOT EXISTS`
|
|
21
|
+
* on first write so opting in requires no out-of-band DDL.
|
|
22
|
+
*/
|
|
23
|
+
import type { AgentEvent } from "../agent/loop.js";
|
|
24
|
+
import { getConfig } from "../config/loader.js";
|
|
25
|
+
import type { CompactionLogsClickHouseConfig } from "../config/schemas/compaction-logs.js";
|
|
26
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
27
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
28
|
+
import { getLogger } from "../util/logger.js";
|
|
29
|
+
|
|
30
|
+
const log = getLogger("compaction-log-writer");
|
|
31
|
+
|
|
32
|
+
export type CompactionStartEvent = Extract<
|
|
33
|
+
AgentEvent,
|
|
34
|
+
{ type: "context_compacting" }
|
|
35
|
+
>;
|
|
36
|
+
export type CompactionEndEvent = Extract<
|
|
37
|
+
AgentEvent,
|
|
38
|
+
{ type: "compaction_completed" }
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
/** Cap stored summary text so a pathological summary can't bloat rows. */
|
|
42
|
+
const SUMMARY_TEXT_MAX_CHARS = 4000;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Wire row written as one JSONEachRow line. Sentinels follow the
|
|
46
|
+
* llm_request_logs mirror convention: ClickHouse columns are non-Nullable,
|
|
47
|
+
* so `''` means "not set" for strings and `-1` means "unknown" for counts
|
|
48
|
+
* that only exist on one phase of the pair.
|
|
49
|
+
*/
|
|
50
|
+
interface CompactionLogRow {
|
|
51
|
+
assistant_id: string;
|
|
52
|
+
conversation_id: string;
|
|
53
|
+
compaction_id: string;
|
|
54
|
+
request_id: string;
|
|
55
|
+
phase: "start" | "end";
|
|
56
|
+
trigger: string;
|
|
57
|
+
started_at: number;
|
|
58
|
+
finished_at: number;
|
|
59
|
+
duration_ms: number;
|
|
60
|
+
pre_message_count: number;
|
|
61
|
+
basis_message_count: number;
|
|
62
|
+
compacted: number;
|
|
63
|
+
previous_estimated_input_tokens: number;
|
|
64
|
+
estimated_input_tokens: number;
|
|
65
|
+
max_input_tokens: number;
|
|
66
|
+
threshold_tokens: number;
|
|
67
|
+
compacted_messages: number;
|
|
68
|
+
compacted_persisted_messages: number;
|
|
69
|
+
preserved_tail_messages: number;
|
|
70
|
+
summary_calls: number;
|
|
71
|
+
summary_input_tokens: number;
|
|
72
|
+
summary_output_tokens: number;
|
|
73
|
+
summary_model: string;
|
|
74
|
+
summary_failed: number;
|
|
75
|
+
reason: string;
|
|
76
|
+
exhausted: number;
|
|
77
|
+
injection_mode: string;
|
|
78
|
+
auto_compress_applied: number;
|
|
79
|
+
summary_text: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function readCredentialOrNull(
|
|
83
|
+
service: string,
|
|
84
|
+
field: string,
|
|
85
|
+
): Promise<string | null> {
|
|
86
|
+
const value = await getSecureKeyAsync(credentialKey(service, field));
|
|
87
|
+
return value ?? null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Injectable fetch override for tests. Defaults to globalThis.fetch. */
|
|
91
|
+
export type CompactionLogFetch = typeof fetch;
|
|
92
|
+
|
|
93
|
+
export interface ClickHouseCompactionLogWriterDeps {
|
|
94
|
+
/** Override the credential read for `clickhouse:url`. */
|
|
95
|
+
resolveUrl?: () => Promise<string | null>;
|
|
96
|
+
/** Override the credential read for `clickhouse:password`. */
|
|
97
|
+
resolvePassword?: () => Promise<string | null>;
|
|
98
|
+
/** Override the credential read for `vellum:platform_assistant_id`. */
|
|
99
|
+
resolveAssistantId?: () => Promise<string | null>;
|
|
100
|
+
/** Override fetch for testing. */
|
|
101
|
+
fetchImpl?: CompactionLogFetch;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class ClickHouseCompactionLogWriter {
|
|
105
|
+
private cachedUrl: string | null = null;
|
|
106
|
+
private cachedPassword: string | null = null;
|
|
107
|
+
private cachedAssistantId: string | null = null;
|
|
108
|
+
private ensureTablePromise: Promise<void> | null = null;
|
|
109
|
+
|
|
110
|
+
private readonly resolveUrl: () => Promise<string | null>;
|
|
111
|
+
private readonly resolvePassword: () => Promise<string | null>;
|
|
112
|
+
private readonly resolveAssistantId: () => Promise<string | null>;
|
|
113
|
+
private readonly fetchImpl: CompactionLogFetch;
|
|
114
|
+
|
|
115
|
+
constructor(
|
|
116
|
+
private readonly config: CompactionLogsClickHouseConfig,
|
|
117
|
+
deps: ClickHouseCompactionLogWriterDeps = {},
|
|
118
|
+
) {
|
|
119
|
+
this.resolveUrl =
|
|
120
|
+
deps.resolveUrl ?? (() => readCredentialOrNull("clickhouse", "url"));
|
|
121
|
+
this.resolvePassword =
|
|
122
|
+
deps.resolvePassword ??
|
|
123
|
+
(() => readCredentialOrNull("clickhouse", "password"));
|
|
124
|
+
this.resolveAssistantId =
|
|
125
|
+
deps.resolveAssistantId ??
|
|
126
|
+
(() => readCredentialOrNull("vellum", "platform_assistant_id"));
|
|
127
|
+
this.fetchImpl = deps.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async writeStart(
|
|
131
|
+
conversationId: string,
|
|
132
|
+
event: CompactionStartEvent,
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
await this.insert({
|
|
135
|
+
...emptyRow(await this.assistantId(), conversationId, event),
|
|
136
|
+
phase: "start",
|
|
137
|
+
pre_message_count: event.messages.length,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async writeEnd(
|
|
142
|
+
conversationId: string,
|
|
143
|
+
event: CompactionEndEvent,
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
const r = event.result;
|
|
146
|
+
await this.insert({
|
|
147
|
+
...emptyRow(await this.assistantId(), conversationId, event),
|
|
148
|
+
phase: "end",
|
|
149
|
+
finished_at: event.finishedAt,
|
|
150
|
+
duration_ms: event.finishedAt - event.startedAt,
|
|
151
|
+
basis_message_count: event.messages.length,
|
|
152
|
+
compacted: r.compacted ? 1 : 0,
|
|
153
|
+
previous_estimated_input_tokens: r.previousEstimatedInputTokens,
|
|
154
|
+
estimated_input_tokens: r.estimatedInputTokens,
|
|
155
|
+
max_input_tokens: r.maxInputTokens,
|
|
156
|
+
threshold_tokens: r.thresholdTokens,
|
|
157
|
+
compacted_messages: r.compactedMessages,
|
|
158
|
+
compacted_persisted_messages: r.compactedPersistedMessages,
|
|
159
|
+
preserved_tail_messages: r.preservedTailMessages ?? -1,
|
|
160
|
+
summary_calls: r.summaryCalls,
|
|
161
|
+
summary_input_tokens: r.summaryInputTokens,
|
|
162
|
+
summary_output_tokens: r.summaryOutputTokens,
|
|
163
|
+
summary_model: r.summaryModel,
|
|
164
|
+
summary_failed:
|
|
165
|
+
r.summaryFailed === undefined ? -1 : r.summaryFailed ? 1 : 0,
|
|
166
|
+
reason: r.reason ?? "",
|
|
167
|
+
exhausted: r.exhausted ? 1 : 0,
|
|
168
|
+
injection_mode: r.injectionMode ?? "",
|
|
169
|
+
auto_compress_applied: r.autoCompressApplied ? 1 : 0,
|
|
170
|
+
summary_text: r.summaryText.slice(0, SUMMARY_TEXT_MAX_CHARS),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async insert(row: CompactionLogRow): Promise<void> {
|
|
175
|
+
await this.ensureTable();
|
|
176
|
+
await this.exec(
|
|
177
|
+
`INSERT INTO ${this.tableRef()} FORMAT JSONEachRow`,
|
|
178
|
+
JSON.stringify(row),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create the table on first write so opting in is self-serve. Idempotent
|
|
184
|
+
* (`IF NOT EXISTS`) and memoized per writer instance; a rejected attempt
|
|
185
|
+
* clears the memo so the next write retries.
|
|
186
|
+
*/
|
|
187
|
+
private ensureTable(): Promise<void> {
|
|
188
|
+
if (!this.ensureTablePromise) {
|
|
189
|
+
this.ensureTablePromise = this.exec(
|
|
190
|
+
`CREATE TABLE IF NOT EXISTS ${this.tableRef()} (
|
|
191
|
+
assistant_id String,
|
|
192
|
+
conversation_id String,
|
|
193
|
+
compaction_id String,
|
|
194
|
+
request_id String,
|
|
195
|
+
phase String,
|
|
196
|
+
trigger String,
|
|
197
|
+
started_at DateTime64(3),
|
|
198
|
+
finished_at DateTime64(3),
|
|
199
|
+
duration_ms Int64,
|
|
200
|
+
pre_message_count Int64,
|
|
201
|
+
basis_message_count Int64,
|
|
202
|
+
compacted UInt8,
|
|
203
|
+
previous_estimated_input_tokens Int64,
|
|
204
|
+
estimated_input_tokens Int64,
|
|
205
|
+
max_input_tokens Int64,
|
|
206
|
+
threshold_tokens Int64,
|
|
207
|
+
compacted_messages Int64,
|
|
208
|
+
compacted_persisted_messages Int64,
|
|
209
|
+
preserved_tail_messages Int64,
|
|
210
|
+
summary_calls Int64,
|
|
211
|
+
summary_input_tokens Int64,
|
|
212
|
+
summary_output_tokens Int64,
|
|
213
|
+
summary_model String,
|
|
214
|
+
summary_failed Int8,
|
|
215
|
+
reason String,
|
|
216
|
+
exhausted UInt8,
|
|
217
|
+
injection_mode String,
|
|
218
|
+
auto_compress_applied UInt8,
|
|
219
|
+
summary_text String,
|
|
220
|
+
created_at DateTime64(3) DEFAULT now64(3)
|
|
221
|
+
) ENGINE = MergeTree
|
|
222
|
+
ORDER BY (assistant_id, conversation_id, started_at, compaction_id)`,
|
|
223
|
+
).catch((err: unknown) => {
|
|
224
|
+
this.ensureTablePromise = null;
|
|
225
|
+
throw err;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return this.ensureTablePromise;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private tableRef(): string {
|
|
232
|
+
// Database is set via the `database=` URL param in `exec`, so only the
|
|
233
|
+
// table identifier needs quoting. Backtick-quote to tolerate
|
|
234
|
+
// non-default names with special characters.
|
|
235
|
+
return `\`${this.config.table.replace(/`/g, "``")}\``;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async exec(sql: string, body?: string): Promise<void> {
|
|
239
|
+
const baseUrl = await this.url();
|
|
240
|
+
const password = await this.password();
|
|
241
|
+
|
|
242
|
+
let target: URL;
|
|
243
|
+
try {
|
|
244
|
+
target = new URL(baseUrl);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`clickhouse:url is not a valid URL: ${err instanceof Error ? err.message : String(err)}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
target.searchParams.set("database", this.config.database);
|
|
251
|
+
// For INSERTs the statement goes in the `query` param and the row data
|
|
252
|
+
// in the body; DDL ships as the body itself.
|
|
253
|
+
// Integer timestamps land correctly in DateTime64(3) columns: ClickHouse
|
|
254
|
+
// treats integers inserted into DateTime64 as appropriately scaled Unix
|
|
255
|
+
// timestamps, so epoch-millis map 1:1 at scale 3.
|
|
256
|
+
if (body !== undefined) {
|
|
257
|
+
target.searchParams.set("query", sql);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const auth =
|
|
261
|
+
"Basic " +
|
|
262
|
+
Buffer.from(`${this.config.user}:${password}`, "utf8").toString("base64");
|
|
263
|
+
|
|
264
|
+
const res = await this.fetchImpl(target.toString(), {
|
|
265
|
+
method: "POST",
|
|
266
|
+
headers: { Authorization: auth, "Content-Type": "text/plain" },
|
|
267
|
+
body: body ?? sql,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (!res.ok) {
|
|
271
|
+
const text = await res.text().catch(() => "");
|
|
272
|
+
throw new Error(
|
|
273
|
+
`ClickHouse compaction-log write failed (HTTP ${res.status}): ${text.slice(0, 500)}`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
// Drain the body so the connection can be reused.
|
|
277
|
+
await res.text().catch(() => "");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private async assistantId(): Promise<string> {
|
|
281
|
+
if (this.cachedAssistantId) return this.cachedAssistantId;
|
|
282
|
+
const val = await this.resolveAssistantId();
|
|
283
|
+
if (!val) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
"vellum:platform_assistant_id credential is required when compactionLogs.destination=clickhouse",
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
this.cachedAssistantId = val;
|
|
289
|
+
return val;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private async url(): Promise<string> {
|
|
293
|
+
if (this.cachedUrl) return this.cachedUrl;
|
|
294
|
+
const val = await this.resolveUrl();
|
|
295
|
+
if (!val) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
"clickhouse:url credential is required when compactionLogs.destination=clickhouse",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
this.cachedUrl = val;
|
|
301
|
+
return val;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private async password(): Promise<string> {
|
|
305
|
+
if (this.cachedPassword) return this.cachedPassword;
|
|
306
|
+
const val = await this.resolvePassword();
|
|
307
|
+
if (!val) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
"clickhouse:password credential is required when compactionLogs.destination=clickhouse",
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
this.cachedPassword = val;
|
|
313
|
+
return val;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Shared row scaffold with phase-dependent fields at their sentinels. */
|
|
318
|
+
function emptyRow(
|
|
319
|
+
assistantId: string,
|
|
320
|
+
conversationId: string,
|
|
321
|
+
event: CompactionStartEvent | CompactionEndEvent,
|
|
322
|
+
): CompactionLogRow {
|
|
323
|
+
return {
|
|
324
|
+
assistant_id: assistantId,
|
|
325
|
+
conversation_id: conversationId,
|
|
326
|
+
compaction_id: event.compactionId,
|
|
327
|
+
request_id: event.requestId,
|
|
328
|
+
phase: "start",
|
|
329
|
+
trigger: event.trigger,
|
|
330
|
+
started_at: event.startedAt,
|
|
331
|
+
finished_at: 0,
|
|
332
|
+
duration_ms: -1,
|
|
333
|
+
pre_message_count: -1,
|
|
334
|
+
basis_message_count: -1,
|
|
335
|
+
compacted: 0,
|
|
336
|
+
previous_estimated_input_tokens: -1,
|
|
337
|
+
estimated_input_tokens: -1,
|
|
338
|
+
max_input_tokens: -1,
|
|
339
|
+
threshold_tokens: -1,
|
|
340
|
+
compacted_messages: -1,
|
|
341
|
+
compacted_persisted_messages: -1,
|
|
342
|
+
preserved_tail_messages: -1,
|
|
343
|
+
summary_calls: -1,
|
|
344
|
+
summary_input_tokens: -1,
|
|
345
|
+
summary_output_tokens: -1,
|
|
346
|
+
summary_model: "",
|
|
347
|
+
summary_failed: -1,
|
|
348
|
+
reason: "",
|
|
349
|
+
exhausted: 0,
|
|
350
|
+
injection_mode: "",
|
|
351
|
+
auto_compress_applied: 0,
|
|
352
|
+
summary_text: "",
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Writer cache keyed by the serialized connection config so config edits
|
|
358
|
+
* take effect on the next event without restarting, while steady-state
|
|
359
|
+
* writes reuse one instance (and its cached credentials / ensured table).
|
|
360
|
+
*/
|
|
361
|
+
let cachedWriter: {
|
|
362
|
+
key: string;
|
|
363
|
+
writer: ClickHouseCompactionLogWriter;
|
|
364
|
+
} | null = null;
|
|
365
|
+
|
|
366
|
+
function getWriter(): ClickHouseCompactionLogWriter | null {
|
|
367
|
+
const cfg = getConfig().compactionLogs;
|
|
368
|
+
if (!cfg || cfg.destination !== "clickhouse") return null;
|
|
369
|
+
const key = JSON.stringify(cfg.clickhouse);
|
|
370
|
+
if (!cachedWriter || cachedWriter.key !== key) {
|
|
371
|
+
cachedWriter = {
|
|
372
|
+
key,
|
|
373
|
+
writer: new ClickHouseCompactionLogWriter(cfg.clickhouse),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return cachedWriter.writer;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Test hook: drop the memoized writer so config/dep changes are picked up. */
|
|
380
|
+
export function resetCompactionLogWriterForTests(): void {
|
|
381
|
+
cachedWriter = null;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Record a compaction start event. Fire-and-forget: resolves immediately
|
|
386
|
+
* when logging is disabled and never throws.
|
|
387
|
+
*/
|
|
388
|
+
export function recordCompactionStartBestEffort(
|
|
389
|
+
conversationId: string,
|
|
390
|
+
event: CompactionStartEvent,
|
|
391
|
+
): void {
|
|
392
|
+
const writer = getWriter();
|
|
393
|
+
if (!writer) return;
|
|
394
|
+
writer.writeStart(conversationId, event).catch((err: unknown) => {
|
|
395
|
+
log.warn(
|
|
396
|
+
{ err, conversationId, compactionId: event.compactionId },
|
|
397
|
+
"Failed to write compaction start log (non-fatal)",
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Record a compaction end event. Fire-and-forget: resolves immediately
|
|
404
|
+
* when logging is disabled and never throws.
|
|
405
|
+
*/
|
|
406
|
+
export function recordCompactionEndBestEffort(
|
|
407
|
+
conversationId: string,
|
|
408
|
+
event: CompactionEndEvent,
|
|
409
|
+
): void {
|
|
410
|
+
const writer = getWriter();
|
|
411
|
+
if (!writer) return;
|
|
412
|
+
writer.writeEnd(conversationId, event).catch((err: unknown) => {
|
|
413
|
+
log.warn(
|
|
414
|
+
{ err, conversationId, compactionId: event.compactionId },
|
|
415
|
+
"Failed to write compaction end log (non-fatal)",
|
|
416
|
+
);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
@@ -28,6 +28,11 @@ import { findDisplayTurnEndIndex } from "../conversations/message-consolidation.
|
|
|
28
28
|
import { conversationMetadataSyncTag } from "../daemon/message-types/sync.js";
|
|
29
29
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
30
30
|
import { clearAllConversationIds } from "../home/feed-writer.js";
|
|
31
|
+
import {
|
|
32
|
+
forkEverInjected,
|
|
33
|
+
MEMORY_V3_INJECTED_BLOCK_METADATA_KEY,
|
|
34
|
+
seedEverInjectedFromSlugs,
|
|
35
|
+
} from "../plugins/defaults/memory-v3-shadow/ever-injected-store.js";
|
|
31
36
|
import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
|
|
32
37
|
import { UserError } from "../util/errors.js";
|
|
33
38
|
import { safeParseRecord } from "../util/json.js";
|
|
@@ -67,10 +72,15 @@ import {
|
|
|
67
72
|
memorySummaries,
|
|
68
73
|
messageAttachments,
|
|
69
74
|
messages,
|
|
75
|
+
skillLoadedEvents,
|
|
70
76
|
toolInvocations,
|
|
71
77
|
} from "./schema.js";
|
|
72
78
|
import { cancelPendingJobsForConversation } from "./task-memory-cleanup.js";
|
|
73
|
-
import {
|
|
79
|
+
import {
|
|
80
|
+
forkActivationState,
|
|
81
|
+
seedForkActivationState,
|
|
82
|
+
} from "./v2/activation-store.js";
|
|
83
|
+
import { extractInjectedConceptSlugs } from "./v2/injected-block-slugs.js";
|
|
74
84
|
|
|
75
85
|
const log = getLogger("conversation-store");
|
|
76
86
|
|
|
@@ -129,6 +139,9 @@ export const messageMetadataSchema = z
|
|
|
129
139
|
/** Image source paths from desktop attachments, keyed by filename. */
|
|
130
140
|
imageSourcePaths: z.record(z.string(), z.string()).optional(),
|
|
131
141
|
memoryInjectedBlock: z.string().optional(),
|
|
142
|
+
/** Memory-v3 frozen net-new card block (unwrapped) — the v3 counterpart
|
|
143
|
+
* of `memoryInjectedBlock`. A row carries at most one of the two. */
|
|
144
|
+
[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY]: z.string().optional(),
|
|
132
145
|
turnContextBlock: z.string().optional(),
|
|
133
146
|
pkbSystemReminderBlock: z.string().optional(),
|
|
134
147
|
workspaceBlock: z.string().optional(),
|
|
@@ -166,6 +179,30 @@ function cloneForkMessageMetadata(
|
|
|
166
179
|
return JSON.stringify({ forkSourceMessageId: sourceMessageId });
|
|
167
180
|
}
|
|
168
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Read a persisted memory-injection block off a message's metadata JSON, or
|
|
184
|
+
* `null` when absent/malformed. `key` selects the injection layer: v2's
|
|
185
|
+
* `memoryInjectedBlock` or memory-v3's card block
|
|
186
|
+
* (`MEMORY_V3_INJECTED_BLOCK_METADATA_KEY`). The block is what the request
|
|
187
|
+
* builder re-attaches as the message's `<memory>` content each turn.
|
|
188
|
+
*/
|
|
189
|
+
function readInjectedBlock(
|
|
190
|
+
metadata: string | null,
|
|
191
|
+
key: string,
|
|
192
|
+
): string | null {
|
|
193
|
+
if (!metadata) return null;
|
|
194
|
+
try {
|
|
195
|
+
const parsed: unknown = JSON.parse(metadata);
|
|
196
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
197
|
+
const block = (parsed as Record<string, unknown>)[key];
|
|
198
|
+
if (typeof block === "string") return block;
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Malformed metadata — treat as no block.
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
169
206
|
/**
|
|
170
207
|
* Extract provenance metadata fields from a TrustContext.
|
|
171
208
|
* When no guardian context is provided, defaults to 'unknown' because the
|
|
@@ -227,6 +264,7 @@ export interface ConversationRow {
|
|
|
227
264
|
scheduleJobId: string | null;
|
|
228
265
|
lastMessageAt: number | null;
|
|
229
266
|
archivedAt: number | null;
|
|
267
|
+
surfacedAt: number | null;
|
|
230
268
|
inferenceProfile: string | null;
|
|
231
269
|
inferenceProfileSessionId: string | null;
|
|
232
270
|
inferenceProfileExpiresAt: number | null;
|
|
@@ -261,6 +299,7 @@ export const parseConversation = createRowMapper<
|
|
|
261
299
|
scheduleJobId: "scheduleJobId",
|
|
262
300
|
lastMessageAt: "lastMessageAt",
|
|
263
301
|
archivedAt: "archivedAt",
|
|
302
|
+
surfacedAt: "surfacedAt",
|
|
264
303
|
inferenceProfile: "inferenceProfile",
|
|
265
304
|
inferenceProfileSessionId: "inferenceProfileSessionId",
|
|
266
305
|
inferenceProfileExpiresAt: "inferenceProfileExpiresAt",
|
|
@@ -1033,7 +1072,52 @@ export function forkConversation(params: {
|
|
|
1033
1072
|
const isFullHistoryFork = copyBoundaryIndex === sourceMessages.length - 1;
|
|
1034
1073
|
if (isFullHistoryFork) {
|
|
1035
1074
|
forkActivationState(db, sourceConversation.id, fc.id);
|
|
1075
|
+
forkEverInjected(db, sourceConversation.id, fc.id);
|
|
1036
1076
|
forkGraphMemoryState(sourceConversation.id, fc.id);
|
|
1077
|
+
} else {
|
|
1078
|
+
// Truncated fork: the wholesale copy above would over-claim, but
|
|
1079
|
+
// seeding nothing makes the child re-select and re-attach every page
|
|
1080
|
+
// whose `<memory>` attachment it already inherited (observed in
|
|
1081
|
+
// production: 89 duplicate page injections on one fork). Derive
|
|
1082
|
+
// `everInjected` from the inherited attachments themselves — scoped to
|
|
1083
|
+
// the child's visible window, since attachments behind an inherited
|
|
1084
|
+
// compaction boundary are not rendered and must stay re-injectable.
|
|
1085
|
+
// The v2 and v3 layers persist under separate metadata keys with the
|
|
1086
|
+
// same `# memory/concepts/<slug>.md` header convention, so each seeds
|
|
1087
|
+
// its own dedup record from its own blocks.
|
|
1088
|
+
const visibleStartIndex = preserveSourceCompactionState
|
|
1089
|
+
? visibleWindowStartIndex
|
|
1090
|
+
: 0;
|
|
1091
|
+
const inheritedSlugs = new Set<string>();
|
|
1092
|
+
const inheritedV3Slugs = new Set<string>();
|
|
1093
|
+
for (const message of messagesToCopy.slice(visibleStartIndex)) {
|
|
1094
|
+
const block = readInjectedBlock(
|
|
1095
|
+
message.metadata,
|
|
1096
|
+
"memoryInjectedBlock",
|
|
1097
|
+
);
|
|
1098
|
+
if (block) {
|
|
1099
|
+
for (const slug of extractInjectedConceptSlugs(block)) {
|
|
1100
|
+
inheritedSlugs.add(slug);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const v3Block = readInjectedBlock(
|
|
1104
|
+
message.metadata,
|
|
1105
|
+
MEMORY_V3_INJECTED_BLOCK_METADATA_KEY,
|
|
1106
|
+
);
|
|
1107
|
+
if (v3Block) {
|
|
1108
|
+
for (const slug of extractInjectedConceptSlugs(v3Block)) {
|
|
1109
|
+
inheritedV3Slugs.add(slug);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
seedForkActivationState(db, fc.id, [...inheritedSlugs]);
|
|
1114
|
+
seedEverInjectedFromSlugs(
|
|
1115
|
+
db,
|
|
1116
|
+
sourceConversation.id,
|
|
1117
|
+
fc.id,
|
|
1118
|
+
[...inheritedV3Slugs],
|
|
1119
|
+
Date.now(),
|
|
1120
|
+
);
|
|
1037
1121
|
}
|
|
1038
1122
|
forkRetrospectiveState({
|
|
1039
1123
|
database: db,
|
|
@@ -1104,6 +1188,9 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
1104
1188
|
tx.delete(toolInvocations)
|
|
1105
1189
|
.where(eq(toolInvocations.conversationId, id))
|
|
1106
1190
|
.run();
|
|
1191
|
+
tx.delete(skillLoadedEvents)
|
|
1192
|
+
.where(eq(skillLoadedEvents.conversationId, id))
|
|
1193
|
+
.run();
|
|
1107
1194
|
// Cascade deletes memory_segments, message_attachments.
|
|
1108
1195
|
tx.delete(messages).where(eq(messages.conversationId, id)).run();
|
|
1109
1196
|
|
|
@@ -1126,6 +1213,9 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
1126
1213
|
tx.delete(toolInvocations)
|
|
1127
1214
|
.where(eq(toolInvocations.conversationId, id))
|
|
1128
1215
|
.run();
|
|
1216
|
+
tx.delete(skillLoadedEvents)
|
|
1217
|
+
.where(eq(skillLoadedEvents.conversationId, id))
|
|
1218
|
+
.run();
|
|
1129
1219
|
}
|
|
1130
1220
|
|
|
1131
1221
|
tx.delete(conversations).where(eq(conversations.id, id)).run();
|
|
@@ -1183,7 +1273,7 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
1183
1273
|
|
|
1184
1274
|
// Step D — Delegate to deleteConversation which handles messages (cascade
|
|
1185
1275
|
// segments, attachments), llmRequestLogs, toolInvocations,
|
|
1186
|
-
// embeddings, and the conversation row.
|
|
1276
|
+
// skillLoadedEvents, embeddings, and the conversation row.
|
|
1187
1277
|
const deletedMemoryIds = deleteConversation(id);
|
|
1188
1278
|
|
|
1189
1279
|
// Step E — Return the combined result.
|
|
@@ -1771,6 +1861,36 @@ export function unarchiveConversation(id: string): boolean {
|
|
|
1771
1861
|
return true;
|
|
1772
1862
|
}
|
|
1773
1863
|
|
|
1864
|
+
/**
|
|
1865
|
+
* Set or clear the `surfaced_at` promotion marker for a conversation.
|
|
1866
|
+
*
|
|
1867
|
+
* A non-null `surfaced_at` promotes a background/scheduled conversation
|
|
1868
|
+
* into the default ("standard") conversation listing so clients show it in
|
|
1869
|
+
* the Recents sidebar grouping. Promotion is always explicit — callers are
|
|
1870
|
+
* product flows that decide a background run deserves foreground visibility
|
|
1871
|
+
* (e.g. the user sent a follow-up message in it). Nothing sets this
|
|
1872
|
+
* automatically.
|
|
1873
|
+
*
|
|
1874
|
+
* Returns `null` when the conversation does not exist; otherwise the new
|
|
1875
|
+
* `surfacedAt` value (`number` when surfacing, `null` when clearing).
|
|
1876
|
+
*/
|
|
1877
|
+
export function setConversationSurfaced(
|
|
1878
|
+
id: string,
|
|
1879
|
+
surfaced: boolean,
|
|
1880
|
+
): { surfacedAt: number | null } | null {
|
|
1881
|
+
const conv = getConversation(id);
|
|
1882
|
+
if (!conv) return null;
|
|
1883
|
+
const now = Date.now();
|
|
1884
|
+
const surfacedAt = surfaced ? now : null;
|
|
1885
|
+
rawRun(
|
|
1886
|
+
"UPDATE conversations SET surfaced_at = ?, updated_at = ? WHERE id = ?",
|
|
1887
|
+
surfacedAt,
|
|
1888
|
+
now,
|
|
1889
|
+
id,
|
|
1890
|
+
);
|
|
1891
|
+
return { surfacedAt };
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1774
1894
|
/**
|
|
1775
1895
|
* Set or clear the inference profile override for a conversation.
|
|
1776
1896
|
* Pass `null` to clear the override and fall back to the workspace
|
|
@@ -2024,6 +2144,7 @@ export async function clearAll(): Promise<{
|
|
|
2024
2144
|
await runOrThrow("DELETE FROM message_attachments");
|
|
2025
2145
|
await runOrThrow("DELETE FROM attachments");
|
|
2026
2146
|
await runOrThrow("DELETE FROM tool_invocations");
|
|
2147
|
+
await runOrThrow("DELETE FROM skill_loaded_events");
|
|
2027
2148
|
let messagesFtsCorrupted = false;
|
|
2028
2149
|
const ftsResult = await runAsyncSqlite("DELETE FROM messages_fts");
|
|
2029
2150
|
if (!ftsResult.ok) {
|
|
@@ -2492,8 +2613,18 @@ export function batchSetDisplayOrders(
|
|
|
2492
2613
|
) {
|
|
2493
2614
|
safeGroupId = "system:all";
|
|
2494
2615
|
}
|
|
2616
|
+
// Moving a conversation into the Scheduled/Background system groups
|
|
2617
|
+
// is an explicit demotion out of Recents, so clear any `surfaced_at`
|
|
2618
|
+
// promotion in the same write — otherwise the surfaced marker would
|
|
2619
|
+
// keep the row in the standard listing and the move would appear to
|
|
2620
|
+
// do nothing.
|
|
2621
|
+
const clearsSurfaced =
|
|
2622
|
+
safeGroupId === "system:background" ||
|
|
2623
|
+
safeGroupId === "system:scheduled";
|
|
2495
2624
|
rawRun(
|
|
2496
|
-
|
|
2625
|
+
`UPDATE conversations SET display_order = ?, is_pinned = ?, group_id = ?${
|
|
2626
|
+
clearsSurfaced ? ", surfaced_at = NULL" : ""
|
|
2627
|
+
} WHERE id = ?`,
|
|
2497
2628
|
update.displayOrder,
|
|
2498
2629
|
safeGroupId === "system:pinned" ? 1 : 0,
|
|
2499
2630
|
safeGroupId,
|