@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
|
@@ -19,29 +19,28 @@
|
|
|
19
19
|
* host_file_* are filtered out for chrome-extension regardless of the
|
|
20
20
|
* hasNoClient flag.
|
|
21
21
|
*
|
|
22
|
-
* Cross-client exception
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* Cross-client exception: tools whose capabilities are in
|
|
23
|
+
* CROSS_CLIENT_EXPOSED_CAPABILITIES (host_bash, host_file) are allowed for
|
|
24
|
+
* non-host-proxy interfaces (e.g. "web") when at least one capable client
|
|
25
|
+
* is connected via the event hub. host_browser is excluded (chrome-extension
|
|
26
|
+
* is its own executor; web turns have no CDP target model).
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
29
30
|
|
|
30
31
|
// ── Module-level mocks ─────────────────────────────────────────────
|
|
31
32
|
|
|
32
|
-
// Control how many
|
|
33
|
-
|
|
33
|
+
// Control how many capable clients the hub reports per capability.
|
|
34
|
+
const mockClientCountByCapability = new Map<string, number>();
|
|
34
35
|
|
|
35
36
|
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
36
37
|
assistantEventHub: {
|
|
37
38
|
listClientsByCapability: (cap: string) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
return [];
|
|
39
|
+
const count = mockClientCountByCapability.get(cap) ?? 0;
|
|
40
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
41
|
+
clientId: `mock-${cap}-client-${i}`,
|
|
42
|
+
capabilities: [cap],
|
|
43
|
+
}));
|
|
45
44
|
},
|
|
46
45
|
},
|
|
47
46
|
broadcastMessage: () => {},
|
|
@@ -72,7 +71,7 @@ function makeCtx(
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
beforeEach(() => {
|
|
75
|
-
|
|
74
|
+
mockClientCountByCapability.clear();
|
|
76
75
|
});
|
|
77
76
|
|
|
78
77
|
describe("isToolActiveForContext — host tool capability gating", () => {
|
|
@@ -213,7 +212,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
213
212
|
test("host_bash is active for web transport when a host_bash-capable client is connected", () => {
|
|
214
213
|
// Cross-client path: a web turn should see host_bash when a macOS client
|
|
215
214
|
// with host_bash capability is connected via the event hub.
|
|
216
|
-
|
|
215
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
217
216
|
expect(
|
|
218
217
|
isToolActiveForContext(
|
|
219
218
|
"host_bash",
|
|
@@ -224,7 +223,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
224
223
|
|
|
225
224
|
test("host_bash is NOT active for web transport when no capable client is connected", () => {
|
|
226
225
|
// No cross-client fallback: hub has no host_bash-capable subscribers.
|
|
227
|
-
|
|
226
|
+
mockClientCountByCapability.set("host_bash", 0);
|
|
228
227
|
expect(
|
|
229
228
|
isToolActiveForContext(
|
|
230
229
|
"host_bash",
|
|
@@ -233,11 +232,11 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
233
232
|
).toBe(false);
|
|
234
233
|
});
|
|
235
234
|
|
|
236
|
-
test("host_file_read is NOT active for web transport
|
|
237
|
-
// The cross-client exception is
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
235
|
+
test("host_file_read is NOT active for web transport when only a host_bash client is connected", () => {
|
|
236
|
+
// The cross-client exception is per-capability: a host_bash-capable
|
|
237
|
+
// client in the hub does not satisfy host_file's exposure check, since
|
|
238
|
+
// listClientsByCapability is queried with the tool's actual capability.
|
|
239
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
241
240
|
expect(
|
|
242
241
|
isToolActiveForContext(
|
|
243
242
|
"host_file_read",
|
|
@@ -249,7 +248,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
249
248
|
test("host_bash for macos transport is unaffected by the cross-client exception", () => {
|
|
250
249
|
// macos natively supports host_bash via host proxy — the supportsHostProxy
|
|
251
250
|
// check passes, so the cross-client branch is never reached.
|
|
252
|
-
|
|
251
|
+
mockClientCountByCapability.set("host_bash", 0);
|
|
253
252
|
expect(
|
|
254
253
|
isToolActiveForContext(
|
|
255
254
|
"host_bash",
|
|
@@ -262,7 +261,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
262
261
|
// Even with a capable client in the hub, the macos SSE path takes
|
|
263
262
|
// precedence — it passes the supportsHostProxy check, bypasses the
|
|
264
263
|
// cross-client branch, and reaches the hasNoClient gate.
|
|
265
|
-
|
|
264
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
266
265
|
expect(
|
|
267
266
|
isToolActiveForContext(
|
|
268
267
|
"host_bash",
|
|
@@ -275,7 +274,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
275
274
|
// Security boundary: chrome-extension only gets host_browser. The
|
|
276
275
|
// cross-client exception explicitly excludes chrome-extension transport
|
|
277
276
|
// regardless of how many host_bash-capable clients are in the hub.
|
|
278
|
-
|
|
277
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
279
278
|
expect(
|
|
280
279
|
isToolActiveForContext(
|
|
281
280
|
"host_bash",
|
|
@@ -287,7 +286,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
287
286
|
test("host_bash is NOT active for web transport when hasNoClient is true (no approval UI)", () => {
|
|
288
287
|
// hasNoClient gate: no interactive approval UI available for this turn.
|
|
289
288
|
// Cross-client exception must not bypass this gate.
|
|
290
|
-
|
|
289
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
291
290
|
expect(
|
|
292
291
|
isToolActiveForContext(
|
|
293
292
|
"host_bash",
|
|
@@ -297,6 +296,68 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
297
296
|
});
|
|
298
297
|
});
|
|
299
298
|
|
|
299
|
+
describe("isToolActiveForContext — cross-client exposure for host_file_*", () => {
|
|
300
|
+
const HOST_FILE_TOOLS = [
|
|
301
|
+
"host_file_read",
|
|
302
|
+
"host_file_write",
|
|
303
|
+
"host_file_edit",
|
|
304
|
+
"host_file_transfer",
|
|
305
|
+
] as const;
|
|
306
|
+
|
|
307
|
+
for (const tool of HOST_FILE_TOOLS) {
|
|
308
|
+
test(`${tool} is exposed for web transport when a host_file client is connected`, () => {
|
|
309
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
310
|
+
expect(
|
|
311
|
+
isToolActiveForContext(
|
|
312
|
+
tool,
|
|
313
|
+
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
314
|
+
),
|
|
315
|
+
).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test(`${tool} is NOT exposed for web when no host_file client is connected`, () => {
|
|
319
|
+
mockClientCountByCapability.set("host_file", 0);
|
|
320
|
+
expect(
|
|
321
|
+
isToolActiveForContext(
|
|
322
|
+
tool,
|
|
323
|
+
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
324
|
+
),
|
|
325
|
+
).toBe(false);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test(`${tool} is NOT exposed for chrome-extension (security boundary)`, () => {
|
|
329
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
330
|
+
expect(
|
|
331
|
+
isToolActiveForContext(
|
|
332
|
+
tool,
|
|
333
|
+
makeCtx({ hasNoClient: true, transportInterface: "chrome-extension" }),
|
|
334
|
+
),
|
|
335
|
+
).toBe(false);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test(`${tool} is NOT exposed when hasNoClient is true (no approval UI)`, () => {
|
|
339
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
340
|
+
expect(
|
|
341
|
+
isToolActiveForContext(
|
|
342
|
+
tool,
|
|
343
|
+
makeCtx({ hasNoClient: true, transportInterface: "web" }),
|
|
344
|
+
),
|
|
345
|
+
).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
test("listClientsByCapability is queried with the actual capability, not host_bash (regression guard for D5 latent bug)", () => {
|
|
350
|
+
mockClientCountByCapability.set("host_bash", 0);
|
|
351
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
352
|
+
expect(
|
|
353
|
+
isToolActiveForContext(
|
|
354
|
+
"host_file_transfer",
|
|
355
|
+
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
356
|
+
),
|
|
357
|
+
).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
300
361
|
describe("HOST_TOOL_NAMES derivation", () => {
|
|
301
362
|
test("HOST_TOOL_NAMES is derived from HOST_TOOL_TO_CAPABILITY", () => {
|
|
302
363
|
// Sanity check: every tool in the names set has a capability mapping.
|
|
@@ -125,7 +125,7 @@ export function classifyKind(mimeType: string): "image" | "video" | "document" {
|
|
|
125
125
|
// Validation / cap enforcement
|
|
126
126
|
// ---------------------------------------------------------------------------
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
interface ValidatedDrafts {
|
|
129
129
|
accepted: AssistantAttachmentDraft[];
|
|
130
130
|
warnings: string[];
|
|
131
131
|
}
|
|
@@ -171,13 +171,13 @@ export interface DirectiveRequest {
|
|
|
171
171
|
mimeType: string | undefined;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
interface DirectiveParseResult {
|
|
175
175
|
cleanText: string;
|
|
176
176
|
directiveRequests: DirectiveRequest[];
|
|
177
177
|
parseWarnings: string[];
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
interface DirectiveDisplayDrainResult {
|
|
181
181
|
emitText: string;
|
|
182
182
|
bufferedRemainder: string;
|
|
183
183
|
}
|
|
@@ -362,7 +362,7 @@ export function drainDirectiveDisplayBuffer(
|
|
|
362
362
|
// Sandbox file resolution
|
|
363
363
|
// ---------------------------------------------------------------------------
|
|
364
364
|
|
|
365
|
-
|
|
365
|
+
interface ResolveResult {
|
|
366
366
|
draft: AssistantAttachmentDraft | null;
|
|
367
367
|
warning: string | null;
|
|
368
368
|
}
|
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
type FSWatcher,
|
|
9
9
|
mkdirSync,
|
|
10
10
|
readdirSync,
|
|
11
|
+
unwatchFile,
|
|
11
12
|
watch,
|
|
13
|
+
watchFile,
|
|
12
14
|
} from "node:fs";
|
|
13
15
|
import { join } from "node:path";
|
|
14
16
|
|
|
@@ -17,7 +19,6 @@ import type { MemoryCleanupConfig } from "../config/schemas/memory-lifecycle.js"
|
|
|
17
19
|
import { resetCleanupScheduleThrottle } from "../memory/cleanup-schedule-state.js";
|
|
18
20
|
import { clearEmbeddingBackendCache } from "../memory/embedding-backend.js";
|
|
19
21
|
import { initializeProviders } from "../providers/registry.js";
|
|
20
|
-
import { handleBashSignal } from "../signals/bash.js";
|
|
21
22
|
import { handleCancelSignal } from "../signals/cancel.js";
|
|
22
23
|
import { handleConversationUndoSignal } from "../signals/conversation-undo.js";
|
|
23
24
|
import { handleEmitEventSignal } from "../signals/emit-event.js";
|
|
@@ -47,19 +48,44 @@ function attachWatcherErrorHandler(watcher: FSWatcher, dir: string): void {
|
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Poll interval for `fs.watchFile()`. Use the stat-polling watcher
|
|
53
|
+
* because Bun's per-file `fs.watch()` doesn't detect renames on Linux
|
|
54
|
+
* (seemingly works on macOS). See https://github.com/oven-sh/bun/issues/15010.
|
|
55
|
+
*/
|
|
56
|
+
const WATCH_FILE_POLL_MS = 2_000;
|
|
57
|
+
|
|
50
58
|
export class ConfigWatcher {
|
|
51
59
|
private watchers: FSWatcher[] = [];
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
protectedKeyPrefix: "__",
|
|
56
|
-
});
|
|
60
|
+
private watchedFiles: Set<string> = new Set();
|
|
61
|
+
private stopped = false;
|
|
62
|
+
private debounceTimers: DebouncerMap;
|
|
57
63
|
private suppressReload = false;
|
|
58
64
|
lastFingerprint = "";
|
|
65
|
+
private lastConfig: ReturnType<typeof getConfig> | null = null;
|
|
59
66
|
private lastRefreshTime = 0;
|
|
60
67
|
|
|
61
68
|
static readonly REFRESH_INTERVAL_MS = 30_000;
|
|
62
69
|
|
|
70
|
+
/**
|
|
71
|
+
* @param pollIntervalMs Per-file stat poll interval (passed to
|
|
72
|
+
* `fs.watchFile`). Default `WATCH_FILE_POLL_MS` (2s); tests pass a
|
|
73
|
+
* smaller value for fast turnaround.
|
|
74
|
+
* @param debounceMs Debounce window applied to any detected file
|
|
75
|
+
* change before invoking its handler. Default 200ms; tests pass a
|
|
76
|
+
* smaller value to avoid sleeping unnecessarily.
|
|
77
|
+
*/
|
|
78
|
+
constructor(
|
|
79
|
+
private readonly pollIntervalMs: number = WATCH_FILE_POLL_MS,
|
|
80
|
+
debounceMs = 200,
|
|
81
|
+
) {
|
|
82
|
+
this.debounceTimers = new DebouncerMap({
|
|
83
|
+
defaultDelayMs: debounceMs,
|
|
84
|
+
maxEntries: 1000,
|
|
85
|
+
protectedKeyPrefix: "__",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
63
89
|
/** Expose the debounce timers so handlers can schedule debounced work. */
|
|
64
90
|
get timers(): DebouncerMap {
|
|
65
91
|
return this.debounceTimers;
|
|
@@ -88,12 +114,15 @@ export class ConfigWatcher {
|
|
|
88
114
|
|
|
89
115
|
/** Initialize the config fingerprint (call after first config load). */
|
|
90
116
|
initFingerprint(config: ReturnType<typeof getConfig>): void {
|
|
117
|
+
this.lastConfig = config;
|
|
91
118
|
this.lastFingerprint = this.configFingerprint(config);
|
|
92
119
|
}
|
|
93
120
|
|
|
94
121
|
/** Update the fingerprint to match the current config. */
|
|
95
122
|
updateFingerprint(): void {
|
|
96
|
-
|
|
123
|
+
const config = getConfig();
|
|
124
|
+
this.lastConfig = config;
|
|
125
|
+
this.lastFingerprint = this.configFingerprint(config);
|
|
97
126
|
this.lastRefreshTime = Date.now();
|
|
98
127
|
}
|
|
99
128
|
|
|
@@ -103,7 +132,7 @@ export class ConfigWatcher {
|
|
|
103
132
|
* Returns true if config actually changed.
|
|
104
133
|
*/
|
|
105
134
|
async refreshConfigFromSources(): Promise<boolean> {
|
|
106
|
-
const prevCleanup =
|
|
135
|
+
const prevCleanup = this.lastConfig?.memory?.cleanup;
|
|
107
136
|
invalidateConfigCache();
|
|
108
137
|
const config = getConfig();
|
|
109
138
|
const fingerprint = this.configFingerprint(config);
|
|
@@ -120,6 +149,7 @@ export class ConfigWatcher {
|
|
|
120
149
|
}
|
|
121
150
|
const isFirstInit = this.lastFingerprint === "";
|
|
122
151
|
await initializeProviders(config);
|
|
152
|
+
this.lastConfig = config;
|
|
123
153
|
this.lastFingerprint = fingerprint;
|
|
124
154
|
return !isFirstInit;
|
|
125
155
|
}
|
|
@@ -136,19 +166,24 @@ export class ConfigWatcher {
|
|
|
136
166
|
onAvatarChanged?: () => void,
|
|
137
167
|
onConfigChanged?: () => void,
|
|
138
168
|
): void {
|
|
169
|
+
// Reset the stopped flag so a stop()→start() cycle on the same
|
|
170
|
+
// instance resumes hot-reload instead of silently bailing in every
|
|
171
|
+
// watchFile callback. This matters because getConfigWatcher() is a
|
|
172
|
+
// module-level singleton — a daemon restart path that reuses it
|
|
173
|
+
// would otherwise be permanently mute.
|
|
174
|
+
this.stopped = false;
|
|
139
175
|
const workspaceDir = getWorkspaceDir();
|
|
140
176
|
|
|
141
177
|
const workspaceHandlers: Record<string, () => void> = {
|
|
142
178
|
"config.json": async () => {
|
|
143
179
|
if (this.suppressReload) return;
|
|
144
180
|
try {
|
|
145
|
-
const
|
|
146
|
-
const prevMcpFingerprint = JSON.stringify(prevConfig.mcp ?? {});
|
|
181
|
+
const prevMcpFingerprint = JSON.stringify(this.lastConfig?.mcp ?? {});
|
|
147
182
|
const changed = await this.refreshConfigFromSources();
|
|
148
183
|
if (changed) {
|
|
149
184
|
onConversationEvict();
|
|
150
185
|
onConfigChanged?.();
|
|
151
|
-
const newConfig = getConfig();
|
|
186
|
+
const newConfig = this.lastConfig ?? getConfig();
|
|
152
187
|
const newMcpFingerprint = JSON.stringify(newConfig.mcp ?? {});
|
|
153
188
|
if (newMcpFingerprint !== prevMcpFingerprint) {
|
|
154
189
|
reloadMcpServers().catch((err: unknown) => {
|
|
@@ -170,37 +205,11 @@ export class ConfigWatcher {
|
|
|
170
205
|
},
|
|
171
206
|
};
|
|
172
207
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const watcher = watch(dir, (_eventType, filename) => {
|
|
180
|
-
if (!filename) return;
|
|
181
|
-
const file = String(filename);
|
|
182
|
-
if (!handlers[file]) return;
|
|
183
|
-
this.debounceTimers.schedule(`file:${file}`, () => {
|
|
184
|
-
log.info({ file }, "File changed, reloading");
|
|
185
|
-
handlers[file]();
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
attachWatcherErrorHandler(watcher, dir);
|
|
189
|
-
this.watchers.push(watcher);
|
|
190
|
-
log.info({ dir }, `Watching ${label}`);
|
|
191
|
-
} catch (err) {
|
|
192
|
-
log.warn(
|
|
193
|
-
{ err, dir },
|
|
194
|
-
`Failed to watch ${label}. Hot-reload will be unavailable.`,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
watchDir(
|
|
200
|
-
workspaceDir,
|
|
201
|
-
workspaceHandlers,
|
|
202
|
-
"workspace directory for config/prompt changes",
|
|
203
|
-
);
|
|
208
|
+
// Per-file watches; don't watch the workspace directory itself because
|
|
209
|
+
// it contains socket files.
|
|
210
|
+
for (const [filename, handler] of Object.entries(workspaceHandlers)) {
|
|
211
|
+
this.watchFile(join(workspaceDir, filename), handler, filename);
|
|
212
|
+
}
|
|
204
213
|
|
|
205
214
|
if (onSoundsConfigChanged) {
|
|
206
215
|
this.startSoundsWatcher(onSoundsConfigChanged);
|
|
@@ -215,13 +224,46 @@ export class ConfigWatcher {
|
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
stop(): void {
|
|
227
|
+
this.stopped = true;
|
|
218
228
|
this.debounceTimers.cancelAll();
|
|
229
|
+
for (const filePath of this.watchedFiles) {
|
|
230
|
+
unwatchFile(filePath);
|
|
231
|
+
}
|
|
232
|
+
this.watchedFiles.clear();
|
|
219
233
|
for (const watcher of this.watchers) {
|
|
220
234
|
watcher.close();
|
|
221
235
|
}
|
|
222
236
|
this.watchers = [];
|
|
223
237
|
}
|
|
224
238
|
|
|
239
|
+
private watchFile(
|
|
240
|
+
filePath: string,
|
|
241
|
+
handler: () => void,
|
|
242
|
+
label: string,
|
|
243
|
+
): void {
|
|
244
|
+
// Match the defensive pattern used by every other startXWatcher in
|
|
245
|
+
// this file: log the failure and continue. Per AGENTS.md, the daemon
|
|
246
|
+
// must never block startup — a watchFile() throw on some platform
|
|
247
|
+
// edge case must not propagate up to DaemonServer.start().
|
|
248
|
+
try {
|
|
249
|
+
watchFile(filePath, { interval: this.pollIntervalMs }, (curr, prev) => {
|
|
250
|
+
if (this.stopped) return;
|
|
251
|
+
if (curr.ino === prev.ino && curr.mtimeMs === prev.mtimeMs) return;
|
|
252
|
+
this.debounceTimers.schedule(`file:${filePath}`, () => {
|
|
253
|
+
log.info({ file: filePath }, "File changed, reloading");
|
|
254
|
+
handler();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
this.watchedFiles.add(filePath);
|
|
258
|
+
log.info({ file: filePath }, `Watching ${label}`);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
log.warn(
|
|
261
|
+
{ err, file: filePath },
|
|
262
|
+
`Failed to watch ${label}. Hot-reload will be unavailable until restart.`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
225
267
|
private startSoundsWatcher(onSoundsConfigChanged: () => void): void {
|
|
226
268
|
const soundsDir = getSoundsDir();
|
|
227
269
|
try {
|
|
@@ -341,7 +383,6 @@ export class ConfigWatcher {
|
|
|
341
383
|
string,
|
|
342
384
|
(filename: string) => void | Promise<void>
|
|
343
385
|
> = {
|
|
344
|
-
"bash.": handleBashSignal,
|
|
345
386
|
"user-message.": handleUserMessageSignal,
|
|
346
387
|
};
|
|
347
388
|
|
|
@@ -486,20 +527,6 @@ export class ConfigWatcher {
|
|
|
486
527
|
}
|
|
487
528
|
}
|
|
488
529
|
|
|
489
|
-
/**
|
|
490
|
-
* Snapshot the current cleanup config so we can compare it against the
|
|
491
|
-
* post-reload value. Tolerant of config-load failures — if the config can't
|
|
492
|
-
* be read (e.g. first-load), returns undefined so the comparison below
|
|
493
|
-
* treats it as "no previous value".
|
|
494
|
-
*/
|
|
495
|
-
function safeGetCleanupConfig(): MemoryCleanupConfig | undefined {
|
|
496
|
-
try {
|
|
497
|
-
return getConfig().memory?.cleanup;
|
|
498
|
-
} catch {
|
|
499
|
-
return undefined;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
530
|
/**
|
|
504
531
|
* Return true if any cleanup field the user can change via the UI differs
|
|
505
532
|
* between the previous and next config snapshots. Used to decide whether to
|
|
@@ -530,6 +557,7 @@ export function cleanupSettingsChanged(
|
|
|
530
557
|
return (
|
|
531
558
|
prev.llmRequestLogRetentionMs !== next.llmRequestLogRetentionMs ||
|
|
532
559
|
prev.conversationRetentionDays !== next.conversationRetentionDays ||
|
|
560
|
+
prev.traceEventRetentionDays !== next.traceEventRetentionDays ||
|
|
533
561
|
prev.enabled !== next.enabled
|
|
534
562
|
);
|
|
535
563
|
}
|
|
@@ -137,6 +137,8 @@ export interface EventHandlerState {
|
|
|
137
137
|
readonly directiveWarnings: string[];
|
|
138
138
|
readonly toolUseIdToName: Map<string, string>;
|
|
139
139
|
currentTurnToolNames: string[];
|
|
140
|
+
/** Sticky for the whole run: this turn created/refreshed an app. */
|
|
141
|
+
appBuildToolUsedThisRun: boolean;
|
|
140
142
|
/** Tracks whether the first text delta has been emitted this turn for activity state transitions. */
|
|
141
143
|
firstTextDeltaEmitted: boolean;
|
|
142
144
|
/** Tracks whether a thinking delta has been emitted this turn for activity state transitions. */
|
|
@@ -168,6 +170,16 @@ export interface EventHandlerState {
|
|
|
168
170
|
approvalMode?: string;
|
|
169
171
|
approvalReason?: string;
|
|
170
172
|
riskThreshold?: string;
|
|
173
|
+
/** Display-only regex ladder for the rule editor (narrowest → broadest). */
|
|
174
|
+
riskScopeOptions?: Array<{ pattern: string; label: string }>;
|
|
175
|
+
/** Minimatch save patterns for the rule editor (narrowest → broadest). */
|
|
176
|
+
riskAllowlistOptions?: Array<{
|
|
177
|
+
label: string;
|
|
178
|
+
description: string;
|
|
179
|
+
pattern: string;
|
|
180
|
+
}>;
|
|
181
|
+
/** Directory scope ladder for the rule editor. */
|
|
182
|
+
riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
171
183
|
}
|
|
172
184
|
>;
|
|
173
185
|
/** tool_use_ids emitted in the current turn (populated in handleToolUse, cleared after annotation). */
|
|
@@ -219,6 +231,7 @@ export function createEventHandlerState(): EventHandlerState {
|
|
|
219
231
|
directiveWarnings: [],
|
|
220
232
|
toolUseIdToName: new Map(),
|
|
221
233
|
currentTurnToolNames: [],
|
|
234
|
+
appBuildToolUsedThisRun: false,
|
|
222
235
|
firstTextDeltaEmitted: false,
|
|
223
236
|
firstThinkingDeltaEmitted: false,
|
|
224
237
|
lastCompletedToolName: undefined,
|
|
@@ -365,6 +378,9 @@ export function handleToolUse(
|
|
|
365
378
|
): void {
|
|
366
379
|
state.toolUseIdToName.set(event.id, event.name);
|
|
367
380
|
state.currentTurnToolNames.push(event.name);
|
|
381
|
+
if (event.name === "app_create" || event.name === "app_refresh") {
|
|
382
|
+
state.appBuildToolUsedThisRun = true;
|
|
383
|
+
}
|
|
368
384
|
state.toolCallTimestamps.set(event.id, { startedAt: Date.now() });
|
|
369
385
|
state.currentToolUseId = event.id;
|
|
370
386
|
state.currentTurnToolUseIds.push(event.id);
|
|
@@ -548,6 +564,14 @@ export function handleToolResult(
|
|
|
548
564
|
approvalMode: event.approvalMode,
|
|
549
565
|
approvalReason: event.approvalReason,
|
|
550
566
|
riskThreshold: event.riskThreshold,
|
|
567
|
+
// Capture the 3 risk-option arrays so the persisted tool_use block
|
|
568
|
+
// carries the same chip ladder as the live tool_result event. Without
|
|
569
|
+
// these, hydrated chips from chat history fall back to the synthesized
|
|
570
|
+
// `*` allowlist and an empty scope ladder (see the comment on
|
|
571
|
+
// `synthesizeFallbackOption` in web's RuleEditorModal).
|
|
572
|
+
riskScopeOptions: event.riskScopeOptions,
|
|
573
|
+
riskAllowlistOptions: event.riskAllowlistOptions,
|
|
574
|
+
riskDirectoryScopeOptions: event.riskDirectoryScopeOptions,
|
|
551
575
|
});
|
|
552
576
|
}
|
|
553
577
|
|
|
@@ -627,6 +651,7 @@ export function handleToolResult(
|
|
|
627
651
|
matchedTrustRuleId: event.matchedTrustRuleId,
|
|
628
652
|
isContainerized: event.isContainerized,
|
|
629
653
|
riskScopeOptions: event.riskScopeOptions,
|
|
654
|
+
riskAllowlistOptions: event.riskAllowlistOptions,
|
|
630
655
|
riskDirectoryScopeOptions: event.riskDirectoryScopeOptions,
|
|
631
656
|
approvalMode: event.approvalMode,
|
|
632
657
|
approvalReason: event.approvalReason,
|
|
@@ -688,6 +713,19 @@ function annotatePersistedAssistantMessage(
|
|
|
688
713
|
if (risk.approvalMode) rec._approvalMode = risk.approvalMode;
|
|
689
714
|
if (risk.approvalReason) rec._approvalReason = risk.approvalReason;
|
|
690
715
|
if (risk.riskThreshold) rec._riskThreshold = risk.riskThreshold;
|
|
716
|
+
// Persist the 3 risk-option arrays so the rule editor's chip ladder
|
|
717
|
+
// survives chat-history reload. These mirror the same-named fields
|
|
718
|
+
// on the live `tool_result` event; clients should read them back via
|
|
719
|
+
// `shared.ts` and treat them identically to the live values.
|
|
720
|
+
if (risk.riskScopeOptions && risk.riskScopeOptions.length > 0)
|
|
721
|
+
rec._riskScopeOptions = risk.riskScopeOptions;
|
|
722
|
+
if (risk.riskAllowlistOptions && risk.riskAllowlistOptions.length > 0)
|
|
723
|
+
rec._riskAllowlistOptions = risk.riskAllowlistOptions;
|
|
724
|
+
if (
|
|
725
|
+
risk.riskDirectoryScopeOptions &&
|
|
726
|
+
risk.riskDirectoryScopeOptions.length > 0
|
|
727
|
+
)
|
|
728
|
+
rec._riskDirectoryScopeOptions = risk.riskDirectoryScopeOptions;
|
|
691
729
|
modified = true;
|
|
692
730
|
}
|
|
693
731
|
}
|