@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
|
@@ -2,6 +2,11 @@ import type {
|
|
|
2
2
|
ConversationErrorCode,
|
|
3
3
|
ConversationErrorEvent,
|
|
4
4
|
} from "../api/events/conversation-error.js";
|
|
5
|
+
import {
|
|
6
|
+
ORDERING_ERROR_PATTERNS,
|
|
7
|
+
WEB_SEARCH_ORDERING_PATTERNS,
|
|
8
|
+
} from "../plugins/defaults/history-repair/terminal.js";
|
|
9
|
+
import { isImageDimensionsTooLargeError } from "../plugins/defaults/image-recovery/detect.js";
|
|
5
10
|
import { ConnectionResolutionError } from "../providers/connection-resolution.js";
|
|
6
11
|
import { getProviderRoutingSource } from "../providers/registry.js";
|
|
7
12
|
import { isAbortReason } from "../util/abort-reasons.js";
|
|
@@ -118,21 +123,6 @@ const PROVIDER_API_PATTERNS = [
|
|
|
118
123
|
/gateway timeout/i,
|
|
119
124
|
];
|
|
120
125
|
|
|
121
|
-
// Provider ordering error patterns (tool_use/tool_result mismatches)
|
|
122
|
-
const ORDERING_ERROR_PATTERNS = [
|
|
123
|
-
/tool_result.*not immediately after.*tool_use/i,
|
|
124
|
-
/tool_use.*must have.*tool_result/i,
|
|
125
|
-
/tool_use_id.*without.*tool_result/i,
|
|
126
|
-
/tool_result.*tool_use_id.*not found/i,
|
|
127
|
-
/messages.*invalid.*order/i,
|
|
128
|
-
];
|
|
129
|
-
|
|
130
|
-
// Web-search-specific ordering error patterns
|
|
131
|
-
const WEB_SEARCH_ORDERING_PATTERNS = [
|
|
132
|
-
/web_search.*tool_use.*without/i,
|
|
133
|
-
/web_search.*tool_result/i,
|
|
134
|
-
];
|
|
135
|
-
|
|
136
126
|
// Stale/invalid opaque `encrypted_content` token in replayed web-search results.
|
|
137
127
|
// Anthropic's tokens have bounded validity; the daemon replaces historical
|
|
138
128
|
// `web_search_tool_result` blocks with text summaries to avoid this, but this
|
|
@@ -149,18 +139,6 @@ const STREAMING_ERROR_PATTERNS = [
|
|
|
149
139
|
/stream has ended.*this shouldn't happen/i,
|
|
150
140
|
];
|
|
151
141
|
|
|
152
|
-
// User-initiated cancellation patterns — these should NOT produce conversation_error
|
|
153
|
-
// Image-input validation patterns — Anthropic 400s with one of these messages
|
|
154
|
-
// when an image block violates a hard limit. The first matches the per-side
|
|
155
|
-
// pixel cap ("image dimensions exceed max allowed size"); the second matches the
|
|
156
|
-
// base64 payload cap ("image exceeds 5 MB maximum: 7465044 bytes > 5242880
|
|
157
|
-
// bytes"). Distinct classification matters because retrying with the same
|
|
158
|
-
// oversized image is futile — the recovery path must strip or downscale it.
|
|
159
|
-
const IMAGE_DIMENSIONS_TOO_LARGE_PATTERNS = [
|
|
160
|
-
/image dimensions? exceeds? max allowed size/i,
|
|
161
|
-
/image exceeds \d+\s*MB maximum/i,
|
|
162
|
-
];
|
|
163
|
-
|
|
164
142
|
const VISION_NOT_SUPPORTED_PATTERNS = [
|
|
165
143
|
/no endpoints found that support image input/i,
|
|
166
144
|
/does not support image/i,
|
|
@@ -457,7 +435,7 @@ function classifyCore(
|
|
|
457
435
|
// from "no key configured" (banner: "API key required").
|
|
458
436
|
return invalidApiKeyClassification(attribution);
|
|
459
437
|
}
|
|
460
|
-
if (
|
|
438
|
+
if (isImageDimensionsTooLargeError(message)) {
|
|
461
439
|
return {
|
|
462
440
|
code: "IMAGE_TOO_LARGE",
|
|
463
441
|
userMessage:
|
|
@@ -499,11 +477,6 @@ export function isContextTooLarge(message: string): boolean {
|
|
|
499
477
|
return CONTEXT_TOO_LARGE_PATTERNS.some((p) => p.test(message));
|
|
500
478
|
}
|
|
501
479
|
|
|
502
|
-
/** Check whether an error message indicates an image-input dimension failure. */
|
|
503
|
-
function isImageDimensionsTooLarge(message: string): boolean {
|
|
504
|
-
return IMAGE_DIMENSIONS_TOO_LARGE_PATTERNS.some((p) => p.test(message));
|
|
505
|
-
}
|
|
506
|
-
|
|
507
480
|
function isVisionNotSupported(message: string): boolean {
|
|
508
481
|
return VISION_NOT_SUPPORTED_PATTERNS.some((p) => p.test(message));
|
|
509
482
|
}
|
|
@@ -244,7 +244,7 @@ export function disposeConversation(ctx: DisposeContext): void {
|
|
|
244
244
|
ctx.workspaceTopLevelContext = null;
|
|
245
245
|
// The compaction module owns the per-conversation ContextWindowManager, so
|
|
246
246
|
// teardown releases it directly. Moving this behind a compaction-plugin hook
|
|
247
|
-
// would let the module own disposal end-to-end, but the per-turn `stop` hook
|
|
247
|
+
// would let the module own disposal end-to-end, but the per-turn `stop` hook
|
|
248
248
|
// would first require relocating the manager's only
|
|
249
249
|
// cross-turn state — `nonPersistedPrefixCount` — off the manager so a
|
|
250
250
|
// per-turn dispose/rebuild stays correct.
|
|
@@ -10,9 +10,14 @@ import { join } from "node:path";
|
|
|
10
10
|
|
|
11
11
|
import { type ChannelId, parseInterfaceId } from "../channels/types.js";
|
|
12
12
|
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
13
|
+
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
|
|
13
14
|
import { getConfig } from "../config/loader.js";
|
|
14
|
-
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
15
|
-
import {
|
|
15
|
+
import type { LLMCallSite, LLMConfig } from "../config/schemas/llm.js";
|
|
16
|
+
import {
|
|
17
|
+
NOW_SCRATCHPAD_STRIP_PREFIXES,
|
|
18
|
+
stripSpotlightInjections,
|
|
19
|
+
stripUserTextBlocksByPrefix,
|
|
20
|
+
} from "../context/strip-injections.js";
|
|
16
21
|
import { getDocumentsForConversation } from "../documents/document-store.js";
|
|
17
22
|
import {
|
|
18
23
|
getApp,
|
|
@@ -28,8 +33,9 @@ import { isBackgroundConversationType } from "../memory/conversation-types.js";
|
|
|
28
33
|
import {
|
|
29
34
|
countMemoryPrefixBlocks,
|
|
30
35
|
extractMemoryPrefixBlocks,
|
|
31
|
-
|
|
36
|
+
getLiveGraphMemory,
|
|
32
37
|
} from "../memory/graph/conversation-graph-memory.js";
|
|
38
|
+
import { unwrapMemoryBlock, wrapMemoryBlock } from "../memory/memory-marker.js";
|
|
33
39
|
import {
|
|
34
40
|
readSlackMetadata,
|
|
35
41
|
readSlackMetadataFromMessageMetadata,
|
|
@@ -44,7 +50,10 @@ import {
|
|
|
44
50
|
} from "../messaging/providers/slack/render-transcript.js";
|
|
45
51
|
import { createContextSummaryMessage } from "../plugins/defaults/compaction/window-manager.js";
|
|
46
52
|
import { getInjectorChain } from "../plugins/defaults/memory-retrieval/injector-chain.js";
|
|
47
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
MEMORY_V3_BLOCK_ID,
|
|
55
|
+
MEMORY_V3_COMMIT_META_KEY,
|
|
56
|
+
} from "../plugins/defaults/memory-v3-shadow/types.js";
|
|
48
57
|
import type {
|
|
49
58
|
InjectionBlock,
|
|
50
59
|
InjectionPlacement,
|
|
@@ -210,6 +219,43 @@ export function resolveTurnInboundActorContext(
|
|
|
210
219
|
return resolved.trustClass === "guardian" ? null : resolved;
|
|
211
220
|
}
|
|
212
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Render the `model_profile:` turn-context label for a turn from its resolved
|
|
224
|
+
* inference profile key, for the unified `<turn_context>` block.
|
|
225
|
+
*
|
|
226
|
+
* Returns `null` when there is no key to announce (the caller gates this to the
|
|
227
|
+
* turns where the active profile changed since the one last delivered to the
|
|
228
|
+
* model). Otherwise the human-readable label comes from the profile's
|
|
229
|
+
* configured `label` (falling back to the key) and the model id from the
|
|
230
|
+
* call-site resolution keyed on that profile, yielding `Label (model)` — or
|
|
231
|
+
* just `Label` when no model resolves. Derives purely from the passed key,
|
|
232
|
+
* call site, and config, so callers thread the key (plain turn data) rather
|
|
233
|
+
* than the rendered string and self-resolve the call site from the live
|
|
234
|
+
* conversation.
|
|
235
|
+
*
|
|
236
|
+
* `selectionSeed` is the conversation id, threaded so that a key naming a `mix`
|
|
237
|
+
* profile expands to the same arm the turn's provider calls run on (which seed
|
|
238
|
+
* expansion with the same id). Without it the announced model would be a fresh
|
|
239
|
+
* random arm that can disagree with the model actually serving the turn.
|
|
240
|
+
*/
|
|
241
|
+
export function resolveTurnModelProfileLabel(
|
|
242
|
+
modelProfileKey: string | null,
|
|
243
|
+
callSite: LLMCallSite,
|
|
244
|
+
llm: LLMConfig,
|
|
245
|
+
selectionSeed?: string,
|
|
246
|
+
): string | null {
|
|
247
|
+
if (modelProfileKey == null) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const profileEntry = llm.profiles?.[modelProfileKey];
|
|
251
|
+
const resolved = resolveCallSiteConfig(callSite, llm, {
|
|
252
|
+
overrideProfile: modelProfileKey,
|
|
253
|
+
selectionSeed,
|
|
254
|
+
});
|
|
255
|
+
const label = profileEntry?.label ?? modelProfileKey;
|
|
256
|
+
return resolved.model ? `${label} (${resolved.model})` : label;
|
|
257
|
+
}
|
|
258
|
+
|
|
213
259
|
/** Derive channel capabilities from source channel + interface identifiers. */
|
|
214
260
|
export function resolveChannelCapabilities(
|
|
215
261
|
sourceChannel?: string | null,
|
|
@@ -631,12 +677,7 @@ function injectVoiceCallControlContext(
|
|
|
631
677
|
|
|
632
678
|
/** Strip `<NOW.md>` blocks injected by the `now-md` default injector. */
|
|
633
679
|
export function stripNowScratchpad(messages: Message[]): Message[] {
|
|
634
|
-
return stripUserTextBlocksByPrefix(messages,
|
|
635
|
-
// Shared prefix catches both the current tag and any pre-line-limit
|
|
636
|
-
// variant that may linger in in-flight histories during a rolling deploy.
|
|
637
|
-
"<NOW.md Always keep this up to date",
|
|
638
|
-
"<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
|
|
639
|
-
]);
|
|
680
|
+
return stripUserTextBlocksByPrefix(messages, NOW_SCRATCHPAD_STRIP_PREFIXES);
|
|
640
681
|
}
|
|
641
682
|
|
|
642
683
|
/**
|
|
@@ -1485,6 +1526,24 @@ export interface RuntimeInjectionBlocks {
|
|
|
1485
1526
|
nowScratchpadBlock?: string;
|
|
1486
1527
|
pkbContextBlock?: string;
|
|
1487
1528
|
memoryV2StaticBlock?: string;
|
|
1529
|
+
/**
|
|
1530
|
+
* UNWRAPPED inner text of the memory-v3 frozen net-new card block the v3
|
|
1531
|
+
* injector attached this turn, mirroring v2's unwrapped `memoryInjectedBlock`
|
|
1532
|
+
* contract (rehydration re-wraps on use). Undefined when v3 attached no new
|
|
1533
|
+
* cards (all-repeat turn, v3 off, or v3 failure). Persisted by the
|
|
1534
|
+
* user-prompt-submit hook under `metadata.memoryV3InjectedBlock`
|
|
1535
|
+
* (`MEMORY_V3_INJECTED_BLOCK_METADATA_KEY`).
|
|
1536
|
+
*/
|
|
1537
|
+
memoryV3InjectedBlock?: string;
|
|
1538
|
+
/**
|
|
1539
|
+
* True when memory-v3 superseded v2 as this turn's `<memory>` source — the
|
|
1540
|
+
* `memory-v3-live` flag is on AND the v3 injector produced a block (possibly
|
|
1541
|
+
* empty-text on an all-repeat turn), i.e. exactly when assembly stripped
|
|
1542
|
+
* v2's fresh tail block. The user-prompt-submit hook keys v2's
|
|
1543
|
+
* `memoryInjectedBlock` metadata persist off this so a stripped v2 block is
|
|
1544
|
+
* never rehydrated back into history on reload.
|
|
1545
|
+
*/
|
|
1546
|
+
memoryV3Active?: boolean;
|
|
1488
1547
|
/**
|
|
1489
1548
|
* Composed output of every plugin-registered {@link Injector}, concatenated
|
|
1490
1549
|
* in ascending `order`. Empty string when every injector opted out (returned
|
|
@@ -1637,6 +1696,63 @@ function applyInjectionBlock(
|
|
|
1637
1696
|
}
|
|
1638
1697
|
}
|
|
1639
1698
|
|
|
1699
|
+
/**
|
|
1700
|
+
* Strip v2's freshly-prepended DYNAMIC memory prefix from the tail user
|
|
1701
|
+
* message, preserving every other leading memory-prefix block. Exactly three
|
|
1702
|
+
* shapes are v2's to remove:
|
|
1703
|
+
*
|
|
1704
|
+
* - `v2WrappedBlock` — the wrapped (`wrapMemoryBlock`) form of the text the
|
|
1705
|
+
* graph-memory wiring prepended this turn, matched by full-block IDENTITY.
|
|
1706
|
+
* A prefix match cannot work here: v3's card-block instruction header is
|
|
1707
|
+
* deliberately byte-identical to v2's `INJECTION_HEADER`
|
|
1708
|
+
* (`memory/v2/injection.ts`), and v2's router block leads with that same
|
|
1709
|
+
* header whenever any summary section is present — the dominant case — so
|
|
1710
|
+
* the two layers' blocks are indistinguishable by any shared prefix.
|
|
1711
|
+
* - legacy `<memory __injected>` text blocks — only v2 ever produced them.
|
|
1712
|
+
* - memory-image groups (`<memory_image …>` opener text, the image block,
|
|
1713
|
+
* the `</memory_image>` closer) — only v2 injects images; v3 card blocks
|
|
1714
|
+
* are text-only.
|
|
1715
|
+
*
|
|
1716
|
+
* Everything else in the leading memory prefix survives:
|
|
1717
|
+
*
|
|
1718
|
+
* - memory-v3's frozen card blocks: a same-turn re-entry assembly (overflow
|
|
1719
|
+
* convergence) sees the tail it assembled on first entry, whose leading
|
|
1720
|
+
* block may be this turn's just-frozen v3 cards (while this re-entry's
|
|
1721
|
+
* injector run produces an EMPTY block — the cards are already claimed by
|
|
1722
|
+
* the everInjected store). Their bytes never equal the v2 identity, so
|
|
1723
|
+
* they are kept without needing to be recognized as v3.
|
|
1724
|
+
* - the `<info>` static block: it counts toward the memory prefix for
|
|
1725
|
+
* splice positioning but is never prepended by `prepareMemory`, so the v2
|
|
1726
|
+
* suppression strip has no business removing it.
|
|
1727
|
+
*
|
|
1728
|
+
* `v2WrappedBlock === null` (nothing injected this turn, or no live graph
|
|
1729
|
+
* handle) still strips the image groups and legacy blocks — both are
|
|
1730
|
+
* unambiguously v2's — but leaves every `<memory>` text block in place.
|
|
1731
|
+
*/
|
|
1732
|
+
function stripTailV2DynamicMemoryPrefix(
|
|
1733
|
+
messages: Message[],
|
|
1734
|
+
v2WrappedBlock: string | null,
|
|
1735
|
+
): Message[] {
|
|
1736
|
+
const last = messages[messages.length - 1];
|
|
1737
|
+
if (!last || last.role !== "user") return messages;
|
|
1738
|
+
const prefixCount = countMemoryPrefixBlocksOnContent(last.content);
|
|
1739
|
+
if (prefixCount === 0) return messages;
|
|
1740
|
+
const content = last.content.filter((block, i) => {
|
|
1741
|
+
if (i >= prefixCount) return true;
|
|
1742
|
+
// v2's memory-image groups: opener text, injected image, closer text.
|
|
1743
|
+
if (block.type === "image") return false;
|
|
1744
|
+
if (block.type !== "text") return true;
|
|
1745
|
+
if (block.text.startsWith("<memory_image")) return false;
|
|
1746
|
+
if (block.text === "</memory_image>") return false;
|
|
1747
|
+
// v2's legacy dynamic wrapper.
|
|
1748
|
+
if (block.text.startsWith("<memory __injected>\n")) return false;
|
|
1749
|
+
// v2's current dynamic block — identity match only (see doc comment).
|
|
1750
|
+
return v2WrappedBlock === null || block.text !== v2WrappedBlock;
|
|
1751
|
+
});
|
|
1752
|
+
if (content.length === last.content.length) return messages;
|
|
1753
|
+
return [...messages.slice(0, -1), { ...last, content }];
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1640
1756
|
/**
|
|
1641
1757
|
* Per-turn options accepted by {@link applyRuntimeInjections}.
|
|
1642
1758
|
*
|
|
@@ -1990,6 +2106,7 @@ export async function applyRuntimeInjections(
|
|
|
1990
2106
|
let pkbContextCaptured: string | undefined;
|
|
1991
2107
|
let pkbSystemReminderCaptured: string | undefined;
|
|
1992
2108
|
let memoryV2StaticCaptured: string | undefined;
|
|
2109
|
+
let memoryV3Captured: string | undefined;
|
|
1993
2110
|
const initialTail = runMessages[runMessages.length - 1];
|
|
1994
2111
|
const initialTailIsUser = !!initialTail && initialTail.role === "user";
|
|
1995
2112
|
if (initialTailIsUser) {
|
|
@@ -2013,6 +2130,26 @@ export async function applyRuntimeInjections(
|
|
|
2013
2130
|
case "memory-v2-static":
|
|
2014
2131
|
memoryV2StaticCaptured = block.text;
|
|
2015
2132
|
break;
|
|
2133
|
+
case MEMORY_V3_BLOCK_ID: {
|
|
2134
|
+
// The v3 frozen card block is persisted UNWRAPPED (the v2
|
|
2135
|
+
// `memoryInjectedBlock` contract — rehydration re-wraps on use).
|
|
2136
|
+
// An empty-text block (all-repeat turn) attaches no content, so
|
|
2137
|
+
// nothing is captured for persistence either.
|
|
2138
|
+
if (block.text.length > 0) {
|
|
2139
|
+
memoryV3Captured = unwrapMemoryBlock(block.text);
|
|
2140
|
+
}
|
|
2141
|
+
// Attachment is guaranteed from here (user tail — the gate this
|
|
2142
|
+
// capture loop runs under), so commit the injector's deferred
|
|
2143
|
+
// everInjected-store write. On a non-user tail the block silently
|
|
2144
|
+
// no-ops in `applyInjectionBlock`, and skipping the commit keeps
|
|
2145
|
+
// the store from claiming cards that never attached (which would
|
|
2146
|
+
// suppress them until compaction).
|
|
2147
|
+
const commit = block.meta?.[MEMORY_V3_COMMIT_META_KEY];
|
|
2148
|
+
if (typeof commit === "function") {
|
|
2149
|
+
(commit as () => void)();
|
|
2150
|
+
}
|
|
2151
|
+
break;
|
|
2152
|
+
}
|
|
2016
2153
|
}
|
|
2017
2154
|
}
|
|
2018
2155
|
}
|
|
@@ -2029,15 +2166,32 @@ export async function applyRuntimeInjections(
|
|
|
2029
2166
|
? injectorChainPieces.join("\n\n")
|
|
2030
2167
|
: undefined;
|
|
2031
2168
|
|
|
2032
|
-
// ── Step 0: memory-v3-
|
|
2033
|
-
//
|
|
2034
|
-
//
|
|
2035
|
-
//
|
|
2036
|
-
//
|
|
2037
|
-
//
|
|
2038
|
-
//
|
|
2039
|
-
//
|
|
2040
|
-
//
|
|
2169
|
+
// ── Step 0: memory-v3 ephemeral-spotlight strip + v2 tail suppression ──
|
|
2170
|
+
//
|
|
2171
|
+
// Spotlight strip (unconditional): the `<memory_spotlight>` block is
|
|
2172
|
+
// ephemeral by contract — re-rendered at the current tail each turn — so any
|
|
2173
|
+
// spotlight riding history from a previous turn is stale and is removed
|
|
2174
|
+
// here. This is a SCOPED strip of only that block id: the frozen `<memory>`
|
|
2175
|
+
// card blocks on historical messages are untouched (the cache contract).
|
|
2176
|
+
// With the v3 flag off no spotlight blocks exist and this is a content
|
|
2177
|
+
// no-op, keeping the v2 path bit-for-bit identical.
|
|
2178
|
+
let runMessagesForAssembly = stripSpotlightInjections(runMessages);
|
|
2179
|
+
|
|
2180
|
+
// v2 suppression: when the `memory-v3-live` flag is on AND the v3 injector
|
|
2181
|
+
// produced a block this turn (possibly empty-text on an all-repeat turn), v3
|
|
2182
|
+
// owns the `<memory>` layer. v2's `prepareMemory` already prepended its own
|
|
2183
|
+
// fresh `<memory>` block to the tail user message — strip the TAIL's v2
|
|
2184
|
+
// dynamic prefix only, so the v3 `after-memory-prefix` block (Step 2) lands
|
|
2185
|
+
// at the top of the tail with no v2 prefix ahead of it. Historical user
|
|
2186
|
+
// messages keep their memory blocks byte-identical: frozen v3 card blocks
|
|
2187
|
+
// from prior turns AND pre-cutover v2 blocks both ride the cached prefix
|
|
2188
|
+
// (the old whole-layer `stripAllMemoryInjections` replace is gone). The
|
|
2189
|
+
// strip discriminates v2's dynamic block by IDENTITY ({@link
|
|
2190
|
+
// stripTailV2DynamicMemoryPrefix}): the live graph handle holds the exact
|
|
2191
|
+
// text the wiring layer prepended this turn, so a re-entry tail's
|
|
2192
|
+
// just-frozen v3 card block (and the `<info>` static block) survive even
|
|
2193
|
+
// though v2 and v3 blocks share identical wrapper + header bytes — the v2
|
|
2194
|
+
// prefix this strip exists to remove was already stripped on first entry.
|
|
2041
2195
|
// Keyed off the v3 block being present (not the flag alone) so a v3 failure
|
|
2042
2196
|
// (`produce()` → null) leaves v2's block intact — fallback rather than a
|
|
2043
2197
|
// memory-less turn. Idempotent: re-injection sites that already stripped
|
|
@@ -2047,9 +2201,14 @@ export async function applyRuntimeInjections(
|
|
|
2047
2201
|
getConfig(),
|
|
2048
2202
|
);
|
|
2049
2203
|
const v3ProducedBlock = afterMemory.some((b) => b.id === MEMORY_V3_BLOCK_ID);
|
|
2050
|
-
|
|
2051
|
-
if (
|
|
2052
|
-
|
|
2204
|
+
const memoryV3Active = suppressV2MemoryForV3 && v3ProducedBlock;
|
|
2205
|
+
if (memoryV3Active) {
|
|
2206
|
+
const v2DynamicText =
|
|
2207
|
+
getLiveGraphMemory(conversationId)?.lastInjectedBlockText ?? null;
|
|
2208
|
+
runMessagesForAssembly = stripTailV2DynamicMemoryPrefix(
|
|
2209
|
+
runMessagesForAssembly,
|
|
2210
|
+
v2DynamicText === null ? null : wrapMemoryBlock(v2DynamicText),
|
|
2211
|
+
);
|
|
2053
2212
|
}
|
|
2054
2213
|
|
|
2055
2214
|
let result = runMessagesForAssembly;
|
|
@@ -2214,6 +2373,8 @@ export async function applyRuntimeInjections(
|
|
|
2214
2373
|
nowScratchpadBlock: nowScratchpadCaptured,
|
|
2215
2374
|
pkbContextBlock: pkbContextCaptured,
|
|
2216
2375
|
memoryV2StaticBlock: memoryV2StaticCaptured,
|
|
2376
|
+
memoryV3InjectedBlock: memoryV3Captured,
|
|
2377
|
+
memoryV3Active,
|
|
2217
2378
|
injectorChainBlock,
|
|
2218
2379
|
},
|
|
2219
2380
|
};
|
|
@@ -16,9 +16,12 @@ import { getConfig } from "../config/loader.js";
|
|
|
16
16
|
import { skillFlagKey } from "../config/skill-state.js";
|
|
17
17
|
import type { SkillSummary, SkillToolManifest } from "../config/skills.js";
|
|
18
18
|
import { loadSkillCatalog } from "../config/skills.js";
|
|
19
|
+
import { recordSkillLoadedEvent } from "../memory/skill-loaded-events-store.js";
|
|
19
20
|
import type { Message, ToolDefinition } from "../providers/types.js";
|
|
20
21
|
import type { ActiveSkillEntry } from "../skills/active-skill-tools.js";
|
|
21
22
|
import { deriveActiveSkills } from "../skills/active-skill-tools.js";
|
|
23
|
+
import { getCachedCatalogSync } from "../skills/catalog-cache.js";
|
|
24
|
+
import { readInstallMeta } from "../skills/install-meta.js";
|
|
22
25
|
import { parseToolManifestFile } from "../skills/tool-manifest.js";
|
|
23
26
|
import { computeSkillVersionHash } from "../skills/version-hash.js";
|
|
24
27
|
import {
|
|
@@ -28,10 +31,34 @@ import {
|
|
|
28
31
|
unregisterSkillTools,
|
|
29
32
|
} from "../tools/registry.js";
|
|
30
33
|
import { createSkillToolsFromManifest } from "../tools/skills/skill-tool-factory.js";
|
|
34
|
+
import type { UsageAttributionSnapshot } from "../usage/attribution.js";
|
|
35
|
+
import { toAttributionColumns } from "../usage/attribution.js";
|
|
31
36
|
import { getLogger } from "../util/logger.js";
|
|
32
37
|
|
|
33
38
|
const log = getLogger("conversation-skill-tools");
|
|
34
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Sentinel "version hash" stored in the previously-active map for skills that
|
|
42
|
+
* are active without a loadable TOOLS.json (instruction-only skills — the
|
|
43
|
+
* common case for catalog installs — or skills whose manifest failed to
|
|
44
|
+
* parse). These entries registered no tools, so teardown paths must skip
|
|
45
|
+
* `unregisterSkillTools` for them to avoid decrementing refcounts held by
|
|
46
|
+
* other conversations.
|
|
47
|
+
*/
|
|
48
|
+
const NO_TOOLS_VERSION = "__no_tools__";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether a tracked version-hash entry corresponds to tools that were actually
|
|
52
|
+
* registered. Absent entries and the no-tools sentinel registered nothing, so
|
|
53
|
+
* every teardown path must consult this before calling `unregisterSkillTools`
|
|
54
|
+
* — the registry refcounts, and a spurious unregister would decrement counts
|
|
55
|
+
* held by other conversations. This helper is the single owner of the
|
|
56
|
+
* sentinel check; do not compare against `NO_TOOLS_VERSION` elsewhere.
|
|
57
|
+
*/
|
|
58
|
+
function hasRegisteredTools(trackedHash: string | undefined): boolean {
|
|
59
|
+
return trackedHash !== undefined && trackedHash !== NO_TOOLS_VERSION;
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
// ---------------------------------------------------------------------------
|
|
36
63
|
// Public types
|
|
37
64
|
// ---------------------------------------------------------------------------
|
|
@@ -74,7 +101,8 @@ export interface ProjectSkillToolsOptions {
|
|
|
74
101
|
preactivatedSkillIds?: string[];
|
|
75
102
|
/**
|
|
76
103
|
* Conversation-scoped tracking map of previously active skill IDs to their
|
|
77
|
-
* version hashes
|
|
104
|
+
* version hashes (or the no-tools sentinel for active skills without a
|
|
105
|
+
* loadable manifest). Each conversation should own its own map to prevent
|
|
78
106
|
* cross-conversation state bleed when the daemon serves multiple concurrent
|
|
79
107
|
* conversations. When a skill's hash changes between turns, its tools are
|
|
80
108
|
* unregistered and re-registered with the updated definitions.
|
|
@@ -86,6 +114,11 @@ export interface ProjectSkillToolsOptions {
|
|
|
86
114
|
* reads across agent turns.
|
|
87
115
|
*/
|
|
88
116
|
cache?: SkillProjectionCache;
|
|
117
|
+
/** Telemetry context for skill_loaded events; absent disables recording. */
|
|
118
|
+
telemetry?: {
|
|
119
|
+
conversationId: string;
|
|
120
|
+
attribution: UsageAttributionSnapshot | null;
|
|
121
|
+
};
|
|
89
122
|
}
|
|
90
123
|
|
|
91
124
|
// ---------------------------------------------------------------------------
|
|
@@ -116,6 +149,50 @@ function loadManifestForSkill(skill: SkillSummary): SkillToolManifest | null {
|
|
|
116
149
|
}
|
|
117
150
|
}
|
|
118
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Whether a skill is Vellum-produced for `skill_loaded` telemetry purposes:
|
|
154
|
+
* bundled skills always are; managed skills only when their install metadata
|
|
155
|
+
* records a `vellum` origin (legacy version.json installs are inferred as
|
|
156
|
+
* vellum by `readInstallMeta`). All other sources (workspace, extra, plugin)
|
|
157
|
+
* never emit.
|
|
158
|
+
*/
|
|
159
|
+
function isVellumProducedSkill(skill: SkillSummary): boolean {
|
|
160
|
+
if (skill.source === "bundled") return true;
|
|
161
|
+
if (skill.source === "managed") {
|
|
162
|
+
return readInstallMeta(skill.directoryPath)?.origin === "vellum";
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Record a `skill_loaded` telemetry event for a newly-activated Vellum-produced
|
|
169
|
+
* skill. Metadata only — never skill output or conversation content.
|
|
170
|
+
* `skill_updated_at` comes from the cached merged catalog (sync accessor —
|
|
171
|
+
* this runs on the hot tool-projection path). Failures are swallowed at
|
|
172
|
+
* debug level: recording must never break tool projection.
|
|
173
|
+
*/
|
|
174
|
+
function recordSkillLoadedTelemetry(
|
|
175
|
+
skill: SkillSummary,
|
|
176
|
+
telemetry: ProjectSkillToolsOptions["telemetry"],
|
|
177
|
+
): void {
|
|
178
|
+
if (!telemetry) return;
|
|
179
|
+
try {
|
|
180
|
+
if (!isVellumProducedSkill(skill)) return;
|
|
181
|
+
const catalogEntry = getCachedCatalogSync().find((s) => s.id === skill.id);
|
|
182
|
+
recordSkillLoadedEvent({
|
|
183
|
+
conversationId: telemetry.conversationId,
|
|
184
|
+
skillName: skill.id,
|
|
185
|
+
skillUpdatedAt: catalogEntry?.updatedAt,
|
|
186
|
+
...toAttributionColumns(telemetry.attribution),
|
|
187
|
+
});
|
|
188
|
+
} catch (err) {
|
|
189
|
+
log.debug(
|
|
190
|
+
{ err, skillId: skill.id },
|
|
191
|
+
"Failed to record skill_loaded telemetry event (non-fatal)",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
119
196
|
// ---------------------------------------------------------------------------
|
|
120
197
|
// Cache helpers
|
|
121
198
|
// ---------------------------------------------------------------------------
|
|
@@ -268,8 +345,11 @@ export function projectSkillTools(
|
|
|
268
345
|
}
|
|
269
346
|
}
|
|
270
347
|
|
|
271
|
-
// Unregister tools for skills that are no longer active
|
|
348
|
+
// Unregister tools for skills that are no longer active. Skills tracked
|
|
349
|
+
// with the no-tools sentinel never registered anything — skip them so we
|
|
350
|
+
// don't decrement refcounts held by other conversations.
|
|
272
351
|
for (const id of removedIds) {
|
|
352
|
+
if (!hasRegisteredTools(prevActive.get(id))) continue;
|
|
273
353
|
log.info({ skillId: id }, "Unregistering tools for deactivated skill");
|
|
274
354
|
unregisterSkillTools(id);
|
|
275
355
|
}
|
|
@@ -294,8 +374,36 @@ export function projectSkillTools(
|
|
|
294
374
|
continue;
|
|
295
375
|
}
|
|
296
376
|
|
|
377
|
+
// A skill that newly became active counts as one `skill_loaded`, whether
|
|
378
|
+
// or not it ships a TOOLS.json — instruction-only skills (the common case
|
|
379
|
+
// for catalog installs) must be counted too. The event is recorded only
|
|
380
|
+
// once the skill lands in `successfulEntries`, so a failed registration
|
|
381
|
+
// is retried (and counted) on a later turn instead of emitting one row
|
|
382
|
+
// per failing turn. The once-per-activation guard (`prevActive`) is
|
|
383
|
+
// in-memory per-conversation state: after conversation disposal or a
|
|
384
|
+
// daemon restart, re-projection re-emits skill_loaded for already-active
|
|
385
|
+
// skills. That is intentional — a re-load after restart is a load.
|
|
386
|
+
// Downstream consumers that need activation-level uniqueness dedup on
|
|
387
|
+
// (conversation_id, skill_name).
|
|
388
|
+
const prevHash = prevActive.get(skillId);
|
|
389
|
+
|
|
297
390
|
const manifest = loadManifestForSkill(skill);
|
|
298
391
|
if (!manifest) {
|
|
392
|
+
// No loadable manifest — nothing to register, but keep tracking the
|
|
393
|
+
// activation so it isn't re-recorded every turn. If tools were
|
|
394
|
+
// registered on a previous turn (manifest removed or corrupted since),
|
|
395
|
+
// tear them down like the transiently-failed path would.
|
|
396
|
+
if (hasRegisteredTools(prevHash)) {
|
|
397
|
+
log.info(
|
|
398
|
+
{ skillId },
|
|
399
|
+
"Unregistering tools for skill whose manifest is no longer loadable",
|
|
400
|
+
);
|
|
401
|
+
unregisterSkillTools(skillId);
|
|
402
|
+
}
|
|
403
|
+
successfulEntries.set(skillId, NO_TOOLS_VERSION);
|
|
404
|
+
if (prevHash === undefined) {
|
|
405
|
+
recordSkillLoadedTelemetry(skill, options?.telemetry);
|
|
406
|
+
}
|
|
299
407
|
continue;
|
|
300
408
|
}
|
|
301
409
|
|
|
@@ -321,10 +429,23 @@ export function projectSkillTools(
|
|
|
321
429
|
|
|
322
430
|
if (tools.length > 0) {
|
|
323
431
|
let accepted = tools;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
//
|
|
327
|
-
|
|
432
|
+
if (!hasRegisteredTools(prevHash)) {
|
|
433
|
+
// Newly active skill, or a previously manifest-less activation whose
|
|
434
|
+
// TOOLS.json has since appeared — register for the first time. There
|
|
435
|
+
// is nothing to unregister in the sentinel case: no tools were ever
|
|
436
|
+
// registered for it.
|
|
437
|
+
try {
|
|
438
|
+
accepted = registerSkillTools(skillId, tools);
|
|
439
|
+
} catch (err) {
|
|
440
|
+
log.error({ err, skillId }, "Failed to register skill tools");
|
|
441
|
+
// Not added to successfulEntries — registration (and the
|
|
442
|
+
// skill_loaded record below) is retried next turn.
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
// A first successful registration counts as the load; a manifest-less
|
|
446
|
+
// activation gaining a TOOLS.json re-registers, which — like a
|
|
447
|
+
// version-hash change — counts as a new load.
|
|
448
|
+
recordSkillLoadedTelemetry(skill, options?.telemetry);
|
|
328
449
|
} else if (prevHash !== currentHash) {
|
|
329
450
|
// Hash changed — unregister stale tools, then re-register with new definitions
|
|
330
451
|
log.info(
|
|
@@ -343,6 +464,8 @@ export function projectSkillTools(
|
|
|
343
464
|
// Don't add to successfulEntries — will be cleaned up as transiently-failed
|
|
344
465
|
continue;
|
|
345
466
|
}
|
|
467
|
+
// A version change that re-registers counts as a new skill load.
|
|
468
|
+
recordSkillLoadedTelemetry(skill, options?.telemetry);
|
|
346
469
|
} else {
|
|
347
470
|
// Hash unchanged — filter to only tools that are actually registered
|
|
348
471
|
// for this skill. Some tools may have been skipped during initial
|
|
@@ -372,7 +495,9 @@ export function projectSkillTools(
|
|
|
372
495
|
if (
|
|
373
496
|
activeIds.has(id) &&
|
|
374
497
|
!successfulEntries.has(id) &&
|
|
375
|
-
!alreadyUnregistered.has(id)
|
|
498
|
+
!alreadyUnregistered.has(id) &&
|
|
499
|
+
// Sentinel entries registered no tools — nothing to unregister.
|
|
500
|
+
hasRegisteredTools(prevActive.get(id))
|
|
376
501
|
) {
|
|
377
502
|
log.info(
|
|
378
503
|
{ skillId: id },
|
|
@@ -403,7 +528,11 @@ export function resetSkillToolProjection(
|
|
|
403
528
|
trackedIds?: Map<string, string>,
|
|
404
529
|
): void {
|
|
405
530
|
if (trackedIds) {
|
|
406
|
-
for (const id of trackedIds
|
|
531
|
+
for (const [id, hash] of trackedIds) {
|
|
532
|
+
// Sentinel entries (active skills without a manifest) registered no
|
|
533
|
+
// tools — skip them so we don't decrement refcounts held by other
|
|
534
|
+
// conversations.
|
|
535
|
+
if (!hasRegisteredTools(hash)) continue;
|
|
407
536
|
unregisterSkillTools(id);
|
|
408
537
|
}
|
|
409
538
|
trackedIds.clear();
|
|
@@ -458,17 +458,3 @@ export async function resolveSlash(
|
|
|
458
458
|
|
|
459
459
|
return { kind: "passthrough", content };
|
|
460
460
|
}
|
|
461
|
-
|
|
462
|
-
// ── Provider Ordering Error Detection ────────────────────────────────
|
|
463
|
-
|
|
464
|
-
const ORDERING_ERROR_PATTERNS = [
|
|
465
|
-
/tool_result.*not immediately after.*tool_use/i,
|
|
466
|
-
/tool_use.*must have.*tool_result/i,
|
|
467
|
-
/tool_use_id.*without.*tool_result/i,
|
|
468
|
-
/tool_result.*tool_use_id.*not found/i,
|
|
469
|
-
/messages.*invalid.*order/i,
|
|
470
|
-
];
|
|
471
|
-
|
|
472
|
-
export function isProviderOrderingError(message: string): boolean {
|
|
473
|
-
return ORDERING_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
474
|
-
}
|