@vellumai/assistant 0.8.3 → 0.8.5
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/ARCHITECTURE.md +2 -2
- package/docker-entrypoint.sh +0 -1
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +1492 -100
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +302 -33
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +2 -1
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +55 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +228 -8
- package/src/__tests__/conversation-agent-loop.test.ts +188 -129
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +324 -0
- package/src/__tests__/conversation-lifecycle.test.ts +53 -12
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +279 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +7 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +77 -17
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +161 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +151 -0
- package/src/__tests__/openai-responses-provider.test.ts +118 -16
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +189 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -5
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +161 -124
- package/src/__tests__/terminal-tools.test.ts +12 -2
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +89 -53
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +94 -10
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +65 -20
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +10 -12
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +483 -0
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/program.ts +2 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +124 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +258 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +24 -12
- package/src/config/call-site-defaults.ts +20 -0
- package/src/config/feature-flag-registry.json +115 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +217 -1
- package/src/config/schemas/call-site-catalog.ts +35 -0
- package/src/config/schemas/llm.ts +14 -0
- package/src/config/schemas/memory-v2.ts +294 -1
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +60 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +155 -36
- package/src/daemon/conversation-agent-loop.ts +307 -88
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +149 -118
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +145 -84
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +70 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +54 -32
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +2 -0
- package/src/daemon/handlers/conversations.ts +90 -3
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +5 -5
- package/src/daemon/host-file-proxy.ts +5 -5
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +11 -11
- package/src/daemon/lifecycle.ts +40 -23
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +14 -9
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +66 -7
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -2
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +85 -0
- package/src/home/suggested-prompts.ts +168 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +327 -6
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +191 -100
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +26 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +68 -15
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +11 -3
- package/src/memory/memory-retrospective-job.ts +413 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +41 -14
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +34 -0
- package/src/memory/migrations/registry.ts +58 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +22 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +158 -112
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +660 -4
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +42 -25
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +369 -62
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +38 -50
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +8 -5
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +6 -3
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +100 -20
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +69 -19
- package/src/prompts/system-prompt.ts +118 -216
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +10 -2
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +281 -9
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +159 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +18 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +91 -1
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +63 -23
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +86 -23
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/types.ts +25 -0
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +152 -56
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +43 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -6
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +50 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +161 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +290 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +147 -2
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +196 -31
- package/src/runtime/routes/conversation-routes.ts +472 -425
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +17 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +20 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +427 -0
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +188 -0
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +150 -0
- package/src/tools/browser/browser-execution.ts +106 -0
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +4 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +22 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +42 -2
- package/src/tools/browser/cdp-client/factory.ts +171 -4
- package/src/tools/browser/cdp-client/local-cdp-client.ts +21 -0
- package/src/tools/browser/cdp-client/types.ts +101 -0
- package/src/tools/browser/pinned-tabs.ts +146 -0
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +187 -2
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +216 -73
- package/src/tools/network/web-search.ts +216 -98
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +31 -98
- package/src/tools/ui-surface/definitions.ts +9 -23
- package/src/types/onboarding-context.ts +4 -0
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -7
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/prompts/cache-boundary.ts +0 -8
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory v3 — `memory_v3_consolidate` job handler.
|
|
3
|
+
*
|
|
4
|
+
* The v3 consolidation job drains the SHARED `memory/buffer.md` (the same
|
|
5
|
+
* buffer v2 uses — there is no v3 buffer) into the SHARED concept pages AND the
|
|
6
|
+
* v3 **tree** overlay, while maintaining the SHARED standing-context files
|
|
7
|
+
* (`essentials.md` / `threads.md` / `recent.md`) byte-for-byte the way v2 does.
|
|
8
|
+
* It is the v3 counterpart to `assistant/src/memory/v2/consolidation-job.ts`
|
|
9
|
+
* and mirrors its orchestration exactly — the only divergences are the gating
|
|
10
|
+
* flag (`memory.v3.write.enabled`), the lock path (`memory/.v3-state/`), and the
|
|
11
|
+
* prompt body (which additionally asks the agent to author/refresh the tree).
|
|
12
|
+
*
|
|
13
|
+
* Because the buffer and the standing-context files are shared, exactly one
|
|
14
|
+
* consolidator may own the drain at a time. The scheduler enforces this: when
|
|
15
|
+
* `memory.v3.write.enabled` is on it enqueues `memory_v3_consolidate` INSTEAD of
|
|
16
|
+
* `memory_v2_consolidate` (see `maybeEnqueueGraphMaintenanceJobs` in
|
|
17
|
+
* `jobs-worker.ts`). Concept pages stay the shared canonical store, so the v2
|
|
18
|
+
* router keeps working off pages v3 writes — it just ignores the tree overlay.
|
|
19
|
+
*
|
|
20
|
+
* Lifecycle (identical to v2 except the flag + lock path + tree-authoring
|
|
21
|
+
* prompt):
|
|
22
|
+
* 1. Bail if `config.memory.v3.write.enabled` is false (the worker may have
|
|
23
|
+
* claimed a stale row from before the flag was flipped off).
|
|
24
|
+
* 2. Acquire a single-process lock at `memory/.v3-state/consolidation.lock`.
|
|
25
|
+
* 3. Capture the cutoff timestamp at dispatch.
|
|
26
|
+
* 4. Read the shared `memory/buffer.md`. Bail if empty.
|
|
27
|
+
* 5. Hand off to `runBackgroundJob()` with the v3 consolidation prompt
|
|
28
|
+
* (`suppressFailureNotifications: true`).
|
|
29
|
+
* 6. On success, enqueue follow-ups: `memory_v3_index_maintenance` (mechanical
|
|
30
|
+
* tree/DAG upkeep) and `embed_concept_page` reembed (pages are shared, so
|
|
31
|
+
* reembed is still needed — reuse the existing `memory_v2_reembed` fan-out
|
|
32
|
+
* job type, which enqueues one `embed_concept_page` per slug).
|
|
33
|
+
* 7. Release the lock.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
closeSync,
|
|
38
|
+
mkdirSync,
|
|
39
|
+
openSync,
|
|
40
|
+
readFileSync,
|
|
41
|
+
unlinkSync,
|
|
42
|
+
writeSync,
|
|
43
|
+
} from "node:fs";
|
|
44
|
+
import { dirname, join } from "node:path";
|
|
45
|
+
|
|
46
|
+
import type { AssistantConfig } from "../../config/types.js";
|
|
47
|
+
import { runBackgroundJob } from "../../runtime/background-job-runner.js";
|
|
48
|
+
import { getLogger } from "../../util/logger.js";
|
|
49
|
+
import { getWorkspaceDir } from "../../util/platform.js";
|
|
50
|
+
import { isProcessAlive } from "../../util/process-liveness.js";
|
|
51
|
+
import { formatBufferTimestamp } from "../graph/tool-handlers.js";
|
|
52
|
+
import {
|
|
53
|
+
enqueueMemoryJob,
|
|
54
|
+
type MemoryJob,
|
|
55
|
+
type MemoryJobType,
|
|
56
|
+
} from "../jobs-store.js";
|
|
57
|
+
// The consolidation conversation `source` is a UI/routing concern shared with
|
|
58
|
+
// v2 (the route layer recognizes "this conversation IS background memory
|
|
59
|
+
// consolidation" by this string). v2 and v3 are mutually exclusive drainers, so
|
|
60
|
+
// reusing the same source keeps that recognition working for both without
|
|
61
|
+
// forking a v3 constant.
|
|
62
|
+
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../v2/constants.js";
|
|
63
|
+
import { resolveConsolidationPrompt } from "./prompts/consolidation.js";
|
|
64
|
+
|
|
65
|
+
const log = getLogger("memory-v3-consolidate");
|
|
66
|
+
|
|
67
|
+
/** Stable identifier surfaced in `runBackgroundJob` logs and notifications. */
|
|
68
|
+
const JOB_NAME = "memory.consolidate";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Hard timeout for the consolidation run. Matches v2: consolidation reads the
|
|
72
|
+
* buffer, rewrites several files, re-encodes essentials/threads, and authors
|
|
73
|
+
* the tree — generous upper bound so a slow run isn't killed mid-edit, but
|
|
74
|
+
* bounded so a stuck provider can't pin the worker indefinitely.
|
|
75
|
+
*/
|
|
76
|
+
const CONSOLIDATION_TIMEOUT_MS = 15 * 60 * 1000;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Follow-up jobs to fan out after a successful consolidation:
|
|
80
|
+
* - `memory_v3_index_maintenance` — mechanical (no-LLM) tree/DAG upkeep:
|
|
81
|
+
* validate the tree, report stale composed indices, cycle-check the DAG.
|
|
82
|
+
* - `memory_v2_reembed` — re-embed every shared concept page (the fan-out job
|
|
83
|
+
* enqueues one `embed_concept_page` per slug). Pages are shared, so a v3
|
|
84
|
+
* consolidation that touches them still needs the reembed. Conservatively
|
|
85
|
+
* re-embeds every page; the embedder's content-hash cache makes unchanged
|
|
86
|
+
* pages effectively free.
|
|
87
|
+
*/
|
|
88
|
+
const FOLLOW_UP_JOB_TYPES: readonly MemoryJobType[] = [
|
|
89
|
+
"memory_v3_index_maintenance",
|
|
90
|
+
"memory_v2_reembed",
|
|
91
|
+
] as const;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Job handler. See file header for the full lifecycle. Returns a discriminated
|
|
95
|
+
* union so tests can assert on the path taken (disabled / locked / empty /
|
|
96
|
+
* invoked / failed) without having to spy on the filesystem. Mirrors v2's
|
|
97
|
+
* `ConsolidationOutcome`.
|
|
98
|
+
*/
|
|
99
|
+
export type ConsolidationOutcome =
|
|
100
|
+
| { kind: "disabled" }
|
|
101
|
+
| { kind: "locked"; holder: string }
|
|
102
|
+
| { kind: "empty_buffer" }
|
|
103
|
+
| { kind: "run_failed"; reason?: string }
|
|
104
|
+
| {
|
|
105
|
+
kind: "invoked";
|
|
106
|
+
conversationId: string;
|
|
107
|
+
cutoff: string;
|
|
108
|
+
followUpJobIds: string[];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export async function memoryV3ConsolidateJob(
|
|
112
|
+
_job: MemoryJob,
|
|
113
|
+
config: AssistantConfig,
|
|
114
|
+
): Promise<ConsolidationOutcome> {
|
|
115
|
+
if (!config.memory.v3.write.enabled) {
|
|
116
|
+
log.debug("memory.v3.write.enabled is false; consolidation skipped");
|
|
117
|
+
return { kind: "disabled" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const memoryDir = join(getWorkspaceDir(), "memory");
|
|
121
|
+
const lockPath = join(memoryDir, ".v3-state", "consolidation.lock");
|
|
122
|
+
const bufferPath = join(memoryDir, "buffer.md");
|
|
123
|
+
|
|
124
|
+
// Step 1: acquire lock. Bails immediately if another consolidation is
|
|
125
|
+
// already in flight — the next scheduled run can pick up where we leave off.
|
|
126
|
+
const holder = tryAcquireLock(lockPath);
|
|
127
|
+
if (holder !== null) {
|
|
128
|
+
log.warn({ lockPath, holder }, "consolidation skipped: lock already held");
|
|
129
|
+
return { kind: "locked", holder };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// Step 2: capture cutoff. Formatted to match `buffer.md` entry timestamps
|
|
134
|
+
// (`Mon D, h:mm AM/PM`) so the agent's "timestamp ≥ cutoff" check compares
|
|
135
|
+
// like-with-like at minute precision. Captured here (not at enqueue time)
|
|
136
|
+
// so late-claimed rows get a fresh cutoff.
|
|
137
|
+
const cutoff = formatBufferTimestamp(new Date());
|
|
138
|
+
|
|
139
|
+
// Step 3: bail on empty buffer. The shared buffer has no work to drain.
|
|
140
|
+
const bufferContent = readBufferContent(bufferPath);
|
|
141
|
+
if (bufferContent.trim().length === 0) {
|
|
142
|
+
log.debug("buffer.md empty; consolidation skipped");
|
|
143
|
+
return { kind: "empty_buffer" };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Step 4: hand off to the centralized background-job runner. As with v2,
|
|
147
|
+
// `suppressFailureNotifications: true` opts out of `activity.failed`
|
|
148
|
+
// notifications so a network blip on the tight consolidation interval does
|
|
149
|
+
// not spam the home feed; Sentry-side reporting is unchanged.
|
|
150
|
+
//
|
|
151
|
+
// The prompt override config key (`memory.v2.consolidation_prompt_path`) is
|
|
152
|
+
// shared — there is no separate v3 key, so an operator points one file at
|
|
153
|
+
// whichever consolidator owns the drain.
|
|
154
|
+
const runResult = await runBackgroundJob({
|
|
155
|
+
jobName: JOB_NAME,
|
|
156
|
+
source: MEMORY_V2_CONSOLIDATION_SOURCE,
|
|
157
|
+
prompt: resolveConsolidationPrompt(
|
|
158
|
+
config.memory.v2.consolidation_prompt_path,
|
|
159
|
+
cutoff,
|
|
160
|
+
),
|
|
161
|
+
trustContext: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
162
|
+
callSite: "mainAgent",
|
|
163
|
+
timeoutMs: CONSOLIDATION_TIMEOUT_MS,
|
|
164
|
+
origin: "memory_consolidation",
|
|
165
|
+
suppressFailureNotifications: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!runResult.ok) {
|
|
169
|
+
log.error(
|
|
170
|
+
{
|
|
171
|
+
conversationId: runResult.conversationId,
|
|
172
|
+
errorKind: runResult.errorKind,
|
|
173
|
+
err: runResult.error?.message,
|
|
174
|
+
},
|
|
175
|
+
"consolidation run failed; follow-ups skipped",
|
|
176
|
+
);
|
|
177
|
+
return runResult.error?.message !== undefined
|
|
178
|
+
? { kind: "run_failed", reason: runResult.error.message }
|
|
179
|
+
: { kind: "run_failed" };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Step 5: enqueue follow-up jobs (tree maintenance + page reembed).
|
|
183
|
+
const followUpJobIds: string[] = [];
|
|
184
|
+
for (const jobType of FOLLOW_UP_JOB_TYPES) {
|
|
185
|
+
try {
|
|
186
|
+
followUpJobIds.push(enqueueMemoryJob(jobType, {}));
|
|
187
|
+
} catch (err) {
|
|
188
|
+
// Best-effort: a failed enqueue here doesn't undo the agent's writes,
|
|
189
|
+
// and the next scheduled consolidation will attempt the same fan-out.
|
|
190
|
+
log.warn(
|
|
191
|
+
{ err, jobType },
|
|
192
|
+
"consolidation: failed to enqueue follow-up job; continuing",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
log.info(
|
|
198
|
+
{
|
|
199
|
+
conversationId: runResult.conversationId,
|
|
200
|
+
cutoff,
|
|
201
|
+
followUpJobIds,
|
|
202
|
+
},
|
|
203
|
+
"consolidation invoked",
|
|
204
|
+
);
|
|
205
|
+
return {
|
|
206
|
+
kind: "invoked",
|
|
207
|
+
conversationId: runResult.conversationId,
|
|
208
|
+
cutoff,
|
|
209
|
+
followUpJobIds,
|
|
210
|
+
};
|
|
211
|
+
} finally {
|
|
212
|
+
releaseLock(lockPath);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Read `memory/buffer.md`. Missing file → empty string so the skip-on-empty
|
|
218
|
+
* branch doesn't have to distinguish "no file" from "blank file".
|
|
219
|
+
*/
|
|
220
|
+
function readBufferContent(bufferPath: string): string {
|
|
221
|
+
try {
|
|
222
|
+
return readFileSync(bufferPath, "utf-8");
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return "";
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Atomically create the lock file with `wx` (O_CREAT | O_EXCL) flags. Returns
|
|
231
|
+
* `null` on success, or the current holder string when the file already exists
|
|
232
|
+
* and the holder is still alive. Mirrors v2's lock machinery exactly — single
|
|
233
|
+
* writer per workspace, so a holder whose process died is unambiguously stale
|
|
234
|
+
* and is taken over automatically.
|
|
235
|
+
*/
|
|
236
|
+
function tryAcquireLock(lockPath: string): string | null {
|
|
237
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
238
|
+
|
|
239
|
+
const firstHolder = tryCreate(lockPath);
|
|
240
|
+
if (firstHolder === null) return null;
|
|
241
|
+
if (!isHolderStale(firstHolder)) return firstHolder;
|
|
242
|
+
|
|
243
|
+
log.info(
|
|
244
|
+
{ lockPath, holder: firstHolder },
|
|
245
|
+
"consolidation: taking over stale lock (holder not running)",
|
|
246
|
+
);
|
|
247
|
+
try {
|
|
248
|
+
unlinkSync(lockPath);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
251
|
+
if (code !== "ENOENT") {
|
|
252
|
+
log.warn(
|
|
253
|
+
{ err, lockPath },
|
|
254
|
+
"consolidation: failed to unlink stale lock; reporting as locked",
|
|
255
|
+
);
|
|
256
|
+
return firstHolder;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return tryCreate(lockPath);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Atomically create the lock file. Returns `null` on success, or the holder
|
|
264
|
+
* string read from the file when it already exists (`"unknown"` if the read
|
|
265
|
+
* itself fails). Rethrows any non-EEXIST errno from `openSync`.
|
|
266
|
+
*/
|
|
267
|
+
function tryCreate(lockPath: string): string | null {
|
|
268
|
+
let fd: number;
|
|
269
|
+
try {
|
|
270
|
+
fd = openSync(lockPath, "wx");
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
|
|
273
|
+
try {
|
|
274
|
+
return readFileSync(lockPath, "utf-8").trim() || "unknown";
|
|
275
|
+
} catch {
|
|
276
|
+
return "unknown";
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
writeSync(fd, `${process.pid} ${Date.now()}\n`);
|
|
281
|
+
} catch {
|
|
282
|
+
// best-effort — payload is advisory, the file's existence is the lock
|
|
283
|
+
} finally {
|
|
284
|
+
try {
|
|
285
|
+
closeSync(fd);
|
|
286
|
+
} catch {
|
|
287
|
+
// best-effort
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* A holder string is stale when its PID parses to a non-running process. An
|
|
295
|
+
* unparseable / empty / `"unknown"` payload is also treated as stale: the only
|
|
296
|
+
* writer is `tryCreate`, so corruption indicates a partial write from a crashed
|
|
297
|
+
* prior holder rather than a live writer mid-flush.
|
|
298
|
+
*/
|
|
299
|
+
function isHolderStale(holder: string): boolean {
|
|
300
|
+
const match = /^\d+/.exec(holder);
|
|
301
|
+
if (!match) return true;
|
|
302
|
+
const pid = Number.parseInt(match[0], 10);
|
|
303
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
304
|
+
return !isProcessAlive(pid);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Idempotent unlink of the lock file. Called from the `finally` block so a
|
|
309
|
+
* crash in the run path doesn't leave the lock stranded. ENOENT is swallowed
|
|
310
|
+
* because the lock may have been released by an operator or never created.
|
|
311
|
+
*/
|
|
312
|
+
function releaseLock(lockPath: string): void {
|
|
313
|
+
try {
|
|
314
|
+
unlinkSync(lockPath);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
317
|
+
if (code === "ENOENT") return;
|
|
318
|
+
log.warn(
|
|
319
|
+
{ err, lockPath },
|
|
320
|
+
"consolidation: failed to release lock (best-effort)",
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory v3 — `memory_v3_edge_learning` job (fast lane, no LLM).
|
|
3
|
+
*
|
|
4
|
+
* Reconciles the raw co-activation log (`memory_v3_coactivation`, migration
|
|
5
|
+
* 262) into the weighted learned-edge graph (`memory_v3_auto_edges`, migration
|
|
6
|
+
* 263). One pass does three things:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Decay** — multiplicatively age all existing auto-edge weights toward
|
|
9
|
+
* zero on a half-life schedule (the rich-get-richer counterweight: an edge
|
|
10
|
+
* that stops being reinforced fades, so a once-hot pair can't dominate the
|
|
11
|
+
* adjacency forever).
|
|
12
|
+
* 2. **Reinforce** — for each recent co-activation whose `used` flag is set,
|
|
13
|
+
* bump the `source → target` weight. *Used-only*: we learn associations
|
|
14
|
+
* that proved load-bearing for a turn, not pairs that merely surfaced
|
|
15
|
+
* together. The watermark checkpoint advances so each co-activation is
|
|
16
|
+
* counted once.
|
|
17
|
+
* 3. **Propose** — surface the top-weight auto-edges as advisory promotion
|
|
18
|
+
* *candidates* for the assistant to ratify into curated `edges:` during
|
|
19
|
+
* consolidation. This job PROPOSES; it never auto-writes page frontmatter.
|
|
20
|
+
* Diversity counterweight: candidates are capped and a single source's
|
|
21
|
+
* out-edges are bounded so one hub can't monopolize the slate.
|
|
22
|
+
*
|
|
23
|
+
* Decay runs before reinforce so a fresh reinforcement isn't immediately aged
|
|
24
|
+
* by the same pass. The job is idempotent in effect: re-running with no new
|
|
25
|
+
* co-activations only decays (which is itself elapsed-time-bounded).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { getLogger } from "../../util/logger.js";
|
|
29
|
+
import { getMemoryCheckpoint, setMemoryCheckpoint } from "../checkpoints.js";
|
|
30
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
31
|
+
import { getDb } from "../db-connection.js";
|
|
32
|
+
import type { MemoryJob } from "../jobs-store.js";
|
|
33
|
+
import {
|
|
34
|
+
type AutoEdgeRow,
|
|
35
|
+
decay,
|
|
36
|
+
reinforce,
|
|
37
|
+
topByWeight,
|
|
38
|
+
} from "./auto-edges.js";
|
|
39
|
+
import { readCoactivations } from "./coactivation-store.js";
|
|
40
|
+
|
|
41
|
+
const log = getLogger("memory-v3-edge-learning");
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Half-life of auto-edge weight decay. Matches the v2 injection-score cadence
|
|
45
|
+
* (3 days) — a pair reinforced 3 days ago and never since contributes half its
|
|
46
|
+
* weight, 6 days ago a quarter.
|
|
47
|
+
*/
|
|
48
|
+
export const EDGE_DECAY_HALF_LIFE_MS = 3 * 24 * 60 * 60 * 1000;
|
|
49
|
+
|
|
50
|
+
/** Max promotion candidates surfaced per run (the diversity cap). */
|
|
51
|
+
export const MAX_PROMOTION_CANDIDATES = 20;
|
|
52
|
+
|
|
53
|
+
/** Max candidates contributed by any single source slug (anti-hub diversity). */
|
|
54
|
+
export const MAX_CANDIDATES_PER_SOURCE = 3;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Minimum weight for an auto-edge to be eligible for promotion. A pair must
|
|
58
|
+
* accrue more than a single reinforcement (which decays away) before it's worth
|
|
59
|
+
* proposing as a curated edge.
|
|
60
|
+
*/
|
|
61
|
+
export const PROMOTION_WEIGHT_FLOOR = 1.5;
|
|
62
|
+
|
|
63
|
+
/** Checkpoint key for the high-water mark of reconciled co-activations. */
|
|
64
|
+
const WATERMARK_KEY = "memory_v3_edge_learning:coactivation_watermark";
|
|
65
|
+
|
|
66
|
+
/** Summary of one edge-learning pass, returned for the dispatcher log + tests. */
|
|
67
|
+
export interface EdgeLearningResult {
|
|
68
|
+
/** Used co-activations reinforced this pass. */
|
|
69
|
+
reinforced: number;
|
|
70
|
+
/** Co-activations skipped because `used` was falsy. */
|
|
71
|
+
skippedUnused: number;
|
|
72
|
+
/** Auto-edges pruned by decay (fell below the floor). */
|
|
73
|
+
pruned: number;
|
|
74
|
+
/** Advisory promotion candidates, heaviest first, after the diversity cap. */
|
|
75
|
+
candidates: AutoEdgeRow[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run one edge-learning pass against `database`. Pure of LLM and workspace I/O —
|
|
80
|
+
* the whole pass is bounded DB work, hence the fast lane.
|
|
81
|
+
*/
|
|
82
|
+
export function runEdgeLearning(
|
|
83
|
+
database: DrizzleDb,
|
|
84
|
+
now = Date.now(),
|
|
85
|
+
): EdgeLearningResult {
|
|
86
|
+
// 1. Decay first so this pass's reinforcements aren't immediately aged.
|
|
87
|
+
const pruned = decay(database, now, EDGE_DECAY_HALF_LIFE_MS);
|
|
88
|
+
|
|
89
|
+
// 2. Reinforce from co-activations newer than the watermark. The watermark is
|
|
90
|
+
// a created_at boundary; `since` is inclusive so we nudge it forward by 1ms
|
|
91
|
+
// to avoid re-counting the boundary row.
|
|
92
|
+
const watermark = parseInt(getMemoryCheckpoint(WATERMARK_KEY) ?? "0", 10);
|
|
93
|
+
const since = watermark > 0 ? watermark + 1 : undefined;
|
|
94
|
+
const coactivations = readCoactivations(database, since);
|
|
95
|
+
|
|
96
|
+
let reinforced = 0;
|
|
97
|
+
let skippedUnused = 0;
|
|
98
|
+
let maxCreatedAt = watermark;
|
|
99
|
+
for (const row of coactivations) {
|
|
100
|
+
if (row.createdAt > maxCreatedAt) maxCreatedAt = row.createdAt;
|
|
101
|
+
// Reinforce usefulness, not mere retrieval: skip co-activations the loop
|
|
102
|
+
// (or a later usefulness reconciliation) did not mark as used.
|
|
103
|
+
if (!row.used) {
|
|
104
|
+
skippedUnused += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
reinforce(database, row.sourceSlug, row.targetSlug, now);
|
|
108
|
+
reinforced += 1;
|
|
109
|
+
}
|
|
110
|
+
if (maxCreatedAt > watermark) {
|
|
111
|
+
setMemoryCheckpoint(WATERMARK_KEY, String(maxCreatedAt));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 3. Propose promotion candidates: heaviest auto-edges above the floor, capped
|
|
115
|
+
// overall and per-source so a single hub can't monopolize the slate.
|
|
116
|
+
const candidates = selectPromotionCandidates(
|
|
117
|
+
topByWeight(database, MAX_PROMOTION_CANDIDATES * MAX_CANDIDATES_PER_SOURCE),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
log.info(
|
|
121
|
+
{
|
|
122
|
+
reinforced,
|
|
123
|
+
skippedUnused,
|
|
124
|
+
pruned,
|
|
125
|
+
candidateCount: candidates.length,
|
|
126
|
+
},
|
|
127
|
+
"v3 edge learning complete",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return { reinforced, skippedUnused, pruned, candidates };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Apply the weight floor and the overall / per-source diversity caps to a
|
|
135
|
+
* weight-sorted list of auto-edges. Input must already be sorted heaviest-first
|
|
136
|
+
* (as {@link topByWeight} returns).
|
|
137
|
+
*/
|
|
138
|
+
function selectPromotionCandidates(sorted: AutoEdgeRow[]): AutoEdgeRow[] {
|
|
139
|
+
const out: AutoEdgeRow[] = [];
|
|
140
|
+
const perSource = new Map<string, number>();
|
|
141
|
+
for (const edge of sorted) {
|
|
142
|
+
if (out.length >= MAX_PROMOTION_CANDIDATES) break;
|
|
143
|
+
if (edge.weight < PROMOTION_WEIGHT_FLOOR) continue;
|
|
144
|
+
const count = perSource.get(edge.sourceSlug) ?? 0;
|
|
145
|
+
if (count >= MAX_CANDIDATES_PER_SOURCE) continue;
|
|
146
|
+
perSource.set(edge.sourceSlug, count + 1);
|
|
147
|
+
out.push(edge);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Job handler for `memory_v3_edge_learning`. Thin wrapper over
|
|
154
|
+
* {@link runEdgeLearning} so the heavy lifting (and its tests) live in one
|
|
155
|
+
* place. The job carries no payload — it always reconciles the whole recent
|
|
156
|
+
* co-activation log.
|
|
157
|
+
*/
|
|
158
|
+
export function memoryV3EdgeLearningJob(_job: MemoryJob): EdgeLearningResult {
|
|
159
|
+
return runEdgeLearning(getDb());
|
|
160
|
+
}
|