@vellumai/assistant 0.7.2 → 0.8.0
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 +45 -29
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/docs/architecture/memory.md +5 -2
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +470 -25
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +6 -33
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +12 -7
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +457 -8
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +38 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +2 -20
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +36 -16
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +60 -5
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-request-resolvers.ts +3 -32
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +11 -10
- package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
- package/src/cli/commands/oauth/connect.ts +124 -40
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +199 -27
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +76 -12
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
- package/src/daemon/conversation-agent-loop.ts +183 -43
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +22 -10
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +211 -29
- package/src/daemon/conversation-tool-setup.ts +66 -19
- package/src/daemon/conversation.ts +18 -23
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +26 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +47 -22
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +50 -4
- package/src/daemon/host-file-proxy.ts +44 -8
- package/src/daemon/host-transfer-proxy.ts +97 -6
- package/src/daemon/lifecycle.ts +167 -101
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +66 -15
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +22 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +119 -0
- package/src/filing/filing-service.ts +29 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +149 -38
- package/src/ipc/gateway-client.ts +37 -3
- package/src/ipc/skill-server.ts +99 -42
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +1 -16
- package/src/memory/context-search/sources/memory.ts +2 -3
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +72 -61
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +11 -67
- package/src/memory/graph/graph-search.ts +4 -24
- package/src/memory/graph/retriever.test.ts +12 -1
- package/src/memory/graph/retriever.ts +10 -15
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +53 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +85 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +7 -0
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +10 -72
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +346 -255
- package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
- package/src/memory/v2/__tests__/injection.test.ts +297 -190
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +113 -196
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +77 -14
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +69 -20
- package/src/memory/v2/injection.ts +75 -68
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +41 -1
- package/src/memory/v2/qdrant.ts +306 -46
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +77 -110
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +26 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +17 -10
- package/src/notifications/copy-composer.ts +47 -0
- package/src/notifications/decision-engine.ts +46 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +36 -4
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +914 -0
- package/src/proactive-artifact/job.ts +366 -0
- package/src/proactive-artifact/message-copy.ts +58 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +27 -15
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +165 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +37 -17
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +136 -17
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/include-graph.ts +35 -13
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +14 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +43 -3
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +14 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* substitute in.
|
|
16
16
|
*
|
|
17
17
|
* Lifecycle:
|
|
18
|
-
* 1. Bail if
|
|
19
|
-
*
|
|
18
|
+
* 1. Bail if `config.memory.v2.enabled` is false (the worker may have
|
|
19
|
+
* claimed a stale row from before v2 was disabled).
|
|
20
20
|
* 2. Acquire a single-process lock at `memory/.v2-state/consolidation.lock`
|
|
21
21
|
* so two overlapping schedule windows can't fight over the same files.
|
|
22
22
|
* The lock contains the holder's PID + timestamp so a crashed run leaves
|
|
@@ -53,12 +53,12 @@ import {
|
|
|
53
53
|
} from "node:fs";
|
|
54
54
|
import { dirname, join } from "node:path";
|
|
55
55
|
|
|
56
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
57
56
|
import type { AssistantConfig } from "../../config/types.js";
|
|
58
57
|
import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../../daemon/trust-context.js";
|
|
59
58
|
import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
|
|
60
59
|
import { getLogger } from "../../util/logger.js";
|
|
61
60
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
61
|
+
import { isProcessAlive } from "../../util/process-liveness.js";
|
|
62
62
|
import { bootstrapConversation } from "../conversation-bootstrap.js";
|
|
63
63
|
import { deleteConversation } from "../conversation-crud.js";
|
|
64
64
|
import {
|
|
@@ -84,11 +84,11 @@ const FOLLOW_UP_JOB_TYPES: readonly MemoryJobType[] = [
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Job handler. See file header for the full lifecycle. Returns a discriminated
|
|
87
|
-
* union so tests can assert on the path taken (
|
|
87
|
+
* union so tests can assert on the path taken (disabled / locked / empty /
|
|
88
88
|
* invoked) without having to spy on the filesystem.
|
|
89
89
|
*/
|
|
90
90
|
export type ConsolidationOutcome =
|
|
91
|
-
| { kind: "
|
|
91
|
+
| { kind: "disabled" }
|
|
92
92
|
| { kind: "locked"; holder: string }
|
|
93
93
|
| { kind: "empty_buffer" }
|
|
94
94
|
| { kind: "wake_failed"; reason?: string }
|
|
@@ -103,9 +103,9 @@ export async function memoryV2ConsolidateJob(
|
|
|
103
103
|
_job: MemoryJob,
|
|
104
104
|
config: AssistantConfig,
|
|
105
105
|
): Promise<ConsolidationOutcome> {
|
|
106
|
-
if (!
|
|
107
|
-
log.debug("memory
|
|
108
|
-
return { kind: "
|
|
106
|
+
if (!config.memory.v2.enabled) {
|
|
107
|
+
log.debug("memory.v2.enabled is false; consolidation skipped");
|
|
108
|
+
return { kind: "disabled" };
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
const memoryDir = join(getWorkspaceDir(), "memory");
|
|
@@ -240,14 +240,20 @@ function readBufferContent(bufferPath: string): string {
|
|
|
240
240
|
/**
|
|
241
241
|
* Atomically create the lock file with `wx` (O_CREAT | O_EXCL) flags. Returns
|
|
242
242
|
* `null` on success, or the current holder string (file contents, typically
|
|
243
|
-
* `pid timestamp`) when the file already exists
|
|
244
|
-
* log diagnostics so operators can identify a stuck lock without re-reading.
|
|
243
|
+
* `pid timestamp`) when the file already exists and the holder is still alive.
|
|
245
244
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
245
|
+
* Stale-lock takeover: if the file exists but its holder PID is not running,
|
|
246
|
+
* unlink the stale file and retry the create exactly once. This recovers
|
|
247
|
+
* automatically from a crashed daemon that died with the lock held —
|
|
248
|
+
* otherwise every subsequent scheduled consolidation would skip with `locked`
|
|
249
|
+
* indefinitely until an operator manually removed the file.
|
|
250
|
+
*
|
|
251
|
+
* The simple takeover-then-retry is safe here (unlike `snapshot-lock.ts`'s
|
|
252
|
+
* full rename-aside dance) because only the assistant's jobs worker calls
|
|
253
|
+
* this lock, and at most one assistant process runs per workspace at any
|
|
254
|
+
* time. A holder with an unparseable / empty payload is treated as stale —
|
|
255
|
+
* the only writers ever produce a `<pid> <timestamp>` line, so an
|
|
256
|
+
* unparseable file is corruption from a partial write that crashed.
|
|
251
257
|
*/
|
|
252
258
|
function tryAcquireLock(lockPath: string): string | null {
|
|
253
259
|
// The workspace migration seeds `memory/.v2-state/`, but tests and
|
|
@@ -255,9 +261,40 @@ function tryAcquireLock(lockPath: string): string | null {
|
|
|
255
261
|
// is idempotent, so the call is cheap when the dir already exists.
|
|
256
262
|
mkdirSync(dirname(lockPath), { recursive: true });
|
|
257
263
|
|
|
264
|
+
const firstHolder = tryCreate(lockPath);
|
|
265
|
+
if (firstHolder === null) return null;
|
|
266
|
+
if (!isHolderStale(firstHolder)) return firstHolder;
|
|
267
|
+
|
|
268
|
+
log.info(
|
|
269
|
+
{ lockPath, holder: firstHolder },
|
|
270
|
+
"consolidation: taking over stale lock (holder not running)",
|
|
271
|
+
);
|
|
272
|
+
try {
|
|
273
|
+
unlinkSync(lockPath);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
276
|
+
if (code !== "ENOENT") {
|
|
277
|
+
log.warn(
|
|
278
|
+
{ err, lockPath },
|
|
279
|
+
"consolidation: failed to unlink stale lock; reporting as locked",
|
|
280
|
+
);
|
|
281
|
+
return firstHolder;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// After unlink, the next `wx` create should succeed. If a third party
|
|
285
|
+
// raced in and re-acquired (vanishingly unlikely with one writer per
|
|
286
|
+
// workspace), surface their holder string rather than overwriting.
|
|
287
|
+
return tryCreate(lockPath);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Atomically create the lock file. Returns `null` on success, or the holder
|
|
292
|
+
* string read from the file when it already exists (`"unknown"` if the read
|
|
293
|
+
* itself fails). Rethrows any non-EEXIST errno from `openSync`.
|
|
294
|
+
*/
|
|
295
|
+
function tryCreate(lockPath: string): string | null {
|
|
258
296
|
let fd: number;
|
|
259
297
|
try {
|
|
260
|
-
// `wx` = create-if-not-exists, fail with EEXIST if it does.
|
|
261
298
|
fd = openSync(lockPath, "wx");
|
|
262
299
|
} catch (err) {
|
|
263
300
|
if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
|
|
@@ -267,13 +304,10 @@ function tryAcquireLock(lockPath: string): string | null {
|
|
|
267
304
|
return "unknown";
|
|
268
305
|
}
|
|
269
306
|
}
|
|
270
|
-
|
|
271
|
-
// Best-effort PID + timestamp payload so a stale lock can be diagnosed.
|
|
272
|
-
// The worker only cares that the file exists; the contents are advisory.
|
|
273
307
|
try {
|
|
274
308
|
writeSync(fd, `${process.pid} ${Date.now()}\n`);
|
|
275
309
|
} catch {
|
|
276
|
-
// best-effort
|
|
310
|
+
// best-effort — payload is advisory, the file's existence is the lock
|
|
277
311
|
} finally {
|
|
278
312
|
try {
|
|
279
313
|
closeSync(fd);
|
|
@@ -284,6 +318,21 @@ function tryAcquireLock(lockPath: string): string | null {
|
|
|
284
318
|
return null;
|
|
285
319
|
}
|
|
286
320
|
|
|
321
|
+
/**
|
|
322
|
+
* A holder string is stale when its PID parses to a non-running process.
|
|
323
|
+
* The payload format is `<pid> <timestamp>` (see `tryCreate`'s write), but
|
|
324
|
+
* an unparseable / empty / `"unknown"` payload is also treated as stale:
|
|
325
|
+
* the only writer is `tryCreate` itself, so corruption indicates a partial
|
|
326
|
+
* write from a crashed prior holder rather than a live writer mid-flush.
|
|
327
|
+
*/
|
|
328
|
+
function isHolderStale(holder: string): boolean {
|
|
329
|
+
const match = /^\d+/.exec(holder);
|
|
330
|
+
if (!match) return true;
|
|
331
|
+
const pid = Number.parseInt(match[0], 10);
|
|
332
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
333
|
+
return !isProcessAlive(pid);
|
|
334
|
+
}
|
|
335
|
+
|
|
287
336
|
/**
|
|
288
337
|
* Idempotent unlink of the lock file. Called from the `finally` block so a
|
|
289
338
|
* crash in the wake path doesn't leave the lock stranded. ENOENT is swallowed
|
|
@@ -29,21 +29,18 @@ import { getWorkspaceDir } from "../../util/platform.js";
|
|
|
29
29
|
import type { DrizzleDb } from "../db-connection.js";
|
|
30
30
|
import {
|
|
31
31
|
type MemoryV2ConceptRowRecord,
|
|
32
|
-
type MemoryV2SkillRowRecord,
|
|
33
32
|
recordMemoryV2ActivationLog,
|
|
34
33
|
} from "../memory-v2-activation-log-store.js";
|
|
35
34
|
import {
|
|
36
35
|
computeOwnActivation,
|
|
37
|
-
computeSkillActivation,
|
|
38
36
|
selectCandidates,
|
|
39
37
|
selectInjections,
|
|
40
|
-
selectSkillInjections,
|
|
41
38
|
spreadActivation,
|
|
42
39
|
} from "./activation.js";
|
|
43
40
|
import { hydrate, save } from "./activation-store.js";
|
|
44
41
|
import { getEdgeIndex } from "./edge-index.js";
|
|
45
42
|
import { readPage, renderPageContent } from "./page-store.js";
|
|
46
|
-
import {
|
|
43
|
+
import { getSkillCapability, isSkillSlug } from "./skill-store.js";
|
|
47
44
|
import type { ActivationState, EverInjectedEntry } from "./types.js";
|
|
48
45
|
|
|
49
46
|
const log = getLogger("memory-v2-injection");
|
|
@@ -84,6 +81,7 @@ export interface InjectMemoryV2BlockParams {
|
|
|
84
81
|
*/
|
|
85
82
|
mode?: InjectMemoryV2Mode;
|
|
86
83
|
config: AssistantConfig;
|
|
84
|
+
signal?: AbortSignal;
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
export interface InjectMemoryV2BlockResult {
|
|
@@ -127,30 +125,36 @@ export async function injectMemoryV2Block(
|
|
|
127
125
|
nowText,
|
|
128
126
|
messageId,
|
|
129
127
|
config,
|
|
128
|
+
signal,
|
|
130
129
|
} = params;
|
|
131
130
|
|
|
132
131
|
const workspaceDir = getWorkspaceDir();
|
|
133
132
|
|
|
134
133
|
// (1) Hydrate. Missing rows are normal at conversation start — proceed
|
|
135
134
|
// with an effective empty prior state so the first turn can still inject.
|
|
135
|
+
throwIfAborted(signal);
|
|
136
136
|
const priorState = await hydrate(database, conversationId);
|
|
137
137
|
|
|
138
138
|
// (2) Topology. `getEdgeIndex` walks concept-page frontmatter and caches
|
|
139
139
|
// the result module-locally; an empty workspace yields an empty index.
|
|
140
|
+
throwIfAborted(signal);
|
|
140
141
|
const edgeIndex = await getEdgeIndex(workspaceDir);
|
|
141
142
|
|
|
142
143
|
// (3) Candidate set: prior-state survivors above epsilon ∪ ANN top-50.
|
|
143
144
|
// `selectCandidates` also returns `fromPrior` / `fromAnn` provenance sets so
|
|
144
145
|
// telemetry can attribute each candidate back to its source.
|
|
146
|
+
throwIfAborted(signal);
|
|
145
147
|
const { candidates, fromPrior, fromAnn } = await selectCandidates({
|
|
146
148
|
priorState,
|
|
147
149
|
userText: userMessage,
|
|
148
150
|
assistantText: assistantMessage,
|
|
149
151
|
nowText,
|
|
150
152
|
config,
|
|
153
|
+
signal,
|
|
151
154
|
});
|
|
152
155
|
|
|
153
156
|
// (4) Own activation: A_o = d·prev + c_user·sim_u + c_a·sim_a + c_now·sim_n.
|
|
157
|
+
throwIfAborted(signal);
|
|
154
158
|
const { activation: ownActivation, breakdown: ownBreakdown } =
|
|
155
159
|
await computeOwnActivation({
|
|
156
160
|
candidates,
|
|
@@ -159,9 +163,11 @@ export async function injectMemoryV2Block(
|
|
|
159
163
|
assistantText: assistantMessage,
|
|
160
164
|
nowText,
|
|
161
165
|
config,
|
|
166
|
+
signal,
|
|
162
167
|
});
|
|
163
168
|
|
|
164
169
|
// (5) Spreading activation across the edge graph (k, hops from config).
|
|
170
|
+
throwIfAborted(signal);
|
|
165
171
|
const { k, hops, top_k, epsilon } = config.memory.v2;
|
|
166
172
|
const { final: finalActivation, contribution: spreadContribution } =
|
|
167
173
|
spreadActivation(ownActivation, edgeIndex, k, hops);
|
|
@@ -182,25 +188,6 @@ export async function injectMemoryV2Block(
|
|
|
182
188
|
});
|
|
183
189
|
const slugsToRender = mode === "context-load" ? topNow : toInject;
|
|
184
190
|
|
|
185
|
-
// (6b) Skill pipeline — a sibling pipeline to the concept-page one above.
|
|
186
|
-
// Skills are stateless (no decay, no spread, no `everInjected` dedup) and
|
|
187
|
-
// the catalog is small, so every known skill is scored every turn. The
|
|
188
|
-
// top-K injection slate is re-presented every turn so the agent can drop
|
|
189
|
-
// and pick skills up freely; the inspector renders the full ranked list.
|
|
190
|
-
const skillCandidates = new Set(getAllSkillIds());
|
|
191
|
-
const { activation: skillActivation, breakdown: skillBreakdown } =
|
|
192
|
-
await computeSkillActivation({
|
|
193
|
-
candidates: skillCandidates,
|
|
194
|
-
userText: userMessage,
|
|
195
|
-
assistantText: assistantMessage,
|
|
196
|
-
nowText,
|
|
197
|
-
config,
|
|
198
|
-
});
|
|
199
|
-
const { topNow: topSkillIds } = selectSkillInjections({
|
|
200
|
-
A: skillActivation,
|
|
201
|
-
topK: config.memory.v2.top_k_skills,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
191
|
// Build the next persisted state regardless of whether we render anything:
|
|
205
192
|
// even on a "no new injection" turn, prior-state activations decay via the
|
|
206
193
|
// candidate-set carry-forward and need to be rewritten so `epsilon`-trimmed
|
|
@@ -215,8 +202,10 @@ export async function injectMemoryV2Block(
|
|
|
215
202
|
// just rendered all of them); on per-turn it's just the newly added slugs.
|
|
216
203
|
// We append rather than reset so that compaction-driven eviction
|
|
217
204
|
// (`evictCompactedTurns`) is the only path that can re-enable a previously-
|
|
218
|
-
// injected slug.
|
|
219
|
-
//
|
|
205
|
+
// injected slug. Skill slugs (`skills/<id>`) participate in this dedup just
|
|
206
|
+
// like concept slugs — once attached on a turn, the cached attachment lives
|
|
207
|
+
// on that user message and the agent keeps seeing it across subsequent turns
|
|
208
|
+
// until compaction evicts the turn.
|
|
220
209
|
const everInjectedSet = new Set(priorEverInjected.map((entry) => entry.slug));
|
|
221
210
|
const newlyInjected = slugsToRender.filter(
|
|
222
211
|
(slug) => !everInjectedSet.has(slug),
|
|
@@ -243,7 +232,6 @@ export async function injectMemoryV2Block(
|
|
|
243
232
|
const { block, missingSlugs } = await renderInjectionBlock(
|
|
244
233
|
workspaceDir,
|
|
245
234
|
slugsToRender,
|
|
246
|
-
topSkillIds,
|
|
247
235
|
);
|
|
248
236
|
const missingSlugSet = new Set(missingSlugs);
|
|
249
237
|
if (missingSlugs.length > 0) {
|
|
@@ -262,7 +250,6 @@ export async function injectMemoryV2Block(
|
|
|
262
250
|
// block memory injection.
|
|
263
251
|
const toInjectSet = new Set(toInject);
|
|
264
252
|
const renderedSet = new Set(slugsToRender);
|
|
265
|
-
const topSkillIdSet = new Set(topSkillIds);
|
|
266
253
|
const conceptRows: MemoryV2ConceptRowRecord[] = [...candidates].map(
|
|
267
254
|
(slug) => {
|
|
268
255
|
const breakdown = ownBreakdown.get(slug);
|
|
@@ -301,6 +288,9 @@ export async function injectMemoryV2Block(
|
|
|
301
288
|
simUser: breakdown?.simUser ?? 0,
|
|
302
289
|
simAssistant: breakdown?.simAssistant ?? 0,
|
|
303
290
|
simNow: breakdown?.simNow ?? 0,
|
|
291
|
+
simUserRerankBoost: breakdown?.simUserRerankBoost ?? 0,
|
|
292
|
+
simAssistantRerankBoost: breakdown?.simAssistantRerankBoost ?? 0,
|
|
293
|
+
inRerankPool: breakdown?.inRerankPool ?? false,
|
|
304
294
|
spreadContribution: spreadContribution.get(slug) ?? 0,
|
|
305
295
|
source:
|
|
306
296
|
inPrior && inAnn ? "both" : inPrior ? "prior_state" : "ann_top50",
|
|
@@ -310,19 +300,6 @@ export async function injectMemoryV2Block(
|
|
|
310
300
|
);
|
|
311
301
|
conceptRows.sort((a, b) => b.finalActivation - a.finalActivation);
|
|
312
302
|
|
|
313
|
-
const skillRows: MemoryV2SkillRowRecord[] = [...skillCandidates].map((id) => {
|
|
314
|
-
const breakdown = skillBreakdown.get(id);
|
|
315
|
-
return {
|
|
316
|
-
id,
|
|
317
|
-
activation: skillActivation.get(id) ?? 0,
|
|
318
|
-
simUser: breakdown?.simUser ?? 0,
|
|
319
|
-
simAssistant: breakdown?.simAssistant ?? 0,
|
|
320
|
-
simNow: breakdown?.simNow ?? 0,
|
|
321
|
-
status: topSkillIdSet.has(id) ? "injected" : "not_injected",
|
|
322
|
-
};
|
|
323
|
-
});
|
|
324
|
-
skillRows.sort((a, b) => b.activation - a.activation);
|
|
325
|
-
|
|
326
303
|
const v2Cfg = config.memory.v2;
|
|
327
304
|
try {
|
|
328
305
|
recordMemoryV2ActivationLog({
|
|
@@ -330,7 +307,6 @@ export async function injectMemoryV2Block(
|
|
|
330
307
|
turn: currentTurn,
|
|
331
308
|
mode,
|
|
332
309
|
concepts: conceptRows,
|
|
333
|
-
skills: skillRows,
|
|
334
310
|
config: {
|
|
335
311
|
d: v2Cfg.d,
|
|
336
312
|
c_user: v2Cfg.c_user,
|
|
@@ -339,7 +315,6 @@ export async function injectMemoryV2Block(
|
|
|
339
315
|
k: v2Cfg.k,
|
|
340
316
|
hops: v2Cfg.hops,
|
|
341
317
|
top_k: v2Cfg.top_k,
|
|
342
|
-
top_k_skills: v2Cfg.top_k_skills,
|
|
343
318
|
epsilon: v2Cfg.epsilon,
|
|
344
319
|
},
|
|
345
320
|
});
|
|
@@ -353,6 +328,12 @@ export async function injectMemoryV2Block(
|
|
|
353
328
|
return { block, toInject: newlyInjected };
|
|
354
329
|
}
|
|
355
330
|
|
|
331
|
+
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
332
|
+
if (signal?.aborted) {
|
|
333
|
+
throw new DOMException("Aborted", "AbortError");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
356
337
|
// ---------------------------------------------------------------------------
|
|
357
338
|
// Internal helpers
|
|
358
339
|
// ---------------------------------------------------------------------------
|
|
@@ -380,9 +361,24 @@ interface RenderInjectionBlockResult {
|
|
|
380
361
|
}
|
|
381
362
|
|
|
382
363
|
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
364
|
+
* Leading instruction line emitted at the top of every non-empty injection
|
|
365
|
+
* block. Tells the agent that what follows are page summaries and that it
|
|
366
|
+
* should read the underlying file when a summary looks relevant. Pages
|
|
367
|
+
* without a `summary` field render in full instead — the agent treats
|
|
368
|
+
* those as inline content and doesn't need to follow up.
|
|
369
|
+
*/
|
|
370
|
+
const INJECTION_HEADER =
|
|
371
|
+
"**CRITICAL:** These are page summaries. Read the page file if it looks relevant.";
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Render the inner content of the `<memory>` block for a list of slugs.
|
|
375
|
+
* The caller wraps the result in `<memory>...</memory>` exactly once at
|
|
376
|
+
* injection time.
|
|
377
|
+
*
|
|
378
|
+
* The slug list is partitioned by prefix: slugs starting with `skills/`
|
|
379
|
+
* resolve to a `SkillEntry` via `getSkillCapability` and render under the
|
|
380
|
+
* trailing `### Skills You Can Use` subsection; everything else is read
|
|
381
|
+
* from disk via `readPage` and rendered as a concept-page section.
|
|
386
382
|
*
|
|
387
383
|
* Concept pages are read in parallel via `readPage`. Pages whose file has
|
|
388
384
|
* gone missing between selection and render (e.g. consolidation deleted
|
|
@@ -390,30 +386,31 @@ interface RenderInjectionBlockResult {
|
|
|
390
386
|
* block but reported back via `missingSlugs` so callers can surface the
|
|
391
387
|
* divergence.
|
|
392
388
|
*
|
|
393
|
-
* Skill
|
|
394
|
-
*
|
|
395
|
-
* the
|
|
389
|
+
* Skill slugs whose entry the cache no longer knows (e.g. uninstalled
|
|
390
|
+
* mid-run) are silently dropped, mirroring the missing-pages behavior but
|
|
391
|
+
* without entering `missingSlugs` — the skill catalog is the source of
|
|
392
|
+
* truth for skill availability, not on-disk concept pages, so a missing
|
|
393
|
+
* skill is an expected catalog-level outcome rather than a stale-index
|
|
394
|
+
* bug.
|
|
395
|
+
*
|
|
396
|
+
* Each concept-page section is rendered as a path header followed by either
|
|
397
|
+
* the page's `summary` (when present in frontmatter) or the full page (the
|
|
398
|
+
* fallback for pages predating the summary field). Skills sit at the end
|
|
399
|
+
* under `### Skills You Can Use`, unchanged. The leading `**CRITICAL:**`
|
|
400
|
+
* line tells the agent how to read the block.
|
|
401
|
+
*
|
|
402
|
+
* **CRITICAL:** These are page summaries. Read the page file if it looks relevant.
|
|
396
403
|
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* as it lives on disk — frontmatter (`edges`, `ref_files`) plus body — so
|
|
400
|
-
* the agent sees the page's edges and any referenced media paths alongside
|
|
401
|
-
* the prose:
|
|
404
|
+
* # memory/concepts/<concept-slug-1>.md
|
|
405
|
+
* <summary-1>
|
|
402
406
|
*
|
|
403
|
-
*
|
|
407
|
+
* # memory/concepts/<concept-slug-2>.md
|
|
404
408
|
* ---
|
|
405
409
|
* edges:
|
|
406
410
|
* - <neighbor-slug>
|
|
407
411
|
* ref_files:
|
|
408
412
|
* - <path/to/asset>
|
|
409
413
|
* ---
|
|
410
|
-
* <body-1>
|
|
411
|
-
*
|
|
412
|
-
* ### <slug-2>
|
|
413
|
-
* ---
|
|
414
|
-
* edges: []
|
|
415
|
-
* ref_files: []
|
|
416
|
-
* ---
|
|
417
414
|
* <body-2>
|
|
418
415
|
*
|
|
419
416
|
* ### Skills You Can Use
|
|
@@ -423,10 +420,12 @@ interface RenderInjectionBlockResult {
|
|
|
423
420
|
async function renderInjectionBlock(
|
|
424
421
|
workspaceDir: string,
|
|
425
422
|
slugs: string[],
|
|
426
|
-
skillIds: string[],
|
|
427
423
|
): Promise<RenderInjectionBlockResult> {
|
|
424
|
+
const conceptSlugs = slugs.filter((s) => !isSkillSlug(s));
|
|
425
|
+
const skillSlugs = slugs.filter((s) => isSkillSlug(s));
|
|
426
|
+
|
|
428
427
|
const pages = await Promise.all(
|
|
429
|
-
|
|
428
|
+
conceptSlugs.map(async (slug) => {
|
|
430
429
|
const page = await readPage(workspaceDir, slug);
|
|
431
430
|
return { slug, page };
|
|
432
431
|
}),
|
|
@@ -439,15 +438,23 @@ async function renderInjectionBlock(
|
|
|
439
438
|
missingSlugs.push(slug);
|
|
440
439
|
continue;
|
|
441
440
|
}
|
|
441
|
+
const summary = page.frontmatter.summary?.trim();
|
|
442
|
+
const path = `memory/concepts/${slug}.md`;
|
|
443
|
+
if (summary && summary.length > 0) {
|
|
444
|
+
sections.push(`# ${path}\n${summary}`);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
// Fallback: page predates the `summary` field (or the field was set to
|
|
448
|
+
// empty). Render the full page — frontmatter + body — so retrieval
|
|
449
|
+
// still surfaces the same content the agent saw before this change.
|
|
442
450
|
const content = renderPageContent(page).trim();
|
|
443
451
|
if (content.length === 0) continue;
|
|
444
|
-
sections.push(
|
|
452
|
+
sections.push(`# ${path}\n${content}`);
|
|
445
453
|
}
|
|
446
454
|
|
|
447
|
-
// v2's skills collection is skills-only, so the activation suffix always applies.
|
|
448
455
|
const skillLines: string[] = [];
|
|
449
|
-
for (const
|
|
450
|
-
const entry = getSkillCapability(
|
|
456
|
+
for (const slug of skillSlugs) {
|
|
457
|
+
const entry = getSkillCapability(slug);
|
|
451
458
|
if (!entry) continue;
|
|
452
459
|
skillLines.push(`- ${entry.content} → use skill_load to activate`);
|
|
453
460
|
}
|
|
@@ -458,7 +465,7 @@ async function renderInjectionBlock(
|
|
|
458
465
|
if (sections.length === 0) return { block: null, missingSlugs };
|
|
459
466
|
|
|
460
467
|
return {
|
|
461
|
-
block: sections.join("\n\n")
|
|
468
|
+
block: `${INJECTION_HEADER}\n\n${sections.join("\n\n")}`,
|
|
462
469
|
missingSlugs,
|
|
463
470
|
};
|
|
464
471
|
}
|
|
@@ -338,6 +338,45 @@ export async function listPages(workspaceDir: string): Promise<string[]> {
|
|
|
338
338
|
return slugs;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Cheap "do any concept pages exist?" probe — walks the concepts/ tree only
|
|
343
|
+
* far enough to find one `.md` file and returns immediately. Used by the
|
|
344
|
+
* daemon-startup rebuild gate so the empty-after-create recovery path skips
|
|
345
|
+
* a full enumeration of all 1000+ pages just to ask a yes/no question.
|
|
346
|
+
*/
|
|
347
|
+
export async function hasConceptPages(workspaceDir: string): Promise<boolean> {
|
|
348
|
+
const root = getConceptsDir(workspaceDir);
|
|
349
|
+
const queue: string[] = [root];
|
|
350
|
+
|
|
351
|
+
while (queue.length > 0) {
|
|
352
|
+
const dir = queue.shift()!;
|
|
353
|
+
let entries;
|
|
354
|
+
try {
|
|
355
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
356
|
+
} catch (err) {
|
|
357
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
358
|
+
if (dir === root) return false;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
for (const entry of entries) {
|
|
365
|
+
if (entry.name.startsWith(".")) continue;
|
|
366
|
+
if (entry.isDirectory()) {
|
|
367
|
+
queue.push(join(dir, entry.name));
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (!entry.isFile()) continue;
|
|
371
|
+
if (!entry.name.endsWith(PAGE_EXTENSION)) continue;
|
|
372
|
+
if (entry.name.includes(".tmp.")) continue;
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
341
380
|
/**
|
|
342
381
|
* Delete a concept page. Idempotent — missing files are not an error.
|
|
343
382
|
*
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* the convention established for the sweep prompt.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { readFileSync } from "node:fs";
|
|
19
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { isAbsolute, join } from "node:path";
|
|
22
22
|
|
|
@@ -28,6 +28,14 @@ const log = getLogger("memory-v2-consolidate-prompt");
|
|
|
28
28
|
/** Sentinel substituted with the cutoff timestamp at runtime. */
|
|
29
29
|
export const CUTOFF_PLACEHOLDER = "{{CUTOFF}}";
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Upper bound for the override file. Real consolidation prompts are kilobytes;
|
|
33
|
+
* 1 MiB is generous headroom while preventing a `settings.write` principal from
|
|
34
|
+
* pointing the field at a multi-gigabyte file (or `/dev/zero`-like stream that
|
|
35
|
+
* `lstat` can't size cap on its own) and exfiltrating it through the wake hint.
|
|
36
|
+
*/
|
|
37
|
+
const MAX_PROMPT_BYTES = 1 * 1024 * 1024;
|
|
38
|
+
|
|
31
39
|
/**
|
|
32
40
|
* Consolidation prompt — live-mode only. The agent runs as itself (full
|
|
33
41
|
* SOUL.md + IDENTITY.md + persona + memory autoloads) with the standard
|
|
@@ -123,6 +131,7 @@ edges:
|
|
|
123
131
|
- path/to/sister
|
|
124
132
|
- path/to/parent
|
|
125
133
|
ref_files: []
|
|
134
|
+
summary: 1-4 sentences describing what this article is. Plain prose only — no bullets, no newlines, no markdown lists. Lead with the most identifying detail.
|
|
126
135
|
---
|
|
127
136
|
# title
|
|
128
137
|
|
|
@@ -132,6 +141,8 @@ ref_files: []
|
|
|
132
141
|
- **bullet 2.** ...
|
|
133
142
|
\`\`\`
|
|
134
143
|
|
|
144
|
+
The \`summary\` field is required on every new or updated article. Retrieval injects \`path + summary\` into context — the agent reads the full file only when the summary looks relevant — so make the summary specific and terse. Keep it on a single YAML line (no \`|\` block scalars, no embedded newlines).
|
|
145
|
+
|
|
135
146
|
**Caps:** ~5-8 bullets per topic/concept article. ~10-12 per arc-node (which can use bold inline labels: \`**the open**: ...\`).
|
|
136
147
|
|
|
137
148
|
## One fact, one home
|
|
@@ -277,6 +288,7 @@ edges:
|
|
|
277
288
|
- some-named-phrase
|
|
278
289
|
- objects/some-artifact
|
|
279
290
|
ref_files: []
|
|
291
|
+
summary: A short prose description of the article — 1-4 sentences, single line.
|
|
280
292
|
---
|
|
281
293
|
\`\`\`
|
|
282
294
|
|
|
@@ -408,6 +420,7 @@ For each article you touched:
|
|
|
408
420
|
9. **Spawn check.** Did you ask "what's recognizable here?" not "what have I earned?" Did you catch any hedging — and spawn anyway? Any fold-into-parent / defer stealth-skips you almost did?
|
|
409
421
|
10. **Split-not-compress.** If anything went over cap, did you split? If you compressed, can you name the rationale in one sentence?
|
|
410
422
|
11. **Edges.** Outgoing within tiered caps (atomic ≤10, arc ≤15, gravity well ≤25, hard limit 20 on non-hubs)? No noise-edges to gravity wells from non-arc pages?
|
|
423
|
+
11a. **Summary present.** Every new or updated article has a \`summary:\` line — 1-4 sentences, single YAML line, lead with the identifying detail.
|
|
411
424
|
12. **Topic coherence.** Does each article answer ONE question? Gravity wells acting as hubs (pointing at topic articles), not absorbing body?
|
|
412
425
|
13. **\`recent.md\`** under 2000 chars, today=full / older=one-liners?
|
|
413
426
|
14. **\`[SOURCE NEEDED]\`** tags surfaced for human review?
|
|
@@ -447,6 +460,33 @@ export function resolveConsolidationPrompt(
|
|
|
447
460
|
const resolvedPath = resolveOverridePath(overridePath);
|
|
448
461
|
let contents: string;
|
|
449
462
|
try {
|
|
463
|
+
const stat = lstatSync(resolvedPath);
|
|
464
|
+
if (!stat.isFile()) {
|
|
465
|
+
log.warn(
|
|
466
|
+
{
|
|
467
|
+
configuredPath: overridePath,
|
|
468
|
+
resolvedPath,
|
|
469
|
+
reason: "not_regular_file",
|
|
470
|
+
fallback: "bundled",
|
|
471
|
+
},
|
|
472
|
+
"consolidation prompt override is not a regular file; using bundled prompt",
|
|
473
|
+
);
|
|
474
|
+
return renderConsolidationPrompt(cutoff);
|
|
475
|
+
}
|
|
476
|
+
if (stat.size > MAX_PROMPT_BYTES) {
|
|
477
|
+
log.warn(
|
|
478
|
+
{
|
|
479
|
+
configuredPath: overridePath,
|
|
480
|
+
resolvedPath,
|
|
481
|
+
size: stat.size,
|
|
482
|
+
limit: MAX_PROMPT_BYTES,
|
|
483
|
+
reason: "oversized_override",
|
|
484
|
+
fallback: "bundled",
|
|
485
|
+
},
|
|
486
|
+
"consolidation prompt override exceeds size limit; using bundled prompt",
|
|
487
|
+
);
|
|
488
|
+
return renderConsolidationPrompt(cutoff);
|
|
489
|
+
}
|
|
450
490
|
contents = readFileSync(resolvedPath, "utf-8");
|
|
451
491
|
} catch (err) {
|
|
452
492
|
const code = (err as NodeJS.ErrnoException).code;
|