@vellumai/assistant 0.7.2 → 0.7.3
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 +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- 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 +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- 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 +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -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__/config-loader-backfill.test.ts +379 -0
- 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 +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -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-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- 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 +3 -4
- 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 +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- 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__/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 +15 -4
- 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__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- 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-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- 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 +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- 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/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 +27 -3
- package/src/config/loader.ts +127 -8
- 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 +75 -11
- 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-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 +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- 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 +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- 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 +0 -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 +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- 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 +14 -4
- 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 +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -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 +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- 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 +52 -22
- 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 +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- 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 +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -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-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -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 +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- 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 +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -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/plugins/defaults/injectors.ts +35 -2
- 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 +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -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/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 +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- 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/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -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/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/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- 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/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/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 +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- 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 +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -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
|
@@ -36,6 +36,23 @@ const NETWORK_PATTERNS = [
|
|
|
36
36
|
// Rate limit patterns (HTTP 429 or explicit rate limit messages)
|
|
37
37
|
const RATE_LIMIT_PATTERNS = [/429/, /rate.?limit/i, /too many requests/i];
|
|
38
38
|
|
|
39
|
+
// Managed usage-limit responses are generated by Vellum, even though they can
|
|
40
|
+
// travel through provider SDKs and get wrapped as ProviderError.
|
|
41
|
+
const MANAGED_USAGE_LIMIT_PATTERNS = [
|
|
42
|
+
/"code"\s*:\s*"daily_quota_exceeded"/i,
|
|
43
|
+
/"code"\s*:\s*"rate_limit_exceeded"/i,
|
|
44
|
+
/system credential proxy rate limit/i,
|
|
45
|
+
/you've reached your usage limit for today/i,
|
|
46
|
+
/current plan allows/i,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const PROVIDER_BILLING_PATTERNS = [
|
|
50
|
+
/credit balance is too low/i,
|
|
51
|
+
/insufficient.*credits?/i,
|
|
52
|
+
/requires more credits/i,
|
|
53
|
+
/can only afford/i,
|
|
54
|
+
];
|
|
55
|
+
|
|
39
56
|
// Overloaded patterns — provider is capacity-constrained (distinct from rate limiting)
|
|
40
57
|
const OVERLOADED_PATTERNS = [/overloaded/i];
|
|
41
58
|
|
|
@@ -259,15 +276,21 @@ function classifyCore(
|
|
|
259
276
|
};
|
|
260
277
|
}
|
|
261
278
|
if (error.statusCode === 402) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
retryable: false,
|
|
267
|
-
errorCategory: "credits_exhausted",
|
|
268
|
-
};
|
|
279
|
+
if (isManagedBalanceError(error)) {
|
|
280
|
+
return managedBalanceClassification();
|
|
281
|
+
}
|
|
282
|
+
return providerBillingClassification();
|
|
269
283
|
}
|
|
270
284
|
if (error.statusCode === 429) {
|
|
285
|
+
if (isManagedUsageLimitError(error, message)) {
|
|
286
|
+
return {
|
|
287
|
+
code: "MANAGED_USAGE_LIMIT",
|
|
288
|
+
userMessage:
|
|
289
|
+
"Vellum managed inference is rate limited. This is a Vellum-side usage limit, not an AI provider outage.",
|
|
290
|
+
retryable: true,
|
|
291
|
+
errorCategory: "managed_usage_limit",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
271
294
|
return {
|
|
272
295
|
code: "PROVIDER_RATE_LIMIT",
|
|
273
296
|
userMessage:
|
|
@@ -331,13 +354,8 @@ function classifyCore(
|
|
|
331
354
|
errorCategory: "tool_ordering",
|
|
332
355
|
};
|
|
333
356
|
}
|
|
334
|
-
if (
|
|
335
|
-
return
|
|
336
|
-
code: "PROVIDER_BILLING",
|
|
337
|
-
userMessage: "Your API key has insufficient credits.",
|
|
338
|
-
retryable: false,
|
|
339
|
-
errorCategory: "provider_billing",
|
|
340
|
-
};
|
|
357
|
+
if (isProviderBillingError(message)) {
|
|
358
|
+
return providerBillingClassification();
|
|
341
359
|
}
|
|
342
360
|
if (
|
|
343
361
|
/invalid.*api.?key|invalid.*x-api-key|authentication.?error|invalid.authentication/i.test(
|
|
@@ -362,7 +380,7 @@ function classifyCore(
|
|
|
362
380
|
}
|
|
363
381
|
|
|
364
382
|
// Regex fallback for non-ProviderError or ProviderError without statusCode
|
|
365
|
-
return classifyByMessage(message);
|
|
383
|
+
return classifyByMessage(error, message);
|
|
366
384
|
}
|
|
367
385
|
|
|
368
386
|
/** Check whether an error message indicates a context-too-large failure. */
|
|
@@ -394,7 +412,52 @@ function isStreamingError(message: string): boolean {
|
|
|
394
412
|
return STREAMING_ERROR_PATTERNS.some((p) => p.test(message));
|
|
395
413
|
}
|
|
396
414
|
|
|
415
|
+
function isManagedUsageLimitError(error: unknown, message: string): boolean {
|
|
416
|
+
if (
|
|
417
|
+
error instanceof ProviderError &&
|
|
418
|
+
getProviderRoutingSource(error.provider) === "managed-proxy"
|
|
419
|
+
) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
return MANAGED_USAGE_LIMIT_PATTERNS.some((p) => p.test(message));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function isManagedBalanceError(error: ProviderError): boolean {
|
|
426
|
+
return getProviderRoutingSource(error.provider) === "managed-proxy";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function isProviderBillingError(message: string): boolean {
|
|
430
|
+
return PROVIDER_BILLING_PATTERNS.some((p) => p.test(message));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function managedBalanceClassification(): Omit<
|
|
434
|
+
ClassifiedConversationError,
|
|
435
|
+
"debugDetails"
|
|
436
|
+
> {
|
|
437
|
+
return {
|
|
438
|
+
code: "PROVIDER_BILLING",
|
|
439
|
+
userMessage:
|
|
440
|
+
"You've run out of credits. Add funds to continue using the assistant.",
|
|
441
|
+
retryable: false,
|
|
442
|
+
errorCategory: "credits_exhausted",
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function providerBillingClassification(): Omit<
|
|
447
|
+
ClassifiedConversationError,
|
|
448
|
+
"debugDetails"
|
|
449
|
+
> {
|
|
450
|
+
return {
|
|
451
|
+
code: "PROVIDER_BILLING",
|
|
452
|
+
userMessage:
|
|
453
|
+
"Your API provider account or key needs credits. Add funds with the provider or update the key in Settings.",
|
|
454
|
+
retryable: false,
|
|
455
|
+
errorCategory: "provider_billing",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
397
459
|
function classifyByMessage(
|
|
460
|
+
error: unknown,
|
|
398
461
|
message: string,
|
|
399
462
|
): Omit<ClassifiedConversationError, "debugDetails"> {
|
|
400
463
|
// Check context-too-large before other patterns
|
|
@@ -411,6 +474,15 @@ function classifyByMessage(
|
|
|
411
474
|
// Check rate limit first (before network, since 429 could match both)
|
|
412
475
|
for (const pattern of RATE_LIMIT_PATTERNS) {
|
|
413
476
|
if (pattern.test(message)) {
|
|
477
|
+
if (isManagedUsageLimitError(error, message)) {
|
|
478
|
+
return {
|
|
479
|
+
code: "MANAGED_USAGE_LIMIT",
|
|
480
|
+
userMessage:
|
|
481
|
+
"Vellum managed inference is rate limited. This is a Vellum-side usage limit, not an AI provider outage.",
|
|
482
|
+
retryable: true,
|
|
483
|
+
errorCategory: "managed_usage_limit",
|
|
484
|
+
};
|
|
485
|
+
}
|
|
414
486
|
return {
|
|
415
487
|
code: "PROVIDER_RATE_LIMIT",
|
|
416
488
|
userMessage:
|
|
@@ -153,8 +153,6 @@ export interface DisposeContext extends AbortContext {
|
|
|
153
153
|
trustContext?: { trustClass: TrustClass };
|
|
154
154
|
/** Active memory node IDs snapshotted from the conversation's InContextTracker before disposal. */
|
|
155
155
|
activeContextNodeIds?: string[];
|
|
156
|
-
/** Memory scope for extraction — defaults to "default" if omitted. */
|
|
157
|
-
memoryScopeId?: string;
|
|
158
156
|
abort(): void;
|
|
159
157
|
}
|
|
160
158
|
|
|
@@ -378,7 +376,7 @@ export function disposeConversation(ctx: DisposeContext): void {
|
|
|
378
376
|
try {
|
|
379
377
|
enqueueMemoryJob("graph_extract", {
|
|
380
378
|
conversationId: ctx.conversationId,
|
|
381
|
-
scopeId:
|
|
379
|
+
scopeId: "default",
|
|
382
380
|
...(ctx.activeContextNodeIds?.length
|
|
383
381
|
? { activeContextNodeIds: ctx.activeContextNodeIds }
|
|
384
382
|
: {}),
|
|
@@ -182,6 +182,8 @@ export interface ProcessConversationContext {
|
|
|
182
182
|
forceCompact(): Promise<ContextWindowResult>;
|
|
183
183
|
/** Set transport-derived hints for the conversation. */
|
|
184
184
|
setTransportHints(hints: string[] | undefined): void;
|
|
185
|
+
/** IANA timezone reported by the active client for the current turn. */
|
|
186
|
+
clientTimezone?: string;
|
|
185
187
|
/**
|
|
186
188
|
* Apply client-reported host env (home dir, username) from transport
|
|
187
189
|
* metadata, gating on `supportsHostProxy` so non-host-proxy interfaces
|
|
@@ -189,6 +191,10 @@ export interface ProcessConversationContext {
|
|
|
189
191
|
* `DaemonServer.applyTransportMetadata` and the queue-drain path below.
|
|
190
192
|
*/
|
|
191
193
|
applyHostEnvFromTransport(transport: ConversationTransportMetadata): void;
|
|
194
|
+
/** Apply the per-turn client timezone reported by transport metadata. */
|
|
195
|
+
applyClientTimezoneFromTransport(
|
|
196
|
+
transport: ConversationTransportMetadata,
|
|
197
|
+
): void;
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
function resolveQueuedTurnContext(
|
|
@@ -423,6 +429,7 @@ async function drainSingleMessage(
|
|
|
423
429
|
// setter used by DaemonServer.applyTransportMetadata so create/reuse
|
|
424
430
|
// and queue-drain stay in sync without duplicating the gate logic.
|
|
425
431
|
conversation.applyHostEnvFromTransport(next.transport);
|
|
432
|
+
conversation.applyClientTimezoneFromTransport(next.transport);
|
|
426
433
|
}
|
|
427
434
|
|
|
428
435
|
// Re-preactivate host-proxy skills for interactive desktop turns. The
|
|
@@ -865,6 +872,7 @@ async function drainBatch(
|
|
|
865
872
|
if (head.transport) {
|
|
866
873
|
conversation.setTransportHints(buildTransportHints(head.transport));
|
|
867
874
|
conversation.applyHostEnvFromTransport(head.transport);
|
|
875
|
+
conversation.applyClientTimezoneFromTransport(head.transport);
|
|
868
876
|
}
|
|
869
877
|
|
|
870
878
|
// Re-preactivate host-proxy skills for interactive desktop turns.
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../messaging/providers/slack/render-transcript.js";
|
|
34
34
|
import { getInjectors } from "../plugins/registry.js";
|
|
35
35
|
import type {
|
|
36
|
+
DiskPressureInjectionContext,
|
|
36
37
|
InjectionBlock,
|
|
37
38
|
InjectionPlacement,
|
|
38
39
|
TurnContext,
|
|
@@ -788,6 +789,9 @@ export interface UnifiedTurnContextOptions {
|
|
|
788
789
|
interfaceName?: string;
|
|
789
790
|
channelName?: string;
|
|
790
791
|
actorContext?: InboundActorContext | null;
|
|
792
|
+
configuredUserTimezone?: string | null;
|
|
793
|
+
clientTimezone?: string | null;
|
|
794
|
+
detectedTimezone?: string | null;
|
|
791
795
|
/**
|
|
792
796
|
* Human-readable duration since the previous user message (e.g. "14h ago",
|
|
793
797
|
* "yesterday", "3d ago"). Only populated when the gap exceeds 12 hours so
|
|
@@ -830,6 +834,25 @@ export function buildUnifiedTurnContextBlock(
|
|
|
830
834
|
|
|
831
835
|
const lines: string[] = ["<turn_context>"];
|
|
832
836
|
lines.push(`current_time: ${options.timestamp}`);
|
|
837
|
+
const configuredUserTimezone = options.configuredUserTimezone ?? null;
|
|
838
|
+
const clientDeviceTimezone =
|
|
839
|
+
options.clientTimezone ?? options.detectedTimezone ?? null;
|
|
840
|
+
const hasTimezoneMismatch =
|
|
841
|
+
configuredUserTimezone !== null &&
|
|
842
|
+
clientDeviceTimezone !== null &&
|
|
843
|
+
configuredUserTimezone !== clientDeviceTimezone;
|
|
844
|
+
if (hasTimezoneMismatch) {
|
|
845
|
+
const sanitizedConfiguredTimezone = sanitizeInlineContextValue(
|
|
846
|
+
configuredUserTimezone,
|
|
847
|
+
);
|
|
848
|
+
const sanitizedClientDeviceTimezone =
|
|
849
|
+
sanitizeInlineContextValue(clientDeviceTimezone);
|
|
850
|
+
lines.push(`configured_user_timezone: ${sanitizedConfiguredTimezone}`);
|
|
851
|
+
lines.push(`client_device_timezone: ${sanitizedClientDeviceTimezone}`);
|
|
852
|
+
lines.push(
|
|
853
|
+
`timezone_update_available: after explicit user confirmation, persist client_device_timezone with \`assistant config set ui.userTimezone "${sanitizedClientDeviceTimezone}"\``,
|
|
854
|
+
);
|
|
855
|
+
}
|
|
833
856
|
if (options.timeSinceLastMessage) {
|
|
834
857
|
lines.push(`time_since_last_message: ${options.timeSinceLastMessage}`);
|
|
835
858
|
}
|
|
@@ -1609,6 +1632,7 @@ export function loadSlackActiveThreadFocusBlock(
|
|
|
1609
1632
|
const RUNTIME_INJECTION_PREFIXES = [
|
|
1610
1633
|
"<channel_capabilities>",
|
|
1611
1634
|
"<channel_command_context>",
|
|
1635
|
+
"<disk_pressure_warning>",
|
|
1612
1636
|
"<channel_turn_context>", // backward-compat: strip legacy separate channel blocks
|
|
1613
1637
|
"<guardian_context>",
|
|
1614
1638
|
"<inbound_actor_context>", // backward-compat: strip legacy separate actor blocks
|
|
@@ -1872,6 +1896,7 @@ function applyInjectionBlock(
|
|
|
1872
1896
|
* plugin-overridable default injectors.
|
|
1873
1897
|
*/
|
|
1874
1898
|
export interface RuntimeInjectionOptions {
|
|
1899
|
+
diskPressureContext?: DiskPressureInjectionContext | null;
|
|
1875
1900
|
/**
|
|
1876
1901
|
* Active dashboard-surface context (read from `<active_workspace>`). Kept
|
|
1877
1902
|
* on the options bag rather than an injector because it is a
|
|
@@ -1990,6 +2015,7 @@ function buildTurnInjectionInputs(
|
|
|
1990
2015
|
): TurnInjectionInputs {
|
|
1991
2016
|
return {
|
|
1992
2017
|
mode: options.mode,
|
|
2018
|
+
diskPressureContext: options.diskPressureContext,
|
|
1993
2019
|
workspaceTopLevelContext: options.workspaceTopLevelContext,
|
|
1994
2020
|
unifiedTurnContext: options.unifiedTurnContext,
|
|
1995
2021
|
pkbContext: options.pkbContext,
|
|
@@ -22,7 +22,7 @@ import { RateLimitProvider } from "../providers/ratelimit.js";
|
|
|
22
22
|
import { getProvider } from "../providers/registry.js";
|
|
23
23
|
import { getSubagentManager } from "../subagent/index.js";
|
|
24
24
|
import { getSandboxWorkingDir } from "../util/platform.js";
|
|
25
|
-
import { Conversation
|
|
25
|
+
import { Conversation } from "./conversation.js";
|
|
26
26
|
import type { ConversationEvictor } from "./conversation-evictor.js";
|
|
27
27
|
import type { ConversationCreateOptions } from "./handlers/shared.js";
|
|
28
28
|
import { buildTransportHints } from "./transport-hints.js";
|
|
@@ -180,6 +180,7 @@ function applyTransportMetadata(
|
|
|
180
180
|
if (!transport) return;
|
|
181
181
|
conversation.setTransportHints(buildTransportHints(transport));
|
|
182
182
|
conversation.applyHostEnvFromTransport(transport);
|
|
183
|
+
conversation.applyClientTimezoneFromTransport(transport);
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
/**
|
|
@@ -253,7 +254,6 @@ export async function getOrCreateConversation(
|
|
|
253
254
|
maxTokens,
|
|
254
255
|
sendToClient,
|
|
255
256
|
workingDir,
|
|
256
|
-
DEFAULT_MEMORY_POLICY,
|
|
257
257
|
sharedCesClient,
|
|
258
258
|
storedOptions?.speed,
|
|
259
259
|
undefined,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
assistantEventHub,
|
|
19
19
|
broadcastMessage,
|
|
20
20
|
} from "../runtime/assistant-event-hub.js";
|
|
21
|
+
import { enforceSameActorOrErrorResult } from "../runtime/auth/same-actor.js";
|
|
21
22
|
import type {
|
|
22
23
|
InteractiveUiRequest,
|
|
23
24
|
InteractiveUiResult,
|
|
@@ -53,6 +54,148 @@ const log = getLogger("conversation-surfaces");
|
|
|
53
54
|
|
|
54
55
|
const MAX_UNDO_DEPTH = 10;
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Debounce window for persisting `ui_surface_update` data back to the
|
|
59
|
+
* message row. Surfaces typically receive bursts of updates (e.g. a
|
|
60
|
+
* Workspace Health Check ticking off items rapidly) — collapsing them
|
|
61
|
+
* to a single DB write avoids hammering SQLite while still bounding the
|
|
62
|
+
* "lost work on crash" window to ~half a second.
|
|
63
|
+
*/
|
|
64
|
+
const SURFACE_PERSIST_DEBOUNCE_MS = 500;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* In-flight debounced persist timers keyed by `surfaceId`. Surface IDs
|
|
68
|
+
* are UUIDs and globally unique, so a module-level map is safe across
|
|
69
|
+
* conversations. Each entry holds the latest data snapshot — newer
|
|
70
|
+
* updates clobber older ones since the persisted row carries the full
|
|
71
|
+
* merged state, not a delta.
|
|
72
|
+
*/
|
|
73
|
+
const pendingSurfacePersists = new Map<
|
|
74
|
+
string,
|
|
75
|
+
{
|
|
76
|
+
timer: ReturnType<typeof setTimeout>;
|
|
77
|
+
conversationId: string;
|
|
78
|
+
data: SurfaceData;
|
|
79
|
+
}
|
|
80
|
+
>();
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Persist the latest `data` for a `ui_surface` content block by
|
|
84
|
+
* scanning the conversation's messages for one containing the given
|
|
85
|
+
* `surfaceId` and patching its `data` field. Mirrors the scan-and-patch
|
|
86
|
+
* pattern in `markSurfaceCompleted`.
|
|
87
|
+
*
|
|
88
|
+
* Safe to call before the assistant message has been persisted (mid-stream):
|
|
89
|
+
* the scan simply finds nothing and bails. The next update after
|
|
90
|
+
* `handleMessageComplete` runs will pick up the now-persisted row.
|
|
91
|
+
*/
|
|
92
|
+
function persistSurfaceData(
|
|
93
|
+
conversationId: string,
|
|
94
|
+
surfaceId: string,
|
|
95
|
+
data: SurfaceData,
|
|
96
|
+
): void {
|
|
97
|
+
try {
|
|
98
|
+
const rows = getMessages(conversationId);
|
|
99
|
+
for (let r = rows.length - 1; r >= 0; r--) {
|
|
100
|
+
let parsed: unknown[];
|
|
101
|
+
try {
|
|
102
|
+
const result = JSON.parse(rows[r].content);
|
|
103
|
+
if (!Array.isArray(result)) continue;
|
|
104
|
+
parsed = result;
|
|
105
|
+
} catch {
|
|
106
|
+
// Plain-text content rows — skip and keep scanning.
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let found = false;
|
|
110
|
+
for (const pb of parsed) {
|
|
111
|
+
const rb = pb as Record<string, unknown>;
|
|
112
|
+
if (rb.type === "ui_surface" && rb.surfaceId === surfaceId) {
|
|
113
|
+
rb.data = data;
|
|
114
|
+
found = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (found) {
|
|
119
|
+
updateMessageContent(rows[r].id, JSON.stringify(parsed));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
log.debug(
|
|
125
|
+
{ err, surfaceId, conversationId },
|
|
126
|
+
"Failed to persist surface data update",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Schedule a debounced write of the merged surface data back to the
|
|
133
|
+
* persisted message row. Repeated calls within the debounce window
|
|
134
|
+
* collapse to a single write carrying the latest data.
|
|
135
|
+
*/
|
|
136
|
+
export function scheduleSurfaceDataPersist(
|
|
137
|
+
conversationId: string,
|
|
138
|
+
surfaceId: string,
|
|
139
|
+
data: SurfaceData,
|
|
140
|
+
): void {
|
|
141
|
+
const existing = pendingSurfacePersists.get(surfaceId);
|
|
142
|
+
if (existing) {
|
|
143
|
+
clearTimeout(existing.timer);
|
|
144
|
+
}
|
|
145
|
+
const timer = setTimeout(() => {
|
|
146
|
+
pendingSurfacePersists.delete(surfaceId);
|
|
147
|
+
persistSurfaceData(conversationId, surfaceId, data);
|
|
148
|
+
}, SURFACE_PERSIST_DEBOUNCE_MS);
|
|
149
|
+
pendingSurfacePersists.set(surfaceId, { timer, conversationId, data });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Force-flush any pending debounced persist for `surfaceId`. Called on
|
|
154
|
+
* surface completion so the final state is durable before the surface
|
|
155
|
+
* record transitions to `completed`.
|
|
156
|
+
*/
|
|
157
|
+
export function flushSurfaceDataPersist(surfaceId: string): void {
|
|
158
|
+
const pending = pendingSurfacePersists.get(surfaceId);
|
|
159
|
+
if (!pending) return;
|
|
160
|
+
clearTimeout(pending.timer);
|
|
161
|
+
pendingSurfacePersists.delete(surfaceId);
|
|
162
|
+
persistSurfaceData(pending.conversationId, surfaceId, pending.data);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Cancel all pending debounced persists. Called on conversation
|
|
167
|
+
* teardown to avoid timers firing against torn-down state.
|
|
168
|
+
*
|
|
169
|
+
* Use `flushPendingSurfaceDataPersists` instead on a clean shutdown
|
|
170
|
+
* path where the latest in-flight surface state should still be
|
|
171
|
+
* written before teardown.
|
|
172
|
+
*/
|
|
173
|
+
export function cancelPendingSurfaceDataPersists(
|
|
174
|
+
conversationId?: string,
|
|
175
|
+
): void {
|
|
176
|
+
for (const [surfaceId, pending] of pendingSurfacePersists) {
|
|
177
|
+
if (conversationId && pending.conversationId !== conversationId) continue;
|
|
178
|
+
clearTimeout(pending.timer);
|
|
179
|
+
pendingSurfacePersists.delete(surfaceId);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Synchronously flush all pending debounced persists, optionally scoped
|
|
185
|
+
* to a single conversation. Called on clean conversation teardown so an
|
|
186
|
+
* update that arrived inside the 500ms debounce window still lands in
|
|
187
|
+
* the DB before the conversation goes away. Each entry is removed from
|
|
188
|
+
* the pending map after its write fires.
|
|
189
|
+
*/
|
|
190
|
+
export function flushPendingSurfaceDataPersists(conversationId?: string): void {
|
|
191
|
+
for (const [surfaceId, pending] of pendingSurfacePersists) {
|
|
192
|
+
if (conversationId && pending.conversationId !== conversationId) continue;
|
|
193
|
+
clearTimeout(pending.timer);
|
|
194
|
+
pendingSurfacePersists.delete(surfaceId);
|
|
195
|
+
persistSurfaceData(pending.conversationId, surfaceId, pending.data);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
56
199
|
/**
|
|
57
200
|
* Mark a `ui_surface` content block as completed in the database so that
|
|
58
201
|
* history reconstruction preserves the completion state. Also updates
|
|
@@ -63,6 +206,10 @@ export function markSurfaceCompleted(
|
|
|
63
206
|
surfaceId: string,
|
|
64
207
|
summary: string,
|
|
65
208
|
): void {
|
|
209
|
+
// Force-flush any pending debounced data persist so the completion
|
|
210
|
+
// patch lands on top of the latest data instead of racing with it.
|
|
211
|
+
flushSurfaceDataPersist(surfaceId);
|
|
212
|
+
|
|
66
213
|
// Update in-memory messages when available so subsequent reads within
|
|
67
214
|
// this session see the change without waiting for DB.
|
|
68
215
|
if (ctx.messages) {
|
|
@@ -1783,15 +1930,19 @@ export async function surfaceProxyResolver(
|
|
|
1783
1930
|
// Record the action and proxy to the connected desktop client
|
|
1784
1931
|
const reasoning =
|
|
1785
1932
|
typeof input.reasoning === "string" ? input.reasoning : undefined;
|
|
1786
|
-
|
|
1787
|
-
typeof input.target_client_id === "string" &&
|
|
1933
|
+
let targetClientId: string | undefined =
|
|
1934
|
+
typeof input.target_client_id === "string" &&
|
|
1935
|
+
input.target_client_id !== ""
|
|
1788
1936
|
? input.target_client_id
|
|
1789
1937
|
: undefined;
|
|
1790
1938
|
|
|
1791
|
-
// Validate targetClientId
|
|
1792
|
-
//
|
|
1793
|
-
//
|
|
1794
|
-
//
|
|
1939
|
+
// Validate targetClientId existence, capability, and same-user binding
|
|
1940
|
+
// before recordAction so an invalid or cross-user ID does not burn a
|
|
1941
|
+
// step or pollute action history. HostBashProxy / HostFileProxy
|
|
1942
|
+
// validate at the tool-resolution layer for the same reason. The proxy
|
|
1943
|
+
// re-checks same-user (single authoritative gate); using the shared
|
|
1944
|
+
// helper keeps log payload and error wording identical at both layers.
|
|
1945
|
+
const sourceActorPrincipalId = ctx.trustContext?.guardianPrincipalId;
|
|
1795
1946
|
if (targetClientId != null) {
|
|
1796
1947
|
const client = assistantEventHub.getClientById(targetClientId);
|
|
1797
1948
|
if (!client) {
|
|
@@ -1806,14 +1957,24 @@ export async function surfaceProxyResolver(
|
|
|
1806
1957
|
isError: true,
|
|
1807
1958
|
};
|
|
1808
1959
|
}
|
|
1960
|
+
const rejection = enforceSameActorOrErrorResult({
|
|
1961
|
+
hub: assistantEventHub,
|
|
1962
|
+
sourceActorPrincipalId,
|
|
1963
|
+
targetClientId,
|
|
1964
|
+
op: "host_cu",
|
|
1965
|
+
});
|
|
1966
|
+
if (rejection) return rejection;
|
|
1809
1967
|
}
|
|
1810
1968
|
|
|
1811
|
-
// Guard: require explicit targeting when multiple CU-capable
|
|
1812
|
-
// connected. The tool schemas document target_client_id as
|
|
1813
|
-
// multiple clients support host_cu" but nothing enforced
|
|
1814
|
-
// until now. Without this guard, the request would
|
|
1815
|
-
// capable clients simultaneously, causing the same CU
|
|
1816
|
-
// on multiple machines.
|
|
1969
|
+
// Guard: require explicit targeting when multiple same-user CU-capable
|
|
1970
|
+
// clients are connected. The tool schemas document target_client_id as
|
|
1971
|
+
// "required when multiple clients support host_cu" but nothing enforced
|
|
1972
|
+
// it at runtime until now. Without this guard, the request would
|
|
1973
|
+
// broadcast to all capable clients simultaneously, causing the same CU
|
|
1974
|
+
// action to execute on multiple machines. The filter mirrors
|
|
1975
|
+
// HostFileProxy's auto-resolve: only same-user clients participate, so
|
|
1976
|
+
// a cross-user client connected to the same daemon does not falsely
|
|
1977
|
+
// trigger this ambiguity error.
|
|
1817
1978
|
//
|
|
1818
1979
|
// Asymmetry with host_bash / host_file (host-shell.ts): the bash/file
|
|
1819
1980
|
// guard additionally checks `transportInterface != null &&
|
|
@@ -1824,13 +1985,25 @@ export async function surfaceProxyResolver(
|
|
|
1824
1985
|
// host_cu-capable transport for which auto-routing-to-self would be
|
|
1825
1986
|
// appropriate. We therefore fire whenever there is genuine ambiguity.
|
|
1826
1987
|
if (targetClientId == null) {
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1988
|
+
const allCuClients = assistantEventHub.listClientsByCapability("host_cu");
|
|
1989
|
+
const sameUserCuClients = allCuClients.filter(
|
|
1990
|
+
(c) => c.actorPrincipalId === sourceActorPrincipalId,
|
|
1991
|
+
);
|
|
1992
|
+
if (sameUserCuClients.length > 1) {
|
|
1829
1993
|
return {
|
|
1830
1994
|
content: `Error: multiple clients support host_cu. Specify which client to target with \`target_client_id\`. Run \`assistant clients list --capability host_cu\` to see client IDs and labels.`,
|
|
1831
1995
|
isError: true,
|
|
1832
1996
|
};
|
|
1833
1997
|
}
|
|
1998
|
+
// When cross-user host_cu clients are connected, we MUST auto-resolve
|
|
1999
|
+
// to the unique same-user client (or fail explicitly) — otherwise the
|
|
2000
|
+
// proxy would broadcast untargeted and the CU action would reach the
|
|
2001
|
+
// cross-user client too. Setting targetClientId here forces the proxy
|
|
2002
|
+
// to deliver only to that client, with the same-user check below as
|
|
2003
|
+
// belt-and-suspenders.
|
|
2004
|
+
if (sameUserCuClients.length === 1 && allCuClients.length > 1) {
|
|
2005
|
+
targetClientId = sameUserCuClients[0].clientId;
|
|
2006
|
+
}
|
|
1834
2007
|
}
|
|
1835
2008
|
|
|
1836
2009
|
ctx.hostCuProxy.recordAction(toolName, input, reasoning);
|
|
@@ -1842,6 +2015,7 @@ export async function surfaceProxyResolver(
|
|
|
1842
2015
|
reasoning,
|
|
1843
2016
|
signal,
|
|
1844
2017
|
targetClientId,
|
|
2018
|
+
sourceActorPrincipalId,
|
|
1845
2019
|
);
|
|
1846
2020
|
}
|
|
1847
2021
|
|
|
@@ -1876,7 +2050,7 @@ export async function surfaceProxyResolver(
|
|
|
1876
2050
|
// union on `tool` ("start" | "observe" | "press" | …). The agent's raw
|
|
1877
2051
|
// tool input only carries the action-specific payload (app, x/y, text,
|
|
1878
2052
|
// …) — the discriminator is implied by `toolName` (`app_control_<tool>`).
|
|
1879
|
-
// Inject it here so the proxy's
|
|
2053
|
+
// Inject it here so the proxy's session-lock guard (`input.tool ===
|
|
1880
2054
|
// "start"`) and the Swift client's discriminated-union decoder both see
|
|
1881
2055
|
// the field they require.
|
|
1882
2056
|
const tool = toolName.slice("app_control_".length);
|
|
@@ -2077,6 +2251,12 @@ export async function surfaceProxyResolver(
|
|
|
2077
2251
|
ctx.currentTurnSurfaces[idx].data = mergedData;
|
|
2078
2252
|
}
|
|
2079
2253
|
|
|
2254
|
+
// Persist the merged data back to the assistant message's
|
|
2255
|
+
// `ui_surface` content block so a refresh / restart shows the
|
|
2256
|
+
// current state instead of the original creation-time snapshot.
|
|
2257
|
+
// Debounced to coalesce bursts of rapid updates.
|
|
2258
|
+
scheduleSurfaceDataPersist(ctx.conversationId, surfaceId, mergedData);
|
|
2259
|
+
|
|
2080
2260
|
return { content: "Surface updated", isError: false };
|
|
2081
2261
|
}
|
|
2082
2262
|
|