@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
package/src/approvals/AGENTS.md
CHANGED
|
@@ -21,7 +21,7 @@ Conversational guardian verification control-plane invocation is guardian-only.
|
|
|
21
21
|
|
|
22
22
|
## Memory Provenance Invariant
|
|
23
23
|
|
|
24
|
-
All memory retrieval decisions must consider actor-role provenance. Untrusted actors (non-guardian, unverified_channel) must not receive memory recall results. This invariant is enforced in `indexer.ts` (write gate) and `
|
|
24
|
+
All memory retrieval decisions must consider actor-role provenance. Untrusted actors (non-guardian, unverified_channel) must not receive memory recall results. This invariant is enforced in `indexer.ts` (write gate) and the memory plugin's `injectors.ts` (read gate), which admits personal-memory content only for a turn whose trust context passes `isPersonalMemoryAllowed`.
|
|
25
25
|
|
|
26
26
|
## Guardian Privilege Isolation Invariant
|
|
27
27
|
|
|
@@ -22,10 +22,13 @@
|
|
|
22
22
|
* Every surface is attempted independently; one failure never blocks the rest.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { DELIVERY_STATUS } from "@vellumai/gateway-client";
|
|
26
|
+
|
|
25
27
|
import {
|
|
26
28
|
type GuardianRequestDeliveryWire,
|
|
27
29
|
type GuardianRequestStatus,
|
|
28
30
|
listGuardianRequestDeliveries,
|
|
31
|
+
updateGuardianRequestDelivery,
|
|
29
32
|
} from "../channels/gateway-guardian-requests.js";
|
|
30
33
|
import {
|
|
31
34
|
completeSurfaceAndNotify,
|
|
@@ -35,6 +38,7 @@ import { withdrawDiscordApprovalCard } from "../messaging/providers/discord/with
|
|
|
35
38
|
import { withdrawSlackApprovalCard } from "../messaging/providers/slack/withdraw.js";
|
|
36
39
|
import { withdrawTelegramApprovalCard } from "../messaging/providers/telegram-bot/withdraw.js";
|
|
37
40
|
import { approvalCardSurfaceId } from "../notifications/approval-card-data.js";
|
|
41
|
+
import { writeGuardianFeedReceipt } from "../notifications/guardian-feed-projection.js";
|
|
38
42
|
import {
|
|
39
43
|
type ApprovalAction,
|
|
40
44
|
resolveDecisionStatusWord,
|
|
@@ -66,6 +70,17 @@ export interface WithdrawGuardianCardsParams {
|
|
|
66
70
|
* expiry sweep) to broadcast on every surface.
|
|
67
71
|
*/
|
|
68
72
|
originChannel?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Conversation the in-app decision was made from, when `originChannel` is
|
|
75
|
+
* `vellum`. A request can project a card into more than one conversation
|
|
76
|
+
* (the vellum card, plus any legacy channel rows that paired one), and
|
|
77
|
+
* only the acting conversation's client holds the optimistic completion
|
|
78
|
+
* the broadcast suppression exists to protect; every sibling projection
|
|
79
|
+
* still needs its live `ui_surface_complete`. Absent, every in-app projection is
|
|
80
|
+
* treated as the acting one: a vellum-origin decision then suppresses the
|
|
81
|
+
* broadcast on all of them and only persists their completions.
|
|
82
|
+
*/
|
|
83
|
+
originConversationId?: string;
|
|
69
84
|
/**
|
|
70
85
|
* The action the guardian took, when the terminal status came from a decision
|
|
71
86
|
* (omitted for the expiry sweep). A `denied` status can mean either a neutral
|
|
@@ -84,23 +99,52 @@ export interface WithdrawGuardianCardsParams {
|
|
|
84
99
|
* outcome the guardian's chat gets.
|
|
85
100
|
*/
|
|
86
101
|
hasOriginGuardianReply?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Non-decision cause of the terminal status, for receipt copy only
|
|
104
|
+
* (e.g. "superseded" when a newer inbound message auto-denied the
|
|
105
|
+
* request). Channel card edits render the status word as before; the
|
|
106
|
+
* feed receipt surfaces the reason so the bell can say "Superseded"
|
|
107
|
+
* rather than "Rejected".
|
|
108
|
+
*/
|
|
109
|
+
terminalReason?: string;
|
|
87
110
|
}
|
|
88
111
|
|
|
89
112
|
/**
|
|
90
|
-
* Withdraw a resolved request's approval cards across all delivery
|
|
91
|
-
* Never throws.
|
|
113
|
+
* Withdraw a resolved request's approval cards across all delivery
|
|
114
|
+
* surfaces. Never throws. Each surface that durably withdraws marks its
|
|
115
|
+
* delivery row `withdrawn`; `complete` reports whether every row now
|
|
116
|
+
* carries that mark, so a caller whose own receipt depends on the cards
|
|
117
|
+
* actually being gone (the expiry sweep) can hold its receipt back and
|
|
118
|
+
* retry only what failed, while decision-path callers stay
|
|
119
|
+
* fire-and-forget. A surface with nothing a retry could ever fix (no
|
|
120
|
+
* captured message id, an unusable recorded id, a channel with no
|
|
121
|
+
* in-place edit) marks its row too, so it can never pin a request.
|
|
92
122
|
*/
|
|
93
123
|
export async function withdrawGuardianRequestCards(
|
|
94
124
|
params: WithdrawGuardianCardsParams,
|
|
95
|
-
): Promise<
|
|
125
|
+
): Promise<{ complete: boolean }> {
|
|
96
126
|
const {
|
|
97
127
|
request,
|
|
98
128
|
status,
|
|
99
129
|
originChannel,
|
|
130
|
+
originConversationId,
|
|
100
131
|
decidedAction,
|
|
101
132
|
hasOriginGuardianReply,
|
|
102
133
|
} = params;
|
|
103
134
|
|
|
135
|
+
// The canonical "Needs attention" feed item is a projection like any
|
|
136
|
+
// delivery surface: rewrite it into its terminal receipt first, so it
|
|
137
|
+
// settles even when the gateway (and thus the delivery list) is
|
|
138
|
+
// unreachable. A failed local write gates `complete` the same way a
|
|
139
|
+
// failed surface edit does, so the expiry sweep retries it.
|
|
140
|
+
let complete = await writeGuardianFeedReceipt({
|
|
141
|
+
requestId: request.id,
|
|
142
|
+
status,
|
|
143
|
+
...(decidedAction ? { decidedAction } : {}),
|
|
144
|
+
decidedAtMs: request.updatedAt,
|
|
145
|
+
...(params.terminalReason ? { terminalReason: params.terminalReason } : {}),
|
|
146
|
+
});
|
|
147
|
+
|
|
104
148
|
let deliveries: GuardianRequestDeliveryWire[];
|
|
105
149
|
try {
|
|
106
150
|
deliveries = await listGuardianRequestDeliveries(request.id);
|
|
@@ -109,35 +153,54 @@ export async function withdrawGuardianRequestCards(
|
|
|
109
153
|
{ err, requestId: request.id },
|
|
110
154
|
"Failed to list deliveries for card withdrawal",
|
|
111
155
|
);
|
|
112
|
-
return;
|
|
156
|
+
return { complete: false };
|
|
113
157
|
}
|
|
114
|
-
|
|
115
158
|
for (const delivery of deliveries) {
|
|
159
|
+
if (delivery.status === DELIVERY_STATUS.withdrawn) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
let withdrawn = false;
|
|
116
163
|
try {
|
|
164
|
+
// A delivery can carry two projections of the same card: the
|
|
165
|
+
// channel-native message, and the in-app rendering of any paired
|
|
166
|
+
// conversation (`destinationConversationId`). New channel guardian
|
|
167
|
+
// deliveries pair no conversation, so only the vellum delivery and
|
|
168
|
+
// legacy channel rows carry one; where a row does, withdrawal must
|
|
169
|
+
// settle both projections, or the paired conversation keeps live
|
|
170
|
+
// Approve/Reject buttons after the decision (LUM-3489).
|
|
171
|
+
const inAppWithdrawn = completeInAppProjection(
|
|
172
|
+
request,
|
|
173
|
+
delivery,
|
|
174
|
+
status,
|
|
175
|
+
originChannel,
|
|
176
|
+
originConversationId,
|
|
177
|
+
decidedAction,
|
|
178
|
+
);
|
|
117
179
|
if (delivery.destinationChannel === "vellum") {
|
|
118
|
-
|
|
119
|
-
request,
|
|
120
|
-
delivery,
|
|
121
|
-
status,
|
|
122
|
-
originChannel,
|
|
123
|
-
decidedAction,
|
|
124
|
-
);
|
|
180
|
+
withdrawn = inAppWithdrawn;
|
|
125
181
|
} else if (delivery.destinationChannel === "slack") {
|
|
126
182
|
await withdrawSlackCard(request, delivery, status, decidedAction);
|
|
183
|
+
withdrawn = inAppWithdrawn;
|
|
127
184
|
} else if (delivery.destinationChannel === "telegram") {
|
|
128
|
-
await withdrawTelegramCard(
|
|
185
|
+
const telegram = await withdrawTelegramCard(
|
|
129
186
|
delivery,
|
|
130
187
|
status,
|
|
131
188
|
originChannel,
|
|
132
189
|
decidedAction,
|
|
133
190
|
hasOriginGuardianReply ?? false,
|
|
134
191
|
);
|
|
192
|
+
withdrawn = telegram.complete && inAppWithdrawn;
|
|
135
193
|
} else if (delivery.destinationChannel === "discord") {
|
|
136
194
|
await withdrawDiscordCard(delivery, status, decidedAction);
|
|
195
|
+
withdrawn = inAppWithdrawn;
|
|
196
|
+
} else {
|
|
197
|
+
// WhatsApp direct delivery can't edit a message in place (it would
|
|
198
|
+
// post a new one), so its stale clicks are left to the existing
|
|
199
|
+
// "already resolved" reply until in-place edit support lands.
|
|
200
|
+
// Nothing a channel retry could fix, so only the in-app projection
|
|
201
|
+
// gates the row rather than letting it pin its request.
|
|
202
|
+
withdrawn = inAppWithdrawn;
|
|
137
203
|
}
|
|
138
|
-
// WhatsApp direct delivery can't edit a message in place (it would
|
|
139
|
-
// post a new one), so its stale clicks are left to the existing
|
|
140
|
-
// "already resolved" reply until in-place edit support lands.
|
|
141
204
|
} catch (err) {
|
|
142
205
|
log.warn(
|
|
143
206
|
{
|
|
@@ -148,12 +211,37 @@ export async function withdrawGuardianRequestCards(
|
|
|
148
211
|
"Failed to withdraw guardian card on surface (non-fatal)",
|
|
149
212
|
);
|
|
150
213
|
}
|
|
214
|
+
|
|
215
|
+
if (!withdrawn) {
|
|
216
|
+
complete = false;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await updateGuardianRequestDelivery(delivery.id, {
|
|
221
|
+
status: DELIVERY_STATUS.withdrawn,
|
|
222
|
+
});
|
|
223
|
+
} catch (err) {
|
|
224
|
+
// The card is gone but the receipt write failed: the retry round
|
|
225
|
+
// re-runs this surface's idempotent edit and marks it then.
|
|
226
|
+
complete = false;
|
|
227
|
+
log.warn(
|
|
228
|
+
{ err, requestId: request.id, deliveryId: delivery.id },
|
|
229
|
+
"Failed to record card withdrawal on the delivery row (non-fatal)",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
151
232
|
}
|
|
233
|
+
return { complete };
|
|
152
234
|
}
|
|
153
235
|
|
|
154
236
|
/**
|
|
155
|
-
*
|
|
156
|
-
* keeping its content.
|
|
237
|
+
* Complete a delivery's in-app card projection so it stops offering live
|
|
238
|
+
* actions while keeping its content. Applies to any delivery whose row
|
|
239
|
+
* records a paired conversation: the vellum card, and legacy channel rows
|
|
240
|
+
* written before channel deliveries stopped pairing, whose paired
|
|
241
|
+
* conversation renders the same actionable card in-app (the channel edit
|
|
242
|
+
* alone would leave that projection clickable). No-ops (successfully) when
|
|
243
|
+
* the delivery recorded no conversation, which is every new channel
|
|
244
|
+
* delivery.
|
|
157
245
|
*
|
|
158
246
|
* The completion is always persisted onto the card's `ui_surface` block. The
|
|
159
247
|
* acting client's optimistic completion is in-memory only, so without this write
|
|
@@ -163,32 +251,41 @@ export async function withdrawGuardianRequestCards(
|
|
|
163
251
|
* The `ui_surface_complete` broadcast is the only origin-sensitive half: when the
|
|
164
252
|
* decision came from in-app, the acting client is already showing the resolver's
|
|
165
253
|
* guardian-facing reply, and broadcasting the canonical status label back would
|
|
166
|
-
* replace that richer summary mid-session.
|
|
254
|
+
* replace that richer summary mid-session. When the acting conversation is
|
|
255
|
+
* known (`originConversationId`), only that conversation's broadcast is
|
|
256
|
+
* suppressed; sibling projections still need theirs to converge live.
|
|
167
257
|
*/
|
|
168
|
-
function
|
|
258
|
+
function completeInAppProjection(
|
|
169
259
|
request: WithdrawableGuardianRequest,
|
|
170
260
|
delivery: GuardianRequestDeliveryWire,
|
|
171
261
|
status: GuardianRequestStatus,
|
|
172
262
|
originChannel: string | undefined,
|
|
263
|
+
originConversationId: string | undefined,
|
|
173
264
|
decidedAction: ApprovalAction | undefined,
|
|
174
|
-
):
|
|
265
|
+
): boolean {
|
|
175
266
|
if (!delivery.destinationConversationId) {
|
|
176
|
-
return;
|
|
267
|
+
return true;
|
|
177
268
|
}
|
|
178
269
|
const surfaceId = approvalCardSurfaceId(request.kind, request.id);
|
|
179
270
|
if (!surfaceId) {
|
|
180
|
-
return;
|
|
271
|
+
return true;
|
|
181
272
|
}
|
|
182
273
|
const summary = resolveDecisionStatusWord(status, decidedAction);
|
|
183
|
-
|
|
184
|
-
|
|
274
|
+
// A false here is a failed durable write: after a reload the persisted
|
|
275
|
+
// block would revert to a pending, clickable card, so it must hold the
|
|
276
|
+
// expiry sweep's receipt back and be retried.
|
|
277
|
+
if (
|
|
278
|
+
originChannel === "vellum" &&
|
|
279
|
+
(!originConversationId ||
|
|
280
|
+
delivery.destinationConversationId === originConversationId)
|
|
281
|
+
) {
|
|
282
|
+
return markSurfaceCompleted(
|
|
185
283
|
{ conversationId: delivery.destinationConversationId },
|
|
186
284
|
surfaceId,
|
|
187
285
|
summary,
|
|
188
286
|
);
|
|
189
|
-
return;
|
|
190
287
|
}
|
|
191
|
-
completeSurfaceAndNotify(
|
|
288
|
+
return completeSurfaceAndNotify(
|
|
192
289
|
delivery.destinationConversationId,
|
|
193
290
|
surfaceId,
|
|
194
291
|
summary,
|
|
@@ -259,11 +356,11 @@ async function withdrawTelegramCard(
|
|
|
259
356
|
originChannel: string | undefined,
|
|
260
357
|
decidedAction: ApprovalAction | undefined,
|
|
261
358
|
hasOriginGuardianReply: boolean,
|
|
262
|
-
): Promise<
|
|
359
|
+
): Promise<{ complete: boolean }> {
|
|
263
360
|
if (!delivery.destinationChatId || !delivery.destinationMessageId) {
|
|
264
|
-
return;
|
|
361
|
+
return { complete: true };
|
|
265
362
|
}
|
|
266
|
-
|
|
363
|
+
return withdrawTelegramApprovalCard({
|
|
267
364
|
chatId: delivery.destinationChatId,
|
|
268
365
|
messageId: delivery.destinationMessageId,
|
|
269
366
|
status,
|
|
@@ -155,6 +155,13 @@ export interface ApplyGuardianDecisionParams {
|
|
|
155
155
|
action: ApprovalAction;
|
|
156
156
|
/** Actor context for the entity making the decision. */
|
|
157
157
|
actorContext: ActorContext;
|
|
158
|
+
/**
|
|
159
|
+
* Conversation an in-app (vellum) decision was made from, when known.
|
|
160
|
+
* Threaded to card withdrawal so only the acting conversation's completion
|
|
161
|
+
* broadcast is suppressed; the request's sibling in-app projections still
|
|
162
|
+
* receive theirs.
|
|
163
|
+
*/
|
|
164
|
+
originConversationId?: string;
|
|
158
165
|
/** Optional user-supplied text (e.g. answer text for pending questions). */
|
|
159
166
|
userText?: string;
|
|
160
167
|
/** Optional channel delivery context — present when the decision arrived via a channel message. */
|
|
@@ -227,6 +234,7 @@ export async function applyGuardianDecision(
|
|
|
227
234
|
requestId,
|
|
228
235
|
action,
|
|
229
236
|
actorContext,
|
|
237
|
+
originConversationId,
|
|
230
238
|
userText,
|
|
231
239
|
channelDeliveryContext,
|
|
232
240
|
emissionContext,
|
|
@@ -531,6 +539,7 @@ export async function applyGuardianDecision(
|
|
|
531
539
|
request: resolved,
|
|
532
540
|
status: targetStatus,
|
|
533
541
|
originChannel: actorContext.channel,
|
|
542
|
+
...(originConversationId ? { originConversationId } : {}),
|
|
534
543
|
decidedAction: cardAction,
|
|
535
544
|
// Tells the Telegram projection whether the origin chat is about to get
|
|
536
545
|
// the resolver's own guardian-facing reply (delivered by our caller), so
|
|
@@ -17,9 +17,12 @@
|
|
|
17
17
|
* withdrawn card already reflects the expired state, so a fresh ping would be
|
|
18
18
|
* noise.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Never throws; `complete` reports whether the side effects actually ran,
|
|
21
|
+
* because the sweep confirms a request's expiry (the status CAS) only after
|
|
22
|
+
* they did: a failed notice left the requester untold, so it must keep the
|
|
23
|
+
* row pending and retryable rather than vanish into a log line. A request
|
|
24
|
+
* with no deliverable route or no requester chat reports complete: there is
|
|
25
|
+
* nothing a retry could ever deliver.
|
|
23
26
|
*/
|
|
24
27
|
|
|
25
28
|
import type { GuardianRequestWire } from "../channels/gateway-guardian-requests.js";
|
|
@@ -36,49 +39,48 @@ import {
|
|
|
36
39
|
const log = getLogger("guardian-expiry-notifier");
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
|
-
* Run the expiry side effects for a single guardian request
|
|
40
|
-
*
|
|
42
|
+
* Run the expiry side effects for a single guardian request the sweep is
|
|
43
|
+
* expiring. Dispatches by kind; never throws.
|
|
41
44
|
*/
|
|
42
45
|
export async function notifyExpiredGuardianRequest(
|
|
43
46
|
request: GuardianRequestWire,
|
|
44
|
-
): Promise<
|
|
47
|
+
): Promise<{ complete: boolean }> {
|
|
45
48
|
try {
|
|
46
49
|
switch (request.kind) {
|
|
47
50
|
case "tool_approval":
|
|
48
51
|
releaseExpiredInteraction(request);
|
|
49
|
-
return;
|
|
52
|
+
return { complete: true };
|
|
50
53
|
case "access_request":
|
|
51
54
|
// Admitted-mode nudges expire silently for the requester — the
|
|
52
55
|
// sender made no request and keeps whatever access the floor grants.
|
|
53
56
|
if (!introductionMode(request.requestTrigger).notifyRequesterOnExpiry) {
|
|
54
|
-
|
|
57
|
+
return { complete: true };
|
|
55
58
|
}
|
|
56
|
-
|
|
59
|
+
return notifyRequesterOfExpiry(
|
|
57
60
|
request,
|
|
58
61
|
"Your access request expired before it was reviewed. " +
|
|
59
62
|
"Send a new message if you still need access.",
|
|
60
63
|
);
|
|
61
|
-
return;
|
|
62
64
|
case "tool_grant_request":
|
|
63
|
-
|
|
65
|
+
return notifyRequesterOfExpiry(
|
|
64
66
|
request,
|
|
65
67
|
`Your request to use "${request.toolName ?? "a tool"}" expired ` +
|
|
66
68
|
"before it was reviewed. Ask again if you still need it.",
|
|
67
69
|
);
|
|
68
|
-
return;
|
|
69
70
|
case "pending_question":
|
|
70
71
|
// Voice call sessions own their own lifecycle and timeout. By the time
|
|
71
72
|
// the request TTL lapses the call is long over and there is no
|
|
72
73
|
// durable requester channel to notify, so there is nothing to do.
|
|
73
|
-
return;
|
|
74
|
+
return { complete: true };
|
|
74
75
|
default:
|
|
75
|
-
return;
|
|
76
|
+
return { complete: true };
|
|
76
77
|
}
|
|
77
78
|
} catch (err) {
|
|
78
79
|
log.warn(
|
|
79
80
|
{ err, requestId: request.id, kind: request.kind },
|
|
80
81
|
"Expiry side effects failed for guardian request (non-fatal)",
|
|
81
82
|
);
|
|
83
|
+
return { complete: false };
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -121,15 +123,17 @@ function releaseExpiredInteraction(request: GuardianRequestWire): void {
|
|
|
121
123
|
async function notifyRequesterOfExpiry(
|
|
122
124
|
request: GuardianRequestWire,
|
|
123
125
|
text: string,
|
|
124
|
-
): Promise<
|
|
126
|
+
): Promise<{ complete: boolean }> {
|
|
125
127
|
const channel = request.sourceChannel ?? "";
|
|
126
128
|
const deliverUrl = resolveDeliverCallbackUrlForChannel(channel);
|
|
127
129
|
const requesterChatId =
|
|
128
130
|
request.requesterChatId ?? request.requesterExternalUserId ?? "";
|
|
129
131
|
const requesterExternalUserId = request.requesterExternalUserId ?? "";
|
|
130
132
|
|
|
133
|
+
// No deliverable route or no requester chat: nothing a retry could ever
|
|
134
|
+
// deliver, so this counts as complete rather than pinning the request.
|
|
131
135
|
if (!deliverUrl || !requesterChatId) {
|
|
132
|
-
return;
|
|
136
|
+
return { complete: true };
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
const targetChatId = resolveRequesterDeliveryTarget({
|
|
@@ -148,10 +152,12 @@ async function notifyRequesterOfExpiry(
|
|
|
148
152
|
{ requestId: request.id, kind: request.kind, channel },
|
|
149
153
|
"Notified requester that guardian request expired",
|
|
150
154
|
);
|
|
155
|
+
return { complete: true };
|
|
151
156
|
} catch (err) {
|
|
152
157
|
log.warn(
|
|
153
158
|
{ err, requestId: request.id, channel },
|
|
154
159
|
"Failed to notify requester of guardian request expiry (non-fatal)",
|
|
155
160
|
);
|
|
161
|
+
return { complete: false };
|
|
156
162
|
}
|
|
157
163
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal status sync for guardian requests resolved outside the decision
|
|
3
|
+
* primitive.
|
|
4
|
+
*
|
|
5
|
+
* Some flows resolve a pending tool confirmation in-memory first and treat
|
|
6
|
+
* that as authoritative: the in-app confirm route and the legacy channel
|
|
7
|
+
* approval rail (`Conversation.handleConfirmationResponse`), and the
|
|
8
|
+
* auto-deny sweeps that supersede confirmations when a new message arrives.
|
|
9
|
+
* Those flows still have to move the gateway's `guardian_requests` row to the
|
|
10
|
+
* matching terminal status so stale "pending" rows can't be matched by later
|
|
11
|
+
* guardian reply routing.
|
|
12
|
+
*
|
|
13
|
+
* A status CAS alone is not enough: the request's approval cards are
|
|
14
|
+
* projected onto every surface it was delivered to (the in-app card, channel
|
|
15
|
+
* messages, and each channel card's paired conversation), and a row that goes
|
|
16
|
+
* terminal without card withdrawal leaves every projection offering live
|
|
17
|
+
* Approve/Reject actions for a decision that already happened (LUM-3489). So
|
|
18
|
+
* the sync runs the same cross-surface withdrawal the decision primitive
|
|
19
|
+
* runs, whenever its CAS is the one that actually landed.
|
|
20
|
+
*
|
|
21
|
+
* First-writer-wins: when the primitive already resolved the request (e.g.
|
|
22
|
+
* the channel approval path decided it and ran withdrawal itself), this CAS
|
|
23
|
+
* misses and no second withdrawal runs.
|
|
24
|
+
*
|
|
25
|
+
* Fire-and-forget by contract: the caller's in-memory resolution is
|
|
26
|
+
* authoritative and often synchronous, so failures are logged, never thrown,
|
|
27
|
+
* and lost syncs are reaped by the orphan sweep.
|
|
28
|
+
*
|
|
29
|
+
* Transitional: this exists for the flows that resolve confirmations
|
|
30
|
+
* in-memory first (the legacy rail). New decision paths must route through
|
|
31
|
+
* `applyGuardianDecision`, which owns the CAS, resolver dispatch, grant
|
|
32
|
+
* minting, and withdrawal together; do not add callers here for anything the
|
|
33
|
+
* pipeline can serve.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { decideGuardianRequest } from "../channels/gateway-guardian-requests.js";
|
|
37
|
+
import type { ApprovalAction } from "../runtime/channel-approval-types.js";
|
|
38
|
+
import { getLogger } from "../util/logger.js";
|
|
39
|
+
import { withdrawGuardianRequestCards } from "./guardian-card-withdrawal.js";
|
|
40
|
+
|
|
41
|
+
const log = getLogger("guardian-request-status-sync");
|
|
42
|
+
|
|
43
|
+
export interface SyncTerminalGuardianRequestStatusParams {
|
|
44
|
+
requestId: string;
|
|
45
|
+
status: "approved" | "denied";
|
|
46
|
+
/** Log line context naming the flow that resolved the confirmation. */
|
|
47
|
+
syncContext: string;
|
|
48
|
+
/**
|
|
49
|
+
* Non-decision cause of the terminal status, threaded through to the
|
|
50
|
+
* feed receipt so an auto-deny reads as what it was (e.g.
|
|
51
|
+
* "superseded") rather than as a rejection the guardian chose.
|
|
52
|
+
*/
|
|
53
|
+
terminalReason?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* CAS the gateway request to its terminal status and, when this write is the
|
|
58
|
+
* one that landed, project the outcome onto every delivered approval card.
|
|
59
|
+
*
|
|
60
|
+
* No origin channel is passed to withdrawal: the confirmation flows act on
|
|
61
|
+
* the conversation's confirmation prompt (or on no client at all, for the
|
|
62
|
+
* auto-deny sweeps), never on the approval card itself, so every card
|
|
63
|
+
* projection needs its completion broadcast.
|
|
64
|
+
*/
|
|
65
|
+
export async function syncTerminalGuardianRequestStatus(
|
|
66
|
+
params: SyncTerminalGuardianRequestStatusParams,
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
const { requestId, status, syncContext } = params;
|
|
69
|
+
// The confirmation flows only ever approve or reject, so the resolved
|
|
70
|
+
// cards' action is the plain decision pair derived from the status.
|
|
71
|
+
const decidedAction: ApprovalAction =
|
|
72
|
+
status === "approved" ? "approve_once" : "reject";
|
|
73
|
+
try {
|
|
74
|
+
const decided = await decideGuardianRequest({
|
|
75
|
+
id: requestId,
|
|
76
|
+
expectedStatus: "pending",
|
|
77
|
+
status,
|
|
78
|
+
});
|
|
79
|
+
if (!decided.applied) {
|
|
80
|
+
// The decision primitive (or a concurrent sync) resolved it first and
|
|
81
|
+
// owns the card withdrawal.
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await withdrawGuardianRequestCards({
|
|
85
|
+
request: decided.request,
|
|
86
|
+
status,
|
|
87
|
+
decidedAction,
|
|
88
|
+
...(params.terminalReason
|
|
89
|
+
? { terminalReason: params.terminalReason }
|
|
90
|
+
: {}),
|
|
91
|
+
});
|
|
92
|
+
} catch (err) {
|
|
93
|
+
log.warn(
|
|
94
|
+
{ err, requestId, syncContext },
|
|
95
|
+
"Guardian request terminal status sync failed",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
+
import { getCharacterComponents } from "@vellumai/avatar-catalog";
|
|
5
6
|
import {
|
|
6
7
|
AVATAR_IMAGE_FILENAME,
|
|
7
8
|
AVATAR_TRAITS_FILENAME,
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
import { getLogger } from "../util/logger.js";
|
|
12
13
|
import { getAvatarDir } from "../util/platform.js";
|
|
13
14
|
import { renderCharacterAscii } from "./ascii-renderer.js";
|
|
14
|
-
import { getCharacterComponents } from "./character-components.js";
|
|
15
15
|
import { renderCharacterPng } from "./png-renderer.js";
|
|
16
16
|
import { isResvgAvailable } from "./resvg-lazy.js";
|
|
17
17
|
|