@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
package/ARCHITECTURE.md
CHANGED
|
@@ -21,6 +21,20 @@ This document owns assistant-runtime architecture details. The repo-level archit
|
|
|
21
21
|
- Voice calls mirror the same prompt contract: `CallController` receives guardian context on setup and refreshes it immediately after successful voice challenge verification, so the first post-verification turn is grounded as `actor_role: guardian`.
|
|
22
22
|
- Voice-specific behavior (DTMF/speech verification flow, relay state machine) remains voice-local; only actor-role resolution is shared.
|
|
23
23
|
|
|
24
|
+
### Safe Storage Limits
|
|
25
|
+
|
|
26
|
+
Safe storage limits are gated by the assistant feature flag `safe-storage-limits`, default off. When the flag is off, the disk pressure guard reports a disabled status and no runtime path blocks work, injects cleanup guidance, or changes tool access.
|
|
27
|
+
|
|
28
|
+
**Disk pressure state:** `src/daemon/disk-pressure-guard.ts` samples workspace storage usage every 60 seconds through `src/util/disk-usage.ts`. At or above the 95% critical threshold it creates an in-memory lock with `lockId`, usage snapshot, `acknowledged`, `overrideActive`, `effectivelyLocked`, and the blocked capabilities `agent-turns`, `background-work`, and `remote-ingress`. The lock clears when usage drops below the threshold or the process restarts. `acknowledgeDiskPressureLock()` only lets the guardian enter cleanup mode; `overrideDiskPressureLock()` requires the exact phrase `I understand the risks` and disables the effective lock while usage remains critical.
|
|
29
|
+
|
|
30
|
+
**Runtime API and events:** `src/runtime/routes/disk-pressure-routes.ts` exposes `GET /v1/disk-pressure/status`, `POST /v1/disk-pressure/acknowledge`, and `POST /v1/disk-pressure/override`. Route auth policies require normal runtime protection, and `disk_pressure_status_changed` events are emitted when the status changes so clients can update live.
|
|
31
|
+
|
|
32
|
+
**Turn policy:** `src/daemon/disk-pressure-policy.ts` classifies turns before the main agent loop. Local guardian/owner turns are allowed in cleanup mode; trusted contacts, non-guardian actors, unknown remote senders, background conversations, direct wakes, and non-main LLM call sites are blocked while `effectivelyLocked` is true. Blocked turns emit terminal conversation errors rather than reaching the provider.
|
|
33
|
+
|
|
34
|
+
**Background work:** Heartbeats, scheduled tasks, filing work, retry sweeps, and background tool completions call `src/daemon/disk-pressure-background-gate.ts` before starting work. While effectively locked they skip the wake or job and log throttled disk-pressure fields.
|
|
35
|
+
|
|
36
|
+
**Prompt and tools:** Cleanup-mode turns carry `diskPressureContext` through runtime assembly and receive the `<disk_pressure_warning>` injector in `src/plugins/defaults/injectors.ts`. The instruction tells the assistant to warn first, focus only on freeing storage, inspect before deleting, ask for deletion approval, and explain that background processes and trusted-contact messages are blocked. Tool setup marks the turn as cleanup mode; `src/tools/tool-approval-handler.ts` rejects non-cleanup-safe tools, and foreground shell inspection remains available while background `bash` and `host_bash` modes are rejected. When a new lock is created, active background terminal tools are cancelled with reason `disk_pressure`.
|
|
37
|
+
|
|
24
38
|
### Single-Header JWT Auth Model
|
|
25
39
|
|
|
26
40
|
All HTTP API requests use a single `Authorization: Bearer <jwt>` header for authentication. The JWT carries identity, permissions, and policy versioning in a unified token.
|
|
@@ -966,6 +980,7 @@ The daemon emits two distinct error message types via SSE:
|
|
|
966
980
|
| -------------------------------- | ----------------------------------------------------------------------- | --------- |
|
|
967
981
|
| `PROVIDER_NETWORK` | Unable to reach the LLM provider (connection refused, timeout, DNS) | Yes |
|
|
968
982
|
| `PROVIDER_RATE_LIMIT` | LLM provider rate-limited the request (HTTP 429) | Yes |
|
|
983
|
+
| `MANAGED_USAGE_LIMIT` | Vellum managed inference usage limit or quota was exceeded (HTTP 429) | Yes |
|
|
969
984
|
| `PROVIDER_API` | Provider returned a server error (5xx) or retryable 4xx | Yes |
|
|
970
985
|
| `PROVIDER_BILLING` | Invalid/expired API key or insufficient credits (HTTP 401, billing 4xx) | No |
|
|
971
986
|
| `CONTEXT_TOO_LARGE` | Request exceeds the model's context window (HTTP 413, token limit) | No |
|
|
@@ -980,7 +995,7 @@ The daemon classifies errors via `classifyConversationError()` in `conversation-
|
|
|
980
995
|
|
|
981
996
|
Classification uses a two-tier strategy:
|
|
982
997
|
|
|
983
|
-
1. **Structured provider errors**: If the error is a `ProviderError` with a `statusCode`, the status code determines the category deterministically — `413` maps to `CONTEXT_TOO_LARGE` (not retryable), `401` maps to `PROVIDER_BILLING` (not retryable, invalid/expired key), `429` maps to `PROVIDER_RATE_LIMIT` (retryable), `5xx` to `PROVIDER_API` (retryable), other `4xx` to `PROVIDER_API` (retryable) unless a message pattern matches a more specific non-retryable category (context-too-large, billing/auth).
|
|
998
|
+
1. **Structured provider errors**: If the error is a `ProviderError` with a `statusCode`, the status code determines the category deterministically — `413` maps to `CONTEXT_TOO_LARGE` (not retryable), `401` maps to `PROVIDER_BILLING` (not retryable, invalid/expired key), `429` maps to `MANAGED_USAGE_LIMIT` when the provider is routed through the managed proxy or the payload matches a Vellum managed quota/limit response, otherwise `PROVIDER_RATE_LIMIT` (retryable), `5xx` to `PROVIDER_API` (retryable), other `4xx` to `PROVIDER_API` (retryable) unless a message pattern matches a more specific non-retryable category (context-too-large, billing/auth).
|
|
984
999
|
2. **Regex fallback**: For non-provider errors or `ProviderError` without a status code, regex pattern matching against the error message detects network failures, rate limits, and API errors. Phase-specific overrides handle regeneration contexts.
|
|
985
1000
|
|
|
986
1001
|
Debug details are capped at 4,000 characters to prevent oversized payloads.
|
|
@@ -1263,7 +1278,7 @@ graph TB
|
|
|
1263
1278
|
- Managed-store writes are atomic (tmp file + rename) to prevent partial `SKILL.md` or `SKILLS.md` files.
|
|
1264
1279
|
- After persist or delete, the file watcher triggers conversation eviction; the next turn runs in a fresh conversation. The model's system prompt instructs it to continue normally.
|
|
1265
1280
|
- macOS UI shows Inspect and Delete controls for managed skills only (source = "managed").
|
|
1266
|
-
- `skill_load`
|
|
1281
|
+
- `skill_load` resolves the recursive include graph (via `include-graph.ts`) before emitting output. Missing children are listed as suggested skills without child `<loaded_skill>` markers; cycles still produce `isError: true` with no marker. Valid includes produce an "Included Skills (immediate)" metadata section showing child ID, name, description, and path.
|
|
1267
1282
|
|
|
1268
1283
|
### Skills Authoring via HTTP
|
|
1269
1284
|
|
|
@@ -1274,32 +1289,33 @@ The Skills page in the macOS client can author managed skills through the daemon
|
|
|
1274
1289
|
|
|
1275
1290
|
### Include Graph Validation
|
|
1276
1291
|
|
|
1277
|
-
Skills can declare child relationships via the `includes` frontmatter field (a JSON array of skill IDs). When `skill_load` loads a parent skill, it
|
|
1292
|
+
Skills can declare child relationships via the `includes` frontmatter field (a JSON array of skill IDs). When `skill_load` loads a parent skill, it attempts to resolve and auto-install missing includes before emitting output. Available includes are appended to the loaded skill output; unavailable includes are surfaced as suggestions instead of blocking the parent skill.
|
|
1278
1293
|
|
|
1279
1294
|
```mermaid
|
|
1280
1295
|
graph LR
|
|
1281
1296
|
LOAD["skill_load(parent)"] --> CATALOG["loadSkillCatalog()"]
|
|
1282
1297
|
CATALOG --> INDEX["indexCatalogById()"]
|
|
1283
|
-
INDEX -->
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1298
|
+
INDEX --> AUTOINSTALL["Attempt catalog auto-install<br/>for missing includes"]
|
|
1299
|
+
AUTOINSTALL --> RESOLVE["collectAllMissing(rootId, index)<br/>+ validateIncludeCycles(rootId, index)"]
|
|
1300
|
+
RESOLVE -->|"ok + no missing child"| OUTPUT["Emit output +<br/>Included Skills (immediate)<br/>+ loaded_skill markers"]
|
|
1301
|
+
RESOLVE -->|"ok + missing child"| OUTPUT_MISSING["Emit parent output +<br/>Suggested Included Skills<br/>without child markers"]
|
|
1302
|
+
RESOLVE -->|"cycle detected"| ERR_CYCLE["isError: true<br/>no loaded_skill marker"]
|
|
1287
1303
|
```
|
|
1288
1304
|
|
|
1289
1305
|
**Validation rules:**
|
|
1290
1306
|
|
|
1291
|
-
- **Missing children**:
|
|
1307
|
+
- **Missing children**: Missing includes trigger catalog auto-install attempts. Any include still unavailable is listed under "Suggested Included Skills (not loaded)" and does not receive a `<loaded_skill>` marker.
|
|
1292
1308
|
- **Cycles**: Three-state DFS (unseen → visiting → done) detects direct and indirect cycles. The error includes the cycle path.
|
|
1293
|
-
- **Fail-closed**:
|
|
1309
|
+
- **Fail-closed cycles**: Circular include chains still return `isError: true` with no `<loaded_skill>` marker.
|
|
1294
1310
|
|
|
1295
|
-
**Key constraint**: Include metadata is
|
|
1311
|
+
**Key constraint**: Include metadata is advisory. Available included skills are appended to the parent output and receive explicit `<loaded_skill>` markers; unavailable included skills remain suggestions so the agent can search for and install them if the task needs their guidance or tools.
|
|
1296
1312
|
|
|
1297
|
-
| Source File | Purpose
|
|
1298
|
-
| --------------------------------------- |
|
|
1299
|
-
| `assistant/src/skills/include-graph.ts` | `indexCatalogById()`, `getImmediateChildren()`, `validateIncludes()`, `traverseIncludes()` |
|
|
1300
|
-
| `assistant/src/tools/skills/load.ts` | Include
|
|
1301
|
-
| `assistant/src/config/skills.ts` | `includes` field parsing from SKILL.md frontmatter
|
|
1302
|
-
| `assistant/src/skills/managed-store.ts` | `includes` emission in `buildSkillMarkdown()`
|
|
1313
|
+
| Source File | Purpose |
|
|
1314
|
+
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
1315
|
+
| `assistant/src/skills/include-graph.ts` | `indexCatalogById()`, `getImmediateChildren()`, `validateIncludes()`, `validateIncludeCycles()`, `traverseIncludes()` |
|
|
1316
|
+
| `assistant/src/tools/skills/load.ts` | Include resolution integration in `skill_load` execute path |
|
|
1317
|
+
| `assistant/src/config/skills.ts` | `includes` field parsing from SKILL.md frontmatter |
|
|
1318
|
+
| `assistant/src/skills/managed-store.ts` | `includes` emission in `buildSkillMarkdown()` |
|
|
1303
1319
|
|
|
1304
1320
|
---
|
|
1305
1321
|
|
|
@@ -1538,19 +1554,19 @@ Every layer in the pipeline defaults to rejection rather than silent degradation
|
|
|
1538
1554
|
|
|
1539
1555
|
### Key Source Files
|
|
1540
1556
|
|
|
1541
|
-
| File | Role
|
|
1542
|
-
| --------------------------------------------------- |
|
|
1543
|
-
| `assistant/src/config/skills.ts` | Skill catalog loading: bundled, managed, workspace, extra directories
|
|
1544
|
-
| `assistant/src/config/bundled-skills/` | Bundled skill directories (browser, gmail, computer-use, weather, etc.)
|
|
1545
|
-
| `assistant/src/skills/tool-manifest.ts` | `TOOLS.json` parser and validator
|
|
1546
|
-
| `assistant/src/skills/active-skill-tools.ts` | `deriveActiveSkills()` — scans history for `<loaded_skill>` markers
|
|
1547
|
-
| `assistant/src/skills/include-graph.ts` | Include graph builder: `indexCatalogById()`, `validateIncludes()`,
|
|
1548
|
-
| `assistant/src/daemon/conversation-skill-tools.ts` | `projectSkillTools()` — per-turn projection, register/unregister lifecycle
|
|
1549
|
-
| `assistant/src/tools/skills/skill-tool-factory.ts` | `createSkillToolsFromManifest()` — manifest entries to Tool objects
|
|
1550
|
-
| `assistant/src/tools/skills/skill-script-runner.ts` | Host runner: dynamic import + `run()` call
|
|
1551
|
-
| `assistant/src/tools/skills/sandbox-runner.ts` | Sandbox runner: isolated subprocess execution
|
|
1552
|
-
| `assistant/src/tools/registry.ts` | `registerSkillTools()` / `unregisterSkillTools()` — global tool registry
|
|
1553
|
-
| `assistant/src/permissions/checker.ts` | Skill-origin default-ask permission policy
|
|
1557
|
+
| File | Role |
|
|
1558
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
1559
|
+
| `assistant/src/config/skills.ts` | Skill catalog loading: bundled, managed, workspace, extra directories |
|
|
1560
|
+
| `assistant/src/config/bundled-skills/` | Bundled skill directories (browser, gmail, computer-use, weather, etc.) |
|
|
1561
|
+
| `assistant/src/skills/tool-manifest.ts` | `TOOLS.json` parser and validator |
|
|
1562
|
+
| `assistant/src/skills/active-skill-tools.ts` | `deriveActiveSkills()` — scans history for `<loaded_skill>` markers |
|
|
1563
|
+
| `assistant/src/skills/include-graph.ts` | Include graph builder: `indexCatalogById()`, `validateIncludes()`, `validateIncludeCycles()` |
|
|
1564
|
+
| `assistant/src/daemon/conversation-skill-tools.ts` | `projectSkillTools()` — per-turn projection, register/unregister lifecycle |
|
|
1565
|
+
| `assistant/src/tools/skills/skill-tool-factory.ts` | `createSkillToolsFromManifest()` — manifest entries to Tool objects |
|
|
1566
|
+
| `assistant/src/tools/skills/skill-script-runner.ts` | Host runner: dynamic import + `run()` call |
|
|
1567
|
+
| `assistant/src/tools/skills/sandbox-runner.ts` | Sandbox runner: isolated subprocess execution |
|
|
1568
|
+
| `assistant/src/tools/registry.ts` | `registerSkillTools()` / `unregisterSkillTools()` — global tool registry |
|
|
1569
|
+
| `assistant/src/permissions/checker.ts` | Skill-origin default-ask permission policy |
|
|
1554
1570
|
|
|
1555
1571
|
---
|
|
1556
1572
|
|
package/Dockerfile
CHANGED
|
@@ -22,6 +22,7 @@ COPY packages/service-contracts ./packages/service-contracts
|
|
|
22
22
|
COPY packages/credential-storage ./packages/credential-storage
|
|
23
23
|
COPY packages/egress-proxy ./packages/egress-proxy
|
|
24
24
|
COPY packages/gateway-client ./packages/gateway-client
|
|
25
|
+
COPY packages/ipc-server-utils ./packages/ipc-server-utils
|
|
25
26
|
COPY packages/skill-host-contracts ./packages/skill-host-contracts
|
|
26
27
|
COPY packages/slack-text ./packages/slack-text
|
|
27
28
|
COPY packages/twilio-client ./packages/twilio-client
|
|
@@ -31,18 +31,70 @@ mock.module("../../src/ipc/gateway-client.js", () => ({
|
|
|
31
31
|
},
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
// Capture logger output so coalescing tests can assert on it. Existing
|
|
35
|
+
// tests don't read the array, so capturing is invisible to them.
|
|
36
|
+
//
|
|
37
|
+
// Bun's `mock.module("../../src/util/logger.js", ...)` does not intercept
|
|
38
|
+
// transitive imports (see comment in stt-hints.test.ts and avatar-e2e.test.ts).
|
|
39
|
+
// Mocking `pino` at the package level works because getLogger uses pino
|
|
40
|
+
// child loggers under the hood — intercepting pino captures everything.
|
|
41
|
+
interface LogCall {
|
|
42
|
+
level: "warn" | "info" | "error" | "debug";
|
|
43
|
+
fields: Record<string, unknown>;
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
const logCalls: LogCall[] = [];
|
|
47
|
+
|
|
48
|
+
function makeLogFn(level: LogCall["level"]) {
|
|
49
|
+
return (
|
|
50
|
+
fieldsOrMsg: Record<string, unknown> | string,
|
|
51
|
+
maybeMsg?: string,
|
|
52
|
+
) => {
|
|
53
|
+
if (typeof fieldsOrMsg === "string") {
|
|
54
|
+
logCalls.push({ level, fields: {}, message: fieldsOrMsg });
|
|
55
|
+
} else {
|
|
56
|
+
logCalls.push({
|
|
57
|
+
level,
|
|
58
|
+
fields: fieldsOrMsg,
|
|
59
|
+
message: maybeMsg ?? "",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const mockChildLogger = {
|
|
66
|
+
debug: () => {},
|
|
67
|
+
info: makeLogFn("info"),
|
|
68
|
+
warn: makeLogFn("warn"),
|
|
69
|
+
error: makeLogFn("error"),
|
|
70
|
+
fatal: () => {},
|
|
71
|
+
trace: () => {},
|
|
72
|
+
silent: () => {},
|
|
73
|
+
// pino loggers are themselves callable as a no-op shorthand; child() returns
|
|
74
|
+
// another logger.
|
|
75
|
+
child(): typeof mockChildLogger {
|
|
76
|
+
return mockChildLogger;
|
|
77
|
+
},
|
|
78
|
+
bindings: () => ({}),
|
|
79
|
+
level: "info",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const mockPinoLogger = Object.assign(() => mockChildLogger, {
|
|
83
|
+
destination: () => ({}),
|
|
84
|
+
multistream: () => ({}),
|
|
85
|
+
stdTimeFunctions: { isoTime: () => "" },
|
|
86
|
+
stdSerializers: {},
|
|
87
|
+
symbols: {},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
mock.module("pino", () => ({ default: mockPinoLogger }));
|
|
91
|
+
mock.module("pino-pretty", () => ({ default: () => ({}) }));
|
|
43
92
|
|
|
44
93
|
import {
|
|
45
94
|
_clearGlobalCacheForTesting,
|
|
95
|
+
_getFailureStateForTesting,
|
|
96
|
+
_resetFailureCoalesceForTesting,
|
|
97
|
+
_setFailureWarnIntervalForTesting,
|
|
46
98
|
getAutoApproveThreshold,
|
|
47
99
|
} from "../../src/permissions/gateway-threshold-reader.js";
|
|
48
100
|
|
|
@@ -51,7 +103,9 @@ import {
|
|
|
51
103
|
function resetMocks(): void {
|
|
52
104
|
ipcCallLog.length = 0;
|
|
53
105
|
ipcHandler = () => undefined;
|
|
106
|
+
logCalls.length = 0;
|
|
54
107
|
_clearGlobalCacheForTesting();
|
|
108
|
+
_resetFailureCoalesceForTesting();
|
|
55
109
|
}
|
|
56
110
|
|
|
57
111
|
afterEach(resetMocks);
|
|
@@ -221,3 +275,176 @@ describe("getAutoApproveThreshold", () => {
|
|
|
221
275
|
expect(ipcCallLog).toEqual(["/v1/permissions/thresholds"]);
|
|
222
276
|
});
|
|
223
277
|
});
|
|
278
|
+
|
|
279
|
+
// ── Failure coalescing ───────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
describe("failure-coalescing log behavior", () => {
|
|
282
|
+
test("first failure WARNs immediately and starts a streak", async () => {
|
|
283
|
+
ipcHandler = () => {
|
|
284
|
+
throw new Error("Connection refused");
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
expect(await getAutoApproveThreshold(undefined, "background")).toBe("none");
|
|
288
|
+
|
|
289
|
+
const warns = logCalls.filter((c) => c.level === "warn");
|
|
290
|
+
expect(warns.length).toBe(1);
|
|
291
|
+
expect(warns[0]?.fields).toMatchObject({
|
|
292
|
+
op: "global_thresholds",
|
|
293
|
+
consecutiveFailures: 1,
|
|
294
|
+
event: "ipc_threshold_failure",
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const state = _getFailureStateForTesting("global_thresholds");
|
|
298
|
+
expect(state).toBeDefined();
|
|
299
|
+
expect(state?.consecutiveFailures).toBe(1);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("subsequent failures within the WARN window do not log but still increment state", async () => {
|
|
303
|
+
// 1-hour window so the test never accidentally crosses it.
|
|
304
|
+
_setFailureWarnIntervalForTesting(60 * 60 * 1000);
|
|
305
|
+
ipcHandler = () => {
|
|
306
|
+
throw new Error("ENOENT");
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
for (let i = 0; i < 100; i++) {
|
|
310
|
+
_clearGlobalCacheForTesting(); // force re-fetch each call
|
|
311
|
+
await getAutoApproveThreshold(undefined, "background");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const warns = logCalls.filter((c) => c.level === "warn");
|
|
315
|
+
// At most one WARN — the very first call. All 99 follow-ups suppressed.
|
|
316
|
+
expect(warns.length).toBe(1);
|
|
317
|
+
|
|
318
|
+
const state = _getFailureStateForTesting("global_thresholds");
|
|
319
|
+
expect(state?.consecutiveFailures).toBe(100);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("a fresh WARN fires once the cadence window elapses", async () => {
|
|
323
|
+
// 5ms window so the test runs fast.
|
|
324
|
+
_setFailureWarnIntervalForTesting(5);
|
|
325
|
+
ipcHandler = () => {
|
|
326
|
+
throw new Error("ENOENT");
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
await getAutoApproveThreshold(undefined, "background");
|
|
330
|
+
expect(logCalls.filter((c) => c.level === "warn").length).toBe(1);
|
|
331
|
+
|
|
332
|
+
// Wait past the window then fail again.
|
|
333
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
334
|
+
_clearGlobalCacheForTesting();
|
|
335
|
+
await getAutoApproveThreshold(undefined, "background");
|
|
336
|
+
|
|
337
|
+
const warns = logCalls.filter((c) => c.level === "warn");
|
|
338
|
+
expect(warns.length).toBe(2);
|
|
339
|
+
// Second WARN includes the streak metadata so dashboards can see how
|
|
340
|
+
// many failures were swallowed in between.
|
|
341
|
+
expect(warns[1]?.fields).toMatchObject({
|
|
342
|
+
op: "global_thresholds",
|
|
343
|
+
consecutiveFailures: 2,
|
|
344
|
+
event: "ipc_threshold_failure",
|
|
345
|
+
});
|
|
346
|
+
expect(warns[1]?.fields.streakDurationMs).toBeDefined();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("recovery emits an INFO with the swallowed-failure count and clears state", async () => {
|
|
350
|
+
_setFailureWarnIntervalForTesting(60 * 60 * 1000);
|
|
351
|
+
let working = false;
|
|
352
|
+
ipcHandler = (method) => {
|
|
353
|
+
if (working && method === "get_global_thresholds") {
|
|
354
|
+
return { interactive: "medium", autonomous: "low" };
|
|
355
|
+
}
|
|
356
|
+
throw new Error("ENOENT");
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Three failures, then it recovers.
|
|
360
|
+
for (let i = 0; i < 3; i++) {
|
|
361
|
+
_clearGlobalCacheForTesting();
|
|
362
|
+
await getAutoApproveThreshold(undefined, "background");
|
|
363
|
+
}
|
|
364
|
+
expect(_getFailureStateForTesting("global_thresholds")?.consecutiveFailures).toBe(
|
|
365
|
+
3,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
working = true;
|
|
369
|
+
_clearGlobalCacheForTesting();
|
|
370
|
+
expect(await getAutoApproveThreshold(undefined, "background")).toBe("low");
|
|
371
|
+
|
|
372
|
+
const infos = logCalls.filter((c) => c.level === "info");
|
|
373
|
+
expect(infos.length).toBe(1);
|
|
374
|
+
expect(infos[0]?.fields).toMatchObject({
|
|
375
|
+
op: "global_thresholds",
|
|
376
|
+
swallowedFailures: 3,
|
|
377
|
+
event: "ipc_threshold_recovered",
|
|
378
|
+
});
|
|
379
|
+
expect(infos[0]?.fields.streakDurationMs).toBeDefined();
|
|
380
|
+
|
|
381
|
+
expect(_getFailureStateForTesting("global_thresholds")).toBeUndefined();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("conversation and global ops have independent failure streaks", async () => {
|
|
385
|
+
_setFailureWarnIntervalForTesting(60 * 60 * 1000);
|
|
386
|
+
// conversation IPC fails (transport — returns undefined), global IPC works.
|
|
387
|
+
ipcHandler = (method) => {
|
|
388
|
+
if (method === "get_conversation_threshold") return undefined;
|
|
389
|
+
if (method === "get_global_thresholds") {
|
|
390
|
+
return { interactive: "medium", autonomous: "low" };
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// First call: conversation transport fails, global succeeds.
|
|
396
|
+
expect(await getAutoApproveThreshold("conv-1", "conversation")).toBe(
|
|
397
|
+
"medium",
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(
|
|
401
|
+
_getFailureStateForTesting("conversation_threshold")?.consecutiveFailures,
|
|
402
|
+
).toBe(1);
|
|
403
|
+
expect(_getFailureStateForTesting("global_thresholds")).toBeUndefined();
|
|
404
|
+
|
|
405
|
+
const warns = logCalls.filter((c) => c.level === "warn");
|
|
406
|
+
expect(warns.length).toBe(1);
|
|
407
|
+
expect(warns[0]?.fields.op).toBe("conversation_threshold");
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("a successful conversation override clears the conversation streak even when the gateway returns null (no override)", async () => {
|
|
411
|
+
_setFailureWarnIntervalForTesting(60 * 60 * 1000);
|
|
412
|
+
|
|
413
|
+
// First two calls: conversation IPC returns undefined (transport failure).
|
|
414
|
+
let working = false;
|
|
415
|
+
ipcHandler = (method) => {
|
|
416
|
+
if (method === "get_conversation_threshold") {
|
|
417
|
+
return working ? null : undefined;
|
|
418
|
+
}
|
|
419
|
+
if (method === "get_global_thresholds") {
|
|
420
|
+
return { interactive: "low", autonomous: "none" };
|
|
421
|
+
}
|
|
422
|
+
return undefined;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// Force two transport failures.
|
|
426
|
+
await getAutoApproveThreshold("conv-2", "conversation");
|
|
427
|
+
await new Promise((r) => setTimeout(r, 6)); // bypass the 5s convo cache
|
|
428
|
+
_clearGlobalCacheForTesting();
|
|
429
|
+
// Convo cache is keyed on conversationId — change the id to bypass.
|
|
430
|
+
await getAutoApproveThreshold("conv-3", "conversation");
|
|
431
|
+
expect(
|
|
432
|
+
_getFailureStateForTesting("conversation_threshold")?.consecutiveFailures,
|
|
433
|
+
).toBe(2);
|
|
434
|
+
|
|
435
|
+
// Now the IPC starts working — even a null "no override" response is a
|
|
436
|
+
// successful round-trip and must clear the streak.
|
|
437
|
+
working = true;
|
|
438
|
+
_clearGlobalCacheForTesting();
|
|
439
|
+
await getAutoApproveThreshold("conv-4", "conversation");
|
|
440
|
+
|
|
441
|
+
const infos = logCalls.filter((c) => c.level === "info");
|
|
442
|
+
expect(infos.length).toBe(1);
|
|
443
|
+
expect(infos[0]?.fields).toMatchObject({
|
|
444
|
+
op: "conversation_threshold",
|
|
445
|
+
swallowedFailures: 2,
|
|
446
|
+
event: "ipc_threshold_recovered",
|
|
447
|
+
});
|
|
448
|
+
expect(_getFailureStateForTesting("conversation_threshold")).toBeUndefined();
|
|
449
|
+
});
|
|
450
|
+
});
|
package/bun.lock
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@vellumai/credential-storage": "file:../packages/credential-storage",
|
|
19
19
|
"@vellumai/egress-proxy": "file:../packages/egress-proxy",
|
|
20
20
|
"@vellumai/gateway-client": "file:../packages/gateway-client",
|
|
21
|
+
"@vellumai/ipc-server-utils": "file:../packages/ipc-server-utils",
|
|
21
22
|
"@vellumai/service-contracts": "file:../packages/service-contracts",
|
|
22
23
|
"@vellumai/skill-host-contracts": "file:../packages/skill-host-contracts",
|
|
23
24
|
"@vellumai/slack-text": "file:../packages/slack-text",
|
|
@@ -421,6 +422,8 @@
|
|
|
421
422
|
|
|
422
423
|
"@vellumai/gateway-client": ["@vellumai/gateway-client@file:../packages/gateway-client", { "dependencies": { "@vellumai/service-contracts": "file:../service-contracts" }, "devDependencies": { "@types/bun": "1.3.10", "typescript": "5.9.3" } }],
|
|
423
424
|
|
|
425
|
+
"@vellumai/ipc-server-utils": ["@vellumai/ipc-server-utils@file:../packages/ipc-server-utils", { "devDependencies": { "@types/bun": "1.3.10", "typescript": "5.9.3" } }],
|
|
426
|
+
|
|
424
427
|
"@vellumai/service-contracts": ["@vellumai/service-contracts@file:../packages/service-contracts", { "dependencies": { "zod": "4.3.6" }, "devDependencies": { "@types/bun": "1.2.4", "typescript": "5.7.3" } }],
|
|
425
428
|
|
|
426
429
|
"@vellumai/skill-host-contracts": ["@vellumai/skill-host-contracts@file:../packages/skill-host-contracts", { "devDependencies": { "@types/bun": "1.3.10", "typescript": "5.9.3" } }],
|
|
@@ -472,7 +472,7 @@ The Anthropic provider places `cache_control: { type: 'ephemeral' }` on the **la
|
|
|
472
472
|
|
|
473
473
|
The session injects a unified `<turn_context>` block into every user message, giving the model awareness of the current timestamp (with timezone), interface, channel, and actor identity. This replaces the former separate `<temporal_context>`, `<inbound_actor_context>`, and per-channel turn context blocks. The unified block persists in conversation history so the assistant retains temporal and actor grounding across turns. Legacy blocks from pre-change history are stripped for backward compatibility.
|
|
474
474
|
|
|
475
|
-
The `current_time:` field format is: `2026-04-02 (Wednesday) 14:30:00 -05:00 (America/Chicago)` — date, weekday name, local time, UTC offset, and IANA timezone name.
|
|
475
|
+
The `current_time:` field format is: `2026-04-02 (Wednesday) 14:30:00 -05:00 (America/Chicago)` — date, weekday name, local time, UTC offset, and IANA timezone name. The timestamp is grounded in the user's effective timezone, not UTC, so a message sent at 10pm local time is represented as 10pm for date/time reasoning.
|
|
476
476
|
|
|
477
477
|
### Per-turn flow
|
|
478
478
|
|
|
@@ -492,7 +492,10 @@ graph TB
|
|
|
492
492
|
|
|
493
493
|
- **Fresh each turn**: `buildUnifiedTurnContextBlock()` is called at the start of every agent loop invocation, ensuring the model always sees the current timestamp even in long-running conversations.
|
|
494
494
|
- **Clock source invariant**: Absolute time (`now`) always comes from the assistant host clock (`Date.now()`), never from channel/client clocks.
|
|
495
|
-
- **Timezone precedence**:
|
|
495
|
+
- **Timezone precedence**: Turn context resolves the effective timezone in this order: explicit runtime override for tests/legacy callers, manual `ui.userTimezone`, current turn `clientTimezone`, persisted `ui.detectedTimezone`, then assistant host timezone. The host clock still supplies the absolute instant; this cascade only selects the local timezone used to render `current_time`.
|
|
496
|
+
- **Manual override semantics**: `ui.userTimezone` is a historical config path, but it is runtime-affecting, not purely presentational. When set, it is authoritative for `current_time` across all clients until the user clears or changes it.
|
|
497
|
+
- **Device timezone semantics**: `clientTimezone` is the timezone reported with the active message for this turn. `ui.detectedTimezone` is the last device-detected timezone persisted by a client and is only used when there is no manual override and the current message does not carry a client timezone.
|
|
498
|
+
- **Timezone mismatch guidance**: When `ui.userTimezone` differs from the current device timezone (`clientTimezone`, or `ui.detectedTimezone` when no current client value exists), `<turn_context>` also includes `configured_user_timezone`, `client_device_timezone`, and `timezone_update_available`. The last line tells the assistant that, after explicit user confirmation, it can persist the device timezone with `assistant config set ui.userTimezone "<IANA zone>"`. This gives the assistant a natural-language path to fix stale manual overrides without adding a dedicated tool.
|
|
496
499
|
- **Timezone-aware**: Uses `Intl.DateTimeFormat` APIs for DST-safe date arithmetic and timezone validation/canonicalization.
|
|
497
500
|
- **Persists in history**: The `<turn_context>` block persists in conversation history. Legacy `<temporal_context>`, `<inbound_actor_context>`, and separate channel context blocks from pre-change history are stripped for backward compatibility.
|
|
498
501
|
- **Retry paths**: Turn context is included in all `applyRuntimeInjections` call sites (main path, compact retry, media-trim retry).
|
package/knip.json
CHANGED
|
@@ -62,13 +62,22 @@ const CONNECT_TIMEOUT_MS = 3_000;
|
|
|
62
62
|
* Designed for CLI and daemon startup where we need a single RPC call
|
|
63
63
|
* without leaving open handles. Returns `undefined` on any failure
|
|
64
64
|
* (socket not found, timeout, parse error) so callers can fall back.
|
|
65
|
+
*
|
|
66
|
+
* @param timeoutMs - Optional override for both the connect and call
|
|
67
|
+
* timeouts. When omitted, defaults to the module constants
|
|
68
|
+
* (CONNECT_TIMEOUT_MS / DEFAULT_CALL_TIMEOUT_MS). Pass a small value
|
|
69
|
+
* (e.g. 200) for opportunistic CLI checks where a slow/absent gateway
|
|
70
|
+
* should fail fast rather than block startup.
|
|
65
71
|
*/
|
|
66
72
|
export async function ipcCall(
|
|
67
73
|
socketPath: string,
|
|
68
74
|
method: string,
|
|
69
75
|
params?: Record<string, unknown>,
|
|
70
76
|
log: Logger = noopLogger,
|
|
77
|
+
timeoutMs?: number,
|
|
71
78
|
): Promise<unknown> {
|
|
79
|
+
const connectTimeoutMs = timeoutMs ?? CONNECT_TIMEOUT_MS;
|
|
80
|
+
const callTimeoutMs = timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
|
|
72
81
|
return new Promise<unknown>((resolve) => {
|
|
73
82
|
let settled = false;
|
|
74
83
|
let callTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -84,11 +93,11 @@ export async function ipcCall(
|
|
|
84
93
|
|
|
85
94
|
const connectTimer = setTimeout(() => {
|
|
86
95
|
log.warn(
|
|
87
|
-
{ method, socketPath, timeoutMs:
|
|
96
|
+
{ method, socketPath, timeoutMs: connectTimeoutMs },
|
|
88
97
|
"IPC connect timed out",
|
|
89
98
|
);
|
|
90
99
|
finish(undefined);
|
|
91
|
-
},
|
|
100
|
+
}, connectTimeoutMs);
|
|
92
101
|
|
|
93
102
|
const socket: Socket = connect(socketPath);
|
|
94
103
|
socket.unref();
|
|
@@ -103,11 +112,11 @@ export async function ipcCall(
|
|
|
103
112
|
|
|
104
113
|
callTimer = setTimeout(() => {
|
|
105
114
|
log.warn(
|
|
106
|
-
{ method, socketPath, timeoutMs:
|
|
115
|
+
{ method, socketPath, timeoutMs: callTimeoutMs },
|
|
107
116
|
"IPC call timed out waiting for response",
|
|
108
117
|
);
|
|
109
118
|
finish(undefined);
|
|
110
|
-
},
|
|
119
|
+
}, callTimeoutMs);
|
|
111
120
|
|
|
112
121
|
socket.on("data", (chunk) => {
|
|
113
122
|
buffer += chunk.toString();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@vellumai/ipc-server-utils",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/bun": "1.3.10",
|
|
9
|
+
"typescript": "5.9.3",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
"packages": {
|
|
14
|
+
"@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
|
|
15
|
+
|
|
16
|
+
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
|
17
|
+
|
|
18
|
+
"bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
|
|
19
|
+
|
|
20
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
21
|
+
|
|
22
|
+
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vellumai/ipc-server-utils",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"typecheck": "bunx tsc --noEmit",
|
|
12
|
+
"test": "bun test src/"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "1.3.10",
|
|
16
|
+
"typescript": "5.9.3"
|
|
17
|
+
}
|
|
18
|
+
}
|