@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
|
@@ -106,6 +106,7 @@ import {
|
|
|
106
106
|
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
107
107
|
import {
|
|
108
108
|
createSurfaceMutex,
|
|
109
|
+
flushPendingSurfaceDataPersists,
|
|
109
110
|
handleSurfaceAction as handleSurfaceActionImpl,
|
|
110
111
|
handleSurfaceUndo as handleSurfaceUndoImpl,
|
|
111
112
|
type SurfaceActionResult,
|
|
@@ -117,6 +118,7 @@ import {
|
|
|
117
118
|
createToolExecutor,
|
|
118
119
|
} from "./conversation-tool-setup.js";
|
|
119
120
|
import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from "./conversation-workspace.js";
|
|
121
|
+
import { canonicalizeTimeZone } from "./date-context.js";
|
|
120
122
|
import type { HostAppControlProxy } from "./host-app-control-proxy.js";
|
|
121
123
|
import { HostCuProxy } from "./host-cu-proxy.js";
|
|
122
124
|
import type {
|
|
@@ -136,17 +138,6 @@ import { TraceEmitter } from "./trace-emitter.js";
|
|
|
136
138
|
|
|
137
139
|
const log = getLogger("conversation");
|
|
138
140
|
|
|
139
|
-
export interface ConversationMemoryPolicy {
|
|
140
|
-
scopeId: string;
|
|
141
|
-
includeDefaultFallback: boolean;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export const DEFAULT_MEMORY_POLICY: Readonly<ConversationMemoryPolicy> =
|
|
145
|
-
Object.freeze({
|
|
146
|
-
scopeId: "default",
|
|
147
|
-
includeDefaultFallback: false,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
141
|
export { findLastUndoableUserMessageIndex } from "./conversation-history.js";
|
|
151
142
|
export type {
|
|
152
143
|
QueueDrainReason,
|
|
@@ -170,6 +161,7 @@ export class Conversation {
|
|
|
170
161
|
/** @internal */ eventBus = new EventBus<AssistantDomainEvents>();
|
|
171
162
|
/** @internal */ workingDir: string;
|
|
172
163
|
/** @internal */ allowedToolNames?: Set<string>;
|
|
164
|
+
/** @internal */ diskPressureCleanupModeActive?: boolean;
|
|
173
165
|
/** @internal */ toolsDisabledDepth = 0;
|
|
174
166
|
/** @internal */ preactivatedSkillIds?: string[];
|
|
175
167
|
/** @internal */ subagentAllowedTools?: Set<string>;
|
|
@@ -318,12 +310,11 @@ export class Conversation {
|
|
|
318
310
|
* @internal
|
|
319
311
|
*/
|
|
320
312
|
hostUsername?: string;
|
|
313
|
+
/** @internal */ clientTimezone?: string;
|
|
321
314
|
public readonly traceEmitter: TraceEmitter;
|
|
322
315
|
/** @internal */ hasSystemPromptOverride: boolean;
|
|
323
|
-
public memoryPolicy: ConversationMemoryPolicy;
|
|
324
316
|
/** @internal */ readonly graphMemory: ConversationGraphMemory;
|
|
325
317
|
/** @internal */ activeContextNodeIds?: string[];
|
|
326
|
-
/** @internal */ memoryScopeId?: string;
|
|
327
318
|
/** @internal */ streamThinking: boolean;
|
|
328
319
|
/** @internal */ turnCount = 0;
|
|
329
320
|
public lastAssistantAttachments: AssistantAttachmentDraft[] = [];
|
|
@@ -358,7 +349,6 @@ export class Conversation {
|
|
|
358
349
|
maxTokens: number | undefined,
|
|
359
350
|
sendToClient: (msg: ServerMessage) => void,
|
|
360
351
|
workingDir: string,
|
|
361
|
-
memoryPolicy?: ConversationMemoryPolicy,
|
|
362
352
|
sharedCesClient?: CesClient,
|
|
363
353
|
speedOverride?: Speed,
|
|
364
354
|
cacheTtl?: "5m" | "1h",
|
|
@@ -369,13 +359,7 @@ export class Conversation {
|
|
|
369
359
|
this.provider = provider;
|
|
370
360
|
this.workingDir = workingDir;
|
|
371
361
|
this.sendToClient = sendToClient;
|
|
372
|
-
this.
|
|
373
|
-
? { ...memoryPolicy }
|
|
374
|
-
: { ...DEFAULT_MEMORY_POLICY };
|
|
375
|
-
this.graphMemory = new ConversationGraphMemory(
|
|
376
|
-
this.memoryPolicy.scopeId,
|
|
377
|
-
conversationId,
|
|
378
|
-
);
|
|
362
|
+
this.graphMemory = new ConversationGraphMemory(conversationId);
|
|
379
363
|
this.traceEmitter = new TraceEmitter(conversationId, sendToClient);
|
|
380
364
|
this.prompter = new PermissionPrompter(sendToClient);
|
|
381
365
|
this.prompter.setOnStateChanged((requestId, state, source, toolUseId) => {
|
|
@@ -764,6 +748,11 @@ export class Conversation {
|
|
|
764
748
|
clearTimeout(timer);
|
|
765
749
|
}
|
|
766
750
|
this.recentlyCompletedStandaloneSurfaces.clear();
|
|
751
|
+
// Flush any pending debounced surface-data persists for this
|
|
752
|
+
// conversation so updates that arrived inside the debounce window
|
|
753
|
+
// still land in the DB before teardown. Flushing also clears the
|
|
754
|
+
// pending entries, so no separate cancel call is needed.
|
|
755
|
+
flushPendingSurfaceDataPersists(this.conversationId);
|
|
767
756
|
// Only dispose the per-conversation CU and app-control proxies.
|
|
768
757
|
// Bash/File/Transfer are singletons — their lifecycle is managed by
|
|
769
758
|
// static disposeInstance().
|
|
@@ -774,7 +763,6 @@ export class Conversation {
|
|
|
774
763
|
// Do NOT close it here; the server manages the CES lifecycle.
|
|
775
764
|
this.cesClient = undefined;
|
|
776
765
|
this.activeContextNodeIds = this.graphMemory.tracker.getActiveNodeIds();
|
|
777
|
-
this.memoryScopeId = this.memoryPolicy.scopeId;
|
|
778
766
|
this.graphMemory.persistState();
|
|
779
767
|
disposeConversation(this);
|
|
780
768
|
}
|
|
@@ -1064,7 +1052,7 @@ export class Conversation {
|
|
|
1064
1052
|
);
|
|
1065
1053
|
}
|
|
1066
1054
|
if (result.compacted) {
|
|
1067
|
-
applyCompactionResult(this, result, this.sendToClient, null, {
|
|
1055
|
+
await applyCompactionResult(this, result, this.sendToClient, null, {
|
|
1068
1056
|
slackContextCompactionWatermarkTs: getSlackCompactionWatermarkForPrefix(
|
|
1069
1057
|
slackChronologicalContext,
|
|
1070
1058
|
result.compactedMessages,
|
|
@@ -1144,6 +1132,13 @@ export class Conversation {
|
|
|
1144
1132
|
}
|
|
1145
1133
|
}
|
|
1146
1134
|
|
|
1135
|
+
applyClientTimezoneFromTransport(
|
|
1136
|
+
transport: ConversationTransportMetadata,
|
|
1137
|
+
): void {
|
|
1138
|
+
this.clientTimezone =
|
|
1139
|
+
canonicalizeTimeZone(transport.clientTimezone) ?? undefined;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1147
1142
|
setAssistantId(assistantId: string | null): void {
|
|
1148
1143
|
this.assistantId = assistantId ?? undefined;
|
|
1149
1144
|
}
|
|
@@ -14,10 +14,31 @@ export interface TemporalContextOptions {
|
|
|
14
14
|
hostTimeZone?: string;
|
|
15
15
|
/** IANA timezone configured in user settings (if available). */
|
|
16
16
|
configuredUserTimeZone?: string | null;
|
|
17
|
-
/** IANA timezone
|
|
17
|
+
/** IANA timezone reported by the active client for the current turn. */
|
|
18
|
+
clientTimezone?: string | null;
|
|
19
|
+
/** IANA timezone persisted from prior client environment detection. */
|
|
20
|
+
detectedTimezone?: string | null;
|
|
21
|
+
/** Profile timezone candidate accepted by legacy callers; not used for turn resolution. */
|
|
18
22
|
userTimeZone?: string | null;
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
export type TurnTimezoneSource =
|
|
26
|
+
| "timeZone"
|
|
27
|
+
| "configuredUserTimezone"
|
|
28
|
+
| "clientTimezone"
|
|
29
|
+
| "detectedTimezone"
|
|
30
|
+
| "hostTimezone"
|
|
31
|
+
| "utcFallback";
|
|
32
|
+
|
|
33
|
+
export interface TurnTimezoneContext {
|
|
34
|
+
configuredUserTimezone: string | null;
|
|
35
|
+
clientTimezone: string | null;
|
|
36
|
+
detectedTimezone: string | null;
|
|
37
|
+
hostTimezone: string | null;
|
|
38
|
+
effectiveTimezone: string;
|
|
39
|
+
source: TurnTimezoneSource;
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
const WEEKDAY_LONG = [
|
|
22
43
|
"Sunday",
|
|
23
44
|
"Monday",
|
|
@@ -86,7 +107,12 @@ function canonicalizeUtcGmtOffsetToken(offsetToken: string): string | null {
|
|
|
86
107
|
).padStart(2, "0")}`;
|
|
87
108
|
}
|
|
88
109
|
|
|
89
|
-
function canonicalizeTimeZone(
|
|
110
|
+
export function canonicalizeTimeZone(
|
|
111
|
+
timeZone: string | null | undefined,
|
|
112
|
+
): string | null {
|
|
113
|
+
if (timeZone == null) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
90
116
|
const trimmed = timeZone.trim();
|
|
91
117
|
if (trimmed.length === 0) {
|
|
92
118
|
return null;
|
|
@@ -121,6 +147,17 @@ function canonicalizeTimeZone(timeZone: string): string | null {
|
|
|
121
147
|
}
|
|
122
148
|
}
|
|
123
149
|
|
|
150
|
+
function firstResolvedTimezone(
|
|
151
|
+
candidates: Array<[TurnTimezoneSource, string | null]>,
|
|
152
|
+
): { source: TurnTimezoneSource; timeZone: string } | null {
|
|
153
|
+
for (const [source, timeZone] of candidates) {
|
|
154
|
+
if (timeZone) {
|
|
155
|
+
return { source, timeZone };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
124
161
|
/**
|
|
125
162
|
* Common timezone abbreviation → IANA identifier mapping.
|
|
126
163
|
* Used as a fallback when `Intl.DateTimeFormat` does not recognize the abbreviation.
|
|
@@ -289,11 +326,41 @@ function formatLocalDate(date: Date, timeZone: string): string {
|
|
|
289
326
|
).padStart(2, "0")}`;
|
|
290
327
|
}
|
|
291
328
|
|
|
329
|
+
export function resolveTurnTimezoneContext(
|
|
330
|
+
options: TemporalContextOptions = {},
|
|
331
|
+
): TurnTimezoneContext {
|
|
332
|
+
const configuredUserTimezone = canonicalizeTimeZone(
|
|
333
|
+
options.configuredUserTimeZone,
|
|
334
|
+
);
|
|
335
|
+
const clientTimezone = canonicalizeTimeZone(options.clientTimezone);
|
|
336
|
+
const detectedTimezone = canonicalizeTimeZone(options.detectedTimezone);
|
|
337
|
+
const hostTimezone = canonicalizeTimeZone(
|
|
338
|
+
options.hostTimeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
339
|
+
);
|
|
340
|
+
const explicitTimezone = canonicalizeTimeZone(options.timeZone);
|
|
341
|
+
const selected = firstResolvedTimezone([
|
|
342
|
+
["timeZone", explicitTimezone],
|
|
343
|
+
["configuredUserTimezone", configuredUserTimezone],
|
|
344
|
+
["clientTimezone", clientTimezone],
|
|
345
|
+
["detectedTimezone", detectedTimezone],
|
|
346
|
+
["hostTimezone", hostTimezone],
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
configuredUserTimezone,
|
|
351
|
+
clientTimezone,
|
|
352
|
+
detectedTimezone,
|
|
353
|
+
hostTimezone,
|
|
354
|
+
effectiveTimezone: selected?.timeZone ?? "UTC",
|
|
355
|
+
source: selected?.source ?? "utcFallback",
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
292
359
|
/**
|
|
293
360
|
* Format time as HH:MM:SS with UTC offset and timezone name.
|
|
294
361
|
*
|
|
295
362
|
* Uses the timezone resolution cascade:
|
|
296
|
-
* explicit override → configured user tz →
|
|
363
|
+
* explicit override → configured user tz → client tz → detected tz → host fallback.
|
|
297
364
|
*
|
|
298
365
|
* Returns format: `2026-04-02 (Thursday) 01:52:33 -05:00 (America/Chicago)`
|
|
299
366
|
*/
|
|
@@ -301,24 +368,7 @@ export function formatTurnTimestamp(
|
|
|
301
368
|
options: TemporalContextOptions = {},
|
|
302
369
|
): string {
|
|
303
370
|
const now = new Date(options.nowMs ?? Date.now());
|
|
304
|
-
const
|
|
305
|
-
canonicalizeTimeZone(
|
|
306
|
-
options.hostTimeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
307
|
-
) ?? "UTC";
|
|
308
|
-
const resolvedConfiguredUserTimeZone = options.configuredUserTimeZone
|
|
309
|
-
? canonicalizeTimeZone(options.configuredUserTimeZone)
|
|
310
|
-
: null;
|
|
311
|
-
const resolvedUserTimeZone = options.userTimeZone
|
|
312
|
-
? canonicalizeTimeZone(options.userTimeZone)
|
|
313
|
-
: null;
|
|
314
|
-
const resolvedTimeZone = options.timeZone
|
|
315
|
-
? canonicalizeTimeZone(options.timeZone)
|
|
316
|
-
: null;
|
|
317
|
-
const timeZone =
|
|
318
|
-
resolvedTimeZone ??
|
|
319
|
-
resolvedConfiguredUserTimeZone ??
|
|
320
|
-
resolvedUserTimeZone ??
|
|
321
|
-
resolvedHostTimeZone;
|
|
371
|
+
const timeZone = resolveTurnTimezoneContext(options).effectiveTimezone;
|
|
322
372
|
|
|
323
373
|
const dateStr = formatLocalDate(now, timeZone);
|
|
324
374
|
const todayParts = localDateParts(now, timeZone);
|
|
@@ -341,4 +391,3 @@ export function formatTurnTimestamp(
|
|
|
341
391
|
|
|
342
392
|
return `${dateStr} (${dayName}) ${hour}:${minute}:${second} ${offset} (${timeZone})`;
|
|
343
393
|
}
|
|
344
|
-
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DiskPressureBlockedCapability,
|
|
3
|
+
type DiskPressureStatus,
|
|
4
|
+
getDiskPressureStatus,
|
|
5
|
+
} from "./disk-pressure-guard.js";
|
|
6
|
+
|
|
7
|
+
export type DiskPressureBackgroundGateDecision =
|
|
8
|
+
| { action: "allow"; status: DiskPressureStatus }
|
|
9
|
+
| {
|
|
10
|
+
action: "skip";
|
|
11
|
+
reason: "disk_pressure";
|
|
12
|
+
status: DiskPressureStatus;
|
|
13
|
+
blockedCapability: DiskPressureBlockedCapability;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const DISK_PRESSURE_BACKGROUND_LOG_THROTTLE_MS = 60_000;
|
|
17
|
+
|
|
18
|
+
const lastSkipLogAtByKey = new Map<string, number>();
|
|
19
|
+
|
|
20
|
+
export function checkDiskPressureBackgroundGate(
|
|
21
|
+
blockedCapability: DiskPressureBlockedCapability = "background-work",
|
|
22
|
+
): DiskPressureBackgroundGateDecision {
|
|
23
|
+
const status = getDiskPressureStatus();
|
|
24
|
+
if (!status.enabled || !status.locked || status.overrideActive) {
|
|
25
|
+
return { action: "allow", status };
|
|
26
|
+
}
|
|
27
|
+
if (!status.effectivelyLocked) {
|
|
28
|
+
return { action: "allow", status };
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
action: "skip",
|
|
32
|
+
reason: "disk_pressure",
|
|
33
|
+
status,
|
|
34
|
+
blockedCapability,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function shouldLogDiskPressureBackgroundSkip(
|
|
39
|
+
key: string,
|
|
40
|
+
nowMs = Date.now(),
|
|
41
|
+
): boolean {
|
|
42
|
+
const lastLoggedAt = lastSkipLogAtByKey.get(key) ?? 0;
|
|
43
|
+
if (nowMs - lastLoggedAt < DISK_PRESSURE_BACKGROUND_LOG_THROTTLE_MS) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
lastSkipLogAtByKey.set(key, nowMs);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function diskPressureBackgroundSkipLogFields(
|
|
51
|
+
decision: Extract<DiskPressureBackgroundGateDecision, { action: "skip" }>,
|
|
52
|
+
): {
|
|
53
|
+
reason: "disk_pressure";
|
|
54
|
+
thresholdPercent: number;
|
|
55
|
+
usagePercent: number | null;
|
|
56
|
+
blockedCapability: DiskPressureBlockedCapability;
|
|
57
|
+
lockId: string | null;
|
|
58
|
+
path: string | null;
|
|
59
|
+
} {
|
|
60
|
+
return {
|
|
61
|
+
reason: decision.reason,
|
|
62
|
+
thresholdPercent: decision.status.thresholdPercent,
|
|
63
|
+
usagePercent: decision.status.usagePercent,
|
|
64
|
+
blockedCapability: decision.blockedCapability,
|
|
65
|
+
lockId: decision.status.lockId,
|
|
66
|
+
path: decision.status.path,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @internal */
|
|
71
|
+
export function __resetDiskPressureBackgroundGateForTests(): void {
|
|
72
|
+
lastSkipLogAtByKey.clear();
|
|
73
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
2
|
+
import { getConfig } from "../config/loader.js";
|
|
3
|
+
import { buildAssistantEvent } from "../runtime/assistant-event.js";
|
|
4
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
5
|
+
import { cancelBackgroundTools } from "../tools/background-tool-registry.js";
|
|
6
|
+
import { getDiskUsageInfo } from "../util/disk-usage.js";
|
|
7
|
+
import { getLogger } from "../util/logger.js";
|
|
8
|
+
|
|
9
|
+
export const DISK_PRESSURE_THRESHOLD_PERCENT = 95;
|
|
10
|
+
export const DISK_PRESSURE_CHECK_INTERVAL_MS = 60_000;
|
|
11
|
+
export const DISK_PRESSURE_OVERRIDE_CONFIRMATION = "I understand the risks";
|
|
12
|
+
export const DISK_PRESSURE_BLOCKED_CAPABILITIES = [
|
|
13
|
+
"agent-turns",
|
|
14
|
+
"background-work",
|
|
15
|
+
"remote-ingress",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type DiskPressureState = "disabled" | "ok" | "critical" | "unknown";
|
|
19
|
+
|
|
20
|
+
export type DiskPressureBlockedCapability =
|
|
21
|
+
(typeof DISK_PRESSURE_BLOCKED_CAPABILITIES)[number];
|
|
22
|
+
|
|
23
|
+
export interface DiskPressureStatus {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
state: DiskPressureState;
|
|
26
|
+
locked: boolean;
|
|
27
|
+
acknowledged: boolean;
|
|
28
|
+
overrideActive: boolean;
|
|
29
|
+
effectivelyLocked: boolean;
|
|
30
|
+
lockId: string | null;
|
|
31
|
+
usagePercent: number | null;
|
|
32
|
+
thresholdPercent: number;
|
|
33
|
+
path: string | null;
|
|
34
|
+
lastCheckedAt: string | null;
|
|
35
|
+
blockedCapabilities: DiskPressureBlockedCapability[];
|
|
36
|
+
error: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type DiskPressureTransitionResult =
|
|
40
|
+
| { ok: true; status: DiskPressureStatus }
|
|
41
|
+
| {
|
|
42
|
+
ok: false;
|
|
43
|
+
reason:
|
|
44
|
+
| "not_locked"
|
|
45
|
+
| "already_acknowledged"
|
|
46
|
+
| "already_overridden"
|
|
47
|
+
| "invalid_confirmation";
|
|
48
|
+
message: string;
|
|
49
|
+
status: DiskPressureStatus;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
interface DiskPressureGuardState {
|
|
53
|
+
timer: ReturnType<typeof setInterval> | null;
|
|
54
|
+
status: DiskPressureStatus;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const log = getLogger("disk-pressure-guard");
|
|
58
|
+
|
|
59
|
+
const DISABLED_STATUS: DiskPressureStatus = {
|
|
60
|
+
enabled: false,
|
|
61
|
+
state: "disabled",
|
|
62
|
+
locked: false,
|
|
63
|
+
acknowledged: false,
|
|
64
|
+
overrideActive: false,
|
|
65
|
+
effectivelyLocked: false,
|
|
66
|
+
lockId: null,
|
|
67
|
+
usagePercent: null,
|
|
68
|
+
thresholdPercent: DISK_PRESSURE_THRESHOLD_PERCENT,
|
|
69
|
+
path: null,
|
|
70
|
+
lastCheckedAt: null,
|
|
71
|
+
blockedCapabilities: [],
|
|
72
|
+
error: null,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const OPEN_STATUS: DiskPressureStatus = {
|
|
76
|
+
...DISABLED_STATUS,
|
|
77
|
+
enabled: true,
|
|
78
|
+
state: "ok",
|
|
79
|
+
thresholdPercent: DISK_PRESSURE_THRESHOLD_PERCENT,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const state: DiskPressureGuardState = {
|
|
83
|
+
timer: null,
|
|
84
|
+
status: cloneStatus(DISABLED_STATUS),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function cloneStatus(status: DiskPressureStatus): DiskPressureStatus {
|
|
88
|
+
return {
|
|
89
|
+
...status,
|
|
90
|
+
blockedCapabilities: [...status.blockedCapabilities],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function statusFingerprint(status: DiskPressureStatus): string {
|
|
95
|
+
const { lastCheckedAt: _lastCheckedAt, ...substantiveStatus } = status;
|
|
96
|
+
return JSON.stringify(substantiveStatus);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function publishStatusChangedIfNeeded(previous: DiskPressureStatus): void {
|
|
100
|
+
if (statusFingerprint(previous) === statusFingerprint(state.status)) return;
|
|
101
|
+
const status = cloneStatus(state.status);
|
|
102
|
+
assistantEventHub
|
|
103
|
+
.publish(
|
|
104
|
+
buildAssistantEvent({
|
|
105
|
+
type: "disk_pressure_status_changed",
|
|
106
|
+
status,
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
log.warn({ err }, "Failed to publish disk pressure status change");
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function replaceStatus(next: DiskPressureStatus): DiskPressureStatus {
|
|
115
|
+
const previous = cloneStatus(state.status);
|
|
116
|
+
state.status = cloneStatus(next);
|
|
117
|
+
publishStatusChangedIfNeeded(previous);
|
|
118
|
+
return cloneStatus(state.status);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isEnabled(): boolean {
|
|
122
|
+
return isAssistantFeatureFlagEnabled("safe-storage-limits", getConfig());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resetToDisabled(): DiskPressureStatus {
|
|
126
|
+
const previous = cloneStatus(state.status);
|
|
127
|
+
stopDiskPressureGuard();
|
|
128
|
+
state.status = cloneStatus(DISABLED_STATUS);
|
|
129
|
+
publishStatusChangedIfNeeded(previous);
|
|
130
|
+
return cloneStatus(state.status);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function ensureEnabledStatus(): DiskPressureStatus | null {
|
|
134
|
+
if (!isEnabled()) return resetToDisabled();
|
|
135
|
+
if (!state.status.enabled) {
|
|
136
|
+
state.status = cloneStatus(OPEN_STATUS);
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function nextLockId(): string {
|
|
142
|
+
return `disk-pressure-${Date.now()}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function roundPercent(value: number): number {
|
|
146
|
+
return Math.round(value * 100) / 100;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function formatError(error: unknown): string {
|
|
150
|
+
return error instanceof Error ? error.message : String(error);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function sampleFailureStatus(error: unknown): DiskPressureStatus {
|
|
154
|
+
const now = new Date().toISOString();
|
|
155
|
+
return {
|
|
156
|
+
...state.status,
|
|
157
|
+
enabled: true,
|
|
158
|
+
state: "unknown",
|
|
159
|
+
locked: false,
|
|
160
|
+
acknowledged: false,
|
|
161
|
+
overrideActive: false,
|
|
162
|
+
effectivelyLocked: false,
|
|
163
|
+
lockId: null,
|
|
164
|
+
usagePercent: null,
|
|
165
|
+
thresholdPercent: DISK_PRESSURE_THRESHOLD_PERCENT,
|
|
166
|
+
path: null,
|
|
167
|
+
lastCheckedAt: now,
|
|
168
|
+
blockedCapabilities: [],
|
|
169
|
+
error: formatError(error),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function cancelTerminalBackgroundToolsForLock(): void {
|
|
174
|
+
const cancelled = cancelBackgroundTools(
|
|
175
|
+
(tool) => tool.toolName === "bash" || tool.toolName === "host_bash",
|
|
176
|
+
"disk_pressure",
|
|
177
|
+
);
|
|
178
|
+
if (cancelled.length === 0) return;
|
|
179
|
+
log.info(
|
|
180
|
+
{ count: cancelled.length, ids: cancelled.map((tool) => tool.id) },
|
|
181
|
+
"Cancelled background terminal tools during disk pressure lock",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function rejectTransition(
|
|
186
|
+
reason: Exclude<DiskPressureTransitionResult, { ok: true }>["reason"],
|
|
187
|
+
message: string,
|
|
188
|
+
status: DiskPressureStatus,
|
|
189
|
+
): DiskPressureTransitionResult {
|
|
190
|
+
return { ok: false, reason, message, status };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function startDiskPressureGuard(): DiskPressureStatus {
|
|
194
|
+
const disabledStatus = ensureEnabledStatus();
|
|
195
|
+
if (disabledStatus) return disabledStatus;
|
|
196
|
+
|
|
197
|
+
if (!state.timer) {
|
|
198
|
+
state.timer = setInterval(() => {
|
|
199
|
+
void evaluateDiskPressureNow();
|
|
200
|
+
}, DISK_PRESSURE_CHECK_INTERVAL_MS);
|
|
201
|
+
(state.timer as { unref?: () => void }).unref?.();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return cloneStatus(state.status);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function stopDiskPressureGuard(): void {
|
|
208
|
+
if (!state.timer) return;
|
|
209
|
+
clearInterval(state.timer);
|
|
210
|
+
state.timer = null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function evaluateDiskPressureNow(): DiskPressureStatus {
|
|
214
|
+
const disabledStatus = ensureEnabledStatus();
|
|
215
|
+
if (disabledStatus) return disabledStatus;
|
|
216
|
+
|
|
217
|
+
let usageInfo: ReturnType<typeof getDiskUsageInfo>;
|
|
218
|
+
try {
|
|
219
|
+
usageInfo = getDiskUsageInfo();
|
|
220
|
+
} catch (error) {
|
|
221
|
+
return replaceStatus(sampleFailureStatus(error));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!usageInfo || usageInfo.totalMb <= 0) {
|
|
225
|
+
return replaceStatus(sampleFailureStatus("Disk usage sample unavailable"));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const usagePercent = roundPercent(
|
|
229
|
+
(usageInfo.usedMb / usageInfo.totalMb) * 100,
|
|
230
|
+
);
|
|
231
|
+
const isCritical = usagePercent >= DISK_PRESSURE_THRESHOLD_PERCENT;
|
|
232
|
+
const lastCheckedAt = new Date().toISOString();
|
|
233
|
+
|
|
234
|
+
if (!isCritical) {
|
|
235
|
+
return replaceStatus({
|
|
236
|
+
...OPEN_STATUS,
|
|
237
|
+
usagePercent,
|
|
238
|
+
path: usageInfo.path,
|
|
239
|
+
lastCheckedAt,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!state.status.locked) {
|
|
244
|
+
cancelTerminalBackgroundToolsForLock();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const lockId = state.status.locked ? state.status.lockId : nextLockId();
|
|
248
|
+
return replaceStatus({
|
|
249
|
+
enabled: true,
|
|
250
|
+
state: "critical",
|
|
251
|
+
locked: true,
|
|
252
|
+
acknowledged: state.status.locked ? state.status.acknowledged : false,
|
|
253
|
+
overrideActive: state.status.locked ? state.status.overrideActive : false,
|
|
254
|
+
effectivelyLocked: state.status.locked
|
|
255
|
+
? !state.status.overrideActive
|
|
256
|
+
: true,
|
|
257
|
+
lockId,
|
|
258
|
+
usagePercent,
|
|
259
|
+
thresholdPercent: DISK_PRESSURE_THRESHOLD_PERCENT,
|
|
260
|
+
path: usageInfo.path,
|
|
261
|
+
lastCheckedAt,
|
|
262
|
+
blockedCapabilities: [...DISK_PRESSURE_BLOCKED_CAPABILITIES],
|
|
263
|
+
error: null,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function getDiskPressureStatus(): DiskPressureStatus {
|
|
268
|
+
if (!isEnabled()) return cloneStatus(DISABLED_STATUS);
|
|
269
|
+
if (!state.status.enabled) return cloneStatus(OPEN_STATUS);
|
|
270
|
+
return cloneStatus(state.status);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function acknowledgeDiskPressureLock(): DiskPressureTransitionResult {
|
|
274
|
+
const disabledStatus = ensureEnabledStatus();
|
|
275
|
+
const status = disabledStatus ?? cloneStatus(state.status);
|
|
276
|
+
if (!status.locked) {
|
|
277
|
+
return rejectTransition(
|
|
278
|
+
"not_locked",
|
|
279
|
+
"No disk pressure lock is active for this assistant.",
|
|
280
|
+
status,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (status.acknowledged) {
|
|
285
|
+
return rejectTransition(
|
|
286
|
+
"already_acknowledged",
|
|
287
|
+
"The disk pressure lock has already been acknowledged.",
|
|
288
|
+
status,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const previous = cloneStatus(state.status);
|
|
293
|
+
state.status.acknowledged = true;
|
|
294
|
+
publishStatusChangedIfNeeded(previous);
|
|
295
|
+
return { ok: true, status: cloneStatus(state.status) };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function overrideDiskPressureLock(
|
|
299
|
+
confirmation: string,
|
|
300
|
+
): DiskPressureTransitionResult {
|
|
301
|
+
const disabledStatus = ensureEnabledStatus();
|
|
302
|
+
const status = disabledStatus ?? cloneStatus(state.status);
|
|
303
|
+
if (!status.locked) {
|
|
304
|
+
return rejectTransition(
|
|
305
|
+
"not_locked",
|
|
306
|
+
"No disk pressure lock is active for this assistant.",
|
|
307
|
+
status,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (status.overrideActive) {
|
|
312
|
+
return rejectTransition(
|
|
313
|
+
"already_overridden",
|
|
314
|
+
"The disk pressure lock has already been overridden.",
|
|
315
|
+
status,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (confirmation.trim() !== DISK_PRESSURE_OVERRIDE_CONFIRMATION) {
|
|
320
|
+
return rejectTransition(
|
|
321
|
+
"invalid_confirmation",
|
|
322
|
+
`Type "${DISK_PRESSURE_OVERRIDE_CONFIRMATION}" to resume normal assistant behavior.`,
|
|
323
|
+
status,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const previous = cloneStatus(state.status);
|
|
328
|
+
state.status.overrideActive = true;
|
|
329
|
+
state.status.effectivelyLocked = false;
|
|
330
|
+
publishStatusChangedIfNeeded(previous);
|
|
331
|
+
return { ok: true, status: cloneStatus(state.status) };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function __resetDiskPressureGuardForTests(): void {
|
|
335
|
+
stopDiskPressureGuard();
|
|
336
|
+
state.status = cloneStatus(DISABLED_STATUS);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function __getDiskPressureGuardTimerForTests(): ReturnType<
|
|
340
|
+
typeof setInterval
|
|
341
|
+
> | null {
|
|
342
|
+
return state.timer;
|
|
343
|
+
}
|