@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
|
@@ -44,6 +44,106 @@ const conversationThresholdCache = new Map<
|
|
|
44
44
|
>();
|
|
45
45
|
const CONVERSATION_CACHE_TTL_MS = 5_000;
|
|
46
46
|
|
|
47
|
+
// ── Failure-coalescing log helper ────────────────────────────────────────────
|
|
48
|
+
// When the gateway IPC socket is broken (e.g. the path was unlinked from
|
|
49
|
+
// disk), every threshold lookup fails with ENOENT on the hot path. Without
|
|
50
|
+
// coalescing the per-call WARN drowns the actual signal ("Strict-when-
|
|
51
|
+
// Relaxed because the gateway lost its socket") in its own log spam.
|
|
52
|
+
//
|
|
53
|
+
// Each `op` (e.g. "conversation_threshold", "global_thresholds") emits at
|
|
54
|
+
// most one WARN per {@link DEFAULT_FAILURE_WARN_INTERVAL_MS} window. The
|
|
55
|
+
// first failure in a streak WARNs immediately so failures aren't lost. When
|
|
56
|
+
// the IPC starts working again, an INFO records the streak duration and
|
|
57
|
+
// how many calls were swallowed — that's the cue dashboards should alert
|
|
58
|
+
// on.
|
|
59
|
+
|
|
60
|
+
interface FailureState {
|
|
61
|
+
consecutiveFailures: number;
|
|
62
|
+
firstFailureAt: number;
|
|
63
|
+
lastWarnAt: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const DEFAULT_FAILURE_WARN_INTERVAL_MS = 30_000;
|
|
67
|
+
let failureWarnIntervalMs = DEFAULT_FAILURE_WARN_INTERVAL_MS;
|
|
68
|
+
const failureStateByOp = new Map<string, FailureState>();
|
|
69
|
+
|
|
70
|
+
function noteFailure(
|
|
71
|
+
op: string,
|
|
72
|
+
fields: Record<string, unknown>,
|
|
73
|
+
message: string,
|
|
74
|
+
): void {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const state = failureStateByOp.get(op);
|
|
77
|
+
if (!state) {
|
|
78
|
+
failureStateByOp.set(op, {
|
|
79
|
+
consecutiveFailures: 1,
|
|
80
|
+
firstFailureAt: now,
|
|
81
|
+
lastWarnAt: now,
|
|
82
|
+
});
|
|
83
|
+
log.warn(
|
|
84
|
+
{
|
|
85
|
+
...fields,
|
|
86
|
+
op,
|
|
87
|
+
consecutiveFailures: 1,
|
|
88
|
+
event: "ipc_threshold_failure",
|
|
89
|
+
},
|
|
90
|
+
message,
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
state.consecutiveFailures += 1;
|
|
95
|
+
if (now - state.lastWarnAt >= failureWarnIntervalMs) {
|
|
96
|
+
log.warn(
|
|
97
|
+
{
|
|
98
|
+
...fields,
|
|
99
|
+
op,
|
|
100
|
+
consecutiveFailures: state.consecutiveFailures,
|
|
101
|
+
streakDurationMs: now - state.firstFailureAt,
|
|
102
|
+
event: "ipc_threshold_failure",
|
|
103
|
+
},
|
|
104
|
+
message,
|
|
105
|
+
);
|
|
106
|
+
state.lastWarnAt = now;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function noteSuccess(op: string): void {
|
|
111
|
+
const state = failureStateByOp.get(op);
|
|
112
|
+
if (!state) return;
|
|
113
|
+
log.info(
|
|
114
|
+
{
|
|
115
|
+
op,
|
|
116
|
+
swallowedFailures: state.consecutiveFailures,
|
|
117
|
+
streakDurationMs: Date.now() - state.firstFailureAt,
|
|
118
|
+
event: "ipc_threshold_recovered",
|
|
119
|
+
},
|
|
120
|
+
"Gateway IPC threshold call recovered after failure streak",
|
|
121
|
+
);
|
|
122
|
+
failureStateByOp.delete(op);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Test-only: clear the failure-coalescing state. */
|
|
126
|
+
export function _resetFailureCoalesceForTesting(): void {
|
|
127
|
+
failureStateByOp.clear();
|
|
128
|
+
failureWarnIntervalMs = DEFAULT_FAILURE_WARN_INTERVAL_MS;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Test-only: read a snapshot of the failure-coalescing state for a given
|
|
133
|
+
* op. Returns `undefined` when no streak is in progress.
|
|
134
|
+
*/
|
|
135
|
+
export function _getFailureStateForTesting(
|
|
136
|
+
op: string,
|
|
137
|
+
): Readonly<FailureState> | undefined {
|
|
138
|
+
const state = failureStateByOp.get(op);
|
|
139
|
+
return state ? { ...state } : undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Test-only: override the WARN cadence. Pass {@link DEFAULT_FAILURE_WARN_INTERVAL_MS} to reset. */
|
|
143
|
+
export function _setFailureWarnIntervalForTesting(intervalMs: number): void {
|
|
144
|
+
failureWarnIntervalMs = intervalMs;
|
|
145
|
+
}
|
|
146
|
+
|
|
47
147
|
/**
|
|
48
148
|
* Clear the global threshold cache. Exported for testing.
|
|
49
149
|
*/
|
|
@@ -112,18 +212,24 @@ export async function getAutoApproveThreshold(
|
|
|
112
212
|
})) as ConversationThreshold | null | undefined;
|
|
113
213
|
|
|
114
214
|
if (result === undefined) {
|
|
115
|
-
|
|
215
|
+
noteFailure(
|
|
216
|
+
"conversation_threshold",
|
|
116
217
|
{ conversationId },
|
|
117
218
|
"IPC call failed for conversation threshold override, falling through to global",
|
|
118
219
|
);
|
|
119
220
|
// Fall through to global threshold fetch below.
|
|
120
|
-
} else if (result && isValidThreshold(result.threshold)) {
|
|
121
|
-
conversationThresholdCache.set(conversationId, {
|
|
122
|
-
threshold: result.threshold,
|
|
123
|
-
timestamp: Date.now(),
|
|
124
|
-
});
|
|
125
|
-
return result.threshold;
|
|
126
221
|
} else {
|
|
222
|
+
// Any defined response (including a null "no override") is a
|
|
223
|
+
// successful round-trip — clear any in-progress failure streak so
|
|
224
|
+
// dashboards see the recovery.
|
|
225
|
+
noteSuccess("conversation_threshold");
|
|
226
|
+
if (result && isValidThreshold(result.threshold)) {
|
|
227
|
+
conversationThresholdCache.set(conversationId, {
|
|
228
|
+
threshold: result.threshold,
|
|
229
|
+
timestamp: Date.now(),
|
|
230
|
+
});
|
|
231
|
+
return result.threshold;
|
|
232
|
+
}
|
|
127
233
|
// result === null (or an unexpected shape) — cache the negative result
|
|
128
234
|
// and fall through to global defaults.
|
|
129
235
|
conversationThresholdCache.set(conversationId, {
|
|
@@ -151,7 +257,8 @@ export async function getAutoApproveThreshold(
|
|
|
151
257
|
} catch (err) {
|
|
152
258
|
// Gateway unreachable — default to "none" (Strict) so no tools are
|
|
153
259
|
// silently auto-approved when the gateway is down.
|
|
154
|
-
|
|
260
|
+
noteFailure(
|
|
261
|
+
"global_thresholds",
|
|
155
262
|
{ error: String(err) },
|
|
156
263
|
"Failed to fetch global thresholds, defaulting to none",
|
|
157
264
|
);
|
|
@@ -176,6 +283,7 @@ async function fetchGlobalThresholds(): Promise<GlobalThresholds> {
|
|
|
176
283
|
throw new Error("Gateway IPC returned no result for global thresholds");
|
|
177
284
|
}
|
|
178
285
|
|
|
286
|
+
noteSuccess("global_thresholds");
|
|
179
287
|
cachedGlobalThresholds = result;
|
|
180
288
|
cachedGlobalTimestamp = Date.now();
|
|
181
289
|
return result;
|
|
@@ -11,19 +11,14 @@ import type { AllowlistOption, ScopeOption, UserDecision } from "./types.js";
|
|
|
11
11
|
|
|
12
12
|
const log = getLogger("permission-prompter");
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}) => void;
|
|
23
|
-
reject: (reason: Error) => void;
|
|
24
|
-
timer: ReturnType<typeof setTimeout>;
|
|
25
|
-
toolUseId?: string;
|
|
26
|
-
}
|
|
14
|
+
type ConfirmResult = {
|
|
15
|
+
decision: UserDecision;
|
|
16
|
+
selectedPattern?: string;
|
|
17
|
+
selectedScope?: string;
|
|
18
|
+
decisionContext?: string;
|
|
19
|
+
wasTimeout?: boolean;
|
|
20
|
+
wasSystemCancel?: boolean;
|
|
21
|
+
};
|
|
27
22
|
|
|
28
23
|
export type ConfirmationStateCallback = (
|
|
29
24
|
requestId: string,
|
|
@@ -33,7 +28,13 @@ export type ConfirmationStateCallback = (
|
|
|
33
28
|
) => void;
|
|
34
29
|
|
|
35
30
|
export class PermissionPrompter {
|
|
36
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Tracks which requestIds belong to this prompter instance so that
|
|
33
|
+
* denyAllPending / dispose can scope their cleanup to this conversation.
|
|
34
|
+
* The full per-request state (callbacks, timer, toolUseId) lives in
|
|
35
|
+
* pendingInteractions, matching the host proxy pattern.
|
|
36
|
+
*/
|
|
37
|
+
private ownedIds = new Set<string>();
|
|
37
38
|
private sendToClient: (msg: ServerMessage) => void;
|
|
38
39
|
private onStateChanged?: ConfirmationStateCallback;
|
|
39
40
|
|
|
@@ -69,74 +70,68 @@ export class PermissionPrompter {
|
|
|
69
70
|
riskReason?: string,
|
|
70
71
|
isContainerized?: boolean,
|
|
71
72
|
directoryScopeOptions?: readonly { scope: string; label: string }[],
|
|
72
|
-
): Promise<{
|
|
73
|
-
decision: UserDecision;
|
|
74
|
-
selectedPattern?: string;
|
|
75
|
-
selectedScope?: string;
|
|
76
|
-
decisionContext?: string;
|
|
77
|
-
wasTimeout?: boolean;
|
|
78
|
-
wasSystemCancel?: boolean;
|
|
79
|
-
wasAbort?: boolean;
|
|
80
|
-
}> {
|
|
73
|
+
): Promise<ConfirmResult & { wasAbort?: boolean }> {
|
|
81
74
|
if (signal?.aborted) return { decision: "deny", wasAbort: true };
|
|
82
75
|
|
|
83
76
|
const requestId = uuid();
|
|
84
77
|
|
|
85
|
-
// Self-register in pendingInteractions so /v1/confirm can route the
|
|
86
|
-
// response to this conversation without going through broadcastMessage.
|
|
87
|
-
if (conversationId) {
|
|
88
|
-
pendingInteractions.register(requestId, {
|
|
89
|
-
conversationId,
|
|
90
|
-
kind: "confirmation",
|
|
91
|
-
confirmationDetails: {
|
|
92
|
-
toolName,
|
|
93
|
-
input: redactSensitiveFields(input),
|
|
94
|
-
riskLevel,
|
|
95
|
-
executionTarget,
|
|
96
|
-
allowlistOptions: allowlistOptions.map((o) => ({
|
|
97
|
-
label: o.label,
|
|
98
|
-
description: o.description,
|
|
99
|
-
pattern: o.pattern,
|
|
100
|
-
})),
|
|
101
|
-
scopeOptions: scopeOptions.map((o) => ({
|
|
102
|
-
label: o.label,
|
|
103
|
-
scope: o.scope,
|
|
104
|
-
})),
|
|
105
|
-
persistentDecisionsAllowed: persistentDecisionsAllowed ?? true,
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
78
|
return new Promise((resolve, reject) => {
|
|
111
79
|
const timeoutMs = getConfig().timeouts.permissionTimeoutSec * 1000;
|
|
80
|
+
|
|
112
81
|
const timer = setTimeout(() => {
|
|
113
|
-
|
|
114
|
-
|
|
82
|
+
const interaction = pendingInteractions.resolve(requestId);
|
|
83
|
+
this.ownedIds.delete(requestId);
|
|
115
84
|
log.warn(
|
|
116
85
|
{ requestId, toolName },
|
|
117
86
|
"Permission prompt timed out, defaulting to deny",
|
|
118
87
|
);
|
|
119
88
|
this.onStateChanged?.(requestId, "timed_out", "timeout", toolUseId);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
89
|
+
(interaction?.rpcResolve as ((v: ConfirmResult) => void) | undefined)?.(
|
|
90
|
+
{
|
|
91
|
+
decision: "deny",
|
|
92
|
+
wasTimeout: true,
|
|
93
|
+
decisionContext: `The permission prompt for the "${toolName}" tool timed out. The user did not explicitly deny this request — they may have been away or busy. You may retry this tool call if it is still needed for the current task.`,
|
|
94
|
+
},
|
|
95
|
+
);
|
|
125
96
|
}, timeoutMs);
|
|
126
97
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
98
|
+
// Register all lifecycle state in pendingInteractions — same pattern as
|
|
99
|
+
// host proxies. The prompter tracks ownership via ownedIds.
|
|
100
|
+
// Always register unconditionally so rpcResolve/rpcReject/timer
|
|
101
|
+
// are reachable by resolveConfirmation, denyAllPending, and the timeout
|
|
102
|
+
// handler even when conversationId is absent. Routes return 404 for
|
|
103
|
+
// interactions with an empty conversationId, which is correct behaviour.
|
|
104
|
+
pendingInteractions.register(requestId, {
|
|
105
|
+
conversationId: conversationId ?? "",
|
|
106
|
+
kind: "confirmation",
|
|
107
|
+
confirmationDetails: {
|
|
108
|
+
toolName,
|
|
109
|
+
input: redactSensitiveFields(input),
|
|
110
|
+
riskLevel,
|
|
111
|
+
executionTarget,
|
|
112
|
+
allowlistOptions: allowlistOptions.map((o) => ({
|
|
113
|
+
label: o.label,
|
|
114
|
+
description: o.description,
|
|
115
|
+
pattern: o.pattern,
|
|
116
|
+
})),
|
|
117
|
+
scopeOptions: scopeOptions.map((o) => ({
|
|
118
|
+
label: o.label,
|
|
119
|
+
scope: o.scope,
|
|
120
|
+
})),
|
|
121
|
+
persistentDecisionsAllowed: persistentDecisionsAllowed ?? true,
|
|
122
|
+
},
|
|
123
|
+
rpcResolve: resolve as (value: unknown) => void,
|
|
124
|
+
rpcReject: reject,
|
|
125
|
+
timer,
|
|
126
|
+
toolUseId,
|
|
127
|
+
});
|
|
128
|
+
this.ownedIds.add(requestId);
|
|
133
129
|
|
|
134
130
|
if (signal) {
|
|
135
131
|
const onAbort = () => {
|
|
136
|
-
if (this.
|
|
137
|
-
clearTimeout(timer);
|
|
138
|
-
this.pending.delete(requestId);
|
|
132
|
+
if (this.ownedIds.has(requestId)) {
|
|
139
133
|
pendingInteractions.resolve(requestId);
|
|
134
|
+
this.ownedIds.delete(requestId);
|
|
140
135
|
resolve({ decision: "deny", wasAbort: true });
|
|
141
136
|
}
|
|
142
137
|
};
|
|
@@ -175,17 +170,17 @@ export class PermissionPrompter {
|
|
|
175
170
|
}
|
|
176
171
|
|
|
177
172
|
hasPendingRequest(requestId: string): boolean {
|
|
178
|
-
return this.
|
|
173
|
+
return this.ownedIds.has(requestId);
|
|
179
174
|
}
|
|
180
175
|
|
|
181
176
|
/** Returns all currently pending request IDs. */
|
|
182
177
|
getPendingRequestIds(): string[] {
|
|
183
|
-
return [...this.
|
|
178
|
+
return [...this.ownedIds];
|
|
184
179
|
}
|
|
185
180
|
|
|
186
181
|
/** Returns the toolUseId associated with a pending request, if any. */
|
|
187
182
|
getToolUseId(requestId: string): string | undefined {
|
|
188
|
-
return
|
|
183
|
+
return pendingInteractions.get(requestId)?.toolUseId;
|
|
189
184
|
}
|
|
190
185
|
|
|
191
186
|
resolveConfirmation(
|
|
@@ -195,22 +190,17 @@ export class PermissionPrompter {
|
|
|
195
190
|
selectedScope?: string,
|
|
196
191
|
decisionContext?: string,
|
|
197
192
|
): void {
|
|
198
|
-
|
|
199
|
-
if (!pending) {
|
|
193
|
+
if (!this.ownedIds.has(requestId)) {
|
|
200
194
|
log.warn({ requestId }, "No pending prompt for confirmation response");
|
|
201
195
|
return;
|
|
202
196
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
selectedPattern,
|
|
211
|
-
selectedScope,
|
|
212
|
-
decisionContext,
|
|
213
|
-
});
|
|
197
|
+
// The prompter owns deregistration; all callers use get() to peek before
|
|
198
|
+
// routing to resolveConfirmation, which fires the rpcResolve callback.
|
|
199
|
+
const interaction = pendingInteractions.resolve(requestId);
|
|
200
|
+
this.ownedIds.delete(requestId);
|
|
201
|
+
(interaction?.rpcResolve as ((v: ConfirmResult) => void) | undefined)?.(
|
|
202
|
+
{ decision, selectedPattern, selectedScope, decisionContext },
|
|
203
|
+
);
|
|
214
204
|
}
|
|
215
205
|
|
|
216
206
|
/**
|
|
@@ -219,31 +209,31 @@ export class PermissionPrompter {
|
|
|
219
209
|
* see the denial and can re-request if still needed.
|
|
220
210
|
*/
|
|
221
211
|
denyAllPending(): void {
|
|
222
|
-
for (const
|
|
223
|
-
|
|
224
|
-
this.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
212
|
+
for (const requestId of [...this.ownedIds]) {
|
|
213
|
+
const interaction = pendingInteractions.resolve(requestId);
|
|
214
|
+
this.ownedIds.delete(requestId);
|
|
215
|
+
(interaction?.rpcResolve as ((v: ConfirmResult) => void) | undefined)?.(
|
|
216
|
+
{
|
|
217
|
+
decision: "deny",
|
|
218
|
+
wasSystemCancel: true,
|
|
219
|
+
decisionContext:
|
|
220
|
+
"The user sent a new message instead of responding to this permission prompt. Stop what you are doing and respond to the user's new message. Do NOT retry this tool or request permission again until the user asks you to.",
|
|
221
|
+
},
|
|
222
|
+
);
|
|
232
223
|
}
|
|
233
224
|
}
|
|
234
225
|
|
|
235
226
|
get hasPending(): boolean {
|
|
236
|
-
return this.
|
|
227
|
+
return this.ownedIds.size > 0;
|
|
237
228
|
}
|
|
238
229
|
|
|
239
230
|
dispose(): void {
|
|
240
|
-
for (const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
231
|
+
for (const requestId of [...this.ownedIds]) {
|
|
232
|
+
const interaction = pendingInteractions.resolve(requestId);
|
|
233
|
+
this.ownedIds.delete(requestId);
|
|
234
|
+
interaction?.rpcReject?.(
|
|
244
235
|
new AssistantError("Prompter disposed", ErrorCode.INTERNAL_ERROR),
|
|
245
236
|
);
|
|
246
237
|
}
|
|
247
|
-
this.pending.clear();
|
|
248
238
|
}
|
|
249
239
|
}
|
|
@@ -20,12 +20,6 @@ export interface SecretPromptResult {
|
|
|
20
20
|
error?: "unsupported_channel";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
interface PendingSecretPrompt {
|
|
24
|
-
resolve: (result: SecretPromptResult) => void;
|
|
25
|
-
reject: (reason: Error) => void;
|
|
26
|
-
timer: ReturnType<typeof setTimeout>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
export interface SecretPrompterChannelContext {
|
|
30
24
|
/** The channel the conversation was initiated from (e.g. "slack", "macos"). */
|
|
31
25
|
channel?: string;
|
|
@@ -34,7 +28,13 @@ export interface SecretPrompterChannelContext {
|
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
export class SecretPrompter {
|
|
37
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Tracks which requestIds belong to this prompter instance so that
|
|
33
|
+
* dispose can scope its cleanup to this conversation.
|
|
34
|
+
* The full per-request state (callbacks, timer) lives in pendingInteractions,
|
|
35
|
+
* matching the host proxy and PermissionPrompter pattern.
|
|
36
|
+
*/
|
|
37
|
+
private ownedIds = new Set<string>();
|
|
38
38
|
private channelContext?: SecretPrompterChannelContext;
|
|
39
39
|
|
|
40
40
|
setChannelContext(ctx: SecretPrompterChannelContext | undefined): void {
|
|
@@ -45,12 +45,9 @@ export class SecretPrompter {
|
|
|
45
45
|
* Broadcast a secret_request to all connected clients and wait for a
|
|
46
46
|
* response.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* Pending interaction registration is handled by {@link broadcastMessage}
|
|
53
|
-
* when the secret_request event is published to the hub.
|
|
48
|
+
* Registers all lifecycle state (rpcResolve, rpcReject, timer) in
|
|
49
|
+
* pendingInteractions before broadcasting — identical to the host proxy
|
|
50
|
+
* and PermissionPrompter pattern.
|
|
54
51
|
*
|
|
55
52
|
* SECURITY: Logs only metadata (requestId, service, field) — never the
|
|
56
53
|
* returned secret value. The timeout path also returns a null value
|
|
@@ -72,21 +69,24 @@ export class SecretPrompter {
|
|
|
72
69
|
|
|
73
70
|
return new Promise((resolve, reject) => {
|
|
74
71
|
const timeoutMs = getConfig().timeouts.permissionTimeoutSec * 1000;
|
|
72
|
+
|
|
75
73
|
const timer = setTimeout(() => {
|
|
76
|
-
this.pending.delete(requestId);
|
|
77
74
|
pendingInteractions.resolve(requestId);
|
|
75
|
+
this.ownedIds.delete(requestId);
|
|
78
76
|
log.warn({ requestId, service, field }, "Secret prompt timed out");
|
|
79
77
|
resolve({ value: null, delivery: "store" });
|
|
80
78
|
}, timeoutMs);
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Self-register in pendingInteractions so /v1/secret can route the
|
|
85
|
-
// response to this conversation without relying on broadcastMessage.
|
|
80
|
+
// Register all lifecycle state in pendingInteractions — same pattern as
|
|
81
|
+
// host proxies and PermissionPrompter. The prompter tracks ownership via ownedIds.
|
|
86
82
|
pendingInteractions.register(requestId, {
|
|
87
83
|
conversationId: effectiveConversationId,
|
|
88
84
|
kind: "secret",
|
|
85
|
+
rpcResolve: resolve as (value: unknown) => void,
|
|
86
|
+
rpcReject: reject,
|
|
87
|
+
timer,
|
|
89
88
|
});
|
|
89
|
+
this.ownedIds.add(requestId);
|
|
90
90
|
|
|
91
91
|
const config = getConfig();
|
|
92
92
|
const msg: SecretRequestMessage = {
|
|
@@ -109,7 +109,7 @@ export class SecretPrompter {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
hasPendingRequest(requestId: string): boolean {
|
|
112
|
-
return this.
|
|
112
|
+
return this.ownedIds.has(requestId);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
@@ -124,26 +124,26 @@ export class SecretPrompter {
|
|
|
124
124
|
value?: string,
|
|
125
125
|
delivery?: SecretDelivery,
|
|
126
126
|
): void {
|
|
127
|
-
|
|
128
|
-
if (!pending) {
|
|
127
|
+
if (!this.ownedIds.has(requestId)) {
|
|
129
128
|
log.warn({ requestId }, "No pending prompt for secret response");
|
|
130
129
|
return;
|
|
131
130
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
// approval-routes calls pendingInteractions.get() before routing here;
|
|
132
|
+
// the prompter owns deregistration so it fires the Promise callback cleanly.
|
|
133
|
+
const interaction = pendingInteractions.resolve(requestId);
|
|
134
|
+
this.ownedIds.delete(requestId);
|
|
135
|
+
(interaction?.rpcResolve as ((v: SecretPromptResult) => void) | undefined)?.(
|
|
136
|
+
{ value: value ?? null, delivery: delivery ?? "store" },
|
|
137
|
+
);
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
dispose(): void {
|
|
140
|
-
for (const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
for (const requestId of [...this.ownedIds]) {
|
|
142
|
+
const interaction = pendingInteractions.resolve(requestId);
|
|
143
|
+
this.ownedIds.delete(requestId);
|
|
144
|
+
interaction?.rpcReject?.(
|
|
144
145
|
new AssistantError("Prompter disposed", ErrorCode.INTERNAL_ERROR),
|
|
145
146
|
);
|
|
146
147
|
}
|
|
147
|
-
this.pending.clear();
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* drives the per-turn injection sequence consumed by
|
|
4
4
|
* `applyRuntimeInjections`.
|
|
5
5
|
*
|
|
6
|
-
* Each
|
|
6
|
+
* Each default injector reads its per-turn inputs from
|
|
7
7
|
* `ctx.injectionInputs` (see {@link TurnInjectionInputs}), runs its gating
|
|
8
8
|
* conditions (injection mode, feature flags, channel type, null-input
|
|
9
9
|
* short-circuits), and returns an {@link InjectionBlock} with a
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* | name | order | placement |
|
|
14
14
|
* | ------------------------ | ----- | ----------------------- |
|
|
15
|
+
* | `disk-pressure-warning` | 5 | prepend-user-tail |
|
|
15
16
|
* | `workspace-context` | 10 | prepend-user-tail |
|
|
16
17
|
* | `unified-turn-context` | 20 | prepend-user-tail |
|
|
17
18
|
* | `pkb-context` | 30 | after-memory-prefix |
|
|
@@ -45,10 +46,10 @@
|
|
|
45
46
|
|
|
46
47
|
import { resolve } from "node:path";
|
|
47
48
|
|
|
49
|
+
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
48
50
|
import { getConfig } from "../../config/loader.js";
|
|
49
51
|
import { getInContextPkbPaths } from "../../daemon/pkb-context-tracker.js";
|
|
50
52
|
import { buildPkbReminder } from "../../daemon/pkb-reminder-builder.js";
|
|
51
|
-
import { isMemoryV2ReadActive } from "../../memory/context-search/sources/memory-v2.js";
|
|
52
53
|
import { searchPkbFiles } from "../../memory/pkb/pkb-search.js";
|
|
53
54
|
import { getLogger } from "../../util/logger.js";
|
|
54
55
|
import { registerPlugin } from "../registry.js";
|
|
@@ -74,7 +75,7 @@ const PKB_HINT_THRESHOLD = 0.5;
|
|
|
74
75
|
const PKB_HINT_ARCHIVE_THRESHOLD = 0.7;
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
|
-
* Fixed order values for the
|
|
78
|
+
* Fixed order values for the default injectors. Exported so tests —
|
|
78
79
|
* and any future integration code — can assert ordering without re-deriving
|
|
79
80
|
* the constants.
|
|
80
81
|
*
|
|
@@ -83,6 +84,7 @@ const PKB_HINT_ARCHIVE_THRESHOLD = 0.7;
|
|
|
83
84
|
* without renumbering the defaults.
|
|
84
85
|
*/
|
|
85
86
|
export const DEFAULT_INJECTOR_ORDER = {
|
|
87
|
+
diskPressureWarning: 5,
|
|
86
88
|
workspaceContext: 10,
|
|
87
89
|
unifiedTurnContext: 20,
|
|
88
90
|
pkbContext: 30,
|
|
@@ -98,6 +100,35 @@ function readInjectionInputs(ctx: TurnContext): TurnInjectionInputs {
|
|
|
98
100
|
return ctx.injectionInputs ?? {};
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
export const DISK_PRESSURE_WARNING_PROMPT = `<disk_pressure_warning>
|
|
104
|
+
Disk usage is critically low: this assistant is in storage cleanup mode because the workspace volume is at least 95% full.
|
|
105
|
+
|
|
106
|
+
In your first paragraph, warn the user that storage is critically low and that normal work is suspended until space is freed.
|
|
107
|
+
|
|
108
|
+
Then help the user clean up storage. Prefer safe inspection steps first, such as checking available space and finding large directories. Ask before deleting files or caches unless the user has already clearly approved the specific cleanup action.
|
|
109
|
+
|
|
110
|
+
Do not work on unrelated tasks until disk usage drops below the critical threshold or the user explicitly overrides the lock. Background processes and messages from trusted contacts are blocked while this cleanup mode is active.
|
|
111
|
+
</disk_pressure_warning>`;
|
|
112
|
+
|
|
113
|
+
function isSafeStorageLimitsEnabled(): boolean {
|
|
114
|
+
return isAssistantFeatureFlagEnabled("safe-storage-limits", getConfig());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const diskPressureWarningInjector: Injector = {
|
|
118
|
+
name: "disk-pressure-warning",
|
|
119
|
+
order: DEFAULT_INJECTOR_ORDER.diskPressureWarning,
|
|
120
|
+
async produce(ctx: TurnContext): Promise<InjectionBlock | null> {
|
|
121
|
+
if (!isSafeStorageLimitsEnabled()) return null;
|
|
122
|
+
const inputs = readInjectionInputs(ctx);
|
|
123
|
+
if (!inputs.diskPressureContext?.cleanupModeActive) return null;
|
|
124
|
+
return {
|
|
125
|
+
id: "disk-pressure-warning",
|
|
126
|
+
text: DISK_PRESSURE_WARNING_PROMPT,
|
|
127
|
+
placement: "prepend-user-tail",
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
101
132
|
/**
|
|
102
133
|
* v2 read-side cutover guard. The `pkb-context` injector silences itself
|
|
103
134
|
* under v2 because the `<knowledge_base>` block surfaces PKB content the v2
|
|
@@ -107,7 +138,7 @@ function readInjectionInputs(ctx: TurnContext): TurnInjectionInputs {
|
|
|
107
138
|
* state independent of PKB and fires unchanged.
|
|
108
139
|
*/
|
|
109
140
|
function isPkbInjectionSilencedByV2(): boolean {
|
|
110
|
-
return
|
|
141
|
+
return getConfig().memory.v2.enabled;
|
|
111
142
|
}
|
|
112
143
|
|
|
113
144
|
/**
|
|
@@ -520,6 +551,7 @@ export const defaultInjectorsPlugin: Plugin = {
|
|
|
520
551
|
},
|
|
521
552
|
},
|
|
522
553
|
injectors: [
|
|
554
|
+
diskPressureWarningInjector,
|
|
523
555
|
workspaceContextInjector,
|
|
524
556
|
unifiedTurnContextInjector,
|
|
525
557
|
pkbContextInjector,
|
|
@@ -72,12 +72,6 @@ export interface GraphMemoryPayload {
|
|
|
72
72
|
* Passed as a second argument to {@link runDefaultMemoryRetrieval} rather
|
|
73
73
|
* than threaded through {@link MemoryArgs} to keep the plugin-facing
|
|
74
74
|
* pipeline surface minimal.
|
|
75
|
-
*
|
|
76
|
-
* The per-turn abort signal lives on {@link MemoryArgs.signal} instead of
|
|
77
|
-
* here so the pipeline runner's `linkAbortSignal` can swap it for an
|
|
78
|
-
* internally-linked signal — that way a plugin timeout actually cancels
|
|
79
|
-
* the underlying `prepareMemory` work instead of letting it run after
|
|
80
|
-
* `Promise.race` has already rejected.
|
|
81
75
|
*/
|
|
82
76
|
export interface DefaultMemoryRetrievalDeps {
|
|
83
77
|
/** Live message list for this turn (pre-injection). */
|
|
@@ -101,6 +95,11 @@ export interface DefaultMemoryRetrievalDeps {
|
|
|
101
95
|
* trusted) or a single {@link GraphMemoryPayload} wrapping the graph
|
|
102
96
|
* retriever's full output. The agent loop narrows via
|
|
103
97
|
* {@link DEFAULT_MEMORY_GRAPH_KIND} to consume it.
|
|
98
|
+
*
|
|
99
|
+
* Memory retrieval blocks the turn — there is no soft timeout here. Memory
|
|
100
|
+
* is critical context, and silently dropping it produces a worse outcome
|
|
101
|
+
* than a slower turn. Cancellation still works via `args.signal`, which is
|
|
102
|
+
* threaded into `prepareMemory`.
|
|
104
103
|
*/
|
|
105
104
|
export async function runDefaultMemoryRetrieval(
|
|
106
105
|
args: MemoryArgs,
|
package/src/plugins/types.ts
CHANGED
|
@@ -784,6 +784,8 @@ export interface TurnInjectionInputs {
|
|
|
784
784
|
* context (unified turn context, etc.). Drives per-injector gating.
|
|
785
785
|
*/
|
|
786
786
|
readonly mode?: InjectionMode;
|
|
787
|
+
/** Disk-pressure cleanup-mode context or null to skip the warning. */
|
|
788
|
+
readonly diskPressureContext?: DiskPressureInjectionContext | null;
|
|
787
789
|
/** Workspace top-level context text (`<workspace>...`) or null to skip. */
|
|
788
790
|
readonly workspaceTopLevelContext?: string | null;
|
|
789
791
|
/** Pre-built unified-turn-context text (`<turn_context>...`) or null to skip. */
|
|
@@ -860,6 +862,11 @@ export interface TurnInjectionInputs {
|
|
|
860
862
|
readonly isNonInteractive?: boolean;
|
|
861
863
|
}
|
|
862
864
|
|
|
865
|
+
export interface DiskPressureInjectionContext {
|
|
866
|
+
/** True when the current turn is allowed to run only for storage cleanup. */
|
|
867
|
+
readonly cleanupModeActive: boolean;
|
|
868
|
+
}
|
|
869
|
+
|
|
863
870
|
/**
|
|
864
871
|
* Per-turn execution context threaded through every middleware invocation.
|
|
865
872
|
*
|