@vellumai/assistant 0.11.7 → 0.11.8-staging.1
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 +10 -10
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +1 -1
- package/docs/error-handling.md +1 -2
- package/docs/guardian-request-flow.md +51 -5
- package/docs/trusted-contact-access.md +13 -12
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
- package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
- package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
- package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
- package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
- package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/openapi.yaml +481 -9
- package/package.json +3 -1
- package/scripts/sync-llm-catalog.ts +3 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
- package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +56 -0
- package/src/__tests__/approval-cascade.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +117 -6
- package/src/__tests__/attachments.test.ts +122 -0
- package/src/__tests__/bundled-asset.test.ts +22 -1
- package/src/__tests__/channel-approval.test.ts +14 -7
- package/src/__tests__/channel-approvals.test.ts +3 -3
- package/src/__tests__/channel-delivery-store.test.ts +58 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
- package/src/__tests__/channel-invite-transport.test.ts +1 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
- package/src/__tests__/compaction-events.test.ts +0 -12
- package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
- package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/contacts-write.test.ts +141 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop.test.ts +95 -13
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
- package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
- package/src/__tests__/conversation-media-retry.test.ts +33 -2
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -12
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
- package/src/__tests__/conversation-queue.test.ts +4 -13
- package/src/__tests__/conversation-retry-route.test.ts +5 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-sight-frames.test.ts +615 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -13
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
- package/src/__tests__/conversation-speed-override.test.ts +0 -12
- package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
- package/src/__tests__/conversation-summarize-route.test.ts +5 -0
- package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
- package/src/__tests__/credential-health-service.test.ts +45 -0
- package/src/__tests__/credential-record-retire.test.ts +256 -0
- package/src/__tests__/credential-record-write-through.test.ts +8 -4
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/delete-propagation.test.ts +121 -3
- package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
- package/src/__tests__/file-ops-service.test.ts +8 -3
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
- package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
- package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
- package/src/__tests__/guardian-gateway-sim.ts +56 -20
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
- package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
- package/src/__tests__/guardian-wait-controller.test.ts +6 -0
- package/src/__tests__/headless-browser-mode.test.ts +27 -0
- package/src/__tests__/helpers/mock-conversation.ts +2 -0
- package/src/__tests__/list-messages-system-card.test.ts +70 -0
- package/src/__tests__/llm-catalog-parity.test.ts +2 -0
- package/src/__tests__/managed-profile-guard.test.ts +2 -2
- package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
- package/src/__tests__/media-stream-server-integration.test.ts +6 -0
- package/src/__tests__/non-member-access-request.test.ts +12 -0
- package/src/__tests__/notification-discord-adapter.test.ts +76 -4
- package/src/__tests__/oauth-commands-routes.test.ts +21 -0
- package/src/__tests__/openai-provider.test.ts +63 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
- package/src/__tests__/persist-unsendable-image.test.ts +19 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -1
- package/src/__tests__/process-message-display-content.test.ts +1 -1
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
- package/src/__tests__/qdrant-manager.test.ts +6 -0
- package/src/__tests__/reaction-persistence.test.ts +58 -62
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
- package/src/__tests__/server-history-render.test.ts +22 -0
- package/src/__tests__/sight-frame-retention.test.ts +362 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
- package/src/__tests__/terminal-tools.test.ts +9 -9
- package/src/__tests__/tool-result-spool.test.ts +5 -8
- package/src/__tests__/usage-attribution.test.ts +26 -0
- package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
- package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
- package/src/acp/acp-auth-marker-store.ts +196 -0
- package/src/acp/acp-claude-oauth.ts +154 -4
- package/src/acp/acp-connect-card-state.ts +49 -8
- package/src/acp/agent-process.ts +1 -0
- package/src/acp/auto-install.ts +1 -0
- package/src/acp/client-handler.ts +1 -0
- package/src/acp/prepare-agent-env.ts +79 -1
- package/src/acp/resolve-agent.ts +12 -0
- package/src/acp/session-manager.test.ts +183 -0
- package/src/acp/session-manager.ts +110 -6
- package/src/acp/types.ts +17 -0
- package/src/agent/attachments.ts +7 -2
- package/src/agent/loop.ts +59 -1
- package/src/api/events/contact-form-closed.ts +32 -0
- package/src/api/events/contact-record-request.ts +63 -0
- package/src/api/events/contact-request.ts +10 -3
- package/src/api/index.ts +20 -0
- package/src/api/responses/conversation-message.ts +20 -0
- package/src/api/responses/home.ts +101 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +127 -30
- package/src/approvals/guardian-decision-primitive.ts +9 -0
- package/src/approvals/guardian-expiry-notifier.ts +22 -16
- package/src/approvals/guardian-request-status-sync.ts +98 -0
- package/src/avatar/svg-compositor.ts +1 -1
- package/src/avatar/traits-png-sync.ts +1 -1
- package/src/bundler/app-compiler.ts +1 -0
- package/src/bundler/compiler-tools.ts +1 -0
- package/src/bundler/package-resolver.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
- package/src/calls/guardian-action-sweep.ts +6 -1
- package/src/calls/voice-session-bridge.ts +46 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
- package/src/channels/gateway-guardian-requests.ts +37 -18
- package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
- package/src/cli/commands/__tests__/browser.test.ts +65 -1
- package/src/cli/commands/browser.ts +30 -10
- package/src/cli/commands/contacts.help.ts +173 -11
- package/src/cli/commands/contacts.ts +343 -4
- package/src/cli/commands/db/refresh.ts +1 -0
- package/src/cli/commands/oauth/index.help.ts +6 -3
- package/src/cli/commands/oauth/request.test.ts +37 -0
- package/src/cli/commands/oauth/request.ts +7 -6
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-github.ts +20 -19
- package/src/cli/lib/install-plugin-dependencies.ts +3 -5
- package/src/cli/lib/merge-plugin-tree.ts +1 -0
- package/src/cli/lib/publish-plugin.ts +4 -1
- package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
- package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
- package/src/config/balanced-model-experiment.ts +15 -9
- package/src/config/bundled-skills/contacts/SKILL.md +61 -18
- package/src/config/default-profile-catalog.ts +12 -11
- package/src/config/feature-flag-registry.json +22 -13
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
- package/src/config/schemas/live-voice.ts +7 -0
- package/src/config/schemas/sight.ts +45 -0
- package/src/config/skills.ts +2 -1
- package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
- package/src/contacts/contact-store.ts +47 -4
- package/src/contacts/contacts-write.ts +5 -2
- package/src/contacts/mirror-reconciler.ts +210 -0
- package/src/contacts/mirror-tombstones.ts +42 -0
- package/src/contacts/notify-contacts-changed.ts +11 -2
- package/src/context/compactor.ts +47 -19
- package/src/context/tool-result-spool.ts +21 -14
- package/src/conversations/message-consolidation.ts +2 -2
- package/src/credential-health/credential-health-service.ts +37 -6
- package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
- package/src/daemon/conversation-agent-loop.ts +100 -1
- package/src/daemon/conversation-media-retry.ts +11 -6
- package/src/daemon/conversation-messaging.ts +293 -30
- package/src/daemon/conversation-runtime-assembly.ts +67 -0
- package/src/daemon/conversation-sight-frames.ts +187 -0
- package/src/daemon/conversation-store.ts +18 -28
- package/src/daemon/conversation-surfaces.ts +12 -2
- package/src/daemon/conversation-tool-setup.ts +39 -10
- package/src/daemon/conversation.ts +159 -16
- package/src/daemon/daemon-control.ts +6 -17
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +10 -11
- package/src/daemon/install-assistant-command.ts +1 -0
- package/src/daemon/lifecycle.ts +10 -13
- package/src/daemon/message-types/sync.ts +4 -0
- package/src/daemon/process-message.ts +2 -17
- package/src/daemon/reaction-history-render.test.ts +162 -0
- package/src/daemon/reaction-history-render.ts +115 -0
- package/src/daemon/reaction-record.test.ts +162 -0
- package/src/daemon/reaction-record.ts +83 -0
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/summarize-boundary.ts +12 -0
- package/src/daemon/video-thumbnail.ts +1 -1
- package/src/home/feed-types.ts +4 -0
- package/src/home/feed-writer.ts +25 -0
- package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
- package/src/live-voice/live-voice-photo.ts +420 -65
- package/src/live-voice/live-voice-session.ts +311 -27
- package/src/live-voice/protocol.ts +147 -11
- package/src/messaging/provider-message-metadata.ts +7 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
- package/src/messaging/providers/channel-transport.ts +37 -0
- package/src/messaging/providers/discord/api.ts +4 -4
- package/src/messaging/providers/discord/send.test.ts +177 -2
- package/src/messaging/providers/discord/send.ts +197 -10
- package/src/messaging/providers/discord/transport.ts +10 -0
- package/src/messaging/providers/index.ts +42 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
- package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
- package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
- package/src/messaging/providers/slack/adapter.ts +112 -27
- package/src/messaging/providers/slack/api.test.ts +10 -0
- package/src/messaging/providers/slack/auth.ts +32 -6
- package/src/messaging/providers/slack/client.ts +0 -15
- package/src/messaging/providers/slack/message-content.ts +137 -0
- package/src/messaging/providers/slack/send.test.ts +43 -2
- package/src/messaging/providers/slack/send.ts +18 -5
- package/src/messaging/providers/slack/transport.ts +10 -0
- package/src/messaging/providers/slack/types.ts +21 -2
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
- package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
- package/src/messaging/providers/telegram-bot/send.ts +41 -1
- package/src/messaging/providers/telegram-bot/transport.ts +10 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
- package/src/messaging/reaction-envelopes.ts +79 -0
- package/src/monitoring/control.ts +2 -2
- package/src/monitoring/page-cache.ts +5 -1
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
- package/src/notifications/adapters/discord.ts +62 -9
- package/src/notifications/conversation-pairing.ts +31 -4
- package/src/notifications/emit-signal.ts +8 -5
- package/src/notifications/guardian-delivery-recorder.ts +47 -6
- package/src/notifications/guardian-feed-projection.ts +492 -0
- package/src/notifications/home-feed-side-effect.ts +59 -11
- package/src/oauth/byo-connection.test.ts +22 -0
- package/src/oauth/byo-connection.ts +11 -5
- package/src/oauth/connection.ts +3 -2
- package/src/oauth/platform-connection.test.ts +35 -0
- package/src/oauth/platform-connection.ts +18 -13
- package/src/oauth/scope-utils.ts +48 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/outbound-proxy/certs.ts +5 -4
- package/src/permissions/question-guardian-request.test.ts +7 -1
- package/src/persistence/attachments-store.ts +134 -31
- package/src/persistence/conversation-crud.ts +137 -4
- package/src/persistence/conversation-types.ts +109 -0
- package/src/persistence/db-connection.ts +3 -21
- package/src/persistence/delivery-crud.ts +42 -12
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
- package/src/persistence/embeddings/embedding-local.ts +7 -112
- package/src/persistence/embeddings/qdrant-manager.ts +4 -1
- package/src/persistence/llm-request-log-store.ts +1 -1
- package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
- package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
- package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
- package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
- package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
- package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
- package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
- package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
- package/src/persistence/schema/acp.ts +25 -0
- package/src/persistence/steps.ts +8 -0
- package/src/platform/platform-patch-queue.test.ts +43 -41
- package/src/plugin-api/index.ts +12 -0
- package/src/plugins/defaults/compaction/window-manager.ts +2 -0
- package/src/plugins/defaults/image-recovery/recover.ts +12 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
- package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
- package/src/plugins/defaults/memory/worker-control.ts +1 -2
- package/src/providers/content-block-schema.ts +1 -0
- package/src/providers/fireworks/client.ts +9 -1
- package/src/providers/gemini/client.ts +22 -15
- package/src/providers/media-resolve.ts +24 -10
- package/src/providers/model-catalog.ts +91 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -5
- package/src/providers/retry.ts +16 -11
- package/src/providers/types.ts +56 -0
- package/src/routes/__tests__/control.test.ts +4 -3
- package/src/routes/control.ts +1 -1
- package/src/routes/route-host-client.ts +1 -1
- package/src/runtime/AGENTS.md +30 -12
- package/src/runtime/__tests__/agent-wake.test.ts +71 -6
- package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
- package/src/runtime/access-request-helper.ts +15 -7
- package/src/runtime/agent-wake.ts +16 -4
- package/src/runtime/channel-approval-types.ts +8 -5
- package/src/runtime/channel-invite-transport.ts +2 -0
- package/src/runtime/channel-invite-transports/discord.ts +27 -0
- package/src/runtime/channel-readiness-service.ts +4 -4
- package/src/runtime/channel-reply-delivery.ts +72 -28
- package/src/runtime/guardian-action-service.ts +9 -5
- package/src/runtime/guardian-form-registry.ts +256 -0
- package/src/runtime/guardian-reply-router.ts +33 -14
- package/src/runtime/invite-instruction-generator.ts +14 -2
- package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
- package/src/runtime/no-response.ts +13 -74
- package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
- package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
- package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
- package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
- package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
- package/src/runtime/routes/acp-routes-list.test.ts +58 -0
- package/src/runtime/routes/acp-routes.ts +253 -47
- package/src/runtime/routes/app-management-routes.ts +80 -47
- package/src/runtime/routes/approval-routes.ts +9 -0
- package/src/runtime/routes/archive-utils.ts +6 -1
- package/src/runtime/routes/avatar-routes.ts +1 -1
- package/src/runtime/routes/channel-route-shared.ts +18 -20
- package/src/runtime/routes/contact-prompt-routes.ts +271 -105
- package/src/runtime/routes/conversation-routes.ts +41 -32
- package/src/runtime/routes/credential-prompt-routes.ts +10 -1
- package/src/runtime/routes/debug-bash-routes.ts +1 -0
- package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
- package/src/runtime/routes/guardian-form-routes.ts +115 -0
- package/src/runtime/routes/inbound-message-handler.ts +82 -29
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/channel.ts +39 -18
- package/src/runtime/routes/migration-routes.ts +1 -3
- package/src/runtime/routes/monitoring-routes.ts +0 -3
- package/src/runtime/routes/oauth-commands-routes.ts +18 -5
- package/src/runtime/routes/route-host-worker-routes.ts +1 -1
- package/src/runtime/routes/schedule-worker-routes.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/run-script.ts +1 -0
- package/src/schedule/scheduler.ts +1 -1
- package/src/schedule/worker-control.ts +2 -2
- package/src/security/oauth2.ts +2 -11
- package/src/security/secure-keys.ts +55 -8
- package/src/skills/catalog-install.ts +7 -4
- package/src/skills/clawhub.ts +1 -0
- package/src/skills/inline-command-runner.ts +1 -0
- package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
- package/src/stt/room-noise-floor.ts +140 -0
- package/src/subagent/notify.ts +49 -3
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/acp/spawn.ts +2 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
- package/src/tools/browser/browser-execution.ts +10 -43
- package/src/tools/browser/browser-manager.ts +1 -0
- package/src/tools/browser/browser-status-constants.ts +6 -5
- package/src/tools/browser/runtime-check.ts +2 -0
- package/src/tools/channel/react-to-message.test.ts +193 -0
- package/src/tools/channel/react-to-message.ts +159 -0
- package/src/tools/credentials/metadata-store.ts +115 -19
- package/src/tools/credentials/store.ts +0 -2
- package/src/tools/filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +11 -38
- package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
- package/src/tools/shared/shell-output.test.ts +104 -0
- package/src/tools/shared/shell-output.ts +112 -39
- package/src/tools/skills/sandbox-runner.ts +1 -0
- package/src/tools/system/request-permission.test.ts +34 -0
- package/src/tools/system/request-permission.ts +29 -5
- package/src/tools/terminal/shell.test.ts +63 -0
- package/src/tools/terminal/shell.ts +12 -44
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/usage/attribution.ts +12 -4
- package/src/util/__tests__/worker-ownership.test.ts +77 -0
- package/src/util/__tests__/worker-process-command.test.ts +246 -1
- package/src/util/bun-runtime.ts +44 -13
- package/src/util/bundled-asset.ts +9 -1
- package/src/util/disk-usage.ts +1 -0
- package/src/util/file-use.ts +4 -1
- package/src/util/guardian-form-timeouts.ts +50 -0
- package/src/util/image-conversion.ts +1 -0
- package/src/util/oauth-request-body.test.ts +59 -0
- package/src/util/oauth-request-body.ts +56 -5
- package/src/util/platform.ts +69 -73
- package/src/util/process-table.ts +16 -5
- package/src/util/read-stdin.ts +5 -0
- package/src/util/spawn.ts +1 -0
- package/src/util/sqlite3-runtime.ts +25 -5
- package/src/util/worker-ownership.ts +193 -0
- package/src/util/worker-process.ts +375 -37
- package/src/workspace/git-service.ts +4 -12
- package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short-lived tombstones for deleted mirror contacts.
|
|
3
|
+
*
|
|
4
|
+
* The mirror reconciler pulls a gateway identity snapshot and then applies it;
|
|
5
|
+
* a contact deleted (or merged away) while that pull is in flight would be
|
|
6
|
+
* resurrected from the stale snapshot, and the reconciler's additive contract
|
|
7
|
+
* (it never deletes) would keep the ghost forever. Recording each deletion
|
|
8
|
+
* here lets the reconciler refuse to heal an id that died after the snapshot
|
|
9
|
+
* was taken.
|
|
10
|
+
*
|
|
11
|
+
* Contact ids are UUIDs and never come back, so a tombstone can never block
|
|
12
|
+
* a legitimate recreation; the TTL only bounds memory.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Generously longer than a snapshot pull plus its debounced follow-up. */
|
|
16
|
+
const TOMBSTONE_TTL_MS = 15 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
const tombstones = new Map<string, number>();
|
|
19
|
+
|
|
20
|
+
/** Record that the mirror contact row for `contactId` was deleted. */
|
|
21
|
+
export function recordContactTombstone(contactId: string): void {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
tombstones.set(contactId, now + TOMBSTONE_TTL_MS);
|
|
24
|
+
for (const [id, expiresAt] of tombstones) {
|
|
25
|
+
if (expiresAt <= now) {
|
|
26
|
+
tombstones.delete(id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Whether `contactId` was deleted recently enough that a snapshot may predate it. */
|
|
32
|
+
export function isContactTombstoned(contactId: string): boolean {
|
|
33
|
+
const expiresAt = tombstones.get(contactId);
|
|
34
|
+
if (expiresAt === undefined) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (expiresAt <= Date.now()) {
|
|
38
|
+
tombstones.delete(contactId);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
@@ -7,14 +7,23 @@ import { invalidateGuardianDeliveryCache } from "./guardian-delivery-reader.js";
|
|
|
7
7
|
* Notify everything that cares about a contact mutation. Called directly after
|
|
8
8
|
* a successful contact write:
|
|
9
9
|
*
|
|
10
|
-
* - broadcasts `contacts_changed` so connected clients refresh their view,
|
|
10
|
+
* - broadcasts `contacts_changed` so connected clients refresh their view,
|
|
11
11
|
* - drops the guardian-contact, guardian-delivery, and contact-threshold
|
|
12
12
|
* caches so the next read refetches from the gateway instead of serving
|
|
13
|
-
* a stale set
|
|
13
|
+
* a stale set, and
|
|
14
|
+
* - asks the mirror reconciler for a debounced convergence pass, so identity
|
|
15
|
+
* drift the per-write mirror ops missed heals on the next contact activity.
|
|
16
|
+
*
|
|
17
|
+
* The reconciler is reached via dynamic import: it applies its heals through
|
|
18
|
+
* the contact-store primitives, whose writes land back here, and a static
|
|
19
|
+
* import would close that cycle at module-load time.
|
|
14
20
|
*/
|
|
15
21
|
export function notifyContactsChanged(): void {
|
|
16
22
|
broadcastMessage({ type: "contacts_changed" });
|
|
17
23
|
invalidateGuardianContactCache();
|
|
18
24
|
invalidateGuardianDeliveryCache();
|
|
19
25
|
invalidateAllContactThresholdCaches();
|
|
26
|
+
void import("./mirror-reconciler.js")
|
|
27
|
+
.then((m) => m.scheduleMirrorReconcile())
|
|
28
|
+
.catch(() => {});
|
|
20
29
|
}
|
package/src/context/compactor.ts
CHANGED
|
@@ -64,17 +64,31 @@ const log = getLogger("compactor");
|
|
|
64
64
|
const COMPACTION_CALL_SITE: LLMCallSite = "mainAgent";
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Tag stamped on `llm_request_logs.call_site`
|
|
67
|
+
* Tag stamped on `llm_request_logs.call_site` and the usage event's
|
|
68
|
+
* `callSite` for compaction-driven rows.
|
|
68
69
|
*
|
|
69
70
|
* Distinct from `COMPACTION_CALL_SITE` (above) on purpose: that constant
|
|
70
|
-
* names the
|
|
71
|
+
* names the provider-config resolution site (set to `mainAgent` so we
|
|
71
72
|
* inherit the agent's profile and keep the prefix cache warm). This
|
|
72
|
-
* constant names the
|
|
73
|
-
* inspectors
|
|
73
|
+
* constant names the observability site (what the row is) so
|
|
74
|
+
* inspectors and usage breakdowns can filter compaction calls. They're
|
|
74
75
|
* semantically different even though both come from the same enum.
|
|
75
76
|
*/
|
|
76
77
|
const COMPACTION_LOG_CALL_SITE: LLMCallSite = "compactionAgent";
|
|
77
78
|
|
|
79
|
+
function compactionUsageAttribution(
|
|
80
|
+
args: Pick<CompactionRunArgs, "overrideProfile">,
|
|
81
|
+
): Pick<
|
|
82
|
+
CompactionRunResult,
|
|
83
|
+
"summaryCallSite" | "summaryResolutionCallSite" | "summaryOverrideProfile"
|
|
84
|
+
> {
|
|
85
|
+
return {
|
|
86
|
+
summaryCallSite: COMPACTION_LOG_CALL_SITE,
|
|
87
|
+
summaryResolutionCallSite: COMPACTION_CALL_SITE,
|
|
88
|
+
summaryOverrideProfile: args.overrideProfile ?? null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
/**
|
|
79
93
|
* What actually served a compaction call, for attribution on both the request
|
|
80
94
|
* log and the usage row. A wrapper may reroute the request away from the
|
|
@@ -354,6 +368,14 @@ export interface CompactionRunResult {
|
|
|
354
368
|
summaryOutputTokens: number;
|
|
355
369
|
summaryModel: string;
|
|
356
370
|
summaryCallSite?: LLMCallSite;
|
|
371
|
+
/**
|
|
372
|
+
* Call site used to resolve the winning inference profile for the
|
|
373
|
+
* summary. Compaction invokes the provider as `mainAgent` so the
|
|
374
|
+
* prefix cache stays warm; {@link summaryCallSite} is the observability
|
|
375
|
+
* tag (`compactionAgent`). Attribution uses this field for profile
|
|
376
|
+
* selection and {@link summaryCallSite} for the usage-row call site.
|
|
377
|
+
*/
|
|
378
|
+
summaryResolutionCallSite?: LLMCallSite;
|
|
357
379
|
summaryOverrideProfile?: string | null;
|
|
358
380
|
/**
|
|
359
381
|
* Provider that actually served the summary call: the response's
|
|
@@ -977,6 +999,18 @@ export async function buildRetainedImageBlocks(
|
|
|
977
999
|
media_type: optimized.mediaType,
|
|
978
1000
|
data: optimized.data,
|
|
979
1001
|
},
|
|
1002
|
+
// The rebuilt block is inline bytes, so the row it came from is only
|
|
1003
|
+
// recoverable from the id the manifest entry already holds. Carrying it
|
|
1004
|
+
// keeps a retained image traceable to its attachment, which is what
|
|
1005
|
+
// camera-frame retention matches on: without it a frame the compaction
|
|
1006
|
+
// model chose to keep would be invisible to every later pass and could
|
|
1007
|
+
// outlive the retention bound.
|
|
1008
|
+
//
|
|
1009
|
+
// Assigned rather than spread through `attachmentIdFragment`, the helper
|
|
1010
|
+
// the conditional producers share: a manifest entry always names a row,
|
|
1011
|
+
// so making the field conditional here would describe an absence the
|
|
1012
|
+
// type rules out.
|
|
1013
|
+
_attachmentId: entry.attachmentId,
|
|
980
1014
|
});
|
|
981
1015
|
resolved.push(name);
|
|
982
1016
|
}
|
|
@@ -1406,8 +1440,7 @@ export async function runAssistantDrivenCompaction(
|
|
|
1406
1440
|
summaryCacheCreationInputTokens:
|
|
1407
1441
|
response.usage.cacheCreationInputTokens ?? 0,
|
|
1408
1442
|
summaryCacheReadInputTokens: response.usage.cacheReadInputTokens ?? 0,
|
|
1409
|
-
|
|
1410
|
-
summaryOverrideProfile: args.overrideProfile ?? null,
|
|
1443
|
+
...compactionUsageAttribution(args),
|
|
1411
1444
|
summaryRawResponses: response.rawResponse ? [response.rawResponse] : [],
|
|
1412
1445
|
summaryRequestLogId,
|
|
1413
1446
|
summaryCalls: 1,
|
|
@@ -1445,8 +1478,7 @@ export async function runAssistantDrivenCompaction(
|
|
|
1445
1478
|
summaryCacheCreationInputTokens:
|
|
1446
1479
|
response.usage.cacheCreationInputTokens ?? 0,
|
|
1447
1480
|
summaryCacheReadInputTokens: response.usage.cacheReadInputTokens ?? 0,
|
|
1448
|
-
|
|
1449
|
-
summaryOverrideProfile: args.overrideProfile ?? null,
|
|
1481
|
+
...compactionUsageAttribution(args),
|
|
1450
1482
|
summaryRawResponses: response.rawResponse ? [response.rawResponse] : [],
|
|
1451
1483
|
summaryCalls: 1,
|
|
1452
1484
|
};
|
|
@@ -1616,8 +1648,7 @@ export async function runAssistantDrivenCompaction(
|
|
|
1616
1648
|
summaryCacheCreationInputTokens:
|
|
1617
1649
|
response.usage.cacheCreationInputTokens ?? 0,
|
|
1618
1650
|
summaryCacheReadInputTokens: response.usage.cacheReadInputTokens ?? 0,
|
|
1619
|
-
|
|
1620
|
-
summaryOverrideProfile: args.overrideProfile ?? null,
|
|
1651
|
+
...compactionUsageAttribution(args),
|
|
1621
1652
|
summaryRawResponses: response.rawResponse ? [response.rawResponse] : [],
|
|
1622
1653
|
summaryRequestLogId,
|
|
1623
1654
|
summaryCalls: 1,
|
|
@@ -1686,8 +1717,7 @@ export async function runAssistantDrivenCompaction(
|
|
|
1686
1717
|
summaryInputTokens: response.usage.inputTokens,
|
|
1687
1718
|
summaryOutputTokens: response.usage.outputTokens,
|
|
1688
1719
|
summaryModel: response.model,
|
|
1689
|
-
|
|
1690
|
-
summaryOverrideProfile: args.overrideProfile ?? null,
|
|
1720
|
+
...compactionUsageAttribution(args),
|
|
1691
1721
|
...servedAttribution(response, args.provider),
|
|
1692
1722
|
summaryCacheCreationInputTokens:
|
|
1693
1723
|
response.usage.cacheCreationInputTokens ?? 0,
|
|
@@ -1874,12 +1904,11 @@ export async function runEmergencyCompaction(
|
|
|
1874
1904
|
summaryInputTokens: response.usage.inputTokens,
|
|
1875
1905
|
summaryOutputTokens: response.usage.outputTokens,
|
|
1876
1906
|
summaryModel: response.model,
|
|
1877
|
-
// This path bills real tokens, so its provider
|
|
1878
|
-
//
|
|
1879
|
-
//
|
|
1880
|
-
// profile attribution null; that gap predates this change and is not
|
|
1881
|
-
// widened by it.
|
|
1907
|
+
// This path bills real tokens, so its provider, call site, and
|
|
1908
|
+
// override profile must follow the same attribution as every other
|
|
1909
|
+
// call-bearing compaction path.
|
|
1882
1910
|
...servedAttribution(response, args.provider),
|
|
1911
|
+
...compactionUsageAttribution(args),
|
|
1883
1912
|
};
|
|
1884
1913
|
}
|
|
1885
1914
|
|
|
@@ -1925,8 +1954,7 @@ export async function runEmergencyCompaction(
|
|
|
1925
1954
|
summaryInputTokens: response.usage.inputTokens,
|
|
1926
1955
|
summaryOutputTokens: response.usage.outputTokens,
|
|
1927
1956
|
summaryModel: response.model,
|
|
1928
|
-
|
|
1929
|
-
summaryOverrideProfile: args.overrideProfile ?? null,
|
|
1957
|
+
...compactionUsageAttribution(args),
|
|
1930
1958
|
...servedAttribution(response, args.provider),
|
|
1931
1959
|
summaryCacheCreationInputTokens:
|
|
1932
1960
|
response.usage.cacheCreationInputTokens ?? 0,
|
|
@@ -20,15 +20,22 @@ const AX_TREE_TAG = "<ax-tree>";
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Tools whose results are explicit, self-sized reads the model paginates
|
|
23
|
-
* itself:
|
|
24
|
-
* "Character Window: X-Y of Z",
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
23
|
+
* itself: each takes `max_chars`/`start_index` and reports where its window
|
|
24
|
+
* ended (`web_fetch`'s "Character Window: X-Y of Z", the file-read tools'
|
|
25
|
+
* "[Truncated: characters X-Y of Z]" notice), so the model pages by character
|
|
26
|
+
* index against the window it requested. Stubbing such a result at result
|
|
27
|
+
* time would hand the model a fraction of the window it just sized — and it
|
|
28
|
+
* keeps paging blind against content it never saw. These reads are honored in
|
|
29
|
+
* full for the turn that made them; the post-turn pass still truncates them
|
|
30
|
+
* at turn end, after the model has consumed the content. This exemption is
|
|
31
|
+
* also what lets `READ_CHAR_BUDGET` exceed `THRESHOLD_CHARS` without a fresh
|
|
32
|
+
* read being stubbed before the model sees it.
|
|
30
33
|
*/
|
|
31
|
-
export const RESULT_TIME_SPOOL_EXEMPT_TOOLS = new Set<string>([
|
|
34
|
+
export const RESULT_TIME_SPOOL_EXEMPT_TOOLS = new Set<string>([
|
|
35
|
+
"web_fetch",
|
|
36
|
+
"file_read",
|
|
37
|
+
"host_file_read",
|
|
38
|
+
]);
|
|
32
39
|
|
|
33
40
|
/**
|
|
34
41
|
* Whether a tool result is eligible for the result-time spool/stub pass: the
|
|
@@ -39,12 +46,12 @@ export const RESULT_TIME_SPOOL_EXEMPT_TOOLS = new Set<string>(["web_fetch"]);
|
|
|
39
46
|
* The spooled-read exemption is what keeps paging possible: a file read is the
|
|
40
47
|
* model's only way to pull spooled content back into context, so stubbing a
|
|
41
48
|
* read of a `.tool-results/` file would write a fresh copy and hand back
|
|
42
|
-
* another stub, putting that content out of reach for good.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
49
|
+
* another stub, putting that content out of reach for good. The file-read
|
|
50
|
+
* tools are also name-exempt as explicit self-sized reads, but this
|
|
51
|
+
* path-scoped guarantee stands on its own so spooled content stays reachable
|
|
52
|
+
* regardless of how the exempt-tools set evolves. Explicit self-sized reads
|
|
53
|
+
* are honored in full; the post-turn pass still truncates them at turn end,
|
|
54
|
+
* after the model has consumed the content.
|
|
48
55
|
*/
|
|
49
56
|
function isSpoolEligible(
|
|
50
57
|
tr: ToolResultContent,
|
|
@@ -39,8 +39,8 @@ import { getLogger } from "../util/logger.js";
|
|
|
39
39
|
const log = getLogger("message-consolidation");
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* True when an assistant row is a standalone display turn (system card
|
|
43
|
-
* provider-error notice) that never merges with adjacent assistant rows in
|
|
42
|
+
* True when an assistant row is a standalone display turn (system card,
|
|
43
|
+
* provider-error notice, or deliberate-silence marker) that never merges with adjacent assistant rows in
|
|
44
44
|
* either direction. Merging one into an adjacent run would let the anchor's
|
|
45
45
|
* metadata win and drop the `messageKind` marker from the wire (or stamp it
|
|
46
46
|
* onto a bubble containing real assistant text).
|
|
@@ -4,9 +4,19 @@
|
|
|
4
4
|
* Enumerates all active OAuth connections and validates each one for:
|
|
5
5
|
* - Token presence in secure storage
|
|
6
6
|
* - Token expiry (expired or expiring within the warning window)
|
|
7
|
-
* - Scope coverage (grantedScopes vs
|
|
7
|
+
* - Scope coverage (grantedScopes vs the request that produced the token)
|
|
8
8
|
* - Liveness ping (for providers with a pingUrl)
|
|
9
9
|
*
|
|
10
|
+
* Scope coverage asks whether a connection carries the scopes the provider
|
|
11
|
+
* currently requests for the token it stores, which is how a newly required
|
|
12
|
+
* scope reaches an existing install and prompts a re-authorization. It applies
|
|
13
|
+
* only where an authorization makes a request. A manual-token provider (a bot
|
|
14
|
+
* token pasted in, e.g. `slack_channel`) has no request and carries empty
|
|
15
|
+
* scope lists here; it is still checked for token presence and liveness. What
|
|
16
|
+
* its token actually carries is read live from the `x-oauth-scopes` response
|
|
17
|
+
* header by `runtime/channel-readiness-service.ts`, which is the only source
|
|
18
|
+
* of truth available for a credential this service never negotiated.
|
|
19
|
+
*
|
|
10
20
|
* Designed to run during the heartbeat cycle. The BYO liveness ping is
|
|
11
21
|
* routed through `withValidToken`, so a stale-but-refreshable access token
|
|
12
22
|
* is refreshed transparently before the ping fires — this prevents the
|
|
@@ -24,7 +34,10 @@ import {
|
|
|
24
34
|
type OAuthConnectionRow,
|
|
25
35
|
type OAuthProviderRow,
|
|
26
36
|
} from "../oauth/oauth-store.js";
|
|
27
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
expectedScopesForStoredToken,
|
|
39
|
+
scopeDifference,
|
|
40
|
+
} from "../oauth/scope-utils.js";
|
|
28
41
|
import {
|
|
29
42
|
TokenExpiredError,
|
|
30
43
|
withValidToken,
|
|
@@ -182,6 +195,8 @@ interface CheckConnectionOpts {
|
|
|
182
195
|
hasRefreshToken: boolean;
|
|
183
196
|
grantedScopesRaw: string;
|
|
184
197
|
defaultScopesRaw: string;
|
|
198
|
+
authorizeParamsRaw: string | null;
|
|
199
|
+
scopeSeparator: string | null;
|
|
185
200
|
pingUrl: string | null;
|
|
186
201
|
pingMethod: string | null;
|
|
187
202
|
pingHeaders: string | null;
|
|
@@ -199,6 +214,8 @@ async function checkConnection(
|
|
|
199
214
|
hasRefreshToken,
|
|
200
215
|
grantedScopesRaw,
|
|
201
216
|
defaultScopesRaw,
|
|
217
|
+
authorizeParamsRaw,
|
|
218
|
+
scopeSeparator,
|
|
202
219
|
pingUrl,
|
|
203
220
|
pingMethod,
|
|
204
221
|
pingHeaders,
|
|
@@ -268,11 +285,21 @@ async function checkConnection(
|
|
|
268
285
|
// Has refresh token — not an issue, auto-refresh will handle it
|
|
269
286
|
}
|
|
270
287
|
|
|
271
|
-
// 3. Check scope coverage
|
|
288
|
+
// 3. Check scope coverage, against the request that produced the stored
|
|
289
|
+
// token rather than the provider's bot-side `scope` parameter. A provider
|
|
290
|
+
// asking for user scopes as well stores the user token and records that
|
|
291
|
+
// token's grant, so the bot request names scopes that grant can never hold.
|
|
272
292
|
const grantedScopes = safeJsonParse<string[]>(grantedScopesRaw, []);
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
293
|
+
const expectedScopes = expectedScopesForStoredToken(
|
|
294
|
+
safeJsonParse<string[]>(defaultScopesRaw, []),
|
|
295
|
+
safeJsonParse<Record<string, string> | undefined>(
|
|
296
|
+
authorizeParamsRaw,
|
|
297
|
+
undefined,
|
|
298
|
+
),
|
|
299
|
+
scopeSeparator ?? undefined,
|
|
300
|
+
);
|
|
301
|
+
if (expectedScopes.length > 0 && grantedScopes.length > 0) {
|
|
302
|
+
const missing = scopeDifference(expectedScopes, grantedScopes);
|
|
276
303
|
if (missing.length > 0) {
|
|
277
304
|
return {
|
|
278
305
|
...base,
|
|
@@ -637,6 +664,8 @@ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
|
|
|
637
664
|
hasRefreshToken: !!conn.hasRefreshToken,
|
|
638
665
|
grantedScopesRaw: conn.grantedScopes,
|
|
639
666
|
defaultScopesRaw: providerRow.defaultScopes,
|
|
667
|
+
authorizeParamsRaw: providerRow.authorizeParams,
|
|
668
|
+
scopeSeparator: providerRow.scopeSeparator,
|
|
640
669
|
pingUrl: providerRow.pingUrl,
|
|
641
670
|
pingMethod: providerRow.pingMethod,
|
|
642
671
|
pingHeaders: providerRow.pingHeaders,
|
|
@@ -750,6 +779,8 @@ export async function checkCredentialForProvider(
|
|
|
750
779
|
hasRefreshToken: !!conn.hasRefreshToken,
|
|
751
780
|
grantedScopesRaw: conn.grantedScopes,
|
|
752
781
|
defaultScopesRaw: providerRow.defaultScopes,
|
|
782
|
+
authorizeParamsRaw: providerRow.authorizeParams,
|
|
783
|
+
scopeSeparator: providerRow.scopeSeparator,
|
|
753
784
|
pingUrl: providerRow.pingUrl,
|
|
754
785
|
pingMethod: providerRow.pingMethod,
|
|
755
786
|
pingHeaders: providerRow.pingHeaders,
|
|
@@ -22,6 +22,7 @@ import { getConfig } from "../config/loader.js";
|
|
|
22
22
|
import { recordEstimate } from "../context/estimator-calibration.js";
|
|
23
23
|
import { stripInjectionsForCompaction } from "../context/strip-injections.js";
|
|
24
24
|
import { getCalibrationProviderKey } from "../context/token-estimator.js";
|
|
25
|
+
import type { ProviderMessageMetadata } from "../messaging/provider-message-metadata.js";
|
|
25
26
|
import {
|
|
26
27
|
formatSlackTimezoneLabel,
|
|
27
28
|
isSlackTs,
|
|
@@ -1399,6 +1400,22 @@ function buildAssistantChannelMetadata(
|
|
|
1399
1400
|
partialSlackMeta as SlackMessageMetadata,
|
|
1400
1401
|
);
|
|
1401
1402
|
}
|
|
1403
|
+
} else if (deps.turnChannelContext.assistantMessageChannel !== "vellum") {
|
|
1404
|
+
// Every other channel row describes itself in the neutral envelope
|
|
1405
|
+
// `readProviderMetadata` serves, the shape a plugin channel writes too.
|
|
1406
|
+
// `messageId` stays absent here and is back-filled by the post-send
|
|
1407
|
+
// reconciliation once the transport returns the sent message's id, which
|
|
1408
|
+
// is what lets a later reaction on this row resolve back to it. No
|
|
1409
|
+
// `threadId`: a value there asserts a thread exists, and the reply's
|
|
1410
|
+
// routing thread is delivery state, not a fact about this row.
|
|
1411
|
+
const chatId = turnOrRestingTrust(deps.ctx)?.requesterChatId;
|
|
1412
|
+
if (chatId) {
|
|
1413
|
+
metadata.providerMeta = JSON.stringify({
|
|
1414
|
+
source: deps.turnChannelContext.assistantMessageChannel,
|
|
1415
|
+
conversationExternalId: chatId,
|
|
1416
|
+
eventKind: "message",
|
|
1417
|
+
} satisfies ProviderMessageMetadata);
|
|
1418
|
+
}
|
|
1402
1419
|
}
|
|
1403
1420
|
|
|
1404
1421
|
return metadata;
|
|
@@ -3163,7 +3180,7 @@ function handleUsage(
|
|
|
3163
3180
|
JSON.stringify(event.rawResponse),
|
|
3164
3181
|
undefined,
|
|
3165
3182
|
providerName,
|
|
3166
|
-
|
|
3183
|
+
deps.ctx.currentCallSite,
|
|
3167
3184
|
latencyBreakdownJson,
|
|
3168
3185
|
);
|
|
3169
3186
|
} catch (err) {
|
|
@@ -3244,7 +3261,7 @@ function handleProviderError(
|
|
|
3244
3261
|
JSON.stringify(buildProviderErrorResponsePayload(event.error)),
|
|
3245
3262
|
undefined,
|
|
3246
3263
|
event.actualProvider,
|
|
3247
|
-
|
|
3264
|
+
deps.ctx.currentCallSite,
|
|
3248
3265
|
);
|
|
3249
3266
|
} catch (err) {
|
|
3250
3267
|
deps.rlog.warn(
|
|
@@ -51,8 +51,10 @@ import {
|
|
|
51
51
|
resolveOverrideProfile,
|
|
52
52
|
updateConversationContextWindow,
|
|
53
53
|
updateConversationSlackContextWatermark,
|
|
54
|
+
updateMessageMetadata,
|
|
54
55
|
} from "../persistence/conversation-crud.js";
|
|
55
56
|
import { isReplaceableTitle } from "../persistence/conversation-title-service.js";
|
|
57
|
+
import { NO_RESPONSE_MESSAGE_KIND } from "../persistence/conversation-types.js";
|
|
56
58
|
import {
|
|
57
59
|
backfillMessageIdOnLogs,
|
|
58
60
|
recordSyntheticAgentErrorMessageLog,
|
|
@@ -73,6 +75,7 @@ import {
|
|
|
73
75
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
74
76
|
import type { Provider } from "../providers/types.js";
|
|
75
77
|
import { resolveCapabilities } from "../runtime/capabilities.js";
|
|
78
|
+
import { isNoResponseOnlyText } from "../runtime/no-response.js";
|
|
76
79
|
import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
|
|
77
80
|
import { stampTurnOutcome } from "../telemetry/turn-outcome.js";
|
|
78
81
|
import {
|
|
@@ -113,6 +116,7 @@ import {
|
|
|
113
116
|
drainConversationNotices,
|
|
114
117
|
} from "./conversation-notices.js";
|
|
115
118
|
import {
|
|
119
|
+
applySightFrameRetention,
|
|
116
120
|
getSlackCompactionWatermarkForPrefix,
|
|
117
121
|
loadSlackChronologicalContext,
|
|
118
122
|
resolveTurnInboundActorContext,
|
|
@@ -133,6 +137,10 @@ import {
|
|
|
133
137
|
unregisterInflightTurn,
|
|
134
138
|
} from "./inflight-turn-registry.js";
|
|
135
139
|
import type { UsageStats } from "./message-protocol.js";
|
|
140
|
+
import {
|
|
141
|
+
persistReactionRecords,
|
|
142
|
+
type QueuedReactionRecord,
|
|
143
|
+
} from "./reaction-record.js";
|
|
136
144
|
import type { TrustContext } from "./trust-context-types.js";
|
|
137
145
|
import { turnOrRestingTrust } from "./trust-context-types.js";
|
|
138
146
|
import { resolveTurnCallSite } from "./turn-call-site.js";
|
|
@@ -775,6 +783,14 @@ export async function runAgentLoopImpl(
|
|
|
775
783
|
// turn's content is settled; the `finally` calls it again as the backstop for
|
|
776
784
|
// the cancel/error paths that never reached the early release.
|
|
777
785
|
let turnReleased = false;
|
|
786
|
+
// The turn's own queued reaction records, captured by `releaseTurn` while
|
|
787
|
+
// the conversation is still exclusive. The `finally` drains this array,
|
|
788
|
+
// never the live conversation field: after release a following turn may
|
|
789
|
+
// queue records of its own there, and draining the shared field would
|
|
790
|
+
// consume the new turn's record before its tool_result settles, the
|
|
791
|
+
// cross-turn variant of the pairing corruption the boundary drain
|
|
792
|
+
// prevents.
|
|
793
|
+
const ownedReactionRecords: QueuedReactionRecord[] = [];
|
|
778
794
|
|
|
779
795
|
/**
|
|
780
796
|
* Free the conversation for its next turn.
|
|
@@ -814,6 +830,11 @@ export async function runAgentLoopImpl(
|
|
|
814
830
|
);
|
|
815
831
|
}
|
|
816
832
|
}
|
|
833
|
+
// Ownership capture happens in the same synchronous step as the
|
|
834
|
+
// release, so records queued after this point belong to the next turn.
|
|
835
|
+
if (ctx.pendingReactionRecords?.length) {
|
|
836
|
+
ownedReactionRecords.push(...ctx.pendingReactionRecords.splice(0));
|
|
837
|
+
}
|
|
817
838
|
ctx.abortController = null;
|
|
818
839
|
ctx.setProcessing(false);
|
|
819
840
|
unregisterInflightTurn(ctx.conversationId, state);
|
|
@@ -1255,10 +1276,22 @@ export async function runAgentLoopImpl(
|
|
|
1255
1276
|
// immediately before the call, after every hook (memory injection, title,
|
|
1256
1277
|
// user plugins) has settled its shape. Runs unconditionally — a malformed
|
|
1257
1278
|
// history is a hard provider rejection, never a per-conversation opt-in.
|
|
1258
|
-
const
|
|
1279
|
+
const repairedMessages = repairHistoryForRun(
|
|
1259
1280
|
finalUserPromptCtx.latestMessages,
|
|
1260
1281
|
ctx.conversationId,
|
|
1261
1282
|
);
|
|
1283
|
+
// Camera-frame retention: keep only the newest few ambient frames as real
|
|
1284
|
+
// images. Applied to the per-turn copy, after every hook and the repair, so
|
|
1285
|
+
// every turn this loop drives inherits it and the stored transcript keeps
|
|
1286
|
+
// its attachments. The loop re-runs the same pass on any history its
|
|
1287
|
+
// in-place compaction rebuilds (see the `transformCompactedHistory` closure
|
|
1288
|
+
// the conversation supplies), so the bound holds across a compaction rather
|
|
1289
|
+
// than only from the next turn. The overflow ladder's media stubbing still
|
|
1290
|
+
// governs everything the user deliberately attached.
|
|
1291
|
+
const runMessages = applySightFrameRetention(
|
|
1292
|
+
repairedMessages,
|
|
1293
|
+
ctx.conversationId,
|
|
1294
|
+
);
|
|
1262
1295
|
|
|
1263
1296
|
// Reset the manager's turn-scoped overflow-recovery ladder at the turn
|
|
1264
1297
|
// boundary so a new turn starts the ladder fresh from the emergency rung.
|
|
@@ -1563,6 +1596,32 @@ export async function runAgentLoopImpl(
|
|
|
1563
1596
|
// of why the turn stopped.
|
|
1564
1597
|
const hasAssistantResponse =
|
|
1565
1598
|
newMessages[newMessages.length - 1]?.role === "assistant";
|
|
1599
|
+
// A reply that is nothing but the `<no_response/>` sentinel is deliberate
|
|
1600
|
+
// silence. Stamp the persisted row so clients render a quiet notice and
|
|
1601
|
+
// resolve their pending-response state, instead of showing the raw
|
|
1602
|
+
// sentinel or waiting forever; the content keeps the sentinel so the
|
|
1603
|
+
// model reads its own convention back from history.
|
|
1604
|
+
if (hasAssistantResponse && state.lastAssistantMessageId) {
|
|
1605
|
+
const tail = newMessages[newMessages.length - 1]!;
|
|
1606
|
+
const tailText = Array.isArray(tail.content)
|
|
1607
|
+
? tail.content
|
|
1608
|
+
.filter((b) => b.type === "text")
|
|
1609
|
+
.map((b) => (b.type === "text" ? b.text : ""))
|
|
1610
|
+
.join("\n")
|
|
1611
|
+
: "";
|
|
1612
|
+
if (isNoResponseOnlyText(tailText)) {
|
|
1613
|
+
try {
|
|
1614
|
+
updateMessageMetadata(state.lastAssistantMessageId, {
|
|
1615
|
+
messageKind: NO_RESPONSE_MESSAGE_KIND,
|
|
1616
|
+
});
|
|
1617
|
+
} catch (err) {
|
|
1618
|
+
rlog.warn(
|
|
1619
|
+
{ err, messageId: state.lastAssistantMessageId },
|
|
1620
|
+
"Failed to stamp deliberate-silence marker (non-fatal)",
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1566
1625
|
if (
|
|
1567
1626
|
!hasAssistantResponse &&
|
|
1568
1627
|
state.providerErrorUserMessage &&
|
|
@@ -1998,6 +2057,16 @@ export async function runAgentLoopImpl(
|
|
|
1998
2057
|
// the API, enabling stable prefix caching across turns. Compaction
|
|
1999
2058
|
// consolidates when it summarizes old messages (cache miss is expected).
|
|
2000
2059
|
} finally {
|
|
2060
|
+
// Backstop for paths that threw before either release site: still
|
|
2061
|
+
// exclusive here, so the release's ownership capture is race-free.
|
|
2062
|
+
releaseTurn();
|
|
2063
|
+
// Reactions the turn delivered get their durable rows on every
|
|
2064
|
+
// terminal path, error exits included: the reaction already happened
|
|
2065
|
+
// on the channel, and the record must not depend on the turn
|
|
2066
|
+
// finishing cleanly. Only records this turn captured at release are
|
|
2067
|
+
// drained; writing after the turn stops producing rows keeps them out
|
|
2068
|
+
// of any tool_use/tool_result pair.
|
|
2069
|
+
await drainQueuedReactionRecords(ctx, ownedReactionRecords);
|
|
2001
2070
|
// kickDrainQueue never rejects: a drain failure here would otherwise be
|
|
2002
2071
|
// an unhandled rejection that strands the queue with nothing left to
|
|
2003
2072
|
// re-trigger it.
|
|
@@ -2009,6 +2078,32 @@ export async function runAgentLoopImpl(
|
|
|
2009
2078
|
}
|
|
2010
2079
|
}
|
|
2011
2080
|
|
|
2081
|
+
/**
|
|
2082
|
+
* Persist and clear the turn's queued reaction records. Non-throwing: a
|
|
2083
|
+
* failure here must never mask the turn's own outcome, and
|
|
2084
|
+
* `persistReactionRecords` already logs per-record failures.
|
|
2085
|
+
*/
|
|
2086
|
+
export async function drainQueuedReactionRecords(
|
|
2087
|
+
ctx: Pick<Conversation, "conversationId" | "markHistoryStale">,
|
|
2088
|
+
ownedRecords: QueuedReactionRecord[] | undefined,
|
|
2089
|
+
): Promise<void> {
|
|
2090
|
+
// Runs inside the loop's finally, where a throw would mask the turn's own
|
|
2091
|
+
// outcome, so every access sits inside the guard. Drains only the records
|
|
2092
|
+
// the finished turn captured at release, never the conversation's live
|
|
2093
|
+
// queue, so one turn cannot consume a following turn's records.
|
|
2094
|
+
try {
|
|
2095
|
+
if (!ownedRecords?.length) {
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const queuedReactions = ownedRecords.splice(0);
|
|
2099
|
+
await persistReactionRecords(ctx.conversationId, queuedReactions);
|
|
2100
|
+
ctx.markHistoryStale();
|
|
2101
|
+
} catch {
|
|
2102
|
+
// persistReactionRecords is itself non-throwing per record; this guard
|
|
2103
|
+
// covers only unexpected faults so the finally block stays safe.
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2012
2107
|
// ── Helper ───────────────────────────────────────────────────────────
|
|
2013
2108
|
|
|
2014
2109
|
function emitUsage(
|
|
@@ -2110,6 +2205,7 @@ export async function applyCompactionResult(
|
|
|
2110
2205
|
summaryCacheReadInputTokens?: number;
|
|
2111
2206
|
summaryRawResponses?: unknown[];
|
|
2112
2207
|
summaryCallSite?: LLMCallSite;
|
|
2208
|
+
summaryResolutionCallSite?: LLMCallSite;
|
|
2113
2209
|
summaryOverrideProfile?: string | null;
|
|
2114
2210
|
summaryActualProvider?: string;
|
|
2115
2211
|
summaryActualInferenceProfile?: string;
|
|
@@ -2200,6 +2296,9 @@ export async function applyCompactionResult(
|
|
|
2200
2296
|
// this parameter independently off their own state and never share it.
|
|
2201
2297
|
{
|
|
2202
2298
|
callSite: result.summaryCallSite ?? null,
|
|
2299
|
+
...(result.summaryResolutionCallSite != null
|
|
2300
|
+
? { profileResolutionCallSite: result.summaryResolutionCallSite }
|
|
2301
|
+
: {}),
|
|
2203
2302
|
overrideProfile:
|
|
2204
2303
|
result.summaryActualInferenceProfile ??
|
|
2205
2304
|
result.summaryOverrideProfile ??
|
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
* When the provider rejects a request because the context is too large,
|
|
5
5
|
* this module replaces older image and file content blocks with lightweight
|
|
6
6
|
* text stubs to shrink the payload before retrying.
|
|
7
|
+
*
|
|
8
|
+
* The proactive counterpart, applied on every assembly and only to ambient
|
|
9
|
+
* camera frames, is `stripAgedSightFrames` in `conversation-sight-frames.ts`.
|
|
10
|
+
* Both paths name what they dropped through `mediaSourceDescriptor`, so the
|
|
11
|
+
* stubs describe media identically. A block that path already stubbed is plain
|
|
12
|
+
* text here, so it is neither counted as media nor stubbed twice.
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
15
|
import { estimateContentBlockTokens } from "../context/token-estimator.js";
|
|
10
16
|
import { getSummaryFromContextMessage } from "../plugins/defaults/compaction/window-manager.js";
|
|
11
|
-
import {
|
|
17
|
+
import { mediaSourceDescriptor } from "../providers/media-resolve.js";
|
|
12
18
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
13
19
|
|
|
14
20
|
export interface StripMediaOptions {
|
|
@@ -219,17 +225,16 @@ export function estimateUnconditionalStubTokens(
|
|
|
219
225
|
function imageBlockToStub(
|
|
220
226
|
block: Extract<ContentBlock, { type: "image" }>,
|
|
221
227
|
): Extract<ContentBlock, { type: "text" }> {
|
|
222
|
-
const sizeBytes = mediaSourceByteLength(block.source);
|
|
223
228
|
return {
|
|
224
229
|
type: "text",
|
|
225
|
-
text: `[Image omitted from retry context: ${block.source
|
|
230
|
+
text: `[Image omitted from retry context: ${mediaSourceDescriptor(block.source)}]`,
|
|
226
231
|
};
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
function fileBlockToStub(
|
|
230
235
|
block: Extract<ContentBlock, { type: "file" }>,
|
|
231
236
|
): Extract<ContentBlock, { type: "text" }> {
|
|
232
|
-
const
|
|
237
|
+
const descriptor = mediaSourceDescriptor(block.source);
|
|
233
238
|
const extracted = (block.extracted_text ?? "").trim();
|
|
234
239
|
const preview =
|
|
235
240
|
extracted.length > MAX_MEDIA_STUB_TEXT
|
|
@@ -239,8 +244,8 @@ function fileBlockToStub(
|
|
|
239
244
|
type: "text",
|
|
240
245
|
text:
|
|
241
246
|
preview.length > 0
|
|
242
|
-
? `[File omitted from retry context: ${block.source.filename} (${
|
|
243
|
-
: `[File omitted from retry context: ${block.source.filename} (${
|
|
247
|
+
? `[File omitted from retry context: ${block.source.filename} (${descriptor})]\n${preview}`
|
|
248
|
+
: `[File omitted from retry context: ${block.source.filename} (${descriptor})]`,
|
|
244
249
|
};
|
|
245
250
|
}
|
|
246
251
|
|