@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
|
@@ -1,39 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IPC
|
|
2
|
+
* IPC routes for the contact forms the guardian fills in their app.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 4. User enters an address; client POSTs to the gateway's
|
|
9
|
-
* `POST /v1/contacts/prompt` HTTP route.
|
|
10
|
-
* 5. Gateway upserts the contact + channel (gateway owns all contact writes).
|
|
11
|
-
* 6. If the prompt was opened with --verify, the gateway reads
|
|
12
|
-
* `contact_prompt_flags` and attests the new channel.
|
|
13
|
-
* 7. Gateway calls daemon IPC `resolve_contact_prompt` with the new contact info.
|
|
14
|
-
* 8. Daemon resolves the pending promise; `contacts/prompt` IPC returns to CLI.
|
|
4
|
+
* Two commands park on the guardian-form rail:
|
|
5
|
+
* - `contacts/prompt` collects a channel address and binds a channel.
|
|
6
|
+
* - `contacts/record-prompt` confirms a contact record write the assistant
|
|
7
|
+
* proposed (create, update, delete). No channel is touched.
|
|
15
8
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
9
|
+
* Flow, identical for both:
|
|
10
|
+
* 1. CLI calls the IPC route with what the assistant proposes.
|
|
11
|
+
* 2. The route parks the call in the guardian-form registry, which broadcasts
|
|
12
|
+
* a `contact_request` / `contact_record_request` to connected clients.
|
|
13
|
+
* 3. Client shows the form. The guardian edits and submits, or dismisses.
|
|
14
|
+
* 4. Client POSTs to the gateway (`/v1/contacts/prompt/submit` or
|
|
15
|
+
* `/v1/contacts/record/submit`).
|
|
16
|
+
* 5. Gateway performs the write (gateway owns all contact writes), attesting
|
|
17
|
+
* the channel when the guardian left the verify box checked.
|
|
18
|
+
* 6. Gateway calls daemon IPC `resolve_contact_prompt`.
|
|
19
|
+
* 7. The registry resolves the parked promise and the CLI call returns.
|
|
20
|
+
*
|
|
21
|
+
* The daemon only broadcasts and waits. It never writes contacts. That is what
|
|
22
|
+
* makes these commands guardian-gated: with no human at a form, nothing is
|
|
23
|
+
* written, and the call times out.
|
|
24
|
+
*
|
|
25
|
+
* The lifecycle itself lives in `runtime/guardian-form-registry.ts` and is
|
|
26
|
+
* shared by every guardian form. What is here is the contact-shaped half: the
|
|
27
|
+
* schemas, the two wire events, and the result the CLI gets back.
|
|
18
28
|
*/
|
|
19
29
|
|
|
20
|
-
import { v4 as uuid } from "uuid";
|
|
21
30
|
import { z } from "zod";
|
|
22
31
|
|
|
23
|
-
import {
|
|
32
|
+
import { GUARDIAN_FORM_MAX_TIMEOUT_MS } from "../../util/guardian-form-timeouts.js";
|
|
24
33
|
import { broadcastMessage } from "../assistant-event-hub.js";
|
|
25
34
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
35
|
+
import {
|
|
36
|
+
getGuardianFormMeta,
|
|
37
|
+
type GuardianFormClosedReason,
|
|
38
|
+
hasUnclaimedGuardianForm,
|
|
39
|
+
openGuardianForm,
|
|
40
|
+
} from "../guardian-form-registry.js";
|
|
41
|
+
import { claimForm, resolveFormFromCallback } from "./guardian-form-routes.js";
|
|
26
42
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Form kinds on the guardian-form rail that carry a contact card.
|
|
46
|
+
*
|
|
47
|
+
* The gateway names the same kind when it claims, so these two strings are a
|
|
48
|
+
* cross-package contract. `contact-form-kinds.test.ts` on each side pins the
|
|
49
|
+
* literals, since a rename on one side alone would only surface as claims
|
|
50
|
+
* rejected at runtime.
|
|
51
|
+
*/
|
|
52
|
+
export const CONTACT_ADDRESS_FORM_KIND = "contacts.address";
|
|
53
|
+
export const CONTACT_RECORD_FORM_KIND = "contacts.record";
|
|
54
|
+
const ADDRESS_FORM = CONTACT_ADDRESS_FORM_KIND;
|
|
55
|
+
const RECORD_FORM = CONTACT_RECORD_FORM_KIND;
|
|
56
|
+
const CONTACT_FORMS = [ADDRESS_FORM, RECORD_FORM] as const;
|
|
32
57
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
const TimeoutMsParam = z
|
|
59
|
+
.number()
|
|
60
|
+
.int()
|
|
61
|
+
.positive()
|
|
62
|
+
.max(GUARDIAN_FORM_MAX_TIMEOUT_MS)
|
|
63
|
+
.optional()
|
|
64
|
+
.describe(
|
|
65
|
+
"How long to hold the form open (ms). The caller waits slightly longer than this, so the form closing is what ends the wait. Defaults to 300000.",
|
|
66
|
+
);
|
|
36
67
|
|
|
68
|
+
/** What the CLI gets back once the guardian has answered, or not. */
|
|
37
69
|
export interface ContactPromptResult {
|
|
38
70
|
ok: boolean;
|
|
39
71
|
error?: string;
|
|
@@ -41,56 +73,28 @@ export interface ContactPromptResult {
|
|
|
41
73
|
channelId?: string;
|
|
42
74
|
channelType?: string;
|
|
43
75
|
address?: string;
|
|
76
|
+
/** Whether the channel is attested, as the guardian's checkbox left it. */
|
|
77
|
+
verified?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Whether submitted notes reached storage. Absent when none were submitted.
|
|
80
|
+
* False means the contact was written without them.
|
|
81
|
+
*/
|
|
82
|
+
notesSaved?: boolean;
|
|
83
|
+
/** Whether nothing the submission asked for landed. */
|
|
84
|
+
nothingWritten?: boolean;
|
|
44
85
|
}
|
|
45
86
|
|
|
46
|
-
interface PendingContactPrompt {
|
|
47
|
-
resolve: (result: ContactPromptResult) => void;
|
|
48
|
-
timer: ReturnType<typeof setTimeout>;
|
|
49
|
-
/** When true, the gateway marks the submitted channel verified (manual attest). */
|
|
50
|
-
verify: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const pendingContactPrompts = new Map<string, PendingContactPrompt>();
|
|
54
|
-
|
|
55
87
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
88
|
+
* Tell every client a contact form is over.
|
|
89
|
+
*
|
|
90
|
+
* The form went to all of them, so one client answering leaves the others
|
|
91
|
+
* holding a card that would now be refused. This is what takes those down.
|
|
58
92
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
requestId: string;
|
|
65
|
-
contactId?: string;
|
|
66
|
-
channelId?: string;
|
|
67
|
-
channelType?: string;
|
|
68
|
-
address?: string;
|
|
69
|
-
error?: string;
|
|
70
|
-
};
|
|
71
|
-
const pending = pendingContactPrompts.get(requestId);
|
|
72
|
-
if (!pending) {
|
|
73
|
-
log.warn({ requestId }, "resolve_contact_prompt: no pending prompt found");
|
|
74
|
-
return { resolved: false };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
clearTimeout(pending.timer);
|
|
78
|
-
pendingContactPrompts.delete(requestId);
|
|
79
|
-
|
|
80
|
-
if (error) {
|
|
81
|
-
pending.resolve({ ok: false, error });
|
|
82
|
-
} else {
|
|
83
|
-
pending.resolve({
|
|
84
|
-
ok: true,
|
|
85
|
-
contactId,
|
|
86
|
-
channelId,
|
|
87
|
-
channelType,
|
|
88
|
-
address,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
log.info({ requestId, contactId }, "Contact prompt resolved");
|
|
93
|
-
return { resolved: true };
|
|
93
|
+
function announceContactFormClosed(
|
|
94
|
+
requestId: string,
|
|
95
|
+
reason: GuardianFormClosedReason,
|
|
96
|
+
): void {
|
|
97
|
+
broadcastMessage({ type: "contact_form_closed", requestId, reason });
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
// ---------------------------------------------------------------------------
|
|
@@ -130,8 +134,62 @@ const ContactPromptParams = z.object({
|
|
|
130
134
|
.boolean()
|
|
131
135
|
.optional()
|
|
132
136
|
.describe(
|
|
133
|
-
"
|
|
137
|
+
"Pre-check the form's 'mark verified' box. The guardian's answer on submit decides the attest, so an unchecked box leaves the channel unverified.",
|
|
138
|
+
),
|
|
139
|
+
timeoutMs: TimeoutMsParam,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const ContactRecordPromptParams = z.object({
|
|
143
|
+
operation: z
|
|
144
|
+
.enum(["create", "update", "delete"])
|
|
145
|
+
.describe("Which record write the guardian is being asked to confirm."),
|
|
146
|
+
contactId: z
|
|
147
|
+
.string()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("Target contact. Required for update and delete."),
|
|
150
|
+
currentDisplayName: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe(
|
|
154
|
+
"The target's current name, resolved by the caller, so the form can show what is changing. Gateway-owned facts are not read here.",
|
|
155
|
+
),
|
|
156
|
+
currentNotes: z
|
|
157
|
+
.string()
|
|
158
|
+
.optional()
|
|
159
|
+
.describe(
|
|
160
|
+
"The target's current notes, resolved by the caller. The form submits a field only when it differs from what is stored.",
|
|
161
|
+
),
|
|
162
|
+
channels: z
|
|
163
|
+
.array(z.object({ type: z.string(), address: z.string() }))
|
|
164
|
+
.optional()
|
|
165
|
+
.describe(
|
|
166
|
+
"The target's channels, resolved by the caller, so a delete confirmation can identify the contact and show what access is about to be lost.",
|
|
167
|
+
),
|
|
168
|
+
|
|
169
|
+
displayName: z
|
|
170
|
+
.string()
|
|
171
|
+
.optional()
|
|
172
|
+
.describe(
|
|
173
|
+
"Proposed name, prefilled into the form. The guardian can edit it.",
|
|
174
|
+
),
|
|
175
|
+
notes: z
|
|
176
|
+
.string()
|
|
177
|
+
.optional()
|
|
178
|
+
.describe(
|
|
179
|
+
"Proposed notes, prefilled into the form. The guardian can edit them.",
|
|
180
|
+
),
|
|
181
|
+
notesProposed: z
|
|
182
|
+
.boolean()
|
|
183
|
+
.optional()
|
|
184
|
+
.describe(
|
|
185
|
+
"Whether the caller asked for these notes explicitly, so the form submits them even when they match what is stored.",
|
|
134
186
|
),
|
|
187
|
+
label: z.string().optional().describe("Display label shown in the form."),
|
|
188
|
+
description: z
|
|
189
|
+
.string()
|
|
190
|
+
.optional()
|
|
191
|
+
.describe("Longer description shown in the form."),
|
|
192
|
+
timeoutMs: TimeoutMsParam,
|
|
135
193
|
});
|
|
136
194
|
|
|
137
195
|
const ContactPromptFlagsParams = z.object({
|
|
@@ -139,7 +197,7 @@ const ContactPromptFlagsParams = z.object({
|
|
|
139
197
|
});
|
|
140
198
|
|
|
141
199
|
// ---------------------------------------------------------------------------
|
|
142
|
-
//
|
|
200
|
+
// Handlers
|
|
143
201
|
// ---------------------------------------------------------------------------
|
|
144
202
|
|
|
145
203
|
async function handleContactPrompt({
|
|
@@ -153,52 +211,112 @@ async function handleContactPrompt({
|
|
|
153
211
|
description,
|
|
154
212
|
role,
|
|
155
213
|
verify,
|
|
214
|
+
timeoutMs,
|
|
156
215
|
} = ContactPromptParams.parse(body);
|
|
157
216
|
|
|
158
|
-
|
|
217
|
+
return openGuardianForm<ContactPromptResult>({
|
|
218
|
+
kind: ADDRESS_FORM,
|
|
219
|
+
timeoutMs,
|
|
220
|
+
// Read back by the gateway when a client too old for the checkbox submits
|
|
221
|
+
// no answer of its own.
|
|
222
|
+
meta: { verify: verify === true },
|
|
223
|
+
logContext: { channel, role, verify: verify === true },
|
|
224
|
+
broadcast: {
|
|
225
|
+
open: (requestId) =>
|
|
226
|
+
broadcastMessage({
|
|
227
|
+
type: "contact_request",
|
|
228
|
+
requestId,
|
|
229
|
+
channel,
|
|
230
|
+
placeholder,
|
|
231
|
+
defaultValue,
|
|
232
|
+
label,
|
|
233
|
+
description,
|
|
234
|
+
role,
|
|
235
|
+
// The checkbox's initial state. Carried in the broadcast so the form
|
|
236
|
+
// can show what submitting will do; the client's answer is what the
|
|
237
|
+
// gateway acts on.
|
|
238
|
+
verify: verify === true,
|
|
239
|
+
}),
|
|
240
|
+
closed: announceContactFormClosed,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
159
244
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Park a proposed contact-record write until the guardian answers the form.
|
|
247
|
+
*
|
|
248
|
+
* The daemon writes nothing. It broadcasts the proposal and waits for the
|
|
249
|
+
* gateway to report what the guardian actually submitted, which may differ
|
|
250
|
+
* from what was proposed or may be a dismissal.
|
|
251
|
+
*/
|
|
252
|
+
async function handleContactRecordPrompt({
|
|
253
|
+
body = {},
|
|
254
|
+
}: RouteHandlerArgs): Promise<ContactPromptResult> {
|
|
255
|
+
const {
|
|
256
|
+
operation,
|
|
257
|
+
contactId,
|
|
258
|
+
currentDisplayName,
|
|
259
|
+
currentNotes,
|
|
260
|
+
channels,
|
|
261
|
+
displayName,
|
|
262
|
+
notes,
|
|
263
|
+
notesProposed,
|
|
264
|
+
label,
|
|
265
|
+
description,
|
|
266
|
+
timeoutMs,
|
|
267
|
+
} = ContactRecordPromptParams.parse(body);
|
|
166
268
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
verify: verify === true,
|
|
171
|
-
});
|
|
269
|
+
if (operation !== "create" && !contactId) {
|
|
270
|
+
return { ok: false, error: `contactId is required to ${operation}` };
|
|
271
|
+
}
|
|
172
272
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
273
|
+
// Clients hold one contact card at a time, so a second broadcast replaces the
|
|
274
|
+
// first and leaves its command waiting on a form nobody can answer. Refusing
|
|
275
|
+
// here fails the second command immediately instead, which is a caller that
|
|
276
|
+
// can retry rather than one that hangs.
|
|
277
|
+
if (hasUnclaimedGuardianForm(CONTACT_FORMS)) {
|
|
278
|
+
return {
|
|
279
|
+
ok: false,
|
|
280
|
+
error:
|
|
281
|
+
"Another contact form is already open. Wait for it to be answered, then try again.",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
183
284
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
285
|
+
return openGuardianForm<ContactPromptResult>({
|
|
286
|
+
kind: RECORD_FORM,
|
|
287
|
+
timeoutMs,
|
|
288
|
+
logContext: { operation, contactId },
|
|
289
|
+
broadcast: {
|
|
290
|
+
open: (requestId) =>
|
|
291
|
+
broadcastMessage({
|
|
292
|
+
type: "contact_record_request",
|
|
293
|
+
requestId,
|
|
294
|
+
operation,
|
|
295
|
+
contactId,
|
|
296
|
+
currentDisplayName,
|
|
297
|
+
currentNotes,
|
|
298
|
+
channels,
|
|
299
|
+
displayName,
|
|
300
|
+
notes,
|
|
301
|
+
notesProposed,
|
|
302
|
+
label,
|
|
303
|
+
description,
|
|
304
|
+
}),
|
|
305
|
+
closed: announceContactFormClosed,
|
|
306
|
+
},
|
|
188
307
|
});
|
|
189
308
|
}
|
|
190
309
|
|
|
191
310
|
/**
|
|
192
|
-
* Read-only flags for a pending prompt. The gateway asks this after it
|
|
193
|
-
*
|
|
194
|
-
*
|
|
311
|
+
* Read-only flags for a pending prompt. The gateway asks this after it writes
|
|
312
|
+
* the channel so a `--verify` prompt can attest without the client having to
|
|
313
|
+
* echo the flag on submit.
|
|
195
314
|
*/
|
|
196
315
|
function readContactPromptFlags({ body = {} }: RouteHandlerArgs): {
|
|
197
316
|
verify: boolean;
|
|
198
317
|
} {
|
|
199
318
|
const { requestId } = ContactPromptFlagsParams.parse(body);
|
|
200
|
-
|
|
201
|
-
return { verify: pending?.verify === true };
|
|
319
|
+
return { verify: getGuardianFormMeta(requestId)?.verify === true };
|
|
202
320
|
}
|
|
203
321
|
|
|
204
322
|
// ---------------------------------------------------------------------------
|
|
@@ -217,7 +335,7 @@ export const CONTACT_PROMPT_ROUTES: RouteDefinition[] = [
|
|
|
217
335
|
handler: handleContactPrompt,
|
|
218
336
|
summary: "Prompt user to register a contact channel",
|
|
219
337
|
description:
|
|
220
|
-
"Broadcasts a contact_request to connected clients, waits for the user to submit an address via the gateway. The gateway owns the contact write and
|
|
338
|
+
"Broadcasts a contact_request to connected clients, waits for the user to submit an address via the gateway. The gateway owns the contact write and reports it back via resolve_contact_prompt.",
|
|
221
339
|
tags: ["contacts"],
|
|
222
340
|
requestBody: ContactPromptParams,
|
|
223
341
|
responseBody: z.object({
|
|
@@ -227,6 +345,29 @@ export const CONTACT_PROMPT_ROUTES: RouteDefinition[] = [
|
|
|
227
345
|
channelId: z.string().optional(),
|
|
228
346
|
channelType: z.string().optional(),
|
|
229
347
|
address: z.string().optional(),
|
|
348
|
+
verified: z.boolean().optional(),
|
|
349
|
+
}),
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
operationId: "contacts_record_prompt",
|
|
353
|
+
endpoint: "contacts/record-prompt",
|
|
354
|
+
method: "POST",
|
|
355
|
+
policy: {
|
|
356
|
+
requiredScopes: ["settings.write"],
|
|
357
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
358
|
+
},
|
|
359
|
+
handler: handleContactRecordPrompt,
|
|
360
|
+
summary: "Ask the guardian to confirm a contact record write",
|
|
361
|
+
description:
|
|
362
|
+
"Broadcasts a contact_record_request to connected clients and waits for the guardian to submit the form. The gateway owns the write and reports it back via resolve_contact_prompt. Nothing is written if nobody answers.",
|
|
363
|
+
tags: ["contacts"],
|
|
364
|
+
requestBody: ContactRecordPromptParams,
|
|
365
|
+
responseBody: z.object({
|
|
366
|
+
ok: z.boolean(),
|
|
367
|
+
error: z.string().optional(),
|
|
368
|
+
contactId: z.string().optional(),
|
|
369
|
+
notesSaved: z.boolean().optional(),
|
|
370
|
+
nothingWritten: z.boolean().optional(),
|
|
230
371
|
}),
|
|
231
372
|
},
|
|
232
373
|
{
|
|
@@ -237,12 +378,37 @@ export const CONTACT_PROMPT_ROUTES: RouteDefinition[] = [
|
|
|
237
378
|
requiredScopes: ["settings.write"],
|
|
238
379
|
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
239
380
|
},
|
|
240
|
-
handler:
|
|
381
|
+
handler: resolveFormFromCallback,
|
|
241
382
|
summary: "Gateway callback: resolve a pending contact prompt",
|
|
242
383
|
description:
|
|
243
|
-
"Called by the gateway after it writes the contact and channel. Unblocks the waiting contacts/prompt
|
|
384
|
+
"Called by the gateway after it writes the contact and channel. Unblocks the waiting contacts/prompt call.",
|
|
244
385
|
tags: ["contacts"],
|
|
245
386
|
},
|
|
387
|
+
{
|
|
388
|
+
operationId: "contact_prompt_claim",
|
|
389
|
+
endpoint: "contact_prompt_claim",
|
|
390
|
+
method: "POST",
|
|
391
|
+
policy: {
|
|
392
|
+
requiredScopes: ["settings.write"],
|
|
393
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
394
|
+
},
|
|
395
|
+
handler: claimForm,
|
|
396
|
+
summary: "Claim a pending contact form for one submission",
|
|
397
|
+
description:
|
|
398
|
+
"Marks a pending form as answered so a second client submitting the same form writes nothing. Returns claimed=false with reason 'already_claimed' when somebody got there first, or 'unknown' when no such form is pending. A granted claim carries the window its write has to report back in.",
|
|
399
|
+
tags: ["contacts"],
|
|
400
|
+
requestBody: ContactPromptFlagsParams,
|
|
401
|
+
responseBody: z.object({
|
|
402
|
+
claimed: z.boolean(),
|
|
403
|
+
reason: z.enum(["already_claimed", "unknown"]).optional(),
|
|
404
|
+
settleMs: z
|
|
405
|
+
.number()
|
|
406
|
+
.optional()
|
|
407
|
+
.describe(
|
|
408
|
+
"How long a granted claim has to report its write back before the waiting call gives up.",
|
|
409
|
+
),
|
|
410
|
+
}),
|
|
411
|
+
},
|
|
246
412
|
{
|
|
247
413
|
operationId: "contact_prompt_flags",
|
|
248
414
|
endpoint: "contact_prompt_flags",
|
|
@@ -23,8 +23,9 @@ import {
|
|
|
23
23
|
type ConversationMessage,
|
|
24
24
|
ConversationMessageSchema,
|
|
25
25
|
} from "../../api/responses/conversation-message.js";
|
|
26
|
+
import { GUARDIAN_TERMINAL_REASON_SUPERSEDED } from "../../api/responses/home.js";
|
|
27
|
+
import { syncTerminalGuardianRequestStatus } from "../../approvals/guardian-request-status-sync.js";
|
|
26
28
|
import {
|
|
27
|
-
decideGuardianRequest,
|
|
28
29
|
expireGuardianRequest,
|
|
29
30
|
listGuardianRequestsOrEmpty,
|
|
30
31
|
listPendingRequestsByScopeOrEmpty,
|
|
@@ -97,13 +98,14 @@ import {
|
|
|
97
98
|
readSlackMetadataFromMessageMetadata,
|
|
98
99
|
type SlackMessageMetadata,
|
|
99
100
|
} from "../../messaging/providers/slack/message-metadata.js";
|
|
101
|
+
import { readProviderMetadata } from "../../messaging/read-provider-metadata.js";
|
|
100
102
|
import { recordOnboardingEvent } from "../../onboarding/onboarding-events-store.js";
|
|
101
103
|
import {
|
|
102
104
|
classifyKind,
|
|
103
105
|
getAttachmentById,
|
|
104
106
|
getAttachmentMetadataForMessage,
|
|
105
107
|
getAttachmentsByIds,
|
|
106
|
-
|
|
108
|
+
resolveAttachmentsForPersist,
|
|
107
109
|
} from "../../persistence/attachments-store.js";
|
|
108
110
|
import {
|
|
109
111
|
addMessage,
|
|
@@ -127,6 +129,7 @@ import {
|
|
|
127
129
|
getOrCreateConversation,
|
|
128
130
|
} from "../../persistence/conversation-key-store.js";
|
|
129
131
|
import { searchConversations } from "../../persistence/conversation-queries.js";
|
|
132
|
+
import { isNoResponseMetadata } from "../../persistence/conversation-types.js";
|
|
130
133
|
import { linkRequestLogsToMessage } from "../../persistence/llm-request-log-store.js";
|
|
131
134
|
import { MEMORY_RETROSPECTIVE_FORK_SOURCE } from "../../plugins/defaults/memory/memory-retrospective-constants.js";
|
|
132
135
|
import { normalizeOnboardingContext } from "../../prompts/normalize-onboarding.js";
|
|
@@ -163,6 +166,7 @@ import {
|
|
|
163
166
|
resolveActorPrincipalIdForLocalGuardian,
|
|
164
167
|
} from "../local-actor-identity.js";
|
|
165
168
|
import { resolveLocalPrincipalTrustContext } from "../local-principal-trust.js";
|
|
169
|
+
import { stripNoResponseMarkers } from "../no-response.js";
|
|
166
170
|
import * as pendingInteractions from "../pending-interactions.js";
|
|
167
171
|
import {
|
|
168
172
|
publishConversationListAndMetadataChanged,
|
|
@@ -194,8 +198,6 @@ import { RouteResponse } from "./types.js";
|
|
|
194
198
|
|
|
195
199
|
const log = getLogger("conversation-routes");
|
|
196
200
|
|
|
197
|
-
/** Matches the `<no_response/>` sentinel used by channel delivery suppression. */
|
|
198
|
-
const NO_RESPONSE_INLINE_RE = /<no_response\s*\/?>/g;
|
|
199
201
|
const ATTACHMENT_ENTRY_RE = /^attachment:(\d+)$/;
|
|
200
202
|
|
|
201
203
|
/** Rewrites a rendered `contentOrder` to reflect attachment alignment. */
|
|
@@ -997,6 +999,8 @@ export async function handleListMessages({
|
|
|
997
999
|
{};
|
|
998
1000
|
let backgroundToolCompletion: ConversationMessage["backgroundToolCompletion"];
|
|
999
1001
|
let systemCard: boolean | undefined;
|
|
1002
|
+
let noResponse: boolean | undefined;
|
|
1003
|
+
let reaction: ConversationMessage["reaction"];
|
|
1000
1004
|
let providerError: ConversationMessage["providerError"];
|
|
1001
1005
|
if (msg.metadata) {
|
|
1002
1006
|
try {
|
|
@@ -1009,6 +1013,25 @@ export async function handleListMessages({
|
|
|
1009
1013
|
if (isSystemCardMetadata(meta)) {
|
|
1010
1014
|
systemCard = true;
|
|
1011
1015
|
}
|
|
1016
|
+
if (isNoResponseMetadata(meta)) {
|
|
1017
|
+
noResponse = true;
|
|
1018
|
+
}
|
|
1019
|
+
// A reaction row, either direction, projects its structured fact so
|
|
1020
|
+
// clients never render the stored "[reaction]" sentinel.
|
|
1021
|
+
if (msg.metadata.includes("reaction")) {
|
|
1022
|
+
const reactionMeta = readProviderMetadata(msg.metadata);
|
|
1023
|
+
if (reactionMeta?.eventKind === "reaction" && reactionMeta.reaction) {
|
|
1024
|
+
reaction = {
|
|
1025
|
+
emoji: reactionMeta.reaction.emoji,
|
|
1026
|
+
op: reactionMeta.reaction.op,
|
|
1027
|
+
targetMessageId: reactionMeta.reaction.targetMessageId,
|
|
1028
|
+
...(reactionMeta.reaction.actorDisplayName
|
|
1029
|
+
? { actorDisplayName: reactionMeta.reaction.actorDisplayName }
|
|
1030
|
+
: {}),
|
|
1031
|
+
...(msg.role === "assistant" ? { selfAuthored: true } : {}),
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1012
1035
|
// Daemon-persisted provider-failure notices carry the classified
|
|
1013
1036
|
// error code/category so clients can render a themed card instead
|
|
1014
1037
|
// of a persona bubble.
|
|
@@ -1061,6 +1084,8 @@ export async function handleListMessages({
|
|
|
1061
1084
|
backgroundEventNotification: notifications.backgroundEventNotification,
|
|
1062
1085
|
backgroundToolCompletion,
|
|
1063
1086
|
systemCard,
|
|
1087
|
+
noResponse,
|
|
1088
|
+
reaction,
|
|
1064
1089
|
providerError,
|
|
1065
1090
|
slackMessage,
|
|
1066
1091
|
clientMessageId: msg.clientMessageId ?? undefined,
|
|
@@ -1175,9 +1200,7 @@ export async function handleListMessages({
|
|
|
1175
1200
|
const keepIndices: number[] = [];
|
|
1176
1201
|
const filteredSegments: string[] = [];
|
|
1177
1202
|
for (let i = 0; i < rendered.textSegments.length; i++) {
|
|
1178
|
-
const cleaned = rendered.textSegments[i]
|
|
1179
|
-
.replace(NO_RESPONSE_INLINE_RE, "")
|
|
1180
|
-
.trim();
|
|
1203
|
+
const cleaned = stripNoResponseMarkers(rendered.textSegments[i]);
|
|
1181
1204
|
if (cleaned.length > 0) {
|
|
1182
1205
|
keepIndices.push(i);
|
|
1183
1206
|
filteredSegments.push(cleaned);
|
|
@@ -1201,7 +1224,7 @@ export async function handleListMessages({
|
|
|
1201
1224
|
block.type === "text"
|
|
1202
1225
|
? {
|
|
1203
1226
|
type: "text" as const,
|
|
1204
|
-
text: block.text
|
|
1227
|
+
text: stripNoResponseMarkers(block.text),
|
|
1205
1228
|
}
|
|
1206
1229
|
: block,
|
|
1207
1230
|
)
|
|
@@ -1271,6 +1294,8 @@ export async function handleListMessages({
|
|
|
1271
1294
|
? { backgroundToolCompletion: m.backgroundToolCompletion }
|
|
1272
1295
|
: {}),
|
|
1273
1296
|
...(m.systemCard ? { systemCard: true } : {}),
|
|
1297
|
+
...(m.noResponse ? { noResponse: true } : {}),
|
|
1298
|
+
...(m.reaction ? { reaction: m.reaction } : {}),
|
|
1274
1299
|
...(m.providerError ? { providerError: m.providerError } : {}),
|
|
1275
1300
|
...(m.slackMessage ? { slackMessage: m.slackMessage } : {}),
|
|
1276
1301
|
};
|
|
@@ -2323,18 +2348,16 @@ export async function handleSendMessage(
|
|
|
2323
2348
|
source: "auto_deny" as const,
|
|
2324
2349
|
});
|
|
2325
2350
|
// Sync the gateway request status so stale "pending" records don't
|
|
2326
|
-
// get matched by later guardian reply routing
|
|
2351
|
+
// get matched by later guardian reply routing, and withdraw the
|
|
2352
|
+
// request's delivered approval cards so no surface keeps offering a
|
|
2353
|
+
// decision that can no longer resolve anything. Fire-and-forget: the
|
|
2327
2354
|
// in-memory denial is authoritative here; a CAS miss (already
|
|
2328
2355
|
// decided elsewhere) or a lost sync is reaped by the orphan sweep.
|
|
2329
|
-
void
|
|
2330
|
-
|
|
2331
|
-
expectedStatus: "pending",
|
|
2356
|
+
void syncTerminalGuardianRequestStatus({
|
|
2357
|
+
requestId: interaction.requestId,
|
|
2332
2358
|
status: "denied",
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
{ err, requestId: interaction.requestId },
|
|
2336
|
-
"Auto-deny guardian request status sync failed",
|
|
2337
|
-
);
|
|
2359
|
+
syncContext: "auto-deny-idle-send",
|
|
2360
|
+
terminalReason: GUARDIAN_TERMINAL_REASON_SUPERSEDED,
|
|
2338
2361
|
});
|
|
2339
2362
|
}
|
|
2340
2363
|
}
|
|
@@ -3027,20 +3050,6 @@ async function handleSearchConversations({
|
|
|
3027
3050
|
const suggestionCache = new Map<string, string>();
|
|
3028
3051
|
const suggestionInFlight = new Map<string, Promise<string | null>>();
|
|
3029
3052
|
|
|
3030
|
-
function resolveAttachments(attachmentIds: string[]) {
|
|
3031
|
-
const resolved = getAttachmentsByIds(attachmentIds, {
|
|
3032
|
-
hydrateFileData: true,
|
|
3033
|
-
});
|
|
3034
|
-
const sourcePaths = getSourcePathsForAttachments(attachmentIds);
|
|
3035
|
-
return resolved.map((a) => ({
|
|
3036
|
-
id: a.id,
|
|
3037
|
-
filename: a.originalFilename,
|
|
3038
|
-
mimeType: a.mimeType,
|
|
3039
|
-
data: a.dataBase64,
|
|
3040
|
-
...(sourcePaths.has(a.id) ? { filePath: sourcePaths.get(a.id) } : {}),
|
|
3041
|
-
}));
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
3053
|
// ---------------------------------------------------------------------------
|
|
3045
3054
|
// Route definitions
|
|
3046
3055
|
// ---------------------------------------------------------------------------
|
|
@@ -3260,7 +3269,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
3260
3269
|
sendMessageDeps: {
|
|
3261
3270
|
getOrCreateConversation: getOrCreateConversationInstance,
|
|
3262
3271
|
assistantEventHub,
|
|
3263
|
-
resolveAttachments,
|
|
3272
|
+
resolveAttachments: resolveAttachmentsForPersist,
|
|
3264
3273
|
},
|
|
3265
3274
|
approvalConversationGenerator: createApprovalConversationGenerator(),
|
|
3266
3275
|
}),
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
|
|
11
|
+
import { acpConnectCardStillWarranted } from "../../acp/acp-claude-oauth.js";
|
|
11
12
|
import { hasAcpConnectCardRaised } from "../../acp/acp-connect-card-state.js";
|
|
12
13
|
import { isAcpClaudeOauthField } from "../../acp/acp-credentials.js";
|
|
13
14
|
import {
|
|
@@ -112,10 +113,18 @@ async function handleCredentialPrompt({ body = {} }: RouteHandlerArgs) {
|
|
|
112
113
|
// fallback, so `claude setup-token` remains usable where no card can appear.
|
|
113
114
|
if (isAcpClaudeOauthField(validated.service, validated.field)) {
|
|
114
115
|
const conversation = findConversation(validated.conversationId);
|
|
116
|
+
// The registry says a card was raised; this asks whether it is still one
|
|
117
|
+
// the user can act on. Auth can be repaired without any credential write
|
|
118
|
+
// (a `config.json` edit to the agent's `CLAUDE_CODE_OAUTH_TOKEN` does it),
|
|
119
|
+
// and nothing runs on that path to forget the entry, so an entry alone
|
|
120
|
+
// would keep suppressing the fallback against a card that stopped
|
|
121
|
+
// rendering.
|
|
115
122
|
if (
|
|
116
123
|
conversation &&
|
|
117
124
|
conversationSupportsDynamicUi(conversation) &&
|
|
118
|
-
|
|
125
|
+
validated.conversationId !== undefined &&
|
|
126
|
+
hasAcpConnectCardRaised(validated.conversationId) &&
|
|
127
|
+
(await acpConnectCardStillWarranted(validated.conversationId))
|
|
119
128
|
) {
|
|
120
129
|
return {
|
|
121
130
|
ok: false,
|